summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AconfigFlags.bp29
-rw-r--r--Android.bp1
-rw-r--r--LSE_APP_COMPAT_OWNERS6
-rw-r--r--Ravenwood.bp68
-rw-r--r--api/Android.bp1
-rw-r--r--cmds/am/src/com/android/commands/am/Am.java2
-rw-r--r--cmds/am/src/com/android/commands/am/Instrument.java19
-rw-r--r--core/api/current.txt11
-rw-r--r--core/api/module-lib-current.txt2
-rw-r--r--core/api/system-current.txt166
-rw-r--r--core/api/system-lint-baseline.txt10
-rw-r--r--core/api/test-current.txt17
-rw-r--r--core/api/test-lint-baseline.txt8
-rw-r--r--core/java/android/animation/Animator.java15
-rw-r--r--core/java/android/app/ActivityThread.java34
-rw-r--r--core/java/android/app/ApplicationPackageManager.java14
-rw-r--r--core/java/android/app/INotificationManager.aidl1
-rw-r--r--core/java/android/app/Instrumentation.java21
-rw-r--r--core/java/android/app/Notification.java98
-rw-r--r--core/java/android/app/NotificationManager.java20
-rw-r--r--core/java/android/app/OWNERS2
-rw-r--r--core/java/android/app/PictureInPictureUiState.java34
-rw-r--r--core/java/android/app/Service.java9
-rw-r--r--core/java/android/app/UiModeManager.java6
-rw-r--r--core/java/android/app/admin/DeviceAdminInfo.java5
-rw-r--r--core/java/android/app/admin/DevicePolicyIdentifiers.java6
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java14
-rw-r--r--core/java/android/app/admin/SecurityLog.java5
-rw-r--r--core/java/android/app/assist/AssistStructure.java10
-rw-r--r--core/java/android/app/ondeviceintelligence/Content.aidl22
-rw-r--r--core/java/android/app/ondeviceintelligence/Content.java90
-rw-r--r--core/java/android/app/ondeviceintelligence/DownloadCallback.java114
-rw-r--r--core/java/android/app/ondeviceintelligence/Feature.aidl22
-rw-r--r--core/java/android/app/ondeviceintelligence/Feature.java279
-rw-r--r--core/java/android/app/ondeviceintelligence/FeatureDetails.aidl22
-rw-r--r--core/java/android/app/ondeviceintelligence/FeatureDetails.java176
-rw-r--r--core/java/android/app/ondeviceintelligence/FilePart.java137
-rw-r--r--core/java/android/app/ondeviceintelligence/IDownloadCallback.aidl32
-rw-r--r--core/java/android/app/ondeviceintelligence/IFeatureCallback.aidl14
-rw-r--r--core/java/android/app/ondeviceintelligence/IFeatureDetailsCallback.aidl14
-rw-r--r--core/java/android/app/ondeviceintelligence/IListFeaturesCallback.aidl15
-rw-r--r--core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl70
-rw-r--r--core/java/android/app/ondeviceintelligence/IProcessingSignal.aidl14
-rw-r--r--core/java/android/app/ondeviceintelligence/IResponseCallback.aidl15
-rw-r--r--core/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl18
-rw-r--r--core/java/android/app/ondeviceintelligence/ITokenCountCallback.aidl13
-rw-r--r--core/java/android/app/ondeviceintelligence/OWNERS1
-rw-r--r--core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java624
-rw-r--r--core/java/android/app/ondeviceintelligence/ProcessingSignal.java221
-rw-r--r--core/java/android/app/ondeviceintelligence/StreamingResponseReceiver.java43
-rw-r--r--core/java/android/app/servertransaction/ClientTransaction.java14
-rw-r--r--core/java/android/app/servertransaction/ClientTransactionListenerController.java9
-rw-r--r--core/java/android/app/servertransaction/TransactionExecutor.java3
-rw-r--r--core/java/android/app/ui_mode_manager.aconfig7
-rw-r--r--core/java/android/app/wearable/IWearableSensingManager.aidl2
-rw-r--r--core/java/android/app/wearable/WearableSensingManager.java6
-rw-r--r--core/java/android/companion/virtual/flags/flags.aconfig15
-rw-r--r--core/java/android/content/ClipData.java74
-rw-r--r--core/java/android/content/Context.java24
-rw-r--r--core/java/android/content/Intent.java116
-rw-r--r--core/java/android/content/pm/ILauncherApps.aidl2
-rw-r--r--core/java/android/content/pm/LauncherApps.java17
-rw-r--r--core/java/android/content/pm/PackageManager.java4
-rw-r--r--core/java/android/content/pm/ShortcutServiceInternal.java5
-rw-r--r--core/java/android/content/pm/SignedPackage.java14
-rw-r--r--core/java/android/content/pm/SignedPackageParcel.aidl2
-rw-r--r--core/java/android/content/pm/UserProperties.java2
-rw-r--r--core/java/android/content/pm/multiuser.aconfig21
-rw-r--r--core/java/android/credentials/GetCredentialResponse.java3
-rw-r--r--core/java/android/credentials/flags.aconfig16
-rw-r--r--core/java/android/credentials/selection/Constants.java7
-rw-r--r--core/java/android/credentials/selection/IntentFactory.java31
-rw-r--r--core/java/android/hardware/SerialManager.java3
-rw-r--r--core/java/android/hardware/SerialManagerInternal.java35
-rw-r--r--core/java/android/hardware/biometrics/BiometricFaceConstants.java20
-rw-r--r--core/java/android/hardware/biometrics/BiometricFingerprintConstants.java20
-rw-r--r--core/java/android/hardware/biometrics/BiometricManager.java7
-rw-r--r--core/java/android/hardware/biometrics/BiometricPrompt.java25
-rw-r--r--core/java/android/hardware/biometrics/PromptInfo.java15
-rw-r--r--core/java/android/hardware/camera2/CameraCharacteristics.java7
-rw-r--r--core/java/android/hardware/camera2/CameraDevice.java2
-rw-r--r--core/java/android/hardware/camera2/CameraExtensionCharacteristics.java5
-rw-r--r--core/java/android/hardware/display/BrightnessInfo.java5
-rw-r--r--core/java/android/hardware/display/DisplayManager.java3
-rw-r--r--core/java/android/hardware/face/FaceEnrollOptions.aidl19
-rw-r--r--core/java/android/hardware/face/FaceEnrollOptions.java259
-rw-r--r--core/java/android/hardware/face/FaceManager.java8
-rw-r--r--core/java/android/hardware/face/IFaceService.aidl3
-rw-r--r--core/java/android/hardware/fingerprint/FingerprintEnrollOptions.aidl19
-rw-r--r--core/java/android/hardware/fingerprint/FingerprintEnrollOptions.java260
-rw-r--r--core/java/android/hardware/fingerprint/FingerprintManager.java5
-rw-r--r--core/java/android/hardware/fingerprint/IFingerprintService.aidl3
-rw-r--r--core/java/android/hardware/input/KeyboardLayoutPreviewDrawable.java80
-rw-r--r--core/java/android/hardware/input/PhysicalKeyLayout.java11
-rw-r--r--core/java/android/hardware/input/VirtualKeyboard.java13
-rw-r--r--core/java/android/net/flags.aconfig8
-rw-r--r--core/java/android/net/thread/flags.aconfig2
-rw-r--r--core/java/android/os/OWNERS1
-rw-r--r--core/java/android/os/PermissionEnforcer.java18
-rw-r--r--core/java/android/os/ServiceManager.java50
-rw-r--r--core/java/android/os/UserManager.java4
-rw-r--r--core/java/android/os/VibrationEffect.java19
-rw-r--r--core/java/android/os/vibrator/PrebakedSegment.java8
-rw-r--r--core/java/android/os/vibrator/PrimitiveSegment.java20
-rw-r--r--core/java/android/os/vibrator/RampSegment.java15
-rw-r--r--core/java/android/os/vibrator/StepSegment.java21
-rw-r--r--core/java/android/os/vibrator/VibrationEffectSegment.java14
-rw-r--r--core/java/android/permission/flags.aconfig8
-rw-r--r--core/java/android/print/pdf/TEST_MAPPING7
-rw-r--r--core/java/android/service/chooser/ChooserResult.java2
-rw-r--r--core/java/android/service/notification/ZenModeConfig.java2
-rw-r--r--core/java/android/service/notification/ZenPolicy.java4
-rw-r--r--core/java/android/service/notification/flags.aconfig2
-rw-r--r--core/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl44
-rw-r--r--core/java/android/service/ondeviceintelligence/IOnDeviceTrustedInferenceService.aidl44
-rw-r--r--core/java/android/service/ondeviceintelligence/IRemoteStorageService.aidl34
-rw-r--r--core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java383
-rw-r--r--core/java/android/service/ondeviceintelligence/OnDeviceTrustedInferenceService.java410
-rw-r--r--core/java/android/service/voice/flags/flags.aconfig2
-rw-r--r--core/java/android/service/wearable/IWearableSensingService.aidl2
-rw-r--r--core/java/android/service/wearable/WearableSensingService.java10
-rw-r--r--core/java/android/text/Layout.java92
-rw-r--r--core/java/android/tracing/flags.aconfig1
-rw-r--r--core/java/android/util/TimingsTraceLog.java1
-rw-r--r--core/java/android/view/HandwritingInitiator.java50
-rw-r--r--core/java/android/view/ISensitiveContentProtectionManager.aidl35
-rw-r--r--core/java/android/view/IWindowManager.aidl7
-rw-r--r--core/java/android/view/KeyboardShortcutGroup.java20
-rw-r--r--core/java/android/view/View.java61
-rw-r--r--core/java/android/view/ViewRootImpl.java8
-rw-r--r--core/java/android/view/autofill/AutofillManager.java89
-rw-r--r--core/java/android/view/autofill/IAutoFillManagerClient.aidl4
-rw-r--r--core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java60
-rw-r--r--core/java/android/view/inputmethod/InputMethodManager.java224
-rw-r--r--core/java/android/view/textclassifier/TextClassificationConstants.java11
-rw-r--r--core/java/android/widget/Editor.java18
-rw-r--r--core/java/android/window/IGlobalDragListener.aidl (renamed from core/java/android/window/IUnhandledDragListener.aidl)11
-rw-r--r--core/java/android/window/flags/OWNERS3
-rw-r--r--core/java/com/android/internal/app/SetScreenLockDialogActivity.java160
-rw-r--r--core/java/com/android/internal/colorextraction/OWNERS3
-rw-r--r--core/java/com/android/internal/inputmethod/IBooleanListener.aidl25
-rw-r--r--core/java/com/android/internal/inputmethod/IInputMethodClient.aidl1
-rw-r--r--core/java/com/android/internal/inputmethod/InputBindResult.java1
-rw-r--r--core/java/com/android/internal/jank/Cuj.java6
-rw-r--r--core/java/com/android/internal/statusbar/IStatusBar.aidl6
-rw-r--r--core/java/com/android/internal/view/IInputMethodManager.aidl24
-rw-r--r--core/java/com/android/internal/widget/CallLayout.java47
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java12
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java10
-rw-r--r--core/java/com/android/internal/widget/remotecompose/core/operations/BitmapData.java2
-rw-r--r--core/jni/android_view_InputEventReceiver.cpp13
-rw-r--r--core/jni/android_view_InputEventSender.cpp2
-rw-r--r--core/jni/android_view_InputQueue.cpp2
-rw-r--r--core/jni/android_view_KeyCharacterMap.cpp8
-rw-r--r--core/jni/android_view_KeyEvent.cpp24
-rw-r--r--core/jni/android_view_KeyEvent.h9
-rw-r--r--core/jni/android_view_MotionEvent.cpp41
-rw-r--r--core/jni/android_view_MotionEvent.h11
-rw-r--r--core/jni/android_view_MotionPredictor.cpp3
-rw-r--r--core/proto/android/content/intent.proto1
-rw-r--r--core/res/AndroidManifest.xml10
-rw-r--r--core/res/res/values/config.xml7
-rw-r--r--core/res/res/values/strings.xml7
-rw-r--r--core/res/res/values/symbols.xml8
-rw-r--r--core/tests/coretests/Android.bp1
-rw-r--r--core/tests/coretests/src/android/app/activity/ActivityThreadTest.java39
-rw-r--r--core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java17
-rw-r--r--core/tests/coretests/src/android/app/servertransaction/ClientTransactionTests.java41
-rw-r--r--core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java137
-rw-r--r--core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java20
-rw-r--r--core/tests/coretests/src/android/content/ContextTest.java38
-rw-r--r--core/tests/coretests/src/android/hardware/face/FaceManagerTest.java6
-rw-r--r--core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java6
-rw-r--r--core/tests/coretests/src/android/text/LayoutTest.java275
-rw-r--r--core/tests/coretests/src/android/view/textclassifier/TextClassificationConstantsTest.java23
-rw-r--r--core/tests/vibrator/src/android/os/vibrator/PrebakedSegmentTest.java7
-rw-r--r--core/tests/vibrator/src/android/os/vibrator/PrimitiveSegmentTest.java21
-rw-r--r--core/tests/vibrator/src/android/os/vibrator/RampSegmentTest.java31
-rw-r--r--core/tests/vibrator/src/android/os/vibrator/StepSegmentTest.java31
-rw-r--r--graphics/java/android/graphics/Canvas.java14
-rw-r--r--libs/WindowManager/Shell/Android.bp2
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt20
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt215
-rw-r--r--libs/WindowManager/Shell/res/layout/bubble_bar_expanded_view.xml5
-rw-r--r--libs/WindowManager/Shell/res/values/dimen.xml13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsAlgorithm.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java17
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java28
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java23
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java113
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt105
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java86
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java30
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/GlobalDragListener.kt (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/UnhandledDragController.kt)49
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java141
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java25
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipWithSourceRectHintTest.kt86
-rw-r--r--libs/WindowManager/Shell/tests/unittest/AndroidManifest.xml1
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java41
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt139
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt46
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java8
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/GlobalDragListenerTest.kt (renamed from libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/UnhandledDragControllerTest.kt)58
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java12
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java12
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java32
-rw-r--r--libs/hwui/CanvasTransform.cpp7
-rw-r--r--libs/hwui/DamageAccumulator.cpp2
-rw-r--r--libs/hwui/hwui/DrawTextFunctor.h1
-rw-r--r--libs/hwui/jni/android_graphics_Canvas.cpp5
-rw-r--r--libs/hwui/renderthread/CanvasContext.cpp1
-rw-r--r--media/java/android/media/browse/MediaBrowser.java34
-rw-r--r--media/java/android/media/flags/media_better_together.aconfig7
-rw-r--r--media/java/android/media/midi/package.html2
-rw-r--r--media/java/android/media/tv/SignalingDataResponse.java4
-rwxr-xr-xmedia/java/android/media/tv/interactive/TvInteractiveAppService.java8
-rwxr-xr-xmedia/java/android/media/tv/interactive/TvInteractiveAppView.java3
-rw-r--r--media/java/android/service/media/IMediaBrowserServiceCallbacks.aidl7
-rw-r--r--media/java/android/service/media/MediaBrowserService.java96
-rw-r--r--native/android/input.cpp12
-rw-r--r--nfc/api/current.txt33
-rw-r--r--nfc/java/android/nfc/INfcCardEmulation.aidl2
-rw-r--r--nfc/java/android/nfc/NfcAdapter.java10
-rw-r--r--nfc/java/android/nfc/cardemulation/ApduServiceInfo.java26
-rw-r--r--nfc/java/android/nfc/cardemulation/CardEmulation.java15
-rw-r--r--nfc/java/android/nfc/cardemulation/HostApduService.java88
-rw-r--r--nfc/java/android/nfc/cardemulation/PollingFrame.java243
-rw-r--r--packages/CompanionDeviceManager/res/values/styles.xml4
-rw-r--r--packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java8
-rw-r--r--packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java300
-rw-r--r--packages/CrashRecovery/services/java/com/android/server/rollback/WatchdogRollbackLogger.java3
-rw-r--r--packages/CredentialManager/shared/src/com/android/credentialmanager/client/CredentialManagerClient.kt22
-rw-r--r--packages/CredentialManager/shared/src/com/android/credentialmanager/client/impl/CredentialManagerClientImpl.kt77
-rw-r--r--packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/IntentKtx.kt6
-rw-r--r--packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestGetMapper.kt2
-rw-r--r--packages/CredentialManager/shared/src/com/android/credentialmanager/model/Request.kt7
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt10
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt16
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt54
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt17
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/UserConfigRepo.kt44
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt8
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt142
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt6
-rw-r--r--packages/CredentialManager/wear/res/values/strings.xml13
-rw-r--r--packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt3
-rw-r--r--packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorApp.kt1
-rw-r--r--packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt71
-rw-r--r--packages/CredentialManager/wear/src/com/android/credentialmanager/FlowEngine.kt45
-rw-r--r--packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt21
-rw-r--r--packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/CredentialsScreenChip.kt74
-rw-r--r--packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/SignInHeader.kt6
-rw-r--r--packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt1
-rw-r--r--packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldScreen.kt12
-rw-r--r--packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt78
-rw-r--r--packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreenViewModel.kt77
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java5
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java6
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt6
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java1
-rw-r--r--packages/SettingsLib/Spa/OWNERS2
-rw-r--r--packages/SettingsLib/Spa/build.gradle.kts2
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt4
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt6
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/EditorMainPageProvider.kt2
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/SettingsDropdownCheckBoxProvider.kt134
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/SettingsExposedDropdownMenuCheckBoxProvider.kt75
-rw-r--r--packages/SettingsLib/Spa/gradle/libs.versions.toml2
-rw-r--r--packages/SettingsLib/Spa/spa/build.gradle.kts4
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/CardModel.kt2
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt10
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsDropdownCheckBox.kt184
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBox.kt164
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsOutlinedTextField.kt8
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ListPreference.kt12
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetPreference.kt5
-rw-r--r--packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SettingsCardTest.kt17
-rw-r--r--packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/editor/SettingsDropdownCheckBoxTest.kt145
-rw-r--r--packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBoxTest.kt115
-rw-r--r--packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/ListPreferenceTest.kt20
-rw-r--r--packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/SemanticsMatcher.kt26
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt3
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt1
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt4
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceTest.kt25
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItemTest.kt24
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/RestrictedTestUtils.kt13
-rw-r--r--packages/SettingsLib/aconfig/settingslib.aconfig21
-rw-r--r--packages/SettingsLib/res/values/strings.xml2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/RestrictedDropDownPreference.java70
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java81
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java11
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java39
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java10
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java3
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothBroadcastUtils.java2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt397
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java40
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java66
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidStatsLogUtils.java113
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java217
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java22
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java13
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/OWNERS3
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java21
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/volume/OWNERS5
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt20
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/volume/data/repository/LocalMediaRepository.kt15
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/volume/data/repository/MediaControllerRepository.kt23
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/volume/shared/AudioManagerEventsReceiver.kt (renamed from packages/SettingsLib/src/com/android/settingslib/volume/shared/AudioManagerIntentsReceiver.kt)45
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/volume/shared/model/AudioManagerEvent.kt37
-rw-r--r--packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java25
-rw-r--r--packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/OWNERS1
-rw-r--r--packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt24
-rw-r--r--packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/LocalMediaRepositoryImplTest.kt6
-rw-r--r--packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/MediaControllerRepositoryImplTest.kt12
-rw-r--r--packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/shared/AudioManagerEventsReceiverTest.kt139
-rw-r--r--packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/shared/FakeAudioManagerEventsReceiver.kt (renamed from packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/shared/FakeAudioManagerIntentsReceiver.kt)16
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/RestrictedPreferenceHelperTest.java15
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidStatsLogUtilsTest.java88
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java209
-rw-r--r--packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowRouter2Manager.java3
-rw-r--r--packages/SettingsLib/tests/unit/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExtTest.kt74
-rw-r--r--packages/SystemUI/accessibility/accessibilitymenu/TEST_MAPPING11
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig62
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt2
-rw-r--r--packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/anc/AncModule.kt21
-rw-r--r--packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt21
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt26
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt1
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt10
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/BurnInState.kt2
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt4
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt44
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt2
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt7
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt1
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt4
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt15
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt9
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt237
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt19
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryExt.kt6
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/AncModule.kt53
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt110
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/bottombar/ui/BottomBarComponent.kt2
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt81
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt3
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt1
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/popup/ui/composable/VolumePanelPopup.kt116
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt2
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelComposeScope.kt4
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt66
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt59
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt22
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt7
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt56
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt45
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt44
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/FaceHelpMessageDeferralTest.kt1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt41
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt54
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt29
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt15
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt11
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt43
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModelTest.kt21
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt72
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt)34
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModelTest.kt97
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModelTest.kt114
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt20
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModelTest.kt115
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt16
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/MinimumTilesResourceRepositoryTest.kt45
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt18
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddableTest.kt28
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorTest.kt41
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/NoLowNumberOfTilesTest.kt147
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractorTest.kt241
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt143
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt265
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractorTest.kt283
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt31
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterTest.kt13
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt5
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt8
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt9
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt9
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt113
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/PrivacyChipRepositoryTest.kt183
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/PrivacyChipInteractorTest.kt154
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt5
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt5
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerMainThreadTest.java614
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt)6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java)4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/volume/OWNERS1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/data/repository/AncSliceRepositoryTest.kt114
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/domain/AncAvailabilityCriteriaTest.kt89
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/domain/interactor/AncSliceInteractorTest.kt101
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/MediaOutputAvailabilityCriteriaTest.kt40
-rw-r--r--packages/SystemUI/proguard_common.flags4
-rw-r--r--packages/SystemUI/res-keyguard/layout/keyguard_presentation.xml2
-rw-r--r--packages/SystemUI/res-keyguard/values/dimens.xml2
-rw-r--r--packages/SystemUI/res/drawable/ic_noise_aware.xml26
-rw-r--r--packages/SystemUI/res/drawable/media_squiggly_progress.xml2
-rw-r--r--packages/SystemUI/res/drawable/qs_media_background.xml2
-rw-r--r--packages/SystemUI/res/drawable/qs_media_light_source.xml2
-rw-r--r--packages/SystemUI/res/layout/anc_slice.xml20
-rw-r--r--packages/SystemUI/res/layout/media_carousel.xml4
-rw-r--r--packages/SystemUI/res/layout/scene_window_root.xml2
-rw-r--r--packages/SystemUI/res/values/dimens.xml9
-rw-r--r--packages/SystemUI/res/values/strings.xml3
-rw-r--r--packages/SystemUI/res/values/styles.xml13
-rw-r--r--packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricUserInfo.kt4
-rw-r--r--packages/SystemUI/src/com/android/keyguard/ConnectedDisplayKeyguardPresentation.kt131
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java111
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java48
-rw-r--r--packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/CameraProtectionInfo.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/SysUICutoutProvider.kt40
-rw-r--r--packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/FaceHelpMessageDeferral.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt29
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/classifier/domain/interactor/FalsingInteractor.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/CommunalTutorialIndicatorSection.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/complication/DreamMediaEntryComplication.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/qualifiers/NotifInflation.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/BiometricMessageInteractor.kt42
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricSettingsInteractor.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/FaceHelpMessageDeferralInteractor.kt112
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/shared/model/BiometricMessageModels.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/display/DisplayExtensions.kt47
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt96
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamOverlayViewModel.kt61
-rw-r--r--packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt102
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/FlagDependenciesBase.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/globalactions/data/repository/GlobalActionsRepository.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/globalactions/domain/interactor/GlobalActionsInteractor.kt42
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/shared/model/StickyKey.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt53
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt67
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeMediaSection.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerMessageAreaViewModel.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModel.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt59
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt58
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt34
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt56
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/MediaBrowserLog.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/MediaCarouselControllerLog.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/MediaTimeoutListenerLog.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/MediaViewLog.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/NavbarOrientationTrackingLog.java33
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatest.kt (renamed from packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatest.kt)8
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilter.kt (renamed from packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt)8
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataManager.kt (renamed from packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt)35
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt (renamed from packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt)7
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilter.kt (renamed from packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaSessionBasedFilter.kt)10
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListener.kt (renamed from packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListener.kt)6
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutLogger.kt (renamed from packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutLogger.kt)2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/domain/resume/MediaBrowserFactory.java (renamed from packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaBrowserFactory.java)2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/domain/resume/MediaResumeListener.kt (renamed from packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt)8
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/domain/resume/ResumeMediaBrowser.java (renamed from packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowser.java)2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/domain/resume/ResumeMediaBrowserFactory.java (renamed from packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserFactory.java)2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/domain/resume/ResumeMediaBrowserLogger.kt (renamed from packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserLogger.kt)2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaData.kt (renamed from packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt)4
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/shared/model/SmartspaceMediaData.kt (renamed from packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaData.kt)4
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/shared/model/SmartspaceMediaDataProvider.kt (renamed from packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaDataProvider.kt)2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/animation/AnimationBindHandler.kt (renamed from packages/SystemUI/src/com/android/systemui/media/controls/ui/AnimationBindHandler.kt)2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/animation/ColorSchemeTransition.kt (renamed from packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt)4
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/animation/MediaColorSchemes.kt (renamed from packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaColorSchemes.kt)2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/animation/MetadataAnimationHandler.kt (renamed from packages/SystemUI/src/com/android/systemui/media/controls/ui/MetadataAnimationHandler.kt)2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/SeekBarObserver.kt (renamed from packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarObserver.kt)6
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaController.kt (renamed from packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt)32
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaControllerLogger.kt (renamed from packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerLogger.kt)20
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt (renamed from packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt)17
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerLogger.kt (renamed from packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselControllerLogger.kt)2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java (renamed from packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java)36
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt (renamed from packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt)5
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHostStatesManager.kt (renamed from packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHostStatesManager.kt)3
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt (renamed from packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt)13
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewLogger.kt (renamed from packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewLogger.kt)2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/drawable/IlluminationDrawable.kt (renamed from packages/SystemUI/src/com/android/systemui/media/controls/ui/IlluminationDrawable.kt)2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/drawable/LightSourceDrawable.kt (renamed from packages/SystemUI/src/com/android/systemui/media/controls/ui/LightSourceDrawable.kt)2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/drawable/SquigglyProgress.kt (renamed from packages/SystemUI/src/com/android/systemui/media/controls/ui/SquigglyProgress.kt)2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/view/GutsViewHolder.kt (renamed from packages/SystemUI/src/com/android/systemui/media/controls/models/GutsViewHolder.kt)10
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandler.kt (renamed from packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt)3
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaHost.kt (renamed from packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHost.kt)11
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaScrollView.kt (renamed from packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaScrollView.kt)2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaViewHolder.kt (renamed from packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaViewHolder.kt)3
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/view/RecommendationViewHolder.kt (renamed from packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/RecommendationViewHolder.kt)5
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/SeekBarViewModel.kt (renamed from packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt)2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/util/LocalMediaManagerFactory.kt (renamed from packages/SystemUI/src/com/android/systemui/media/controls/pipeline/LocalMediaManagerFactory.kt)2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/util/MediaUiEventLogger.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dream/MediaComplicationViewController.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttFlags.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/NavbarOrientationTrackingLogger.kt82
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java22
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSImpl.java41
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/MinimumTilesRepository.kt45
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepository.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddable.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractor.kt98
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractor.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractor.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileServiceInteractor.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractor.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt34
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/data/repository/PrivacyChipRepository.kt133
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PrivacyChipInteractor.kt88
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/slice/SliceViewManagerExt.kt40
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java29
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java43
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeKeyguardTransitionController.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java295
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java77
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt45
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/ColorUpdateLogger.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpNotificationRepository.kt59
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt27
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/CallLayoutSetDataAsyncFactory.kt37
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java48
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStatsLoggerImpl.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt48
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ComponentSystemUIDialog.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialogFactory.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt53
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.java30
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/kotlin/FlowDumper.kt142
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/OWNERS8
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dagger/AncModule.kt40
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/data/repository/AncSliceRepository.kt108
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/domain/AncAvailabilityCriteria.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/domain/interactor/AncSliceInteractor.kt76
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/ui/viewmodel/AncViewModel.kt67
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/button/ui/viewmodel/ButtonViewModel.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/LocalMediaRepositoryFactory.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/MediaOutputAvailabilityCriteria.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/DeviceIconViewModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/shared/model/VolumePanelComponents.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/domain/DomainModule.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/ui/UiModule.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java22
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardPresentationTest.java116
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java15
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/CameraProtectionLoaderImplTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/FakeCameraProtectionLoader.kt17
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/SysUICutoutProviderTest.kt169
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt20
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java32
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java36
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/complication/DreamMediaEntryComplicationTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/BiometricMessageInteractorTest.kt56
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java20
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/globalactions/data/repository/GlobalActionsRepositoryTest.kt69
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/globalactions/domain/interactor/GlobalActionsInteractorTest.kt67
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt35
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt40
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt71
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelTest.kt18
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt39
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/MediaTestUtils.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatestTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatestTest.java)6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt)10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataManagerTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt)60
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt)7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilterTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaSessionBasedFilterTest.kt)4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListenerTest.kt)6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/resume/MediaResumeListenerTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt)10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/resume/ResumeMediaBrowserTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserTest.kt)2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/shared/SmartspaceMediaDataTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaDataTest.kt)6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaPlayerDataTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/AnimationBindHandlerTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/AnimationBindHandlerTest.kt)2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/ColorSchemeTransitionTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/ColorSchemeTransitionTest.kt)6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/MetadataAnimationHandlerTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MetadataAnimationHandlerTest.kt)2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/binder/SeekBarObserverTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarObserverTest.kt)6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaControllerTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt)4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt)14
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt)32
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt)7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaViewControllerTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt)7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/drawable/SquigglyProgressTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/SquigglyProgressTest.kt)2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandlerTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandlerTest.kt)2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/view/MediaViewHolderTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/MediaViewHolderTest.kt)2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/viewmodel/SeekBarViewModelTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarViewModelTest.kt)2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java9
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java20
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaComplicationViewControllerTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java25
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java23
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt32
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt14
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt202
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java43
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt48
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java1
-rw-r--r--packages/SystemUI/tests/utils/src/android/graphics/drawable/TestStubDrawable.kt19
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/FaceHelpMessageDeferralKosmos.kt31
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/FakeCredentialInteractor.kt5
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/classifier/domain/interactor/FalsingInteractorKosmos.kt2
-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/domain/interactor/CommunalInteractorKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/BiometricMessageInteractorKosmos.kt1
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/FaceHelpMessageDeferralInteractorKosmos.kt33
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/globalactions/data/repository/GlobalActionsRepositoryKosmos.kt21
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/globalactions/domain/interactor/GlobalActionsInteractorKosmos.kt23
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModelKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt27
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModelKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModelKosmos.kt3
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerKosmos.kt (renamed from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/MediaHierarchyManagerKosmos.kt)2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt9
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/privacy/PrivacyDialogControllerKosmos.kt23
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/privacy/PrivacyDialogControllerV2Kosmos.kt23
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/FakeIQSTileService.kt61
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/FakeTileServiceManagerFacade.kt59
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/FakeTileServicesFacade.kt39
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TilesExternalKosmos.kt37
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeDefaultTilesRepository.kt22
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeTileSpecRepository.kt8
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/QSPipelineRepositoryKosmos.kt12
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/CustomTileKosmos.kt56
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakePrivacyChipRepository.kt55
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/PrivacyChipRepositoryKosmos.kt (renamed from packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileLifecycleManagerFactoryKosmos.kt)10
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/PrivacyChipInteractorKosmos.kt35
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/HeadsUpNotificationRepositoryKosmos.kt28
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HeadsUpNotificationInteractorKosmos.kt26
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/anc/FakeSliceFactory.kt51
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/anc/VolumePanelAncKosmos.kt29
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/anc/data/repository/FakeAncSliceRepository.kt33
-rw-r--r--packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java75
-rw-r--r--ravenwood/Android.bp9
-rw-r--r--ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodReplace.java10
-rw-r--r--ravenwood/framework-minus-apex-ravenwood-policies.txt1
-rw-r--r--ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java90
-rw-r--r--ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodPermissionEnforcer.java38
-rw-r--r--ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java25
-rw-r--r--ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java85
-rw-r--r--ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java47
-rw-r--r--ravenwood/ravenwood-annotation-allowed-classes.txt13
-rw-r--r--ravenwood/ravenwood-services-jarjar-rules.txt11
-rwxr-xr-xravenwood/run-ravenwood-tests.sh8
-rw-r--r--ravenwood/services-test/Android.bp21
-rw-r--r--ravenwood/services-test/test/com/android/ravenwood/RavenwoodServicesTest.java83
-rw-r--r--ravenwood/services.core-ravenwood-policies.txt7
-rw-r--r--services/accessibility/accessibility.aconfig7
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java49
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java168
-rw-r--r--services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java2
-rw-r--r--services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java10
-rw-r--r--services/autofill/java/com/android/server/autofill/Session.java112
-rw-r--r--services/autofill/java/com/android/server/autofill/ui/FillUi.java4
-rw-r--r--services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java13
-rw-r--r--services/companion/java/com/android/server/companion/virtual/InputController.java37
-rw-r--r--services/companion/java/com/android/server/companion/virtual/SensorController.java11
-rw-r--r--services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java24
-rw-r--r--services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java5
-rw-r--r--services/companion/java/com/android/server/companion/virtual/audio/VirtualAudioController.java10
-rw-r--r--services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java10
-rw-r--r--services/core/Android.bp1
-rw-r--r--services/core/java/com/android/server/IntentResolver.java16
-rw-r--r--services/core/java/com/android/server/SensitiveContentProtectionManagerService.java104
-rw-r--r--services/core/java/com/android/server/SerialService.java102
-rw-r--r--services/core/java/com/android/server/SystemConfig.java15
-rw-r--r--services/core/java/com/android/server/SystemService.java1
-rw-r--r--services/core/java/com/android/server/SystemServiceManager.java35
-rw-r--r--services/core/java/com/android/server/UiModeManagerService.java4
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java31
-rw-r--r--services/core/java/com/android/server/am/LmkdStatsReporter.java11
-rw-r--r--services/core/java/com/android/server/am/OomAdjuster.java52
-rw-r--r--services/core/java/com/android/server/am/ProcessList.java11
-rw-r--r--services/core/java/com/android/server/am/ProcessRecord.java5
-rw-r--r--services/core/java/com/android/server/am/ProcessStateRecord.java60
-rw-r--r--services/core/java/com/android/server/am/SettingsToPropertiesMapper.java1
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceInventory.java53
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java39
-rw-r--r--services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java24
-rw-r--r--services/core/java/com/android/server/biometrics/BiometricNotificationLogger.java104
-rw-r--r--services/core/java/com/android/server/biometrics/BiometricService.java23
-rw-r--r--services/core/java/com/android/server/biometrics/TEST_MAPPING10
-rw-r--r--services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java12
-rw-r--r--services/core/java/com/android/server/biometrics/log/BiometricLogger.java5
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java52
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/EnrollClient.java9
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/FaceService.java29
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/FaceShellCommand.java9
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java3
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java4
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java9
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java12
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java4
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java17
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java10
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java27
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintShellCommand.java9
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java4
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java7
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java9
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java13
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java7
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java19
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java9
-rw-r--r--services/core/java/com/android/server/display/DisplayDeviceConfig.java2
-rw-r--r--services/core/java/com/android/server/display/LocalDisplayAdapter.java7
-rw-r--r--services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java2
-rw-r--r--services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java2
-rw-r--r--services/core/java/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeClamper.java2
-rw-r--r--services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionManagerInternal.java5
-rw-r--r--services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java18
-rw-r--r--services/core/java/com/android/server/input/InputManagerInternal.java30
-rw-r--r--services/core/java/com/android/server/input/InputManagerService.java65
-rw-r--r--services/core/java/com/android/server/input/KeyboardMetricsCollector.java7
-rw-r--r--services/core/java/com/android/server/input/NativeInputManagerService.java7
-rw-r--r--services/core/java/com/android/server/inputmethod/IInputMethodClientInvoker.java24
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodManagerService.java67
-rw-r--r--services/core/java/com/android/server/inputmethod/ZeroJankProxy.java420
-rw-r--r--services/core/java/com/android/server/location/LocationManagerService.java9
-rw-r--r--services/core/java/com/android/server/media/MediaSession2Record.java6
-rw-r--r--services/core/java/com/android/server/media/MediaSessionService.java35
-rw-r--r--services/core/java/com/android/server/net/NetworkPolicyManagerService.java5
-rwxr-xr-xservices/core/java/com/android/server/notification/NotificationManagerService.java8
-rw-r--r--services/core/java/com/android/server/notification/ZenModeHelper.java58
-rw-r--r--services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java419
-rw-r--r--services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceIntelligenceService.java56
-rw-r--r--services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceTrustedInferenceService.java64
-rw-r--r--services/core/java/com/android/server/pdb/PersistentDataBlockService.java5
-rw-r--r--services/core/java/com/android/server/pm/BackgroundInstallControlService.java72
-rw-r--r--services/core/java/com/android/server/pm/BroadcastHelper.java35
-rw-r--r--services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java99
-rw-r--r--services/core/java/com/android/server/pm/LauncherAppsService.java185
-rw-r--r--services/core/java/com/android/server/pm/PackageArchiver.java52
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerServiceUtils.java7
-rw-r--r--services/core/java/com/android/server/pm/ShortcutRequestPinProcessor.java4
-rw-r--r--services/core/java/com/android/server/pm/ShortcutService.java25
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java25
-rw-r--r--services/core/java/com/android/server/pm/UserTypeFactory.java6
-rw-r--r--services/core/java/com/android/server/pm/VerifyingSession.java8
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java12
-rw-r--r--services/core/java/com/android/server/power/stats/BatteryStatsImpl.java4
-rw-r--r--services/core/java/com/android/server/power/stats/MobileRadioPowerCalculator.java1
-rw-r--r--services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java21
-rw-r--r--services/core/java/com/android/server/search/Searchables.java3
-rw-r--r--services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java5
-rw-r--r--services/core/java/com/android/server/statusbar/StatusBarManagerService.java9
-rw-r--r--services/core/java/com/android/server/utils/TimingsTraceAndSlog.java1
-rw-r--r--services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java9
-rw-r--r--services/core/java/com/android/server/vibrator/HalVibration.java17
-rw-r--r--services/core/java/com/android/server/vibrator/VibrationScaler.java9
-rw-r--r--services/core/java/com/android/server/vibrator/VibrationSettings.java90
-rw-r--r--services/core/java/com/android/server/vibrator/VibrationStepConductor.java3
-rw-r--r--services/core/java/com/android/server/vibrator/VibratorFrameworkStatsLogger.java19
-rw-r--r--services/core/java/com/android/server/vibrator/VibratorManagerService.java14
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperManagerService.java42
-rw-r--r--services/core/java/com/android/server/wearable/RemoteWearableSensingService.java58
-rw-r--r--services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java6
-rw-r--r--services/core/java/com/android/server/wearable/WearableSensingManagerService.java60
-rw-r--r--services/core/java/com/android/server/wearable/WearableSensingShellCommand.java24
-rw-r--r--services/core/java/com/android/server/wm/AbsAppSnapshotController.java16
-rw-r--r--services/core/java/com/android/server/wm/AccessibilityController.java496
-rw-r--r--services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java21
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java37
-rw-r--r--services/core/java/com/android/server/wm/ActivitySnapshotController.java7
-rw-r--r--services/core/java/com/android/server/wm/ActivityStartController.java10
-rw-r--r--services/core/java/com/android/server/wm/ActivityStarter.java15
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerService.java58
-rw-r--r--services/core/java/com/android/server/wm/AnrController.java2
-rw-r--r--services/core/java/com/android/server/wm/BackNavigationController.java323
-rw-r--r--services/core/java/com/android/server/wm/BackgroundActivityStartController.java75
-rw-r--r--services/core/java/com/android/server/wm/ClientLifecycleManager.java17
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java2
-rw-r--r--services/core/java/com/android/server/wm/DisplayPolicy.java17
-rw-r--r--services/core/java/com/android/server/wm/DragDropController.java55
-rw-r--r--services/core/java/com/android/server/wm/EmbeddedWindowController.java4
-rw-r--r--services/core/java/com/android/server/wm/Letterbox.java2
-rw-r--r--services/core/java/com/android/server/wm/LetterboxUiController.java41
-rw-r--r--services/core/java/com/android/server/wm/RootWindowContainer.java5
-rw-r--r--services/core/java/com/android/server/wm/SensitiveContentPackages.java78
-rw-r--r--services/core/java/com/android/server/wm/Session.java76
-rw-r--r--services/core/java/com/android/server/wm/TaskFragment.java6
-rw-r--r--services/core/java/com/android/server/wm/TaskPositioningController.java10
-rw-r--r--services/core/java/com/android/server/wm/TaskSnapshotController.java5
-rw-r--r--services/core/java/com/android/server/wm/Transition.java7
-rw-r--r--services/core/java/com/android/server/wm/TransitionController.java3
-rw-r--r--services/core/java/com/android/server/wm/WallpaperController.java63
-rw-r--r--services/core/java/com/android/server/wm/WallpaperWindowToken.java23
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerInternal.java32
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java46
-rw-r--r--services/core/java/com/android/server/wm/WindowOrganizerController.java5
-rw-r--r--services/core/java/com/android/server/wm/WindowProcessController.java3
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java22
-rw-r--r--services/core/jni/com_android_server_input_InputManagerService.cpp51
-rw-r--r--services/credentials/java/com/android/server/credentials/CreateRequestSession.java5
-rw-r--r--services/credentials/java/com/android/server/credentials/CredentialManagerUi.java45
-rw-r--r--services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java7
-rw-r--r--services/credentials/java/com/android/server/credentials/GetRequestSession.java6
-rw-r--r--services/credentials/java/com/android/server/credentials/MetricUtilities.java2
-rw-r--r--services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java5
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java8
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java16
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java73
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java6
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java4
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java9
-rw-r--r--services/java/com/android/server/SystemServer.java9
-rw-r--r--services/midi/java/com/android/server/midi/MidiService.java6
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeClamperTest.java2
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceContentTest.java145
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java (renamed from services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceTest.java)10
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java1
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java13
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/ServiceBindingOomAdjPolicyTest.java6
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java798
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/rollback/TEST_MAPPING12
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/am/UserControllerTest.java8
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/BiometricNotificationLoggerTest.java157
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java10
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java19
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapterTest.java3
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java19
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapterTest.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java10
-rw-r--r--services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/companion/virtual/SensorControllerTest.java3
-rw-r--r--services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java3
-rw-r--r--services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java5
-rw-r--r--services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java31
-rw-r--r--services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java6
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java154
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/search/SearchablesTest.java315
-rw-r--r--services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java58
-rw-r--r--services/tests/servicestests/src/com/android/server/utils/AnrTimerTest.java50
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java115
-rw-r--r--services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java35
-rw-r--r--services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java50
-rw-r--r--services/tests/wmtests/AndroidTest.xml7
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java6
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityStartControllerTests.java27
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java424
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java19
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java26
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncherTest.java101
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java147
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SensitiveContentPackagesTest.java134
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java322
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java5
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TrustedOverlayTests.java7
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java32
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java80
-rw-r--r--telephony/common/com/android/internal/telephony/util/TelephonyUtils.java19
-rw-r--r--telephony/java/android/telephony/CarrierConfigManager.java39
-rw-r--r--telephony/java/android/telephony/TelephonyManager.java8
-rw-r--r--telephony/java/android/telephony/data/ApnSetting.java55
-rw-r--r--tests/BootImageProfileTest/AndroidTest.xml1
-rw-r--r--tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt5
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/res/layout/activity_pip.xml16
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PipActivity.java25
-rw-r--r--tests/InputScreenshotTest/assets/phone/light_landscape_layout-preview.pngbin77207 -> 77171 bytes
-rw-r--r--tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview-ansi.pngbin70708 -> 70094 bytes
-rw-r--r--tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview-jis.pngbin73441 -> 72593 bytes
-rw-r--r--tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview.pngbin71619 -> 70807 bytes
-rw-r--r--tests/InputScreenshotTest/assets/tablet/dark_portrait_layout-preview.pngbin45707 -> 45677 bytes
-rw-r--r--tests/Internal/src/com/android/internal/protolog/OWNERS3
-rw-r--r--tests/vcn/Android.bp2
-rw-r--r--tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java19
-rw-r--r--wifi/TEST_MAPPING4
999 files changed, 26105 insertions, 7794 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 3f834fa883c1..20471682dd2e 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -82,7 +82,7 @@ aconfig_declarations_group {
"com.android.media.flags.bettertogether-aconfig-java",
"com.android.media.flags.editing-aconfig-java",
"com.android.media.flags.projection-aconfig-java",
- "com.android.net.thread.flags-aconfig-java",
+ "com.android.net.thread.platform.flags-aconfig-java",
"com.android.server.flags.services-aconfig-java",
"com.android.text.flags-aconfig-java",
"com.android.window.flags.window-aconfig-java",
@@ -786,8 +786,8 @@ aconfig_declarations {
// Thread network
aconfig_declarations {
- name: "com.android.net.thread.flags-aconfig",
- package: "com.android.net.thread.flags",
+ name: "com.android.net.thread.platform.flags-aconfig",
+ package: "com.android.net.thread.platform.flags",
srcs: ["core/java/android/net/thread/flags.aconfig"],
}
@@ -799,8 +799,8 @@ java_aconfig_library {
}
java_aconfig_library {
- name: "com.android.net.thread.flags-aconfig-java",
- aconfig_declarations: "com.android.net.thread.flags-aconfig",
+ name: "com.android.net.thread.platform.flags-aconfig-java",
+ aconfig_declarations: "com.android.net.thread.platform.flags-aconfig",
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
@@ -910,6 +910,8 @@ java_aconfig_library {
aconfig_declarations {
name: "android.service.notification.flags-aconfig",
package: "android.service.notification",
+ exportable: true,
+ container: "system",
srcs: ["core/java/android/service/notification/flags.aconfig"],
}
@@ -919,6 +921,18 @@ java_aconfig_library {
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+java_aconfig_library {
+ name: "android.service.notification.flags-aconfig-export-java",
+ aconfig_declarations: "android.service.notification.flags-aconfig",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+ mode: "exported",
+ min_sdk_version: "30",
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.extservices",
+ ],
+}
+
// Smartspace
aconfig_declarations {
name: "android.app.smartspace.flags-aconfig",
@@ -985,6 +999,11 @@ java_aconfig_library {
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+cc_aconfig_library {
+ name: "android.tracing.flags_c_lib",
+ aconfig_declarations: "android.tracing.flags-aconfig",
+}
+
// App Widgets
aconfig_declarations {
name: "android.appwidget.flags-aconfig",
diff --git a/Android.bp b/Android.bp
index 870df5a5723e..019bf6508774 100644
--- a/Android.bp
+++ b/Android.bp
@@ -508,7 +508,6 @@ java_library {
lint: {
baseline_filename: "lint-baseline.xml",
},
- // For jarjar repackaging
jarjar_prefix: "com.android.internal.hidden_from_bootclasspath",
}
diff --git a/LSE_APP_COMPAT_OWNERS b/LSE_APP_COMPAT_OWNERS
new file mode 100644
index 000000000000..3db0cd47ce65
--- /dev/null
+++ b/LSE_APP_COMPAT_OWNERS
@@ -0,0 +1,6 @@
+# Owners for the App Compat flags (large_screen_experiences_app_compat)
+mcarli@google.com
+eevlachavas@google.com
+gracielawputri@google.com
+minagranic@google.com
+mariiasand@google.com
diff --git a/Ravenwood.bp b/Ravenwood.bp
index 633702233cf4..2df6d5811d44 100644
--- a/Ravenwood.bp
+++ b/Ravenwood.bp
@@ -78,6 +78,73 @@ java_genrule {
}
java_library {
+ name: "services.core-for-hoststubgen",
+ installable: false, // host only jar.
+ static_libs: [
+ "services.core",
+ ],
+ sdk_version: "core_platform",
+ visibility: ["//visibility:private"],
+}
+
+java_genrule {
+ name: "services.core.ravenwood-base",
+ tools: ["hoststubgen"],
+ cmd: "$(location hoststubgen) " +
+ "@$(location ravenwood/ravenwood-standard-options.txt) " +
+
+ "--debug-log $(location hoststubgen_services.core.log) " +
+ "--stats-file $(location hoststubgen_services.core_stats.csv) " +
+
+ "--out-impl-jar $(location ravenwood.jar) " +
+
+ "--gen-keep-all-file $(location hoststubgen_keep_all.txt) " +
+ "--gen-input-dump-file $(location hoststubgen_dump.txt) " +
+
+ "--in-jar $(location :services.core-for-hoststubgen) " +
+ "--policy-override-file $(location ravenwood/services.core-ravenwood-policies.txt) " +
+ "--annotation-allowed-classes-file $(location ravenwood/ravenwood-annotation-allowed-classes.txt) ",
+ srcs: [
+ ":services.core-for-hoststubgen",
+ "ravenwood/services.core-ravenwood-policies.txt",
+ "ravenwood/ravenwood-standard-options.txt",
+ "ravenwood/ravenwood-annotation-allowed-classes.txt",
+ ],
+ out: [
+ "ravenwood.jar",
+
+ // Following files are created just as FYI.
+ "hoststubgen_keep_all.txt",
+ "hoststubgen_dump.txt",
+
+ "hoststubgen_services.core.log",
+ "hoststubgen_services.core_stats.csv",
+ ],
+ visibility: ["//visibility:private"],
+}
+
+java_genrule {
+ name: "services.core.ravenwood",
+ defaults: ["ravenwood-internal-only-visibility-genrule"],
+ cmd: "cp $(in) $(out)",
+ srcs: [
+ ":services.core.ravenwood-base{ravenwood.jar}",
+ ],
+ out: [
+ "services.core.ravenwood.jar",
+ ],
+}
+
+java_library {
+ name: "services.core.ravenwood-jarjar",
+ installable: false,
+ static_libs: [
+ "services.core.ravenwood",
+ ],
+ jarjar_rules: ":ravenwood-services-jarjar-rules",
+}
+
+java_library {
name: "mockito-ravenwood-prebuilt",
installable: false,
static_libs: [
@@ -121,6 +188,7 @@ android_ravenwood_libgroup {
"android.test.mock.ravenwood",
"ravenwood-helper-runtime",
"hoststubgen-helper-runtime.ravenwood",
+ "services.core.ravenwood-jarjar",
// Provide runtime versions of utils linked in below
"junit",
diff --git a/api/Android.bp b/api/Android.bp
index eeb76fbd81ef..8e063667826c 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -113,6 +113,7 @@ combined_apis {
"framework-nfc",
"framework-ondevicepersonalization",
"framework-pdf",
+ "framework-pdf-v",
"framework-permission",
"framework-permission-s",
"framework-profiling",
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index d79131ca5d7c..3b16cab06bab 100644
--- a/cmds/am/src/com/android/commands/am/Am.java
+++ b/cmds/am/src/com/android/commands/am/Am.java
@@ -183,6 +183,8 @@ public class Am extends BaseCommand {
instrument.disableTestApiChecks = false;
} else if (opt.equals("--no-isolated-storage")) {
instrument.disableIsolatedStorage = true;
+ } else if (opt.equals("--no-logcat")) {
+ instrument.captureLogcat = false;
} else if (opt.equals("--user")) {
instrument.userId = parseUserArg(nextArgRequired());
} else if (opt.equals("--abi")) {
diff --git a/cmds/am/src/com/android/commands/am/Instrument.java b/cmds/am/src/com/android/commands/am/Instrument.java
index e60593e8b633..e0d949e04a92 100644
--- a/cmds/am/src/com/android/commands/am/Instrument.java
+++ b/cmds/am/src/com/android/commands/am/Instrument.java
@@ -85,6 +85,7 @@ public class Instrument {
public String profileFile = null;
public boolean wait = false;
public boolean rawMode = false;
+ public boolean captureLogcat = true;
boolean protoStd = false; // write proto to stdout
boolean protoFile = false; // write proto to a file
String logPath = null;
@@ -266,16 +267,18 @@ public class Instrument {
proto.write(InstrumentationData.TestStatus.RESULT_CODE, resultCode);
writeBundle(proto, InstrumentationData.TestStatus.RESULTS, results);
- if (resultCode == STATUS_TEST_STARTED) {
- // Logcat -T takes wall clock time (!?)
- mTestStartMs = System.currentTimeMillis();
- } else {
- if (mTestStartMs > 0) {
- proto.write(InstrumentationData.TestStatus.LOGCAT, readLogcat(mTestStartMs));
+ if (captureLogcat) {
+ if (resultCode == STATUS_TEST_STARTED) {
+ // Logcat -T takes wall clock time (!?)
+ mTestStartMs = System.currentTimeMillis();
+ } else {
+ if (mTestStartMs > 0) {
+ proto.write(InstrumentationData.TestStatus.LOGCAT,
+ readLogcat(mTestStartMs));
+ }
+ mTestStartMs = 0;
}
- mTestStartMs = 0;
}
-
proto.end(testStatusToken);
outputProto(proto);
diff --git a/core/api/current.txt b/core/api/current.txt
index 4a2abf69c503..e8eace1c51c8 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -7024,6 +7024,7 @@ package android.app {
method public void deleteNotificationChannelGroup(String);
method public android.service.notification.StatusBarNotification[] getActiveNotifications();
method public android.app.AutomaticZenRule getAutomaticZenRule(String);
+ method @FlaggedApi("android.app.modes_api") public int getAutomaticZenRuleState(@NonNull String);
method public java.util.Map<java.lang.String,android.app.AutomaticZenRule> getAutomaticZenRules();
method public int getBubblePreference();
method @NonNull public android.app.NotificationManager.Policy getConsolidatedNotificationPolicy();
@@ -7238,8 +7239,8 @@ package android.app {
public final class PictureInPictureUiState implements android.os.Parcelable {
method public int describeContents();
- method @FlaggedApi("android.app.enable_pip_ui_state_callback_on_entering") public boolean isEnteringPip();
method public boolean isStashed();
+ method @FlaggedApi("android.app.enable_pip_ui_state_callback_on_entering") public boolean isTransitioningToPip();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.PictureInPictureUiState> CREATOR;
}
@@ -10088,7 +10089,7 @@ package android.content {
method public CharSequence coerceToText(android.content.Context);
method public String getHtmlText();
method public android.content.Intent getIntent();
- method @FlaggedApi("com.android.window.flags.delegate_unhandled_drags") @Nullable public android.app.PendingIntent getPendingIntent();
+ method @FlaggedApi("com.android.window.flags.delegate_unhandled_drags") @Nullable public android.content.IntentSender getIntentSender();
method public CharSequence getText();
method @Nullable public android.view.textclassifier.TextLinks getTextLinks();
method public android.net.Uri getUri();
@@ -10099,7 +10100,7 @@ package android.content {
method @FlaggedApi("com.android.window.flags.delegate_unhandled_drags") @NonNull public android.content.ClipData.Item build();
method @FlaggedApi("com.android.window.flags.delegate_unhandled_drags") @NonNull public android.content.ClipData.Item.Builder setHtmlText(@Nullable String);
method @FlaggedApi("com.android.window.flags.delegate_unhandled_drags") @NonNull public android.content.ClipData.Item.Builder setIntent(@Nullable android.content.Intent);
- method @FlaggedApi("com.android.window.flags.delegate_unhandled_drags") @NonNull public android.content.ClipData.Item.Builder setPendingIntent(@Nullable android.app.PendingIntent);
+ method @FlaggedApi("com.android.window.flags.delegate_unhandled_drags") @NonNull public android.content.ClipData.Item.Builder setIntentSender(@Nullable android.content.IntentSender);
method @FlaggedApi("com.android.window.flags.delegate_unhandled_drags") @NonNull public android.content.ClipData.Item.Builder setText(@Nullable CharSequence);
method @FlaggedApi("com.android.window.flags.delegate_unhandled_drags") @NonNull public android.content.ClipData.Item.Builder setUri(@Nullable android.net.Uri);
}
@@ -11074,6 +11075,7 @@ package android.content {
method public boolean hasCategory(String);
method public boolean hasExtra(String);
method public boolean hasFileDescriptors();
+ method @FlaggedApi("android.security.enforce_intent_filter_match") public boolean isMismatchingFilter();
method public static android.content.Intent makeMainActivity(android.content.ComponentName);
method public static android.content.Intent makeMainSelectorActivity(String, String);
method public static android.content.Intent makeRestartActivityTask(android.content.ComponentName);
@@ -53136,7 +53138,7 @@ package android.view {
field public static final int DRAG_FLAG_GLOBAL_URI_READ = 1; // 0x1
field public static final int DRAG_FLAG_GLOBAL_URI_WRITE = 2; // 0x2
field public static final int DRAG_FLAG_OPAQUE = 512; // 0x200
- field @FlaggedApi("com.android.window.flags.delegate_unhandled_drags") public static final int DRAG_FLAG_START_PENDING_INTENT_ON_UNHANDLED_DRAG = 8192; // 0x2000
+ field @FlaggedApi("com.android.window.flags.delegate_unhandled_drags") public static final int DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG = 8192; // 0x2000
field @Deprecated public static final int DRAWING_CACHE_QUALITY_AUTO = 0; // 0x0
field @Deprecated public static final int DRAWING_CACHE_QUALITY_HIGH = 1048576; // 0x100000
field @Deprecated public static final int DRAWING_CACHE_QUALITY_LOW = 524288; // 0x80000
@@ -56276,6 +56278,7 @@ package android.view.inputmethod {
public final class InputMethodManager {
method public boolean acceptStylusHandwritingDelegation(@NonNull android.view.View);
method public boolean acceptStylusHandwritingDelegation(@NonNull android.view.View, @NonNull String);
+ method @FlaggedApi("android.view.inputmethod.use_zero_jank_proxy") public void acceptStylusHandwritingDelegation(@NonNull android.view.View, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
method @FlaggedApi("android.view.inputmethod.home_screen_handwriting_delegator") public boolean acceptStylusHandwritingDelegation(@NonNull android.view.View, @NonNull String, int);
method public void dispatchKeyEventFromInputMethod(@Nullable android.view.View, @NonNull android.view.KeyEvent);
method public void displayCompletions(android.view.View, android.view.inputmethod.CompletionInfo[]);
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 7c4df280eaa7..9c1a8e854e92 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -135,7 +135,7 @@ package android.content.pm {
@FlaggedApi("android.permission.flags.enhanced_confirmation_mode_apis_enabled") public class SignedPackage {
method @NonNull public byte[] getCertificateDigest();
- method @NonNull public String getPkgName();
+ method @NonNull public String getPackageName();
}
}
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index d339992129dc..6b784d2c548e 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -2193,6 +2193,139 @@ package android.app.job {
}
+package android.app.ondeviceintelligence {
+
+ @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class Content implements android.os.Parcelable {
+ ctor public Content(@NonNull android.os.Bundle);
+ method public int describeContents();
+ method @NonNull public android.os.Bundle getData();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.ondeviceintelligence.Content> CREATOR;
+ }
+
+ @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public interface DownloadCallback {
+ method public void onDownloadCompleted(@NonNull android.os.PersistableBundle);
+ method public void onDownloadFailed(int, @Nullable String, @NonNull android.os.PersistableBundle);
+ method public default void onDownloadProgress(long);
+ method public default void onDownloadStarted(long);
+ field public static final int DOWNLOAD_FAILURE_STATUS_DOWNLOADING = 3; // 0x3
+ field public static final int DOWNLOAD_FAILURE_STATUS_NETWORK_FAILURE = 2; // 0x2
+ field public static final int DOWNLOAD_FAILURE_STATUS_NOT_ENOUGH_DISK_SPACE = 1; // 0x1
+ field public static final int DOWNLOAD_FAILURE_STATUS_UNAVAILABLE = 4; // 0x4
+ field public static final int DOWNLOAD_FAILURE_STATUS_UNKNOWN = 0; // 0x0
+ }
+
+ @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class Feature implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public android.os.PersistableBundle getFeatureParams();
+ method public int getId();
+ method @Nullable public String getModelName();
+ method @Nullable public String getName();
+ method public int getType();
+ method public int getVariant();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.ondeviceintelligence.Feature> CREATOR;
+ }
+
+ public static final class Feature.Builder {
+ ctor public Feature.Builder(int, int, int, @NonNull android.os.PersistableBundle);
+ method @NonNull public android.app.ondeviceintelligence.Feature build();
+ method @NonNull public android.app.ondeviceintelligence.Feature.Builder setFeatureParams(@NonNull android.os.PersistableBundle);
+ method @NonNull public android.app.ondeviceintelligence.Feature.Builder setId(int);
+ method @NonNull public android.app.ondeviceintelligence.Feature.Builder setModelName(@NonNull String);
+ method @NonNull public android.app.ondeviceintelligence.Feature.Builder setName(@NonNull String);
+ method @NonNull public android.app.ondeviceintelligence.Feature.Builder setType(int);
+ method @NonNull public android.app.ondeviceintelligence.Feature.Builder setVariant(int);
+ }
+
+ @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class FeatureDetails implements android.os.Parcelable {
+ ctor public FeatureDetails(@android.app.ondeviceintelligence.FeatureDetails.Status int, @NonNull android.os.PersistableBundle);
+ ctor public FeatureDetails(@android.app.ondeviceintelligence.FeatureDetails.Status int);
+ method public int describeContents();
+ method @NonNull public android.os.PersistableBundle getFeatureDetailParams();
+ method @android.app.ondeviceintelligence.FeatureDetails.Status public int getStatus();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.ondeviceintelligence.FeatureDetails> CREATOR;
+ field public static final int FEATURE_STATUS_AVAILABLE = 3; // 0x3
+ field public static final int FEATURE_STATUS_DOWNLOADABLE = 1; // 0x1
+ field public static final int FEATURE_STATUS_DOWNLOADING = 2; // 0x2
+ field public static final int FEATURE_STATUS_SERVICE_UNAVAILABLE = 4; // 0x4
+ field public static final int FEATURE_STATUS_UNAVAILABLE = 0; // 0x0
+ }
+
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE_USE, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD}) public static @interface FeatureDetails.Status {
+ }
+
+ @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class FilePart implements android.os.Parcelable {
+ ctor public FilePart(@NonNull String, @NonNull android.os.PersistableBundle, @NonNull String) throws java.io.FileNotFoundException;
+ ctor public FilePart(@NonNull String, @NonNull android.os.PersistableBundle, @NonNull java.io.FileInputStream) throws java.io.IOException;
+ method public int describeContents();
+ method @NonNull public java.io.FileInputStream getFileInputStream();
+ method @NonNull public String getFilePartKey();
+ method @NonNull public android.os.PersistableBundle getFilePartParams();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.ondeviceintelligence.FilePart> CREATOR;
+ }
+
+ @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public class OnDeviceIntelligenceManager {
+ method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void getFeature(int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.Feature,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
+ method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void getFeatureDetails(@NonNull android.app.ondeviceintelligence.Feature, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.FeatureDetails,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
+ method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void getVersion(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.LongConsumer);
+ method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void listFeatures(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.util.List<android.app.ondeviceintelligence.Feature>,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
+ method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void processRequest(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.Content,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException>);
+ method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void processRequestStreaming(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.StreamingResponseReceiver<android.app.ondeviceintelligence.Content,android.app.ondeviceintelligence.Content,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException>);
+ method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void requestFeatureDownload(@NonNull android.app.ondeviceintelligence.Feature, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.DownloadCallback);
+ method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void requestTokenCount(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Long,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
+ field public static final String API_VERSION_BUNDLE_KEY = "ApiVersionBundleKey";
+ field public static final int REQUEST_TYPE_EMBEDDINGS = 2; // 0x2
+ field public static final int REQUEST_TYPE_INFERENCE = 0; // 0x0
+ field public static final int REQUEST_TYPE_PREPARE = 1; // 0x1
+ }
+
+ public static class OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException extends java.lang.Exception {
+ ctor public OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException(int, @NonNull String, @NonNull android.os.PersistableBundle);
+ ctor public OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException(int, @NonNull android.os.PersistableBundle);
+ method public int getErrorCode();
+ method @NonNull public android.os.PersistableBundle getErrorParams();
+ field public static final int ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE = 1000; // 0x3e8
+ }
+
+ public static class OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException extends android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException {
+ ctor public OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException(int, @NonNull String, @NonNull android.os.PersistableBundle);
+ ctor public OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException(int, @NonNull android.os.PersistableBundle);
+ field public static final int PROCESSING_ERROR_BAD_DATA = 2; // 0x2
+ field public static final int PROCESSING_ERROR_BAD_REQUEST = 3; // 0x3
+ field public static final int PROCESSING_ERROR_BUSY = 9; // 0x9
+ field public static final int PROCESSING_ERROR_CANCELLED = 7; // 0x7
+ field public static final int PROCESSING_ERROR_COMPUTE_ERROR = 5; // 0x5
+ field public static final int PROCESSING_ERROR_INTERNAL = 14; // 0xe
+ field public static final int PROCESSING_ERROR_IPC_ERROR = 6; // 0x6
+ field public static final int PROCESSING_ERROR_NOT_AVAILABLE = 8; // 0x8
+ field public static final int PROCESSING_ERROR_REQUEST_NOT_SAFE = 4; // 0x4
+ field public static final int PROCESSING_ERROR_REQUEST_TOO_LARGE = 12; // 0xc
+ field public static final int PROCESSING_ERROR_RESPONSE_NOT_SAFE = 11; // 0xb
+ field public static final int PROCESSING_ERROR_SAFETY_ERROR = 10; // 0xa
+ field public static final int PROCESSING_ERROR_SERVICE_UNAVAILABLE = 15; // 0xf
+ field public static final int PROCESSING_ERROR_SUSPENDED = 13; // 0xd
+ field public static final int PROCESSING_ERROR_UNKNOWN = 1; // 0x1
+ }
+
+ @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class ProcessingSignal {
+ ctor public ProcessingSignal();
+ method public void sendSignal(@NonNull android.os.PersistableBundle);
+ method public void setOnProcessingSignalCallback(@NonNull java.util.concurrent.Executor, @Nullable android.app.ondeviceintelligence.ProcessingSignal.OnProcessingSignalCallback);
+ }
+
+ public static interface ProcessingSignal.OnProcessingSignalCallback {
+ method public void onSignalReceived(@NonNull android.os.PersistableBundle);
+ }
+
+ @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public interface StreamingResponseReceiver<R, T, E extends java.lang.Throwable> extends android.os.OutcomeReceiver<R,E> {
+ method public void onNewContent(@NonNull T);
+ }
+
+}
+
package android.app.people {
public final class PeopleManager {
@@ -3206,9 +3339,9 @@ package android.app.wearable {
public class WearableSensingManager {
method @FlaggedApi("android.app.wearable.enable_data_request_observer_api") @Nullable public static android.app.wearable.WearableSensingDataRequest getDataRequestFromIntent(@NonNull android.content.Intent);
+ method @FlaggedApi("android.app.wearable.enable_provide_wearable_connection_api") @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void provideConnection(@NonNull android.os.ParcelFileDescriptor, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void provideData(@NonNull android.os.PersistableBundle, @Nullable android.os.SharedMemory, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void provideDataStream(@NonNull android.os.ParcelFileDescriptor, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
- method @FlaggedApi("android.app.wearable.enable_provide_wearable_connection_api") @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void provideWearableConnection(@NonNull android.os.ParcelFileDescriptor, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @FlaggedApi("android.app.wearable.enable_data_request_observer_api") @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void registerDataRequestObserver(int, @NonNull android.app.PendingIntent, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @FlaggedApi("android.app.wearable.enable_hotword_wearable_sensing_api") @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void startHotwordRecognition(@Nullable android.content.ComponentName, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @FlaggedApi("android.app.wearable.enable_hotword_wearable_sensing_api") @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void stopHotwordRecognition(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
@@ -3640,6 +3773,7 @@ package android.content {
field public static final String NETD_SERVICE = "netd";
field @Deprecated public static final String NETWORK_SCORE_SERVICE = "network_score";
field public static final String OEM_LOCK_SERVICE = "oem_lock";
+ field @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public static final String ON_DEVICE_INTELLIGENCE_SERVICE = "on_device_intelligence";
field public static final String PERMISSION_CONTROLLER_SERVICE = "permission_controller";
field public static final String PERMISSION_SERVICE = "permission";
field public static final String PERSISTENT_DATA_BLOCK_SERVICE = "persistent_data_block";
@@ -12824,6 +12958,34 @@ package android.service.oemlock {
}
+package android.service.ondeviceintelligence {
+
+ @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public abstract class OnDeviceIntelligenceService extends android.app.Service {
+ ctor public OnDeviceIntelligenceService();
+ method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
+ method public abstract void onDownloadFeature(@NonNull android.app.ondeviceintelligence.Feature, @Nullable android.os.CancellationSignal, @NonNull android.app.ondeviceintelligence.DownloadCallback);
+ method public abstract void onGetFeature(int, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.Feature,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
+ method public abstract void onGetFeatureDetails(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.FeatureDetails,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
+ method public abstract void onGetReadOnlyFeatureFileDescriptorMap(@NonNull android.app.ondeviceintelligence.Feature, @NonNull java.util.function.Consumer<java.util.Map<java.lang.String,android.os.ParcelFileDescriptor>>);
+ method public abstract void onGetVersion(@NonNull java.util.function.LongConsumer);
+ method public abstract void onListFeatures(@NonNull android.os.OutcomeReceiver<java.util.List<android.app.ondeviceintelligence.Feature>,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
+ field public static final String SERVICE_INTERFACE = "android.service.ondeviceintelligence.OnDeviceIntelligenceService";
+ }
+
+ @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public abstract class OnDeviceTrustedInferenceService extends android.app.Service {
+ ctor public OnDeviceTrustedInferenceService();
+ method public final void fetchFeatureFileInputStreamMap(@NonNull android.app.ondeviceintelligence.Feature, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.util.Map<java.lang.String,java.io.FileInputStream>>);
+ method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
+ method @NonNull public abstract void onCountTokens(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, @Nullable android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<java.lang.Long,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException>);
+ method @NonNull public abstract void onProcessRequest(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.Content,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException>);
+ method @NonNull public abstract void onProcessRequestStreaming(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull android.app.ondeviceintelligence.StreamingResponseReceiver<android.app.ondeviceintelligence.Content,android.app.ondeviceintelligence.Content,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException>);
+ method public final java.io.FileInputStream openFileInput(@NonNull String) throws java.io.FileNotFoundException;
+ method public final void openFileInputAsync(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.io.FileInputStream>) throws java.io.FileNotFoundException;
+ field public static final String SERVICE_INTERFACE = "android.service.ondeviceintelligence.OnDeviceTrustedInferenceService";
+ }
+
+}
+
package android.service.persistentdata {
@FlaggedApi("android.security.frp_enforcement") public class PersistentDataBlockManager {
@@ -13603,7 +13765,7 @@ package android.service.wearable {
method @FlaggedApi("android.app.wearable.enable_data_request_observer_api") @BinderThread public void onDataRequestObserverUnregistered(int, @NonNull String, @NonNull android.service.wearable.WearableSensingDataRequester, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @BinderThread public abstract void onDataStreamProvided(@NonNull android.os.ParcelFileDescriptor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @BinderThread public abstract void onQueryServiceStatus(@NonNull java.util.Set<java.lang.Integer>, @NonNull String, @NonNull java.util.function.Consumer<android.service.ambientcontext.AmbientContextDetectionServiceStatus>);
- method @FlaggedApi("android.app.wearable.enable_provide_wearable_connection_api") @BinderThread public void onSecureWearableConnectionProvided(@NonNull android.os.ParcelFileDescriptor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+ method @FlaggedApi("android.app.wearable.enable_provide_wearable_connection_api") @BinderThread public void onSecureConnectionProvided(@NonNull android.os.ParcelFileDescriptor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @BinderThread public abstract void onStartDetection(@NonNull android.app.ambientcontext.AmbientContextEventRequest, @NonNull String, @NonNull java.util.function.Consumer<android.service.ambientcontext.AmbientContextDetectionServiceStatus>, @NonNull java.util.function.Consumer<android.service.ambientcontext.AmbientContextDetectionResult>);
method @FlaggedApi("android.app.wearable.enable_hotword_wearable_sensing_api") @BinderThread public void onStartHotwordRecognition(@NonNull java.util.function.Consumer<android.service.voice.HotwordAudioStream>, @NonNull java.util.function.Consumer<java.lang.Integer>);
method public abstract void onStopDetection(@NonNull String);
diff --git a/core/api/system-lint-baseline.txt b/core/api/system-lint-baseline.txt
index ca9fab815167..1923641e2d4e 100644
--- a/core/api/system-lint-baseline.txt
+++ b/core/api/system-lint-baseline.txt
@@ -509,6 +509,12 @@ GenericException: android.service.autofill.augmented.FillWindow#finalize():
Methods must not throw generic exceptions (`java.lang.Throwable`)
+InvalidNullabilityOverride: android.service.ondeviceintelligence.OnDeviceIntelligenceService#onBind(android.content.Intent) parameter #0:
+ Invalid nullability on parameter `intent` in method `onBind`. Parameters of overrides cannot be NonNull if the super parameter is unannotated.
+InvalidNullabilityOverride: android.service.ondeviceintelligence.OnDeviceTrustedInferenceService#onBind(android.content.Intent) parameter #0:
+ Invalid nullability on parameter `intent` in method `onBind`. Parameters of overrides cannot be NonNull if the super parameter is unannotated.
+InvalidNullabilityOverride: android.service.ondeviceintelligence.OnDeviceTrustedInferenceService#openFileInput(String) parameter #0:
+ Invalid nullability on parameter `filename` in method `openFileInput`. Parameters of overrides cannot be NonNull if the super parameter is unannotated.
InvalidNullabilityOverride: android.service.textclassifier.TextClassifierService#onUnbind(android.content.Intent) parameter #0:
Invalid nullability on parameter `intent` in method `onUnbind`. Parameters of overrides cannot be NonNull if the super parameter is unannotated.
InvalidNullabilityOverride: android.service.voice.HotwordDetectionService#getSystemService(String) parameter #0:
@@ -565,6 +571,8 @@ MissingNullability: android.service.contentcapture.ContentCaptureService#dump(ja
Missing nullability on parameter `args` in method `dump`
MissingNullability: android.service.notification.NotificationAssistantService#attachBaseContext(android.content.Context) parameter #0:
Missing nullability on parameter `base` in method `attachBaseContext`
+MissingNullability: android.service.ondeviceintelligence.OnDeviceTrustedInferenceService#openFileInput(String):
+ Missing nullability on method `openFileInput` return
MissingNullability: android.telephony.NetworkService#onUnbind(android.content.Intent) parameter #0:
Missing nullability on parameter `intent` in method `onUnbind`
MissingNullability: android.telephony.data.DataService#onUnbind(android.content.Intent) parameter #0:
@@ -1877,6 +1885,8 @@ Todo: android.Manifest.permission#READ_PEOPLE_DATA:
Documentation mentions 'TODO'
Todo: android.app.NotificationManager#isNotificationAssistantAccessGranted(android.content.ComponentName):
Documentation mentions 'TODO'
+Todo: android.app.ondeviceintelligence.OnDeviceIntelligenceManager#requestFeatureDownload(android.app.ondeviceintelligence.Feature, android.app.ondeviceintelligence.CancellationSignal, java.util.concurrent.Executor, android.app.ondeviceintelligence.DownloadCallback):
+ Documentation mentions 'TODO'
Todo: android.hardware.camera2.params.StreamConfigurationMap:
Documentation mentions 'TODO'
Todo: android.hardware.location.ContextHubManager#getNanoAppInstanceInfo(int):
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index de4be3cee0fb..e1e9d09f46fd 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1032,7 +1032,10 @@ package android.content {
}
public class Intent implements java.lang.Cloneable android.os.Parcelable {
+ method @NonNull public android.content.Intent addExtendedFlags(int);
+ method public int getExtendedFlags();
field public static final String ACTION_USER_STOPPED = "android.intent.action.USER_STOPPED";
+ field public static final int EXTENDED_FLAG_FILTER_MISMATCH = 1; // 0x1
}
public class SyncAdapterType implements android.os.Parcelable {
@@ -1548,6 +1551,7 @@ package android.hardware.biometrics {
public static class BiometricPrompt.Builder {
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.TEST_BIOMETRIC, "android.permission.USE_BIOMETRIC_INTERNAL"}) public android.hardware.biometrics.BiometricPrompt.Builder setAllowBackgroundAuthentication(boolean);
+ method @FlaggedApi("android.multiuser.enable_biometrics_to_unlock_private_space") @NonNull @RequiresPermission(anyOf={android.Manifest.permission.TEST_BIOMETRIC, "android.permission.USE_BIOMETRIC_INTERNAL"}) public android.hardware.biometrics.BiometricPrompt.Builder setAllowBackgroundAuthentication(boolean, boolean);
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.TEST_BIOMETRIC, "android.permission.USE_BIOMETRIC_INTERNAL"}) public android.hardware.biometrics.BiometricPrompt.Builder setAllowedSensorIds(@NonNull java.util.List<java.lang.Integer>);
}
@@ -1702,6 +1706,7 @@ package android.hardware.display {
field public static final int SWITCHING_TYPE_WITHIN_GROUPS = 1; // 0x1
field public static final int VIRTUAL_DISPLAY_FLAG_OWN_FOCUS = 16384; // 0x4000
field public static final int VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS = 512; // 0x200
+ field public static final int VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH = 64; // 0x40
}
}
@@ -1750,6 +1755,10 @@ package android.hardware.input {
field public static final int DEFAULT_POINTER_SPEED = 0; // 0x0
}
+ public class VirtualKeyboard implements java.io.Closeable {
+ method public int getInputDeviceId();
+ }
+
}
package android.hardware.lights {
@@ -3057,6 +3066,14 @@ package android.service.autofill.augmented {
}
+package android.service.chooser {
+
+ @FlaggedApi("android.service.chooser.enable_chooser_result") public final class ChooserResult implements android.os.Parcelable {
+ ctor public ChooserResult(int, @Nullable android.content.ComponentName, boolean);
+ }
+
+}
+
package android.service.dreams {
public abstract class DreamOverlayService extends android.app.Service {
diff --git a/core/api/test-lint-baseline.txt b/core/api/test-lint-baseline.txt
index c1181f5b8233..658ddbf0abfd 100644
--- a/core/api/test-lint-baseline.txt
+++ b/core/api/test-lint-baseline.txt
@@ -1931,6 +1931,8 @@ Todo: android.Manifest.permission#READ_PEOPLE_DATA:
Documentation mentions 'TODO'
Todo: android.app.NotificationManager#isNotificationAssistantAccessGranted(android.content.ComponentName):
Documentation mentions 'TODO'
+Todo: android.app.ondeviceintelligence.OnDeviceIntelligenceManager#requestFeatureDownload(android.app.ondeviceintelligence.Feature, android.app.ondeviceintelligence.CancellationSignal, java.util.concurrent.Executor, android.app.ondeviceintelligence.DownloadCallback):
+ Documentation mentions 'TODO'
Todo: android.hardware.camera2.params.StreamConfigurationMap:
Documentation mentions 'TODO'
Todo: android.hardware.location.ContextHubManager#getNanoAppInstanceInfo(int):
@@ -2017,6 +2019,12 @@ UnflaggedApi: android.content.AttributionSource#AttributionSource(int, int, Stri
New API must be flagged with @FlaggedApi: constructor android.content.AttributionSource(int,int,String,String,android.os.IBinder,String[],android.content.AttributionSource)
UnflaggedApi: android.content.AttributionSource#AttributionSource(int, int, String, String, android.os.IBinder, String[], int, android.content.AttributionSource):
New API must be flagged with @FlaggedApi: constructor android.content.AttributionSource(int,int,String,String,android.os.IBinder,String[],int,android.content.AttributionSource)
+UnflaggedApi: android.content.Intent#EXTENDED_FLAG_FILTER_MISMATCH:
+ New API must be flagged with @FlaggedApi: field android.content.Intent.EXTENDED_FLAG_FILTER_MISMATCH
+UnflaggedApi: android.content.Intent#addExtendedFlags(int):
+ New API must be flagged with @FlaggedApi: method android.content.Intent.addExtendedFlags(int)
+UnflaggedApi: android.content.Intent#getExtendedFlags():
+ New API must be flagged with @FlaggedApi: method android.content.Intent.getExtendedFlags()
UnflaggedApi: android.content.pm.UserInfo#isCommunalProfile():
New API must be flagged with @FlaggedApi: method android.content.pm.UserInfo.isCommunalProfile()
UnflaggedApi: android.content.pm.UserInfo#isPrivateProfile():
diff --git a/core/java/android/animation/Animator.java b/core/java/android/animation/Animator.java
index 4cad58521c09..c58624e9dfab 100644
--- a/core/java/android/animation/Animator.java
+++ b/core/java/android/animation/Animator.java
@@ -26,6 +26,7 @@ import android.os.Build;
import android.util.LongArray;
import java.util.ArrayList;
+import java.util.concurrent.atomic.AtomicReference;
/**
* This is the superclass for classes which provide basic support for animations which can be
@@ -76,7 +77,7 @@ public abstract class Animator implements Cloneable {
* of it in case the list is modified while iterating. The array can be reused to avoid
* allocation on every notification.
*/
- private Object[] mCachedList;
+ private AtomicReference<Object[]> mCachedList = new AtomicReference<>();
/**
* Tracks whether we've notified listeners of the onAnimationStart() event. This can be
@@ -452,7 +453,7 @@ public abstract class Animator implements Cloneable {
if (mPauseListeners != null) {
anim.mPauseListeners = new ArrayList<AnimatorPauseListener>(mPauseListeners);
}
- anim.mCachedList = null;
+ anim.mCachedList.set(null);
anim.mStartListenersCalled = false;
return anim;
} catch (CloneNotSupportedException e) {
@@ -654,13 +655,9 @@ public abstract class Animator implements Cloneable {
int size = list == null ? 0 : list.size();
if (size > 0) {
// Try to reuse mCacheList to store the items of list.
- Object[] array;
- if (mCachedList == null || mCachedList.length < size) {
+ Object[] array = mCachedList.getAndSet(null);
+ if (array == null || array.length < size) {
array = new Object[size];
- } else {
- array = mCachedList;
- // Clear it in case there is some reentrancy
- mCachedList = null;
}
list.toArray(array);
for (int i = 0; i < size; i++) {
@@ -670,7 +667,7 @@ public abstract class Animator implements Cloneable {
array[i] = null;
}
// Store it for the next call so we can reuse this array, if needed.
- mCachedList = array;
+ mCachedList.compareAndSet(null, array);
}
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 926e297a1098..ae556c91ab6d 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -237,7 +237,6 @@ import com.android.internal.util.Preconditions;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.org.conscrypt.TrustedCertificateStore;
import com.android.server.am.MemInfoDumpProto;
-import com.android.window.flags.Flags;
import dalvik.annotation.optimization.NeverCompile;
import dalvik.system.AppSpecializationHooks;
@@ -1234,7 +1233,8 @@ public final class ActivityThread extends ClientTransactionHandler
}
@Override
- public final void scheduleTimeoutServiceForType(IBinder token, int startId, int fgsType) {
+ public final void scheduleTimeoutServiceForType(IBinder token, int startId,
+ @ServiceInfo.ForegroundServiceType int fgsType) {
if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
Trace.instant(Trace.TRACE_TAG_ACTIVITY_MANAGER,
"scheduleTimeoutServiceForType. token=" + token);
@@ -3762,11 +3762,7 @@ public final class ActivityThread extends ClientTransactionHandler
final ClientTransaction clientTransaction = ClientTransaction.obtain(mAppThread);
final ActivityResultItem activityResultItem = ActivityResultItem.obtain(
activityToken, list);
- if (Flags.bundleClientTransactionFlag()) {
- clientTransaction.addTransactionItem(activityResultItem);
- } else {
- clientTransaction.addCallback(activityResultItem);
- }
+ clientTransaction.addTransactionItem(activityResultItem);
try {
mAppThread.scheduleTransaction(clientTransaction);
} catch (RemoteException e) {
@@ -4553,11 +4549,7 @@ public final class ActivityThread extends ClientTransactionHandler
final PauseActivityItem pauseActivityItem = PauseActivityItem.obtain(r.token,
r.activity.isFinishing(), /* userLeaving */ true, r.activity.mConfigChangeFlags,
/* dontReport */ false, /* autoEnteringPip */ false);
- if (Flags.bundleClientTransactionFlag()) {
- transaction.addTransactionItem(pauseActivityItem);
- } else {
- transaction.setLifecycleStateRequest(pauseActivityItem);
- }
+ transaction.addTransactionItem(pauseActivityItem);
executeTransaction(transaction);
}
@@ -4565,11 +4557,7 @@ public final class ActivityThread extends ClientTransactionHandler
final ClientTransaction transaction = ClientTransaction.obtain(mAppThread);
final ResumeActivityItem resumeActivityItem = ResumeActivityItem.obtain(r.token,
/* isForward */ false, /* shouldSendCompatFakeFocus */ false);
- if (Flags.bundleClientTransactionFlag()) {
- transaction.addTransactionItem(resumeActivityItem);
- } else {
- transaction.setLifecycleStateRequest(resumeActivityItem);
- }
+ transaction.addTransactionItem(resumeActivityItem);
executeTransaction(transaction);
}
@@ -5164,7 +5152,8 @@ public final class ActivityThread extends ClientTransactionHandler
}
}
- private void handleTimeoutServiceForType(IBinder token, int startId, int fgsType) {
+ private void handleTimeoutServiceForType(IBinder token, int startId,
+ @ServiceInfo.ForegroundServiceType int fgsType) {
Service s = mServices.get(token);
if (s != null) {
try {
@@ -6189,13 +6178,8 @@ public final class ActivityThread extends ClientTransactionHandler
TransactionExecutorHelper.getLifecycleRequestForCurrentState(r);
// Schedule the transaction.
final ClientTransaction transaction = ClientTransaction.obtain(mAppThread);
- if (Flags.bundleClientTransactionFlag()) {
- transaction.addTransactionItem(activityRelaunchItem);
- transaction.addTransactionItem(lifecycleRequest);
- } else {
- transaction.addCallback(activityRelaunchItem);
- transaction.setLifecycleStateRequest(lifecycleRequest);
- }
+ transaction.addTransactionItem(activityRelaunchItem);
+ transaction.addTransactionItem(lifecycleRequest);
executeTransaction(transaction);
}
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 3ec39b5145a7..dd6bc55421ef 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -120,6 +120,7 @@ import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.LauncherIcons;
import android.util.Log;
+import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.Immutable;
@@ -4047,11 +4048,16 @@ public class ApplicationPackageManager extends PackageManager {
@Nullable
private Drawable getArchivedAppIcon(String packageName) {
try {
- return new BitmapDrawable(null,
- mPM.getArchivedAppIcon(packageName, new UserHandle(getUserId()),
- mContext.getPackageName()));
+ Bitmap archivedAppIcon = mPM.getArchivedAppIcon(packageName,
+ new UserHandle(getUserId()),
+ mContext.getPackageName());
+ if (archivedAppIcon == null) {
+ return null;
+ }
+ return new BitmapDrawable(null, archivedAppIcon);
} catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
+ Slog.e(TAG, "Failed to retrieve archived app icon: " + e.getMessage());
+ return null;
}
}
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index b5e355638ae8..8f81ae2ae7d6 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -225,6 +225,7 @@ interface INotificationManager
boolean removeAutomaticZenRule(String id, boolean fromUser);
boolean removeAutomaticZenRules(String packageName, boolean fromUser);
int getRuleInstanceCount(in ComponentName owner);
+ int getAutomaticZenRuleState(String id);
void setAutomaticZenRuleState(String id, in Condition condition);
byte[] getBackupPayload(int user);
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index be7199b9a0fc..db216b1af974 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -145,7 +145,7 @@ public class Instrumentation {
* reflection, but it will serve as noticeable discouragement from
* doing such a thing.
*/
- @android.ravenwood.annotation.RavenwoodReplace
+ @android.ravenwood.annotation.RavenwoodKeep
private void checkInstrumenting(String method) {
// Check if we have an instrumentation context, as init should only get called by
// the system in startup processes that are being instrumented.
@@ -155,16 +155,12 @@ public class Instrumentation {
}
}
- private void checkInstrumenting$ravenwood(String method) {
- // At the moment, Ravenwood doesn't attach a Context, but we're only ever
- // running code as part of tests, so we continue quietly
- }
-
/**
* Returns if it is being called in an instrumentation environment.
*
* @hide
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public boolean isInstrumenting() {
// Check if we have an instrumentation context, as init should only get called by
// the system in startup processes that are being instrumented.
@@ -328,6 +324,7 @@ public class Instrumentation {
*
* @see #getTargetContext
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public Context getContext() {
return mInstrContext;
}
@@ -352,6 +349,7 @@ public class Instrumentation {
*
* @see #getContext
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public Context getTargetContext() {
return mAppContext;
}
@@ -2402,6 +2400,17 @@ public class Instrumentation {
mThread = thread;
}
+ /**
+ * Only sets the Context up, keeps everything else null.
+ *
+ * @hide
+ */
+ @android.ravenwood.annotation.RavenwoodKeep
+ public final void basicInit(Context context) {
+ mInstrContext = context;
+ mAppContext = context;
+ }
+
/** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public static void checkStartActivityResult(int res, Object intent) {
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index d6e8ae3e5dff..26f85f723bd9 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -80,6 +80,7 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemClock;
import android.os.SystemProperties;
+import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
@@ -3023,37 +3024,44 @@ public class Notification implements Parcelable
* @hide
*/
public String loadHeaderAppName(Context context) {
- CharSequence name = null;
- // Check if there is a non-empty substitute app name and return that.
- if (extras.containsKey(EXTRA_SUBSTITUTE_APP_NAME)) {
- name = extras.getString(EXTRA_SUBSTITUTE_APP_NAME);
- if (!TextUtils.isEmpty(name)) {
- return name.toString();
+ Trace.beginSection("Notification#loadHeaderAppName");
+
+ try {
+ CharSequence name = null;
+ // Check if there is a non-empty substitute app name and return that.
+ if (extras.containsKey(EXTRA_SUBSTITUTE_APP_NAME)) {
+ name = extras.getString(EXTRA_SUBSTITUTE_APP_NAME);
+ if (!TextUtils.isEmpty(name)) {
+ return name.toString();
+ }
}
- }
- // If not, try getting the app info from extras.
- if (context == null) {
- return null;
- }
- final PackageManager pm = context.getPackageManager();
- if (TextUtils.isEmpty(name)) {
- if (extras.containsKey(EXTRA_BUILDER_APPLICATION_INFO)) {
- final ApplicationInfo info = extras.getParcelable(EXTRA_BUILDER_APPLICATION_INFO,
- ApplicationInfo.class);
- if (info != null) {
- name = pm.getApplicationLabel(info);
+ // If not, try getting the app info from extras.
+ if (context == null) {
+ return null;
+ }
+ final PackageManager pm = context.getPackageManager();
+ if (TextUtils.isEmpty(name)) {
+ if (extras.containsKey(EXTRA_BUILDER_APPLICATION_INFO)) {
+ final ApplicationInfo info = extras.getParcelable(
+ EXTRA_BUILDER_APPLICATION_INFO,
+ ApplicationInfo.class);
+ if (info != null) {
+ name = pm.getApplicationLabel(info);
+ }
}
}
+ // If that's still empty, use the one from the context directly.
+ if (TextUtils.isEmpty(name)) {
+ name = pm.getApplicationLabel(context.getApplicationInfo());
+ }
+ // If there's still nothing, ¯\_(ツ)_/¯
+ if (TextUtils.isEmpty(name)) {
+ return null;
+ }
+ return name.toString();
+ } finally {
+ Trace.endSection();
}
- // If that's still empty, use the one from the context directly.
- if (TextUtils.isEmpty(name)) {
- name = pm.getApplicationLabel(context.getApplicationInfo());
- }
- // If there's still nothing, ¯\_(ツ)_/¯
- if (TextUtils.isEmpty(name)) {
- return null;
- }
- return name.toString();
}
/**
@@ -6722,23 +6730,29 @@ public class Notification implements Parcelable
*/
@NonNull
public static Notification.Builder recoverBuilder(Context context, Notification n) {
- // Re-create notification context so we can access app resources.
- ApplicationInfo applicationInfo = n.extras.getParcelable(
- EXTRA_BUILDER_APPLICATION_INFO, ApplicationInfo.class);
- Context builderContext;
- if (applicationInfo != null) {
- try {
- builderContext = context.createApplicationContext(applicationInfo,
- Context.CONTEXT_RESTRICTED);
- } catch (NameNotFoundException e) {
- Log.e(TAG, "ApplicationInfo " + applicationInfo + " not found");
- builderContext = context; // try with our context
+ Trace.beginSection("Notification.Builder#recoverBuilder");
+
+ try {
+ // Re-create notification context so we can access app resources.
+ ApplicationInfo applicationInfo = n.extras.getParcelable(
+ EXTRA_BUILDER_APPLICATION_INFO, ApplicationInfo.class);
+ Context builderContext;
+ if (applicationInfo != null) {
+ try {
+ builderContext = context.createApplicationContext(applicationInfo,
+ Context.CONTEXT_RESTRICTED);
+ } catch (NameNotFoundException e) {
+ Log.e(TAG, "ApplicationInfo " + applicationInfo + " not found");
+ builderContext = context; // try with our context
+ }
+ } else {
+ builderContext = context; // try with given context
}
- } else {
- builderContext = context; // try with given context
- }
- return new Builder(builderContext, n);
+ return new Builder(builderContext, n);
+ } finally {
+ Trace.endSection();
+ }
}
/**
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 9dfb5b0dedbd..d49a2542eed8 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -1406,6 +1406,26 @@ public class NotificationManager {
}
/**
+ * Returns the current activation state of an {@link AutomaticZenRule}.
+ *
+ * <p>Returns {@link Condition#STATE_UNKNOWN} if the rule does not exist or the calling
+ * package doesn't have access to it.
+ *
+ * @param id The id of the rule
+ * @return the state of the rule.
+ */
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ @Condition.State
+ public int getAutomaticZenRuleState(@NonNull String id) {
+ INotificationManager service = getService();
+ try {
+ return service.getAutomaticZenRuleState(id);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Informs the notification manager that the state of an {@link AutomaticZenRule} has changed.
* Use this method to put the system into Do Not Disturb mode or request that it exits Do Not
* Disturb mode. The calling app must own the provided {@link android.app.AutomaticZenRule}.
diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS
index da0cc01e363d..41b97d0ad5d2 100644
--- a/core/java/android/app/OWNERS
+++ b/core/java/android/app/OWNERS
@@ -118,6 +118,8 @@ per-file *ScreenCapture* = file:/services/core/java/com/android/server/wm/OWNERS
# Multitasking
per-file multitasking.aconfig = file:/services/core/java/com/android/server/wm/OWNERS
per-file multitasking.aconfig = file:/libs/WindowManager/Shell/OWNERS
+per-file PictureInPicture* = file:/services/core/java/com/android/server/wm/OWNERS
+per-file PictureInPicture* = file:/libs/WindowManager/Shell/OWNERS
# Zygote
per-file *Zygote* = file:/ZYGOTE_OWNERS
diff --git a/core/java/android/app/PictureInPictureUiState.java b/core/java/android/app/PictureInPictureUiState.java
index 39ba54cfeb0f..162953662d39 100644
--- a/core/java/android/app/PictureInPictureUiState.java
+++ b/core/java/android/app/PictureInPictureUiState.java
@@ -31,12 +31,12 @@ import java.util.Objects;
public final class PictureInPictureUiState implements Parcelable {
private final boolean mIsStashed;
- private final boolean mIsEnteringPip;
+ private final boolean mIsTransitioningToPip;
/** {@hide} */
PictureInPictureUiState(Parcel in) {
mIsStashed = in.readBoolean();
- mIsEnteringPip = in.readBoolean();
+ mIsTransitioningToPip = in.readBoolean();
}
/** {@hide} */
@@ -45,9 +45,9 @@ public final class PictureInPictureUiState implements Parcelable {
this(isStashed, false /* isEnteringPip */);
}
- private PictureInPictureUiState(boolean isStashed, boolean isEnteringPip) {
+ private PictureInPictureUiState(boolean isStashed, boolean isTransitioningToPip) {
mIsStashed = isStashed;
- mIsEnteringPip = isEnteringPip;
+ mIsTransitioningToPip = isTransitioningToPip;
}
/**
@@ -77,14 +77,14 @@ public final class PictureInPictureUiState implements Parcelable {
* whether via auto enter PiP or calling
* {@link Activity#enterPictureInPictureMode(PictureInPictureParams)} explicitly, app can expect
* {@link Activity#onPictureInPictureUiStateChanged(PictureInPictureUiState)} callback with
- * {@link #isEnteringPip()} to be {@code true} first,
+ * {@link #isTransitioningToPip()} to be {@code true} first,
* followed by {@link Activity#onPictureInPictureModeChanged(boolean, Configuration)} when it
* fully settles in PiP mode.
*
* When app receives the
* {@link Activity#onPictureInPictureUiStateChanged(PictureInPictureUiState)} callback with
- * {@link #isEnteringPip()} being {@code true}, it's recommended to hide certain UI elements,
- * such as video controls, to archive a clean entering PiP animation.
+ * {@link #isTransitioningToPip()} being {@code true}, it's recommended to hide certain UI
+ * elements, such as video controls, to archive a clean entering PiP animation.
*
* In case an application wants to restore the previously hidden UI elements when exiting
* PiP, it is recommended to do that in
@@ -92,8 +92,8 @@ public final class PictureInPictureUiState implements Parcelable {
* than the beginning of exit PiP animation.
*/
@FlaggedApi(Flags.FLAG_ENABLE_PIP_UI_STATE_CALLBACK_ON_ENTERING)
- public boolean isEnteringPip() {
- return mIsEnteringPip;
+ public boolean isTransitioningToPip() {
+ return mIsTransitioningToPip;
}
@Override
@@ -102,12 +102,12 @@ public final class PictureInPictureUiState implements Parcelable {
if (!(o instanceof PictureInPictureUiState)) return false;
PictureInPictureUiState that = (PictureInPictureUiState) o;
return mIsStashed == that.mIsStashed
- && mIsEnteringPip == that.mIsEnteringPip;
+ && mIsTransitioningToPip == that.mIsTransitioningToPip;
}
@Override
public int hashCode() {
- return Objects.hash(mIsStashed, mIsEnteringPip);
+ return Objects.hash(mIsStashed, mIsTransitioningToPip);
}
@Override
@@ -118,7 +118,7 @@ public final class PictureInPictureUiState implements Parcelable {
@Override
public void writeToParcel(@NonNull Parcel out, int flags) {
out.writeBoolean(mIsStashed);
- out.writeBoolean(mIsEnteringPip);
+ out.writeBoolean(mIsTransitioningToPip);
}
public static final @android.annotation.NonNull Creator<PictureInPictureUiState> CREATOR =
@@ -138,7 +138,7 @@ public final class PictureInPictureUiState implements Parcelable {
@FlaggedApi(Flags.FLAG_ENABLE_PIP_UI_STATE_CALLBACK_ON_ENTERING)
public static final class Builder {
private boolean mIsStashed;
- private boolean mIsEnteringPip;
+ private boolean mIsTransitioningToPip;
/** Empty constructor. */
public Builder() {
@@ -154,11 +154,11 @@ public final class PictureInPictureUiState implements Parcelable {
}
/**
- * Sets the {@link #mIsEnteringPip} state.
+ * Sets the {@link #mIsTransitioningToPip} state.
* @return The same {@link Builder} instance.
*/
- public Builder setEnteringPip(boolean isEnteringPip) {
- mIsEnteringPip = isEnteringPip;
+ public Builder setTransitioningToPip(boolean isEnteringPip) {
+ mIsTransitioningToPip = isEnteringPip;
return this;
}
@@ -166,7 +166,7 @@ public final class PictureInPictureUiState implements Parcelable {
* @return The constructed {@link PictureInPictureUiState} instance.
*/
public PictureInPictureUiState build() {
- return new PictureInPictureUiState(mIsStashed, mIsEnteringPip);
+ return new PictureInPictureUiState(mIsStashed, mIsTransitioningToPip);
}
}
}
diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java
index d4702998ce3e..fe8655c13562 100644
--- a/core/java/android/app/Service.java
+++ b/core/java/android/app/Service.java
@@ -1164,7 +1164,7 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac
}
/** @hide */
- public final void callOnTimeLimitExceeded(int startId, int fgsType) {
+ public final void callOnTimeLimitExceeded(int startId, @ForegroundServiceType int fgsType) {
// Note, because all the service callbacks (and other similar callbacks, e.g. activity
// callbacks) are delivered using the main handler, it's possible the service is already
// stopped when before this method is called, so we do a double check here.
@@ -1189,10 +1189,11 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac
* Callback called when a particular foreground service type has timed out.
*
* @param startId the startId passed to {@link #onStartCommand(Intent, int, int)} when
- * the service started.
- * @param fgsType the foreground service type which caused the timeout.
+ * the service started.
+ * @param fgsType the {@link ServiceInfo.ForegroundServiceType foreground service type} which
+ * caused the timeout.
*/
@FlaggedApi(Flags.FLAG_INTRODUCE_NEW_SERVICE_ONTIMEOUT_CALLBACK)
- public void onTimeout(int startId, int fgsType) {
+ public void onTimeout(int startId, @ForegroundServiceType int fgsType) {
}
}
diff --git a/core/java/android/app/UiModeManager.java b/core/java/android/app/UiModeManager.java
index a045eae9e108..7903f1c0c5c3 100644
--- a/core/java/android/app/UiModeManager.java
+++ b/core/java/android/app/UiModeManager.java
@@ -16,7 +16,7 @@
package android.app;
-import static android.app.Flags.enableNightModeCache;
+import static android.app.Flags.enableNightModeBinderCache;
import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
@@ -916,7 +916,7 @@ public class UiModeManager {
*
* @hide
*/
- @FlaggedApi(Flags.FLAG_ENABLE_NIGHT_MODE_CACHE)
+ @FlaggedApi(Flags.FLAG_ENABLE_NIGHT_MODE_BINDER_CACHE)
public static void invalidateNightModeCache() {
IpcDataCache.invalidateCache(IpcDataCache.MODULE_SYSTEM,
NIGHT_MODE_API);
@@ -938,7 +938,7 @@ public class UiModeManager {
* @see #setNightMode(int)
*/
public @NightMode int getNightMode() {
- if (enableNightModeCache()) {
+ if (enableNightModeBinderCache()) {
return mNightModeCache.query(null);
} else {
return getNightModeFromServer();
diff --git a/core/java/android/app/admin/DeviceAdminInfo.java b/core/java/android/app/admin/DeviceAdminInfo.java
index 7d5d5c162271..986205a346f7 100644
--- a/core/java/android/app/admin/DeviceAdminInfo.java
+++ b/core/java/android/app/admin/DeviceAdminInfo.java
@@ -16,10 +16,11 @@
package android.app.admin;
+import static android.app.admin.flags.Flags.FLAG_HEADLESS_DEVICE_OWNER_SINGLE_USER_ENABLED;
+
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
-import android.app.admin.flags.Flags;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
@@ -185,7 +186,7 @@ public final class DeviceAdminInfo implements Parcelable {
* <p>This mode only allows a single secondary user on the device blocking the creation of
* additional secondary users.
*/
- @FlaggedApi(Flags.FLAG_HEADLESS_DEVICE_OWNER_SINGLE_USER_ENABLED)
+ @FlaggedApi(FLAG_HEADLESS_DEVICE_OWNER_SINGLE_USER_ENABLED)
public static final int HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER = 2;
@IntDef({HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED, HEADLESS_DEVICE_OWNER_MODE_AFFILIATED,
diff --git a/core/java/android/app/admin/DevicePolicyIdentifiers.java b/core/java/android/app/admin/DevicePolicyIdentifiers.java
index 3c56aaf33ef3..eeaf0b3706fc 100644
--- a/core/java/android/app/admin/DevicePolicyIdentifiers.java
+++ b/core/java/android/app/admin/DevicePolicyIdentifiers.java
@@ -16,13 +16,13 @@
package android.app.admin;
+import static android.app.admin.flags.Flags.FLAG_POLICY_ENGINE_MIGRATION_V2_ENABLED;
import static android.app.admin.flags.Flags.FLAG_SECURITY_LOG_V2_ENABLED;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.annotation.TestApi;
-import android.app.admin.flags.Flags;
import android.os.UserManager;
import java.util.Objects;
@@ -188,13 +188,13 @@ public final class DevicePolicyIdentifiers {
/**
* String identifier for {@link DevicePolicyManager#setUsbDataSignalingEnabled}.
*/
- @FlaggedApi(Flags.FLAG_POLICY_ENGINE_MIGRATION_V2_ENABLED)
+ @FlaggedApi(FLAG_POLICY_ENGINE_MIGRATION_V2_ENABLED)
public static final String USB_DATA_SIGNALING_POLICY = "usbDataSignaling";
/**
* String identifier for {@link DevicePolicyManager#setRequiredPasswordComplexity}.
*/
- @FlaggedApi(Flags.FLAG_POLICY_ENGINE_MIGRATION_V2_ENABLED)
+ @FlaggedApi(FLAG_POLICY_ENGINE_MIGRATION_V2_ENABLED)
public static final String PASSWORD_COMPLEXITY_POLICY = "passwordComplexity";
/**
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index a6fda9d23aca..083705bca09e 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -53,8 +53,11 @@ import static android.Manifest.permission.QUERY_ADMIN_POLICY;
import static android.Manifest.permission.REQUEST_PASSWORD_COMPLEXITY;
import static android.Manifest.permission.SET_TIME;
import static android.Manifest.permission.SET_TIME_ZONE;
+import static android.app.admin.flags.Flags.FLAG_DEVICE_THEFT_API_ENABLED;
import static android.app.admin.flags.Flags.FLAG_ESIM_MANAGEMENT_ENABLED;
import static android.app.admin.flags.Flags.FLAG_DEVICE_POLICY_SIZE_TRACKING_ENABLED;
+import static android.app.admin.flags.Flags.FLAG_HEADLESS_DEVICE_OWNER_SINGLE_USER_ENABLED;
+import static android.app.admin.flags.Flags.FLAG_PERMISSION_MIGRATION_FOR_ZERO_TRUST_API_ENABLED;
import static android.app.admin.flags.Flags.FLAG_SECURITY_LOG_V2_ENABLED;
import static android.app.admin.flags.Flags.onboardingBugreportV2Enabled;
import static android.app.admin.flags.Flags.FLAG_IS_MTE_POLICY_ENFORCED;
@@ -90,7 +93,6 @@ import android.app.Activity;
import android.app.IServiceConnection;
import android.app.KeyguardManager;
import android.app.admin.SecurityLog.SecurityEvent;
-import android.app.admin.flags.Flags;
import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
@@ -153,10 +155,10 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.infra.AndroidFuture;
import com.android.internal.net.NetworkUtilsInternal;
import com.android.internal.os.BackgroundThread;
+import com.android.internal.os.Zygote;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.Preconditions;
import com.android.org.conscrypt.TrustedCertificateStore;
-import com.android.internal.os.Zygote;
import java.io.ByteArrayInputStream;
import java.io.FileNotFoundException;
@@ -2879,7 +2881,7 @@ public class DevicePolicyManager {
* @hide
*/
@SystemApi
- @FlaggedApi(Flags.FLAG_HEADLESS_DEVICE_OWNER_SINGLE_USER_ENABLED)
+ @FlaggedApi(FLAG_HEADLESS_DEVICE_OWNER_SINGLE_USER_ENABLED)
public static final int STATUS_HEADLESS_ONLY_SYSTEM_USER = 17;
/**
@@ -13447,7 +13449,7 @@ public class DevicePolicyManager {
*/
@RequiresPermission(value = MANAGE_DEVICE_POLICY_QUERY_SYSTEM_UPDATES, conditional = true)
@SuppressLint("RequiresPermission")
- @FlaggedApi(Flags.FLAG_PERMISSION_MIGRATION_FOR_ZERO_TRUST_API_ENABLED)
+ @FlaggedApi(FLAG_PERMISSION_MIGRATION_FOR_ZERO_TRUST_API_ENABLED)
public @Nullable SystemUpdateInfo getPendingSystemUpdate(@Nullable ComponentName admin) {
throwIfParentInstance("getPendingSystemUpdate");
try {
@@ -16608,7 +16610,7 @@ public class DevicePolicyManager {
*/
@RequiresPermission(value = MANAGE_DEVICE_POLICY_CERTIFICATES, conditional = true)
@SuppressLint("RequiresPermission")
- @FlaggedApi(Flags.FLAG_PERMISSION_MIGRATION_FOR_ZERO_TRUST_API_ENABLED)
+ @FlaggedApi(FLAG_PERMISSION_MIGRATION_FOR_ZERO_TRUST_API_ENABLED)
@NonNull public String getEnrollmentSpecificId() {
throwIfParentInstance("getEnrollmentSpecificId");
if (mService == null) {
@@ -17134,7 +17136,7 @@ public class DevicePolicyManager {
*/
@SystemApi
@RequiresPermission(value = MANAGE_DEVICE_POLICY_THEFT_DETECTION)
- @FlaggedApi(Flags.FLAG_DEVICE_THEFT_API_ENABLED)
+ @FlaggedApi(FLAG_DEVICE_THEFT_API_ENABLED)
public boolean isTheftDetectionTriggered() {
throwIfParentInstance("isTheftDetectionTriggered");
if (mService == null) {
diff --git a/core/java/android/app/admin/SecurityLog.java b/core/java/android/app/admin/SecurityLog.java
index ed1b8ca9b5bd..477f2e007b33 100644
--- a/core/java/android/app/admin/SecurityLog.java
+++ b/core/java/android/app/admin/SecurityLog.java
@@ -16,6 +16,8 @@
package android.app.admin;
+import static android.app.admin.flags.Flags.FLAG_BACKUP_SERVICE_SECURITY_LOG_EVENT_ENABLED;
+
import android.Manifest;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
@@ -24,7 +26,6 @@ import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.TestApi;
-import android.app.admin.flags.Flags;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.os.Build;
@@ -610,7 +611,7 @@ public class SecurityLog {
* <li> [2] backup service state ({@code Integer}, 1 for enabled, 0 for disabled)
* @see DevicePolicyManager#setBackupServiceEnabled(ComponentName, boolean)
*/
- @FlaggedApi(Flags.FLAG_BACKUP_SERVICE_SECURITY_LOG_EVENT_ENABLED)
+ @FlaggedApi(FLAG_BACKUP_SERVICE_SECURITY_LOG_EVENT_ENABLED)
public static final int TAG_BACKUP_SERVICE_TOGGLED =
SecurityLogTags.SECURITY_BACKUP_SERVICE_TOGGLED;
/**
diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java
index 9fa73627de05..d139134c7c1a 100644
--- a/core/java/android/app/assist/AssistStructure.java
+++ b/core/java/android/app/assist/AssistStructure.java
@@ -913,6 +913,7 @@ public class AssistStructure implements Parcelable {
if ((flags&FLAGS_HAS_EXTRAS) != 0) {
mExtras = in.readBundle();
}
+ mGetCredentialRequest = in.readTypedObject(GetCredentialRequest.CREATOR);
}
/**
@@ -1149,6 +1150,7 @@ public class AssistStructure implements Parcelable {
if ((flags&FLAGS_HAS_EXTRAS) != 0) {
out.writeBundle(mExtras);
}
+ out.writeTypedObject(mGetCredentialRequest, flags);
return flags;
}
@@ -1287,11 +1289,7 @@ public class AssistStructure implements Parcelable {
}
/**
- *
- * @return
- *
* @hide
- *
*/
@FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION)
@Nullable
@@ -2569,7 +2567,7 @@ public class AssistStructure implements Parcelable {
}
AutofillId autofillId = node.getAutofillId();
if (autofillId == null) {
- Log.i(TAG, prefix + " NO autofill ID");
+ Log.i(TAG, prefix + " No autofill ID");
} else {
Log.i(TAG, prefix + " Autofill info: id= " + autofillId
+ ", type=" + node.getAutofillType()
@@ -2584,7 +2582,7 @@ public class AssistStructure implements Parcelable {
}
GetCredentialRequest getCredentialRequest = node.getCredentialManagerRequest();
if (getCredentialRequest == null) {
- Log.i(TAG, prefix + " NO Credential Manager Request");
+ Log.i(TAG, prefix + " No Credential Manager Request");
} else {
Log.i(TAG, prefix + " GetCredentialRequest: no. of options= "
+ getCredentialRequest.getCredentialOptions().size()
diff --git a/core/java/android/app/ondeviceintelligence/Content.aidl b/core/java/android/app/ondeviceintelligence/Content.aidl
new file mode 100644
index 000000000000..40f0ef9a8541
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/Content.aidl
@@ -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.app.ondeviceintelligence;
+
+/**
+ * @hide
+ */
+parcelable Content;
diff --git a/core/java/android/app/ondeviceintelligence/Content.java b/core/java/android/app/ondeviceintelligence/Content.java
new file mode 100644
index 000000000000..51bd156fc946
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/Content.java
@@ -0,0 +1,90 @@
+/*
+ * 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.app.ondeviceintelligence;
+
+import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Represents content sent to and received from the on-device inference service.
+ * Can contain a collection of text, image, and binary parts or any combination of these.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
+public final class Content implements Parcelable {
+ //TODO: Improve javadoc after adding validation logic.
+ private static final String TAG = "Content";
+ private final Bundle mData;
+
+ /**
+ * Create a content object using a Bundle of only known types that are read-only.
+ */
+ public Content(@NonNull Bundle data) {
+ Objects.requireNonNull(data);
+ validateBundleData(data);
+ this.mData = data;
+ }
+
+ /**
+ * Returns the Content's data represented as a Bundle.
+ */
+ @NonNull
+ public Bundle getData() {
+ return mData;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeBundle(mData);
+ }
+
+ @Override
+ public int describeContents() {
+ int mask = 0;
+ mask |= mData.describeContents();
+ return mask;
+ }
+
+ @NonNull
+ public static final Creator<Content> CREATOR = new Creator<>() {
+ @Override
+ @NonNull
+ public Content createFromParcel(@NonNull Parcel in) {
+ return new Content(in.readBundle(getClass().getClassLoader()));
+ }
+
+ @Override
+ @NonNull
+ public Content[] newArray(int size) {
+ return new Content[size];
+ }
+ };
+
+ private void validateBundleData(Bundle unused) {
+ // TODO: Validate there are only known types.
+ }
+}
diff --git a/core/java/android/app/ondeviceintelligence/DownloadCallback.java b/core/java/android/app/ondeviceintelligence/DownloadCallback.java
new file mode 100644
index 000000000000..684c71f9144c
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/DownloadCallback.java
@@ -0,0 +1,114 @@
+/*
+ * 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.app.ondeviceintelligence;
+
+import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.PersistableBundle;
+
+import androidx.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Callback functions used for feature downloading via the
+ * {@link OnDeviceIntelligenceManager#requestFeatureDownload}.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
+public interface DownloadCallback {
+ int DOWNLOAD_FAILURE_STATUS_UNKNOWN = 0;
+
+ /**
+ * Sent when feature download could not succeed due to there being no available disk space on
+ * the device.
+ */
+ int DOWNLOAD_FAILURE_STATUS_NOT_ENOUGH_DISK_SPACE = 1;
+
+ /**
+ * Sent when feature download could not succeed due to a network error.
+ */
+ int DOWNLOAD_FAILURE_STATUS_NETWORK_FAILURE = 2;
+
+ /**
+ * Sent when feature download has been initiated already, hence no need to request download
+ * again. Caller can query {@link OnDeviceIntelligenceManager#getFeatureStatus} to check if
+ * download has been completed.
+ */
+ int DOWNLOAD_FAILURE_STATUS_DOWNLOADING = 3;
+
+ /**
+ * Sent when feature download did not start due to errors (e.g. remote exception of features not
+ * available). Caller can query {@link OnDeviceIntelligenceManager#getFeatureStatus} to check
+ * if feature-status is {@link FeatureDetails#FEATURE_STATUS_DOWNLOADABLE}.
+ */
+ int DOWNLOAD_FAILURE_STATUS_UNAVAILABLE = 4;
+
+ /** @hide */
+ @IntDef(value = {
+ DOWNLOAD_FAILURE_STATUS_UNKNOWN,
+ DOWNLOAD_FAILURE_STATUS_NOT_ENOUGH_DISK_SPACE,
+ DOWNLOAD_FAILURE_STATUS_NETWORK_FAILURE,
+ DOWNLOAD_FAILURE_STATUS_DOWNLOADING,
+ DOWNLOAD_FAILURE_STATUS_UNAVAILABLE
+ }, open = true)
+ @Retention(RetentionPolicy.SOURCE)
+ @interface DownloadFailureStatus {
+ }
+
+ /**
+ * Called when model download started properly.
+ *
+ * @param bytesToDownload the total bytes to be downloaded for this {@link Feature}
+ */
+ default void onDownloadStarted(long bytesToDownload) {
+ }
+
+ /**
+ * Called when model download failed.
+ *
+ * @param failureStatus the download failure status
+ * @param errorMessage the error message associated with the download failure
+ */
+ void onDownloadFailed(
+ @DownloadFailureStatus int failureStatus,
+ @Nullable String errorMessage,
+ @NonNull PersistableBundle errorParams);
+
+ /**
+ * Called when model download is in progress.
+ *
+ * @param totalBytesDownloaded the already downloaded bytes for this {@link Feature}
+ */
+ default void onDownloadProgress(long totalBytesDownloaded) {
+ }
+
+ /**
+ * Called when model download via MDD completed. The remote implementation can populate any
+ * associated download params like file stats etc. in this callback to inform the client.
+ *
+ * @param downloadParams params containing info about the completed download.
+ */
+ void onDownloadCompleted(@NonNull PersistableBundle downloadParams);
+}
diff --git a/core/java/android/app/ondeviceintelligence/Feature.aidl b/core/java/android/app/ondeviceintelligence/Feature.aidl
new file mode 100644
index 000000000000..18494d754674
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/Feature.aidl
@@ -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.app.ondeviceintelligence;
+
+/**
+ * @hide
+ */
+parcelable Feature;
diff --git a/core/java/android/app/ondeviceintelligence/Feature.java b/core/java/android/app/ondeviceintelligence/Feature.java
new file mode 100644
index 000000000000..510735461553
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/Feature.java
@@ -0,0 +1,279 @@
+/*
+ * 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.app.ondeviceintelligence;
+
+import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.PersistableBundle;
+
+/**
+ * Represents a typical feature associated with on-device intelligence.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
+public final class Feature implements Parcelable {
+ // TODO(b/325315604) - Check if we can expose non-hidden IntDefs in Framework.
+ private final int mId;
+ @Nullable
+ private final String mName;
+ @Nullable
+ private final String mModelName;
+ private final int mType;
+ private final int mVariant;
+ @NonNull
+ private final PersistableBundle mFeatureParams;
+
+ /* package-private */ Feature(
+ int id,
+ @Nullable String name,
+ @Nullable String modelName,
+ int type,
+ int variant,
+ @NonNull PersistableBundle featureParams) {
+ this.mId = id;
+ this.mName = name;
+ this.mModelName = modelName;
+ this.mType = type;
+ this.mVariant = variant;
+ this.mFeatureParams = featureParams;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mFeatureParams);
+ }
+
+ /** Returns the unique and immutable identifier of this feature. */
+ public int getId() {
+ return mId;
+ }
+
+ /** Returns human-readable name of this feature. */
+ public @Nullable String getName() {
+ return mName;
+ }
+
+ /** Returns base model name of this feature. */
+ public @Nullable String getModelName() {
+ return mModelName;
+ }
+
+ /** Returns type identifier of this feature. */
+ public int getType() {
+ return mType;
+ }
+
+ /** Returns variant kind for this feature. */
+ public int getVariant() {
+ return mVariant;
+ }
+
+ public @NonNull PersistableBundle getFeatureParams() {
+ return mFeatureParams;
+ }
+
+ @Override
+ public String toString() {
+ return "Feature { " +
+ "id = " + mId + ", " +
+ "name = " + mName + ", " +
+ "modelName = " + mModelName + ", " +
+ "type = " + mType + ", " +
+ "variant = " + mVariant + ", " +
+ "featureParams = " + mFeatureParams +
+ " }";
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ Feature that = (Feature) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && mId == that.mId
+ && java.util.Objects.equals(mName, that.mName)
+ && java.util.Objects.equals(mModelName, that.mModelName)
+ && mType == that.mType
+ && mVariant == that.mVariant
+ && java.util.Objects.equals(mFeatureParams, that.mFeatureParams);
+ }
+
+ @Override
+ public int hashCode() {
+ int _hash = 1;
+ _hash = 31 * _hash + mId;
+ _hash = 31 * _hash + java.util.Objects.hashCode(mName);
+ _hash = 31 * _hash + java.util.Objects.hashCode(mModelName);
+ _hash = 31 * _hash + mType;
+ _hash = 31 * _hash + mVariant;
+ _hash = 31 * _hash + java.util.Objects.hashCode(mFeatureParams);
+ return _hash;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ byte flg = 0;
+ if (mName != null) flg |= 0x2;
+ if (mModelName != null) flg |= 0x4;
+ dest.writeByte(flg);
+ dest.writeInt(mId);
+ if (mName != null) dest.writeString8(mName);
+ if (mModelName != null) dest.writeString8(mModelName);
+ dest.writeInt(mType);
+ dest.writeInt(mVariant);
+ dest.writeTypedObject(mFeatureParams, flags);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ /* package-private */ Feature(@NonNull Parcel in) {
+ byte flg = in.readByte();
+ int id = in.readInt();
+ String name = (flg & 0x2) == 0 ? null : in.readString();
+ String modelName = (flg & 0x4) == 0 ? null : in.readString();
+ int type = in.readInt();
+ int variant = in.readInt();
+ PersistableBundle featureParams = (PersistableBundle) in.readTypedObject(
+ PersistableBundle.CREATOR);
+
+ this.mId = id;
+ this.mName = name;
+ this.mModelName = modelName;
+ this.mType = type;
+ this.mVariant = variant;
+ this.mFeatureParams = featureParams;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mFeatureParams);
+ }
+
+ public static final @NonNull Parcelable.Creator<Feature> CREATOR
+ = new Parcelable.Creator<Feature>() {
+ @Override
+ public Feature[] newArray(int size) {
+ return new Feature[size];
+ }
+
+ @Override
+ public Feature createFromParcel(@NonNull Parcel in) {
+ return new Feature(in);
+ }
+ };
+
+ /**
+ * A builder for {@link Feature}
+ */
+ @SuppressWarnings("WeakerAccess")
+ public static final class Builder {
+ private int mId;
+ private @Nullable String mName;
+ private @Nullable String mModelName;
+ private int mType;
+ private int mVariant;
+ private @NonNull PersistableBundle mFeatureParams;
+
+ private long mBuilderFieldsSet = 0L;
+
+ public Builder(
+ int id,
+ int type,
+ int variant,
+ @NonNull PersistableBundle featureParams) {
+ mId = id;
+ mType = type;
+ mVariant = variant;
+ mFeatureParams = featureParams;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mFeatureParams);
+ }
+
+ public @NonNull Builder setId(int value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x1;
+ mId = value;
+ return this;
+ }
+
+ public @NonNull Builder setName(@NonNull String value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x2;
+ mName = value;
+ return this;
+ }
+
+ public @NonNull Builder setModelName(@NonNull String value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x4;
+ mModelName = value;
+ return this;
+ }
+
+ public @NonNull Builder setType(int value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x8;
+ mType = value;
+ return this;
+ }
+
+ public @NonNull Builder setVariant(int value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x10;
+ mVariant = value;
+ return this;
+ }
+
+ public @NonNull Builder setFeatureParams(@NonNull PersistableBundle value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x20;
+ mFeatureParams = value;
+ return this;
+ }
+
+ /** Builds the instance. This builder should not be touched after calling this! */
+ public @NonNull Feature build() {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x40; // Mark builder used
+
+ Feature o = new Feature(
+ mId,
+ mName,
+ mModelName,
+ mType,
+ mVariant,
+ mFeatureParams);
+ return o;
+ }
+
+ private void checkNotUsed() {
+ if ((mBuilderFieldsSet & 0x40) != 0) {
+ throw new IllegalStateException(
+ "This Builder should not be reused. Use a new Builder instance instead");
+ }
+ }
+ }
+}
diff --git a/core/java/android/app/ondeviceintelligence/FeatureDetails.aidl b/core/java/android/app/ondeviceintelligence/FeatureDetails.aidl
new file mode 100644
index 000000000000..0589bf8bacb9
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/FeatureDetails.aidl
@@ -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.app.ondeviceintelligence;
+
+/**
+ * @hide
+ */
+parcelable FeatureDetails;
diff --git a/core/java/android/app/ondeviceintelligence/FeatureDetails.java b/core/java/android/app/ondeviceintelligence/FeatureDetails.java
new file mode 100644
index 000000000000..92f351362f70
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/FeatureDetails.java
@@ -0,0 +1,176 @@
+/*
+ * 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.app.ondeviceintelligence;
+
+import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcelable;
+import android.os.PersistableBundle;
+
+import androidx.annotation.IntDef;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.text.MessageFormat;
+
+/**
+ * Represents a status of a requested {@link Feature}.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
+public final class FeatureDetails implements Parcelable {
+ @Status
+ private final int mStatus;
+ @NonNull
+ private final PersistableBundle mFeatureDetailParams;
+
+ /** Invalid or unavailable {@code AiFeature}. */
+ public static final int FEATURE_STATUS_UNAVAILABLE = 0;
+
+ /** Feature can be downloaded on request. */
+ public static final int FEATURE_STATUS_DOWNLOADABLE = 1;
+
+ /** Feature is being downloaded. */
+ public static final int FEATURE_STATUS_DOWNLOADING = 2;
+
+ /** Feature is fully downloaded and ready to use. */
+ public static final int FEATURE_STATUS_AVAILABLE = 3;
+
+ /** Underlying service is unavailable and feature status cannot be fetched. */
+ public static final int FEATURE_STATUS_SERVICE_UNAVAILABLE = 4;
+
+ @IntDef(value = {
+ FEATURE_STATUS_UNAVAILABLE,
+ FEATURE_STATUS_DOWNLOADABLE,
+ FEATURE_STATUS_DOWNLOADING,
+ FEATURE_STATUS_AVAILABLE,
+ FEATURE_STATUS_SERVICE_UNAVAILABLE
+ }, open = true)
+ @Target({ElementType.TYPE_USE, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Status {
+ }
+
+ public FeatureDetails(
+ @Status int status,
+ @NonNull PersistableBundle featureDetailParams) {
+ this.mStatus = status;
+ com.android.internal.util.AnnotationValidations.validate(
+ Status.class, null, mStatus);
+ this.mFeatureDetailParams = featureDetailParams;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mFeatureDetailParams);
+ }
+
+ public FeatureDetails(
+ @Status int status) {
+ this.mStatus = status;
+ com.android.internal.util.AnnotationValidations.validate(
+ Status.class, null, mStatus);
+ this.mFeatureDetailParams = new PersistableBundle();
+ }
+
+
+ /**
+ * Returns an integer value associated with the feature status.
+ */
+ public @Status int getStatus() {
+ return mStatus;
+ }
+
+
+ /**
+ * Returns a persistable bundle contain any additional status related params.
+ */
+ public @NonNull PersistableBundle getFeatureDetailParams() {
+ return mFeatureDetailParams;
+ }
+
+ @Override
+ public String toString() {
+ return MessageFormat.format("FeatureDetails '{' status = {0}, "
+ + "persistableBundle = {1} '}'",
+ mStatus,
+ mFeatureDetailParams);
+ }
+
+ @Override
+ public boolean equals(@android.annotation.Nullable Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ FeatureDetails that = (FeatureDetails) o;
+ return mStatus == that.mStatus
+ && java.util.Objects.equals(mFeatureDetailParams, that.mFeatureDetailParams);
+ }
+
+ @Override
+ public int hashCode() {
+ int _hash = 1;
+ _hash = 31 * _hash + mStatus;
+ _hash = 31 * _hash + java.util.Objects.hashCode(mFeatureDetailParams);
+ return _hash;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+ dest.writeInt(mStatus);
+ dest.writeTypedObject(mFeatureDetailParams, flags);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ FeatureDetails(@NonNull android.os.Parcel in) {
+ int status = in.readInt();
+ PersistableBundle persistableBundle = (PersistableBundle) in.readTypedObject(
+ PersistableBundle.CREATOR);
+
+ this.mStatus = status;
+ com.android.internal.util.AnnotationValidations.validate(
+ Status.class, null, mStatus);
+ this.mFeatureDetailParams = persistableBundle;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mFeatureDetailParams);
+ }
+
+
+ public static final @NonNull Parcelable.Creator<FeatureDetails> CREATOR =
+ new Parcelable.Creator<>() {
+ @Override
+ public FeatureDetails[] newArray(int size) {
+ return new FeatureDetails[size];
+ }
+
+ @Override
+ public FeatureDetails createFromParcel(@NonNull android.os.Parcel in) {
+ return new FeatureDetails(in);
+ }
+ };
+
+}
diff --git a/core/java/android/app/ondeviceintelligence/FilePart.java b/core/java/android/app/ondeviceintelligence/FilePart.java
new file mode 100644
index 000000000000..e9fb5f2cb440
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/FilePart.java
@@ -0,0 +1,137 @@
+/*
+ * 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.app.ondeviceintelligence;
+
+import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
+
+import android.annotation.FlaggedApi;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
+import android.os.PersistableBundle;
+
+import android.annotation.NonNull;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.Objects;
+
+/**
+ * Represents file data with an associated file descriptor sent to and received from remote
+ * processing. The interface ensures that the underlying file-descriptor is always opened in
+ * read-only mode.
+ *
+ * @hide
+ */
+@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
+@SystemApi
+public final class FilePart implements Parcelable {
+ private final String mPartKey;
+ private final PersistableBundle mPartParams;
+ private final ParcelFileDescriptor mParcelFileDescriptor;
+
+ private FilePart(@NonNull String partKey, @NonNull PersistableBundle partParams,
+ @NonNull ParcelFileDescriptor parcelFileDescriptor) {
+ Objects.requireNonNull(partKey);
+ Objects.requireNonNull(partParams);
+ this.mPartKey = partKey;
+ this.mPartParams = partParams;
+ this.mParcelFileDescriptor = Objects.requireNonNull(parcelFileDescriptor);
+ }
+
+ /**
+ * Create a file part using a filePath and any additional params.
+ */
+ public FilePart(@NonNull String partKey, @NonNull PersistableBundle partParams,
+ @NonNull String filePath)
+ throws FileNotFoundException {
+ this(partKey, partParams, Objects.requireNonNull(ParcelFileDescriptor.open(
+ new File(Objects.requireNonNull(filePath)), ParcelFileDescriptor.MODE_READ_ONLY)));
+ }
+
+ /**
+ * Create a file part using a file input stream and any additional params.
+ * It is the caller's responsibility to close the stream. It is safe to do so as soon as this
+ * call returns.
+ */
+ public FilePart(@NonNull String partKey, @NonNull PersistableBundle partParams,
+ @NonNull FileInputStream fileInputStream)
+ throws IOException {
+ this(partKey, partParams, ParcelFileDescriptor.dup(fileInputStream.getFD()));
+ }
+
+ /**
+ * Returns a FileInputStream for the associated File.
+ * Caller must close the associated stream when done reading from it.
+ *
+ * @return the FileInputStream associated with the FilePart.
+ */
+ @NonNull
+ public FileInputStream getFileInputStream() {
+ return new FileInputStream(mParcelFileDescriptor.getFileDescriptor());
+ }
+
+ /**
+ * Returns the unique key associated with the part. Each Part key added to a content object
+ * should be ensured to be unique.
+ */
+ @NonNull
+ public String getFilePartKey() {
+ return mPartKey;
+ }
+
+ /**
+ * Returns the params associated with Part.
+ */
+ @NonNull
+ public PersistableBundle getFilePartParams() {
+ return mPartParams;
+ }
+
+
+ @Override
+ public int describeContents() {
+ return CONTENTS_FILE_DESCRIPTOR;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString8(getFilePartKey());
+ dest.writePersistableBundle(getFilePartParams());
+ mParcelFileDescriptor.writeToParcel(dest, flags
+ | Parcelable.PARCELABLE_WRITE_RETURN_VALUE); // This flag ensures that the sender's
+ // copy of the Pfd is closed as soon as the Binder call succeeds.
+ }
+
+ @NonNull
+ public static final Creator<FilePart> CREATOR = new Creator<>() {
+ @Override
+ public FilePart createFromParcel(Parcel in) {
+ return new FilePart(in.readString(), in.readTypedObject(PersistableBundle.CREATOR),
+ in.readParcelable(
+ getClass().getClassLoader(), ParcelFileDescriptor.class));
+ }
+
+ @Override
+ public FilePart[] newArray(int size) {
+ return new FilePart[size];
+ }
+ };
+}
diff --git a/core/java/android/app/ondeviceintelligence/IDownloadCallback.aidl b/core/java/android/app/ondeviceintelligence/IDownloadCallback.aidl
new file mode 100644
index 000000000000..aba563f84e1b
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/IDownloadCallback.aidl
@@ -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 android.app.ondeviceintelligence;
+
+import android.app.ondeviceintelligence.IProcessingSignal;
+import android.os.PersistableBundle;
+
+/**
+ * Interface for Download callback to passed onto service implementation,
+ *
+ * @hide
+ */
+oneway interface IDownloadCallback {
+ void onDownloadStarted(long bytesToDownload) = 1;
+ void onDownloadProgress(long bytesDownloaded) = 2;
+ void onDownloadFailed(int failureStatus, String errorMessage, in PersistableBundle errorParams) = 3;
+ void onDownloadCompleted(in PersistableBundle downloadParams) = 4;
+} \ No newline at end of file
diff --git a/core/java/android/app/ondeviceintelligence/IFeatureCallback.aidl b/core/java/android/app/ondeviceintelligence/IFeatureCallback.aidl
new file mode 100644
index 000000000000..93a84ec96757
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/IFeatureCallback.aidl
@@ -0,0 +1,14 @@
+package android.app.ondeviceintelligence;
+
+import android.app.ondeviceintelligence.Feature;
+import android.os.PersistableBundle;
+
+/**
+ * Interface for receiving a feature for the given identifier.
+ *
+ * @hide
+ */
+interface IFeatureCallback {
+ void onSuccess(in Feature result) = 1;
+ void onFailure(int errorCode, in String errorMessage, in PersistableBundle errorParams) = 2;
+}
diff --git a/core/java/android/app/ondeviceintelligence/IFeatureDetailsCallback.aidl b/core/java/android/app/ondeviceintelligence/IFeatureDetailsCallback.aidl
new file mode 100644
index 000000000000..d95029059f4a
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/IFeatureDetailsCallback.aidl
@@ -0,0 +1,14 @@
+package android.app.ondeviceintelligence;
+
+import android.app.ondeviceintelligence.FeatureDetails;
+import android.os.PersistableBundle;
+
+/**
+ * Interface for receiving details about a given feature. .
+ *
+ * @hide
+ */
+interface IFeatureDetailsCallback {
+ void onSuccess(in FeatureDetails result) = 1;
+ void onFailure(int errorCode, in String errorMessage, in PersistableBundle errorParams) = 2;
+}
diff --git a/core/java/android/app/ondeviceintelligence/IListFeaturesCallback.aidl b/core/java/android/app/ondeviceintelligence/IListFeaturesCallback.aidl
new file mode 100644
index 000000000000..374cb71977d5
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/IListFeaturesCallback.aidl
@@ -0,0 +1,15 @@
+package android.app.ondeviceintelligence;
+
+import java.util.List;
+import android.app.ondeviceintelligence.Feature;
+import android.os.PersistableBundle;
+
+/**
+ * Interface for receiving list of supported features.
+ *
+ * @hide
+ */
+interface IListFeaturesCallback {
+ void onSuccess(in List<Feature> result) = 1;
+ void onFailure(int errorCode, in String errorMessage, in PersistableBundle errorParams) = 2;
+}
diff --git a/core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl b/core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl
new file mode 100644
index 000000000000..b925f4863de2
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl
@@ -0,0 +1,70 @@
+/*
+ * 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.ondeviceintelligence;
+
+ import com.android.internal.infra.AndroidFuture;
+ import android.os.ICancellationSignal;
+ import android.os.ParcelFileDescriptor;
+ import android.os.PersistableBundle;
+ import android.os.RemoteCallback;
+ import android.app.ondeviceintelligence.Content;
+ import android.app.ondeviceintelligence.Feature;
+ import android.app.ondeviceintelligence.FeatureDetails;
+ import android.app.ondeviceintelligence.IDownloadCallback;
+ import android.app.ondeviceintelligence.IListFeaturesCallback;
+ import android.app.ondeviceintelligence.IFeatureCallback;
+ import android.app.ondeviceintelligence.IFeatureDetailsCallback;
+ import android.app.ondeviceintelligence.IResponseCallback;
+ import android.app.ondeviceintelligence.IStreamingResponseCallback;
+ import android.app.ondeviceintelligence.IProcessingSignal;
+ import android.app.ondeviceintelligence.ITokenCountCallback;
+
+
+ /**
+ * Interface for a OnDeviceIntelligenceManager for managing OnDeviceIntelligenceService and OnDeviceSandboxedInferenceService.
+ *
+ * @hide
+ */
+ oneway interface IOnDeviceIntelligenceManager {
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)")
+ void getVersion(in RemoteCallback remoteCallback) = 1;
+
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)")
+ void getFeature(in int featureId, in IFeatureCallback remoteCallback) = 2;
+
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)")
+ void listFeatures(in IListFeaturesCallback listFeaturesCallback) = 3;
+
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)")
+ void getFeatureDetails(in Feature feature, in IFeatureDetailsCallback featureDetailsCallback) = 4;
+
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)")
+ void requestFeatureDownload(in Feature feature, ICancellationSignal signal, in IDownloadCallback callback) = 5;
+
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)")
+ void requestTokenCount(in Feature feature, in Content request, in ICancellationSignal signal,
+ in ITokenCountCallback tokenCountcallback) = 6;
+
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)")
+ void processRequest(in Feature feature, in Content request, int requestType, in ICancellationSignal cancellationSignal, in IProcessingSignal signal,
+ in IResponseCallback responseCallback) = 7;
+
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)")
+ void processRequestStreaming(in Feature feature,
+ in Content request, int requestType, in ICancellationSignal cancellationSignal, in IProcessingSignal signal,
+ in IStreamingResponseCallback streamingCallback) = 8;
+ }
diff --git a/core/java/android/app/ondeviceintelligence/IProcessingSignal.aidl b/core/java/android/app/ondeviceintelligence/IProcessingSignal.aidl
new file mode 100644
index 000000000000..03946eebd40b
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/IProcessingSignal.aidl
@@ -0,0 +1,14 @@
+package android.app.ondeviceintelligence;
+
+import android.os.PersistableBundle;
+
+/**
+* Signal to provide to the remote implementation in context of a given request or
+* feature specific event.
+*
+* @hide
+*/
+
+oneway interface IProcessingSignal {
+ void sendSignal(in PersistableBundle actionParams) = 2;
+}
diff --git a/core/java/android/app/ondeviceintelligence/IResponseCallback.aidl b/core/java/android/app/ondeviceintelligence/IResponseCallback.aidl
new file mode 100644
index 000000000000..9848e1ddda5a
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/IResponseCallback.aidl
@@ -0,0 +1,15 @@
+package android.app.ondeviceintelligence;
+
+import android.app.ondeviceintelligence.Content;
+import android.app.ondeviceintelligence.IProcessingSignal;
+import android.os.PersistableBundle;
+
+/**
+ * Interface for a IResponseCallback for receiving response from on-device intelligence service.
+ *
+ * @hide
+ */
+interface IResponseCallback {
+ void onSuccess(in Content result) = 1;
+ void onFailure(int errorCode, in String errorMessage, in PersistableBundle errorParams) = 2;
+}
diff --git a/core/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl b/core/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl
new file mode 100644
index 000000000000..a6805749fa04
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl
@@ -0,0 +1,18 @@
+package android.app.ondeviceintelligence;
+
+import android.app.ondeviceintelligence.Content;
+import android.app.ondeviceintelligence.IResponseCallback;
+import android.app.ondeviceintelligence.IProcessingSignal;
+import android.os.PersistableBundle;
+
+
+/**
+ * This callback is a streaming variant of {@link IResponseCallback}.
+ *
+ * @hide
+ */
+interface IStreamingResponseCallback {
+ void onNewContent(in Content result) = 1;
+ void onSuccess(in Content result) = 2;
+ void onFailure(int errorCode, in String errorMessage, in PersistableBundle errorParams) = 3;
+}
diff --git a/core/java/android/app/ondeviceintelligence/ITokenCountCallback.aidl b/core/java/android/app/ondeviceintelligence/ITokenCountCallback.aidl
new file mode 100644
index 000000000000..b724e03fbca4
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/ITokenCountCallback.aidl
@@ -0,0 +1,13 @@
+package android.app.ondeviceintelligence;
+
+import android.os.PersistableBundle;
+
+/**
+ * Interface for receiving the token count of a request for a given features.
+ *
+ * @hide
+ */
+interface ITokenCountCallback {
+ void onSuccess(long tokenCount) = 1;
+ void onFailure(int errorCode, in String errorMessage, in PersistableBundle errorParams) = 2;
+}
diff --git a/core/java/android/app/ondeviceintelligence/OWNERS b/core/java/android/app/ondeviceintelligence/OWNERS
index 6932ba23a8ac..85e9e653e6fb 100644
--- a/core/java/android/app/ondeviceintelligence/OWNERS
+++ b/core/java/android/app/ondeviceintelligence/OWNERS
@@ -4,4 +4,3 @@ sandeepbandaru@google.com
shivanker@google.com
hackz@google.com
volnov@google.com
-
diff --git a/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java b/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java
new file mode 100644
index 000000000000..4d8e0d55e355
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java
@@ -0,0 +1,624 @@
+/*
+ * 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.app.ondeviceintelligence;
+
+import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
+
+import android.Manifest;
+import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.os.Binder;
+import android.os.CancellationSignal;
+import android.os.ICancellationSignal;
+import android.os.OutcomeReceiver;
+import android.os.PersistableBundle;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+
+import androidx.annotation.IntDef;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.function.LongConsumer;
+
+/**
+ * Allows granted apps to manage on-device intelligence service configured on the device. Typical
+ * calling pattern will be to query and setup a required feature before proceeding to request
+ * processing.
+ *
+ * The contracts in this Manager class are designed to be open-ended in general, to allow
+ * interoperability. Therefore, it is recommended that implementations of this system-service
+ * expose this API to the clients via a separate sdk or library which has more defined contract.
+ *
+ * @hide
+ */
+@SystemApi
+@SystemService(Context.ON_DEVICE_INTELLIGENCE_SERVICE)
+@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
+public class OnDeviceIntelligenceManager {
+ public static final String API_VERSION_BUNDLE_KEY = "ApiVersionBundleKey";
+ private final Context mContext;
+ private final IOnDeviceIntelligenceManager mService;
+
+ /**
+ * @hide
+ */
+ public OnDeviceIntelligenceManager(Context context, IOnDeviceIntelligenceManager service) {
+ mContext = context;
+ mService = service;
+ }
+
+ /**
+ * Asynchronously get the version of the underlying remote implementation.
+ *
+ * @param versionConsumer consumer to populate the version of remote implementation.
+ * @param callbackExecutor executor to run the callback on.
+ */
+ @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
+ public void getVersion(
+ @NonNull @CallbackExecutor Executor callbackExecutor,
+ @NonNull LongConsumer versionConsumer) {
+ // TODO explore modifying this method into getServicePackageDetails and return both
+ // version and package name of the remote service implementing this.
+ try {
+ RemoteCallback callback = new RemoteCallback(result -> {
+ if (result == null) {
+ Binder.withCleanCallingIdentity(
+ () -> callbackExecutor.execute(() -> versionConsumer.accept(0)));
+ }
+ long version = result.getLong(API_VERSION_BUNDLE_KEY);
+ Binder.withCleanCallingIdentity(
+ () -> callbackExecutor.execute(() -> versionConsumer.accept(version)));
+ });
+ mService.getVersion(callback);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Asynchronously get feature for a given id.
+ *
+ * @param featureId the identifier pointing to the feature.
+ * @param featureReceiver callback to populate the feature object for given identifier.
+ * @param callbackExecutor executor to run the callback on.
+ */
+ @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
+ public void getFeature(
+ int featureId,
+ @NonNull @CallbackExecutor Executor callbackExecutor,
+ @NonNull OutcomeReceiver<Feature, OnDeviceIntelligenceManagerException> featureReceiver) {
+ try {
+ IFeatureCallback callback =
+ new IFeatureCallback.Stub() {
+ @Override
+ public void onSuccess(Feature result) {
+ Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+ () -> featureReceiver.onResult(result)));
+ }
+
+ @Override
+ public void onFailure(int errorCode, String errorMessage,
+ PersistableBundle errorParams) {
+ Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+ () -> featureReceiver.onError(
+ new OnDeviceIntelligenceManagerException(
+ errorCode, errorMessage, errorParams))));
+ }
+ };
+ mService.getFeature(featureId, callback);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Asynchronously get a list of features that are supported for the caller.
+ *
+ * @param featureListReceiver callback to populate the list of features.
+ * @param callbackExecutor executor to run the callback on.
+ */
+ @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
+ public void listFeatures(
+ @NonNull @CallbackExecutor Executor callbackExecutor,
+ @NonNull OutcomeReceiver<List<Feature>, OnDeviceIntelligenceManagerException> featureListReceiver) {
+ try {
+ IListFeaturesCallback callback =
+ new IListFeaturesCallback.Stub() {
+ @Override
+ public void onSuccess(List<Feature> result) {
+ Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+ () -> featureListReceiver.onResult(result)));
+ }
+
+ @Override
+ public void onFailure(int errorCode, String errorMessage,
+ PersistableBundle errorParams) {
+ Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+ () -> featureListReceiver.onError(
+ new OnDeviceIntelligenceManagerException(
+ errorCode, errorMessage, errorParams))));
+ }
+ };
+ mService.listFeatures(callback);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * This method should be used to fetch details about a feature which need some additional
+ * computation, that can be inefficient to return in all calls to {@link #getFeature}. Callers
+ * and implementation can utilize the {@link Feature#getFeatureParams()} to pass hint on what
+ * details are expected by the caller.
+ *
+ * @param feature the feature to check status for.
+ * @param featureDetailsReceiver callback to populate the feature details to.
+ * @param callbackExecutor executor to run the callback on.
+ */
+ @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
+ public void getFeatureDetails(@NonNull Feature feature,
+ @NonNull @CallbackExecutor Executor callbackExecutor,
+ @NonNull OutcomeReceiver<FeatureDetails, OnDeviceIntelligenceManagerException> featureDetailsReceiver) {
+ try {
+ IFeatureDetailsCallback callback = new IFeatureDetailsCallback.Stub() {
+
+ @Override
+ public void onSuccess(FeatureDetails result) {
+ Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+ () -> featureDetailsReceiver.onResult(result)));
+ }
+
+ @Override
+ public void onFailure(int errorCode, String errorMessage,
+ PersistableBundle errorParams) {
+ Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+ () -> featureDetailsReceiver.onError(
+ new OnDeviceIntelligenceManagerException(errorCode,
+ errorMessage, errorParams))));
+ }
+ };
+ mService.getFeatureDetails(feature, callback);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * This method handles downloading all model and config files required to process requests
+ * sent against a given feature. The caller can listen to updates on the download status via
+ * the callback.
+ *
+ * Note: If a feature was already requested for downloaded previously, the onDownloadFailed
+ * callback would be invoked with {@link DownloadCallback#DOWNLOAD_FAILURE_STATUS_DOWNLOADING}.
+ * In such cases, clients should query the feature status via {@link #getFeatureStatus} to
+ * check
+ * on the feature's download status.
+ *
+ * @param feature feature to request download for.
+ * @param callback callback to populate updates about download status.
+ * @param cancellationSignal signal to invoke cancellation on the operation in the remote
+ * implementation.
+ * @param callbackExecutor executor to run the callback on.
+ */
+ @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
+ public void requestFeatureDownload(@NonNull Feature feature,
+ @Nullable CancellationSignal cancellationSignal,
+ @NonNull @CallbackExecutor Executor callbackExecutor,
+ @NonNull DownloadCallback callback) {
+ try {
+ IDownloadCallback downloadCallback = new IDownloadCallback.Stub() {
+
+ @Override
+ public void onDownloadStarted(long bytesToDownload) {
+ Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+ () -> callback.onDownloadStarted(bytesToDownload)));
+ }
+
+ @Override
+ public void onDownloadProgress(long bytesDownloaded) {
+ Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+ () -> callback.onDownloadProgress(bytesDownloaded)));
+ }
+
+ @Override
+ public void onDownloadFailed(int failureStatus, String errorMessage,
+ PersistableBundle errorParams) {
+ Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+ () -> callback.onDownloadFailed(failureStatus, errorMessage,
+ errorParams)));
+ }
+
+ @Override
+ public void onDownloadCompleted(PersistableBundle downloadParams) {
+ Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+ () -> onDownloadCompleted(downloadParams)));
+ }
+ };
+
+ ICancellationSignal transport = null;
+ if (cancellationSignal != null) {
+ transport = CancellationSignal.createTransport();
+ cancellationSignal.setRemote(transport);
+ }
+
+ mService.requestFeatureDownload(feature, transport, downloadCallback);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * The methods computes the token-count for a given request payload using the provided Feature
+ * details.
+ *
+ * @param feature feature associated with the request.
+ * @param request request that contains the content data and associated params.
+ * @param outcomeReceiver callback to populate the token count or exception in case of
+ * failure.
+ * @param cancellationSignal signal to invoke cancellation on the operation in the remote
+ * implementation.
+ * @param callbackExecutor executor to run the callback on.
+ */
+ @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
+ public void requestTokenCount(@NonNull Feature feature, @NonNull Content request,
+ @Nullable CancellationSignal cancellationSignal,
+ @NonNull @CallbackExecutor Executor callbackExecutor,
+ @NonNull OutcomeReceiver<Long,
+ OnDeviceIntelligenceManagerException> outcomeReceiver) {
+ try {
+ ITokenCountCallback callback = new ITokenCountCallback.Stub() {
+ @Override
+ public void onSuccess(long tokenCount) {
+ Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+ () -> outcomeReceiver.onResult(tokenCount)));
+ }
+
+ @Override
+ public void onFailure(int errorCode, String errorMessage,
+ PersistableBundle errorParams) {
+ Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+ () -> outcomeReceiver.onError(
+ new OnDeviceIntelligenceManagerProcessingException(
+ errorCode, errorMessage, errorParams))));
+ }
+ };
+
+ ICancellationSignal transport = null;
+ if (cancellationSignal != null) {
+ transport = CancellationSignal.createTransport();
+ cancellationSignal.setRemote(transport);
+ }
+
+ mService.requestTokenCount(feature, request, transport, callback);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+
+ /**
+ * Asynchronously Process a request based on the associated params, to populate a
+ * response in
+ * {@link OutcomeReceiver#onResult} callback or failure callback status code if there
+ * was a
+ * failure.
+ *
+ * @param feature feature associated with the request.
+ * @param request request that contains the Content data and
+ * associated params.
+ * @param requestType type of request being sent for processing the content.
+ * @param responseOutcomeReceiver callback to populate the response content and
+ * associated
+ * params.
+ * @param processingSignal signal to invoke custom actions in the
+ * remote implementation.
+ * @param cancellationSignal signal to invoke cancellation or
+ * @param callbackExecutor executor to run the callback on.
+ */
+ @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
+
+ public void processRequest(@NonNull Feature feature, @NonNull Content request,
+ @RequestType int requestType,
+ @Nullable CancellationSignal cancellationSignal,
+ @Nullable ProcessingSignal processingSignal,
+ @NonNull @CallbackExecutor Executor callbackExecutor,
+ @NonNull OutcomeReceiver<Content,
+ OnDeviceIntelligenceManagerProcessingException> responseOutcomeReceiver) {
+ try {
+ IResponseCallback callback = new IResponseCallback.Stub() {
+ @Override
+ public void onSuccess(Content result) {
+ Binder.withCleanCallingIdentity(() -> {
+ callbackExecutor.execute(() -> responseOutcomeReceiver.onResult(result));
+ });
+ }
+
+ @Override
+ public void onFailure(int errorCode, String errorMessage,
+ PersistableBundle errorParams) {
+ Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+ () -> responseOutcomeReceiver.onError(
+ new OnDeviceIntelligenceManagerProcessingException(
+ errorCode, errorMessage, errorParams))));
+ }
+ };
+
+ IProcessingSignal transport = null;
+ if (processingSignal != null) {
+ transport = ProcessingSignal.createTransport();
+ processingSignal.setRemote(transport);
+ }
+
+ ICancellationSignal cancellationTransport = null;
+ if (cancellationSignal != null) {
+ cancellationTransport = CancellationSignal.createTransport();
+ cancellationSignal.setRemote(cancellationTransport);
+ }
+
+ mService.processRequest(feature, request, requestType, cancellationTransport, transport,
+ callback);
+
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Variation of {@link #processRequest} that asynchronously processes a request in a streaming
+ * fashion, where new content is pushed to caller in chunks via the
+ * {@link StreamingResponseReceiver#onNewContent}. After the streaming is complete,
+ * the service should call {@link StreamingResponseReceiver#onResult} and can optionally
+ * populate the complete {@link Response}'s Content as part of the callback when the final
+ * {@link Response} contains an enhanced aggregation of the Contents already streamed.
+ *
+ * @param feature feature associated with the request.
+ * @param request request that contains the Content data and associated
+ * params.
+ * @param requestType type of request being sent for processing the content.
+ * @param processingSignal signal to invoke other custom actions in the
+ * remote implementation.
+ * @param cancellationSignal signal to invoke cancellation
+ * @param streamingResponseReceiver streaming callback to populate the response content and
+ * associated params.
+ * @param callbackExecutor executor to run the callback on.
+ */
+ @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
+ public void processRequestStreaming(@NonNull Feature feature, @NonNull Content request,
+ @RequestType int requestType,
+ @Nullable CancellationSignal cancellationSignal,
+ @Nullable ProcessingSignal processingSignal,
+ @NonNull @CallbackExecutor Executor callbackExecutor,
+ @NonNull StreamingResponseReceiver<Content, Content,
+ OnDeviceIntelligenceManagerProcessingException> streamingResponseReceiver) {
+ try {
+ IStreamingResponseCallback callback = new IStreamingResponseCallback.Stub() {
+ @Override
+ public void onNewContent(Content result) {
+ Binder.withCleanCallingIdentity(() -> {
+ callbackExecutor.execute(
+ () -> streamingResponseReceiver.onNewContent(result));
+ });
+ }
+
+ @Override
+ public void onSuccess(Content result) {
+ Binder.withCleanCallingIdentity(() -> {
+ callbackExecutor.execute(() -> streamingResponseReceiver.onResult(result));
+ });
+ }
+
+ @Override
+ public void onFailure(int errorCode, String errorMessage,
+ PersistableBundle errorParams) {
+ Binder.withCleanCallingIdentity(() -> {
+ callbackExecutor.execute(
+ () -> streamingResponseReceiver.onError(
+ new OnDeviceIntelligenceManagerProcessingException(
+ errorCode, errorMessage, errorParams)));
+ });
+ }
+ };
+
+ IProcessingSignal transport = null;
+ if (processingSignal != null) {
+ transport = ProcessingSignal.createTransport();
+ processingSignal.setRemote(transport);
+ }
+
+ ICancellationSignal cancellationTransport = null;
+ if (cancellationSignal != null) {
+ cancellationTransport = CancellationSignal.createTransport();
+ cancellationSignal.setRemote(cancellationTransport);
+ }
+
+ mService.processRequestStreaming(
+ feature, request, requestType, cancellationTransport, transport, callback);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+
+ /** Request inference with provided Content and Params. */
+ public static final int REQUEST_TYPE_INFERENCE = 0;
+
+ /**
+ * Prepares the remote implementation environment for e.g.loading inference runtime etc.which
+ * are time consuming beforehand to remove overhead and allow quick processing of requests
+ * thereof.
+ */
+ public static final int REQUEST_TYPE_PREPARE = 1;
+
+ /** Request Embeddings of the passed-in Content. */
+ public static final int REQUEST_TYPE_EMBEDDINGS = 2;
+
+ /**
+ * @hide
+ */
+ @IntDef(value = {
+ REQUEST_TYPE_INFERENCE,
+ REQUEST_TYPE_PREPARE,
+ REQUEST_TYPE_EMBEDDINGS
+ }, open = true)
+ @Target({ElementType.TYPE_USE, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface RequestType {
+ }
+
+
+ /**
+ * Exception type to be populated in callbacks to the methods under
+ * {@link OnDeviceIntelligenceManager}.
+ */
+ public static class OnDeviceIntelligenceManagerException extends Exception {
+ /**
+ * Error code returned when the OnDeviceIntelligenceManager service is unavailable.
+ */
+ public static final int ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE = 1000;
+
+ private final int mErrorCode;
+ private final PersistableBundle errorParams;
+
+ public OnDeviceIntelligenceManagerException(int errorCode, @NonNull String errorMessage,
+ @NonNull PersistableBundle errorParams) {
+ super(errorMessage);
+ this.mErrorCode = errorCode;
+ this.errorParams = errorParams;
+ }
+
+ public OnDeviceIntelligenceManagerException(int errorCode,
+ @NonNull PersistableBundle errorParams) {
+ this.mErrorCode = errorCode;
+ this.errorParams = errorParams;
+ }
+
+ public int getErrorCode() {
+ return mErrorCode;
+ }
+
+ @NonNull
+ public PersistableBundle getErrorParams() {
+ return errorParams;
+ }
+ }
+
+ /**
+ * Exception type to be populated in callbacks to the methods under
+ * {@link OnDeviceIntelligenceManager#processRequest} or
+ * {@link OnDeviceIntelligenceManager#processRequestStreaming} .
+ */
+ public static class OnDeviceIntelligenceManagerProcessingException extends
+ OnDeviceIntelligenceManagerException {
+
+ public static final int PROCESSING_ERROR_UNKNOWN = 1;
+
+ /** Request passed contains bad data for e.g. format. */
+ public static final int PROCESSING_ERROR_BAD_DATA = 2;
+
+ /** Bad request for inputs. */
+ public static final int PROCESSING_ERROR_BAD_REQUEST = 3;
+
+ /** Whole request was classified as not safe, and no response will be generated. */
+ public static final int PROCESSING_ERROR_REQUEST_NOT_SAFE = 4;
+
+ /** Underlying processing encountered an error and failed to compute results. */
+ public static final int PROCESSING_ERROR_COMPUTE_ERROR = 5;
+
+ /** Encountered an error while performing IPC */
+ public static final int PROCESSING_ERROR_IPC_ERROR = 6;
+
+ /** Request was cancelled either by user signal or by the underlying implementation. */
+ public static final int PROCESSING_ERROR_CANCELLED = 7;
+
+ /** Underlying processing in the remote implementation is not available. */
+ public static final int PROCESSING_ERROR_NOT_AVAILABLE = 8;
+
+ /** The service is currently busy. Callers should retry with exponential backoff. */
+ public static final int PROCESSING_ERROR_BUSY = 9;
+
+ /** Something went wrong with safety classification service. */
+ public static final int PROCESSING_ERROR_SAFETY_ERROR = 10;
+
+ /** Response generated was classified unsafe. */
+ public static final int PROCESSING_ERROR_RESPONSE_NOT_SAFE = 11;
+
+ /** Request is too large to be processed. */
+ public static final int PROCESSING_ERROR_REQUEST_TOO_LARGE = 12;
+
+ /** Inference suspended so that higher-priority inference can run. */
+ public static final int PROCESSING_ERROR_SUSPENDED = 13;
+
+ /** Underlying processing encountered an internal error, like a violated precondition. */
+ public static final int PROCESSING_ERROR_INTERNAL = 14;
+
+ /**
+ * The processing was not able to be passed on to the remote implementation, as the service
+ * was unavailable.
+ */
+ public static final int PROCESSING_ERROR_SERVICE_UNAVAILABLE = 15;
+
+ /**
+ * Error code of failed processing request.
+ *
+ * @hide
+ */
+ @IntDef(
+ value = {
+ PROCESSING_ERROR_UNKNOWN,
+ PROCESSING_ERROR_BAD_DATA,
+ PROCESSING_ERROR_BAD_REQUEST,
+ PROCESSING_ERROR_REQUEST_NOT_SAFE,
+ PROCESSING_ERROR_COMPUTE_ERROR,
+ PROCESSING_ERROR_IPC_ERROR,
+ PROCESSING_ERROR_CANCELLED,
+ PROCESSING_ERROR_NOT_AVAILABLE,
+ PROCESSING_ERROR_BUSY,
+ PROCESSING_ERROR_SAFETY_ERROR,
+ PROCESSING_ERROR_RESPONSE_NOT_SAFE,
+ PROCESSING_ERROR_REQUEST_TOO_LARGE,
+ PROCESSING_ERROR_SUSPENDED,
+ PROCESSING_ERROR_INTERNAL,
+ PROCESSING_ERROR_SERVICE_UNAVAILABLE
+ }, open = true)
+ @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
+ @interface ProcessingError {
+ }
+
+ public OnDeviceIntelligenceManagerProcessingException(
+ @ProcessingError int errorCode, @NonNull String errorMessage,
+ @NonNull PersistableBundle errorParams) {
+ super(errorCode, errorMessage, errorParams);
+ }
+
+ public OnDeviceIntelligenceManagerProcessingException(
+ @ProcessingError int errorCode,
+ @NonNull PersistableBundle errorParams) {
+ super(errorCode, errorParams);
+ }
+ }
+}
diff --git a/core/java/android/app/ondeviceintelligence/ProcessingSignal.java b/core/java/android/app/ondeviceintelligence/ProcessingSignal.java
new file mode 100644
index 000000000000..3e543d27143a
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/ProcessingSignal.java
@@ -0,0 +1,221 @@
+/*
+ * 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.app.ondeviceintelligence;
+
+import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.PersistableBundle;
+import android.os.RemoteException;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.ArrayDeque;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * A signal to perform orchestration actions on the inference and optionally receive a output about
+ * the result of the signal. This is an extension of {@link android.os.CancellationSignal}.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
+public final class ProcessingSignal {
+ private final Object mLock = new Object();
+
+ private static final int MAX_QUEUE_SIZE = 10;
+
+ @GuardedBy("mLock")
+ private final ArrayDeque<PersistableBundle> mActionParamsQueue;
+
+ @GuardedBy("mLock")
+ private IProcessingSignal mRemote;
+
+ private OnProcessingSignalCallback mOnProcessingSignalCallback;
+ private Executor mExecutor;
+
+ public ProcessingSignal() {
+ mActionParamsQueue = new ArrayDeque<>(MAX_QUEUE_SIZE);
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when processing signals are received.
+ */
+ public interface OnProcessingSignalCallback {
+ /**
+ * Called when a custom signal was received.
+ * This method allows the receiver to provide logic to be executed based on the signal
+ * received.
+ *
+ * @param actionParams Parameters for the signal in the form of a {@link PersistableBundle}.
+ */
+
+ void onSignalReceived(@NonNull PersistableBundle actionParams);
+ }
+
+
+ /**
+ * Sends a custom signal with the provided parameters. It also signals the remote callback
+ * with the same params if already configured, if not the action is queued to be sent when a
+ * remote is configured. Similarly, on the receiver side, the callback will be invoked if
+ * already set, if not all actions are queued to be sent to callback when it is set.
+ *
+ * @param actionParams Parameters for the signal.
+ */
+ public void sendSignal(@NonNull PersistableBundle actionParams) {
+ final OnProcessingSignalCallback callback;
+ final IProcessingSignal remote;
+ synchronized (mLock) {
+ if (mActionParamsQueue.size() > MAX_QUEUE_SIZE) {
+ throw new RuntimeException(
+ "Maximum actions that can be queued are : " + MAX_QUEUE_SIZE);
+ }
+
+ mActionParamsQueue.add(actionParams);
+ callback = mOnProcessingSignalCallback;
+ remote = mRemote;
+
+ if (callback != null) {
+ while (!mActionParamsQueue.isEmpty()) {
+ PersistableBundle params = mActionParamsQueue.removeFirst();
+ mExecutor.execute(
+ () -> callback.onSignalReceived(params));
+ }
+ }
+ if (remote != null) {
+ while (!mActionParamsQueue.isEmpty()) {
+ try {
+ remote.sendSignal(mActionParamsQueue.removeFirst());
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Sets the processing signal callback to be called when signals are received.
+ *
+ * This method is intended to be used by the recipient of a processing signal
+ * such as the remote implementation for {@link OnDeviceIntelligenceManager} to handle
+ * cancellation requests while performing a long-running operation. This method is not
+ * intended
+ * to be used by applications themselves.
+ *
+ * If {@link ProcessingSignal#sendSignal} has already been called, then the provided callback
+ * is invoked immediately and all previously queued actions are passed to remote signal.
+ *
+ * This method is guaranteed that the callback will not be called after it
+ * has been removed.
+ *
+ * @param callback The processing signal callback, or null to remove the current callback.
+ * @param executor Executor to the run the callback methods on.
+ */
+ public void setOnProcessingSignalCallback(
+ @NonNull @CallbackExecutor Executor executor,
+ @Nullable OnProcessingSignalCallback callback) {
+ Objects.requireNonNull(executor);
+ synchronized (mLock) {
+ if (mOnProcessingSignalCallback == callback) {
+ return;
+ }
+
+ mOnProcessingSignalCallback = callback;
+ mExecutor = executor;
+ if (callback == null || mActionParamsQueue.isEmpty()) {
+ return;
+ }
+
+ while (!mActionParamsQueue.isEmpty()) {
+ PersistableBundle params = mActionParamsQueue.removeFirst();
+ mExecutor.execute(() -> callback.onSignalReceived(params));
+ }
+ }
+ }
+
+ /**
+ * Sets the remote transport.
+ *
+ * If there are actions queued from {@link ProcessingSignal#sendSignal}, they are also
+ * sequentially sent to the remote.
+ *
+ * This method is guaranteed that the remote transport will not be called after it
+ * has been removed.
+ *
+ * @param remote The remote transport, or null to remove.
+ * @hide
+ */
+ void setRemote(IProcessingSignal remote) {
+ synchronized (mLock) {
+ mRemote = remote;
+ if (mActionParamsQueue.isEmpty() || remote == null) {
+ return;
+ }
+
+ while (!mActionParamsQueue.isEmpty()) {
+ try {
+ remote.sendSignal(mActionParamsQueue.removeFirst());
+ } catch (RemoteException e) {
+ throw new RuntimeException("Failed to send action to remote signal", e);
+ }
+ }
+ }
+ }
+
+ /**
+ * Creates a transport that can be returned back to the caller of
+ * a Binder function and subsequently used to dispatch a processing signal.
+ *
+ * @return The new processing signal transport.
+ * @hide
+ */
+ public static IProcessingSignal createTransport() {
+ return new Transport();
+ }
+
+ /**
+ * Given a locally created transport, returns its associated cancellation signal.
+ *
+ * @param transport The locally created transport, or null if none.
+ * @return The associated processing signal, or null if none.
+ * @hide
+ */
+ public static ProcessingSignal fromTransport(IProcessingSignal transport) {
+ if (transport instanceof Transport) {
+ return ((Transport) transport).mProcessingSignal;
+ }
+ return null;
+ }
+
+ private static final class Transport extends IProcessingSignal.Stub {
+ final ProcessingSignal mProcessingSignal = new ProcessingSignal();
+
+ @Override
+ public void sendSignal(PersistableBundle actionParams) {
+ mProcessingSignal.sendSignal(actionParams);
+ }
+ }
+
+} \ No newline at end of file
diff --git a/core/java/android/app/ondeviceintelligence/StreamingResponseReceiver.java b/core/java/android/app/ondeviceintelligence/StreamingResponseReceiver.java
new file mode 100644
index 000000000000..ebcf61c8c0c2
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/StreamingResponseReceiver.java
@@ -0,0 +1,43 @@
+/*
+ * 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.app.ondeviceintelligence;
+
+import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.OutcomeReceiver;
+
+/**
+ * Streaming variant of outcome receiver to populate response while processing a given request,
+ * possibly in
+ * chunks to provide a async processing behaviour to the caller.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
+public interface StreamingResponseReceiver<R, T, E extends Throwable> extends
+ OutcomeReceiver<R, E> {
+ /**
+ * Callback to be invoked when a part of the response i.e. some {@link Content} is already
+ * processed and
+ * needs to be passed onto the caller.
+ */
+ void onNewContent(@NonNull T content);
+}
diff --git a/core/java/android/app/servertransaction/ClientTransaction.java b/core/java/android/app/servertransaction/ClientTransaction.java
index 612d433adcd6..79696e047904 100644
--- a/core/java/android/app/servertransaction/ClientTransaction.java
+++ b/core/java/android/app/servertransaction/ClientTransaction.java
@@ -29,6 +29,7 @@ import android.os.Parcelable;
import android.os.RemoteException;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.window.flags.Flags;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -85,12 +86,15 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem {
* @param item A single message that can contain a client activity/window request/callback.
*/
public void addTransactionItem(@NonNull ClientTransactionItem item) {
- if (mTransactionItems == null) {
- mTransactionItems = new ArrayList<>();
+ if (Flags.bundleClientTransactionFlag()) {
+ if (mTransactionItems == null) {
+ mTransactionItems = new ArrayList<>();
+ }
+ mTransactionItems.add(item);
}
- mTransactionItems.add(item);
// TODO(b/324203798): cleanup after remove UnsupportedAppUsage
+ // Populate even if mTransactionItems is set to support the UnsupportedAppUsage.
if (item.isActivityLifecycleItem()) {
setLifecycleStateRequest((ActivityLifecycleItem) item);
} else {
@@ -114,7 +118,7 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem {
*/
// TODO(b/324203798): cleanup after remove UnsupportedAppUsage
@Deprecated
- public void addCallback(@NonNull ClientTransactionItem activityCallback) {
+ private void addCallback(@NonNull ClientTransactionItem activityCallback) {
if (mActivityCallbacks == null) {
mActivityCallbacks = new ArrayList<>();
}
@@ -169,7 +173,7 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem {
*/
// TODO(b/324203798): cleanup after remove UnsupportedAppUsage
@Deprecated
- public void setLifecycleStateRequest(@NonNull ActivityLifecycleItem stateRequest) {
+ private void setLifecycleStateRequest(@NonNull ActivityLifecycleItem stateRequest) {
if (mLifecycleStateRequest != null) {
return;
}
diff --git a/core/java/android/app/servertransaction/ClientTransactionListenerController.java b/core/java/android/app/servertransaction/ClientTransactionListenerController.java
index 9f97f6ff7c39..1a8136e06c28 100644
--- a/core/java/android/app/servertransaction/ClientTransactionListenerController.java
+++ b/core/java/android/app/servertransaction/ClientTransactionListenerController.java
@@ -23,7 +23,6 @@ import static java.util.Objects.requireNonNull;
import android.annotation.NonNull;
import android.app.ActivityThread;
import android.hardware.display.DisplayManagerGlobal;
-import android.os.Process;
import com.android.internal.annotations.VisibleForTesting;
@@ -67,7 +66,7 @@ public class ClientTransactionListenerController {
* window configuration.
*/
public void onDisplayChanged(int displayId) {
- if (!isBundleClientTransactionFlagEnabled()) {
+ if (!bundleClientTransactionFlag()) {
return;
}
if (ActivityThread.isSystem()) {
@@ -76,10 +75,4 @@ public class ClientTransactionListenerController {
}
mDisplayManager.handleDisplayChangeFromWindowManager(displayId);
}
-
- /** Whether {@link #bundleClientTransactionFlag} feature flag is enabled. */
- public boolean isBundleClientTransactionFlagEnabled() {
- // Can't read flag from isolated process.
- return !Process.isIsolated() && bundleClientTransactionFlag();
- }
}
diff --git a/core/java/android/app/servertransaction/TransactionExecutor.java b/core/java/android/app/servertransaction/TransactionExecutor.java
index 406e00a84710..fa73c99be2b8 100644
--- a/core/java/android/app/servertransaction/TransactionExecutor.java
+++ b/core/java/android/app/servertransaction/TransactionExecutor.java
@@ -40,7 +40,6 @@ import android.app.ClientTransactionHandler;
import android.content.Context;
import android.content.res.Configuration;
import android.os.IBinder;
-import android.os.Process;
import android.os.Trace;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -218,8 +217,6 @@ public class TransactionExecutor {
final boolean shouldTrackConfigUpdatedContext =
// No configuration change for local transaction.
!mTransactionHandler.isExecutingLocalTransaction()
- // Can't read flag from isolated process.
- && !Process.isIsolated()
&& bundleClientTransactionFlag();
final Context configUpdatedContext = shouldTrackConfigUpdatedContext
? item.getContextToUpdate(mTransactionHandler)
diff --git a/core/java/android/app/ui_mode_manager.aconfig b/core/java/android/app/ui_mode_manager.aconfig
index 1ae5264a7e8e..27a38cc2bfd6 100644
--- a/core/java/android/app/ui_mode_manager.aconfig
+++ b/core/java/android/app/ui_mode_manager.aconfig
@@ -1,8 +1,11 @@
package: "android.app"
flag {
- namespace: "system_performance"
- name: "enable_night_mode_cache"
+ namespace: "systemui"
+ name: "enable_night_mode_binder_cache"
description: "Enables the use of binder caching for system night mode."
bug: "255999432"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
} \ No newline at end of file
diff --git a/core/java/android/app/wearable/IWearableSensingManager.aidl b/core/java/android/app/wearable/IWearableSensingManager.aidl
index f67802279e26..7d3b28511537 100644
--- a/core/java/android/app/wearable/IWearableSensingManager.aidl
+++ b/core/java/android/app/wearable/IWearableSensingManager.aidl
@@ -30,7 +30,7 @@ import android.os.SharedMemory;
*/
interface IWearableSensingManager {
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE)")
- void provideWearableConnection(in ParcelFileDescriptor parcelFileDescriptor, in RemoteCallback callback);
+ void provideConnection(in ParcelFileDescriptor parcelFileDescriptor, in RemoteCallback callback);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE)")
void provideDataStream(in ParcelFileDescriptor parcelFileDescriptor, in RemoteCallback callback);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE)")
diff --git a/core/java/android/app/wearable/WearableSensingManager.java b/core/java/android/app/wearable/WearableSensingManager.java
index 637f6776bd1b..fd72c491bf16 100644
--- a/core/java/android/app/wearable/WearableSensingManager.java
+++ b/core/java/android/app/wearable/WearableSensingManager.java
@@ -127,7 +127,7 @@ public class WearableSensingManager {
/**
* The value of the status code that indicates an error occurred in the encrypted channel backed
- * by the provided connection. See {@link #provideWearableConnection(ParcelFileDescriptor,
+ * by the provided connection. See {@link #provideConnection(ParcelFileDescriptor,
* Executor, Consumer)}.
*/
@FlaggedApi(Flags.FLAG_ENABLE_PROVIDE_WEARABLE_CONNECTION_API)
@@ -223,13 +223,13 @@ public class WearableSensingManager {
*/
@RequiresPermission(Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE)
@FlaggedApi(Flags.FLAG_ENABLE_PROVIDE_WEARABLE_CONNECTION_API)
- public void provideWearableConnection(
+ public void provideConnection(
@NonNull ParcelFileDescriptor wearableConnection,
@NonNull @CallbackExecutor Executor executor,
@NonNull @StatusCode Consumer<Integer> statusConsumer) {
try {
RemoteCallback callback = createStatusCallback(executor, statusConsumer);
- mService.provideWearableConnection(wearableConnection, callback);
+ mService.provideConnection(wearableConnection, callback);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/companion/virtual/flags/flags.aconfig b/core/java/android/companion/virtual/flags/flags.aconfig
index ab8db6e59ddb..24d6a5cfc42d 100644
--- a/core/java/android/companion/virtual/flags/flags.aconfig
+++ b/core/java/android/companion/virtual/flags/flags.aconfig
@@ -29,3 +29,18 @@ flag {
bug: "291737188"
is_fixed_read_only: true
}
+
+flag {
+ namespace: "virtual_devices"
+ name: "metrics_collection"
+ description: "Enable collection of VDM-related metrics"
+ bug: "324842215"
+ is_fixed_read_only: true
+}
+
+flag {
+ namespace: "virtual_devices"
+ name: "camera_device_awareness"
+ description: "Enable device awareness in camera service"
+ bug: "305170199"
+}
diff --git a/core/java/android/content/ClipData.java b/core/java/android/content/ClipData.java
index eb357fe09a31..728c350bfb51 100644
--- a/core/java/android/content/ClipData.java
+++ b/core/java/android/content/ClipData.java
@@ -26,8 +26,6 @@ import static com.android.window.flags.Flags.FLAG_DELEGATE_UNHANDLED_DRAGS;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.SuppressLint;
-import android.app.PendingIntent;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.ActivityInfo;
import android.content.res.AssetFileDescriptor;
@@ -213,7 +211,7 @@ public class ClipData implements Parcelable {
final CharSequence mText;
final String mHtmlText;
final Intent mIntent;
- final PendingIntent mPendingIntent;
+ final IntentSender mIntentSender;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
Uri mUri;
private TextLinks mTextLinks;
@@ -225,12 +223,11 @@ public class ClipData implements Parcelable {
* A builder for a ClipData Item.
*/
@FlaggedApi(FLAG_DELEGATE_UNHANDLED_DRAGS)
- @SuppressLint("PackageLayering")
public static final class Builder {
private CharSequence mText;
private String mHtmlText;
private Intent mIntent;
- private PendingIntent mPendingIntent;
+ private IntentSender mIntentSender;
private Uri mUri;
/**
@@ -264,18 +261,20 @@ public class ClipData implements Parcelable {
}
/**
- * Sets the PendingIntent for the item to be constructed. To prevent receiving apps from
- * improperly manipulating the intent to launch another activity as this caller, the
- * provided PendingIntent must be immutable (see {@link PendingIntent#FLAG_IMMUTABLE}).
- * The system will clean up the PendingIntent when it is no longer used.
+ * Sets the {@link IntentSender} for the item to be constructed. To prevent receiving
+ * apps from improperly manipulating the intent to launch another activity as this
+ * caller, the provided IntentSender must be immutable.
+ *
+ * If there is a fixed lifetime for this ClipData (ie. for drag and drop), the system
+ * will cancel the IntentSender when it is no longer used.
*/
@FlaggedApi(FLAG_DELEGATE_UNHANDLED_DRAGS)
@NonNull
- public Builder setPendingIntent(@Nullable PendingIntent pendingIntent) {
- if (pendingIntent != null && !pendingIntent.isImmutable()) {
- throw new IllegalArgumentException("Expected pending intent to be immutable");
+ public Builder setIntentSender(@Nullable IntentSender intentSender) {
+ if (intentSender != null && !intentSender.isImmutable()) {
+ throw new IllegalArgumentException("Expected intent sender to be immutable");
}
- mPendingIntent = pendingIntent;
+ mIntentSender = intentSender;
return this;
}
@@ -295,7 +294,7 @@ public class ClipData implements Parcelable {
@FlaggedApi(FLAG_DELEGATE_UNHANDLED_DRAGS)
@NonNull
public Item build() {
- return new Item(mText, mHtmlText, mIntent, mPendingIntent, mUri);
+ return new Item(mText, mHtmlText, mIntent, mIntentSender, mUri);
}
}
@@ -305,7 +304,7 @@ public class ClipData implements Parcelable {
mText = other.mText;
mHtmlText = other.mHtmlText;
mIntent = other.mIntent;
- mPendingIntent = other.mPendingIntent;
+ mIntentSender = other.mIntentSender;
mUri = other.mUri;
mActivityInfo = other.mActivityInfo;
mTextLinks = other.mTextLinks;
@@ -366,7 +365,7 @@ public class ClipData implements Parcelable {
/**
* Builder ctor.
*/
- private Item(CharSequence text, String htmlText, Intent intent, PendingIntent pendingIntent,
+ private Item(CharSequence text, String htmlText, Intent intent, IntentSender intentSender,
Uri uri) {
if (htmlText != null && text == null) {
throw new IllegalArgumentException(
@@ -375,7 +374,7 @@ public class ClipData implements Parcelable {
mText = text;
mHtmlText = htmlText;
mIntent = intent;
- mPendingIntent = pendingIntent;
+ mIntentSender = intentSender;
mUri = uri;
}
@@ -401,12 +400,12 @@ public class ClipData implements Parcelable {
}
/**
- * Returns the pending intent in this Item.
+ * Returns the {@link IntentSender} in this Item.
*/
@FlaggedApi(FLAG_DELEGATE_UNHANDLED_DRAGS)
@Nullable
- public PendingIntent getPendingIntent() {
- return mPendingIntent;
+ public IntentSender getIntentSender() {
+ return mIntentSender;
}
/**
@@ -1131,35 +1130,6 @@ public class ClipData implements Parcelable {
}
/**
- * Checks if this clip data has a pending intent that is an activity type.
- * @hide
- */
- public boolean hasActivityPendingIntents() {
- final int size = mItems.size();
- for (int i = 0; i < size; i++) {
- final Item item = mItems.get(i);
- if (item.mPendingIntent != null && item.mPendingIntent.isActivity()) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Cleans up all pending intents in the ClipData.
- * @hide
- */
- public void cleanUpPendingIntents() {
- final int size = mItems.size();
- for (int i = 0; i < size; i++) {
- final Item item = mItems.get(i);
- if (item.mPendingIntent != null) {
- item.mPendingIntent.cancel();
- }
- }
- }
-
- /**
* Prepare this {@link ClipData} to leave an app process.
*
* @hide
@@ -1361,7 +1331,7 @@ public class ClipData implements Parcelable {
TextUtils.writeToParcel(item.mText, dest, flags);
dest.writeString8(item.mHtmlText);
dest.writeTypedObject(item.mIntent, flags);
- dest.writeTypedObject(item.mPendingIntent, flags);
+ dest.writeTypedObject(item.mIntentSender, flags);
dest.writeTypedObject(item.mUri, flags);
dest.writeTypedObject(mParcelItemActivityInfos ? item.mActivityInfo : null, flags);
dest.writeTypedObject(item.mTextLinks, flags);
@@ -1381,11 +1351,11 @@ public class ClipData implements Parcelable {
CharSequence text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
String htmlText = in.readString8();
Intent intent = in.readTypedObject(Intent.CREATOR);
- PendingIntent pendingIntent = in.readTypedObject(PendingIntent.CREATOR);
+ IntentSender intentSender = in.readTypedObject(IntentSender.CREATOR);
Uri uri = in.readTypedObject(Uri.CREATOR);
ActivityInfo info = in.readTypedObject(ActivityInfo.CREATOR);
TextLinks textLinks = in.readTypedObject(TextLinks.CREATOR);
- Item item = new Item(text, htmlText, intent, pendingIntent, uri);
+ Item item = new Item(text, htmlText, intent, intentSender, uri);
item.setActivityInfo(info);
item.setTextLinks(textLinks);
mItems.add(item);
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 58cea0d46681..7505372fe295 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -4815,7 +4815,9 @@ public abstract class Context {
* @see android.net.thread.ThreadNetworkManager
* @hide
*/
- @FlaggedApi(com.android.net.thread.flags.Flags.FLAG_THREAD_ENABLED_PLATFORM)
+ // TODO (b/325886480): update the flag to
+ // "com.android.net.thread.platform.flags.Flags.FLAG_THREAD_ENABLED_PLATFORM"
+ @FlaggedApi("com.android.net.thread.flags.thread_enabled_platform")
@SystemApi
public static final String THREAD_NETWORK_SERVICE = "thread_network";
@@ -6451,6 +6453,19 @@ public abstract class Context {
@SystemApi
public static final String WEARABLE_SENSING_SERVICE = "wearable_sensing";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.app.ondeviceintelligence.OnDeviceIntelligenceManager}.
+ *
+ * @see #getSystemService(String)
+ * @see OnDeviceIntelligenceManager
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
+ public static final String ON_DEVICE_INTELLIGENCE_SERVICE = "on_device_intelligence";
+
/**
* Use with {@link #getSystemService(String)} to retrieve a
* {@link android.health.connect.HealthConnectManager}.
@@ -6544,6 +6559,13 @@ public abstract class Context {
public static final String ECM_ENHANCED_CONFIRMATION_SERVICE = "ecm_enhanced_confirmation";
/**
+ * Service to protect sensitive content during screen share.
+ * @hide
+ */
+ public static final String SENSITIVE_CONTENT_PROTECTION_SERVICE =
+ "sensitive_content_protection_service";
+
+ /**
* Use with {@link #getSystemService(String)} to retrieve a
* {@link android.provider.ContactKeysManager} to managing contact keys.
*
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 0bcbb8e1868c..fd2af99b6a7e 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -7656,6 +7656,22 @@ public class Intent implements Parcelable, Cloneable {
/** @hide */
public static final int LOCAL_FLAG_FROM_SYSTEM = 1 << 5;
+ /** @hide */
+ @IntDef(flag = true, prefix = { "EXTENDED_FLAG_" }, value = {
+ EXTENDED_FLAG_FILTER_MISMATCH,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ExtendedFlags {}
+
+ /**
+ * This flag is not normally set by application code, but set for you by the system if
+ * an external intent does not match the receiving component's intent filter.
+ *
+ * @hide
+ */
+ @TestApi
+ public static final int EXTENDED_FLAG_FILTER_MISMATCH = 1 << 0;
+
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
// toUri() and parseUri() options.
@@ -7775,6 +7791,7 @@ public class Intent implements Parcelable, Cloneable {
private int mFlags;
/** Set of in-process flags which are never parceled */
private int mLocalFlags;
+ private int mExtendedFlags;
private ArraySet<String> mCategories;
@UnsupportedAppUsage
private Bundle mExtras;
@@ -7834,6 +7851,7 @@ public class Intent implements Parcelable, Cloneable {
if (copyMode != COPY_MODE_FILTER) {
this.mFlags = o.mFlags;
+ this.mExtendedFlags = o.mExtendedFlags;
this.mContentUserHint = o.mContentUserHint;
this.mLaunchToken = o.mLaunchToken;
if (o.mSourceBounds != null) {
@@ -8161,12 +8179,17 @@ public class Intent implements Parcelable, Cloneable {
// launch flags
else if (uri.startsWith("launchFlags=", i)) {
- intent.mFlags = Integer.decode(value).intValue();
+ intent.mFlags = Integer.decode(value);
if ((flags& URI_ALLOW_UNSAFE) == 0) {
intent.mFlags &= ~IMMUTABLE_FLAGS;
}
}
+ // extended flags
+ else if (uri.startsWith("extendedLaunchFlags=", i)) {
+ intent.mExtendedFlags = Integer.decode(value);
+ }
+
// package
else if (uri.startsWith("package=", i)) {
intent.mPackage = value;
@@ -8374,7 +8397,7 @@ public class Intent implements Parcelable, Cloneable {
isIntentFragment = true;
i += 12;
int j = uri.indexOf(')', i);
- intent.mFlags = Integer.decode(uri.substring(i, j)).intValue();
+ intent.mFlags = Integer.decode(uri.substring(i, j));
if ((flags& URI_ALLOW_UNSAFE) == 0) {
intent.mFlags &= ~IMMUTABLE_FLAGS;
}
@@ -9868,6 +9891,22 @@ public class Intent implements Parcelable, Cloneable {
return mFlags;
}
+ /**
+ * Retrieve any extended flags associated with this intent. You will
+ * normally just set them with {@link #setExtendedFlags} and let the system
+ * take the appropriate action with them.
+ *
+ * @return The currently set extended flags.
+ * @see #addExtendedFlags
+ * @see #removeExtendedFlags
+ *
+ * @hide
+ */
+ @TestApi
+ public @ExtendedFlags int getExtendedFlags() {
+ return mExtendedFlags;
+ }
+
/** @hide */
@UnsupportedAppUsage
public boolean isExcludingStopped() {
@@ -11198,6 +11237,23 @@ public class Intent implements Parcelable, Cloneable {
}
/**
+ * Add additional extended flags to the intent (or with existing flags value).
+ *
+ * @param flags The new flags to set.
+ * @return Returns the same Intent object, for chaining multiple calls into
+ * a single statement.
+ * @see #getExtendedFlags
+ * @see #removeExtendedFlags
+ *
+ * @hide
+ */
+ @TestApi
+ public @NonNull Intent addExtendedFlags(@ExtendedFlags int flags) {
+ mExtendedFlags |= flags;
+ return this;
+ }
+
+ /**
* Remove these flags from the intent.
*
* @param flags The flags to remove.
@@ -11210,6 +11266,19 @@ public class Intent implements Parcelable, Cloneable {
}
/**
+ * Remove these extended flags from the intent.
+ *
+ * @param flags The flags to remove.
+ * @see #getExtendedFlags
+ * @see #addExtendedFlags
+ *
+ * @hide
+ */
+ public void removeExtendedFlags(@ExtendedFlags int flags) {
+ mExtendedFlags &= ~flags;
+ }
+
+ /**
* (Usually optional) Set an explicit application package name that limits
* the components this Intent will resolve to. If left to the default
* value of null, all components in all applications will considered.
@@ -11513,6 +11582,7 @@ public class Intent implements Parcelable, Cloneable {
changes |= FILL_IN_COMPONENT;
}
mFlags |= other.mFlags;
+ mExtendedFlags |= other.mExtendedFlags;
if (other.mSourceBounds != null
&& (mSourceBounds == null || (flags&FILL_IN_SOURCE_BOUNDS) != 0)) {
mSourceBounds = new Rect(other.mSourceBounds);
@@ -11748,6 +11818,13 @@ public class Intent implements Parcelable, Cloneable {
first = false;
b.append("flg=0x").append(Integer.toHexString(mFlags));
}
+ if (mExtendedFlags != 0) {
+ if (!first) {
+ b.append(' ');
+ }
+ first = false;
+ b.append("xflg=0x").append(Integer.toHexString(mExtendedFlags));
+ }
if (mPackage != null) {
if (!first) {
b.append(' ');
@@ -11846,6 +11923,9 @@ public class Intent implements Parcelable, Cloneable {
if (mFlags != 0) {
proto.write(IntentProto.FLAG, "0x" + Integer.toHexString(mFlags));
}
+ if (mExtendedFlags != 0) {
+ proto.write(IntentProto.EXTENDED_FLAG, "0x" + Integer.toHexString(mExtendedFlags));
+ }
if (mPackage != null) {
proto.write(IntentProto.PACKAGE, mPackage);
}
@@ -12019,6 +12099,10 @@ public class Intent implements Parcelable, Cloneable {
if (mFlags != 0) {
uri.append("launchFlags=0x").append(Integer.toHexString(mFlags)).append(';');
}
+ if (mExtendedFlags != 0) {
+ uri.append("extendedLaunchFlags=0x").append(Integer.toHexString(mExtendedFlags))
+ .append(';');
+ }
if (mPackage != null && !mPackage.equals(defPackage)) {
uri.append("package=").append(Uri.encode(mPackage)).append(';');
}
@@ -12068,6 +12152,7 @@ public class Intent implements Parcelable, Cloneable {
out.writeString8(mType);
out.writeString8(mIdentifier);
out.writeInt(mFlags);
+ out.writeInt(mExtendedFlags);
out.writeString8(mPackage);
ComponentName.writeToParcel(mComponent, out);
@@ -12136,6 +12221,7 @@ public class Intent implements Parcelable, Cloneable {
mType = in.readString8();
mIdentifier = in.readString8();
mFlags = in.readInt();
+ mExtendedFlags = in.readInt();
mPackage = in.readString8();
mComponent = ComponentName.readFromParcel(in);
@@ -12560,6 +12646,32 @@ public class Intent implements Parcelable, Cloneable {
}
/**
+ * Whether the intent mismatches all intent filters declared in the receiving component.
+ * <p>
+ * When a component receives an intent, normally obtainable through the following methods:
+ * <ul>
+ * <li> {@link BroadcastReceiver#onReceive(Context, Intent)}
+ * <li> {@link Activity#getIntent()}
+ * <li> {@link Activity#onNewIntent)}
+ * <li> {@link android.app.Service#onStartCommand(Intent, int, int)}
+ * <li> {@link android.app.Service#onBind(Intent)}
+ * </ul>
+ * The developer can call this method to check if this intent does not match any of its
+ * declared intent filters. A non-matching intent can be delivered when the intent sender
+ * explicitly set the component through {@link #setComponent} or {@link #setClassName}.
+ * <p>
+ * This method always returns {@code false} if the intent originated from within the same
+ * application or the system, because these cases are always exempted from security checks.
+ *
+ * @return Returns true if the intent does not match any intent filters declared in the
+ * receiving component.
+ */
+ @FlaggedApi(android.security.Flags.FLAG_ENFORCE_INTENT_FILTER_MATCH)
+ public boolean isMismatchingFilter() {
+ return (mExtendedFlags & EXTENDED_FLAG_FILTER_MISMATCH) != 0;
+ }
+
+ /**
* @hide
*/
@android.ravenwood.annotation.RavenwoodThrow
diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl
index 62db65f15df3..cec49c710d92 100644
--- a/core/java/android/content/pm/ILauncherApps.aidl
+++ b/core/java/android/content/pm/ILauncherApps.aidl
@@ -130,4 +130,6 @@ interface ILauncherApps {
void unRegisterDumpCallback(IDumpCallback cb);
void setArchiveCompatibilityOptions(boolean enableIconOverlay, boolean enableUnarchivalConfirmation);
+
+ List<UserHandle> getUserProfiles();
}
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 9c859c442355..e437925693af 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -695,12 +695,23 @@ public class LauncherApps {
* Otherwise it'll return the same list as {@link UserManager#getUserProfiles()} would.
*/
public List<UserHandle> getProfiles() {
- if (mUserManager.isManagedProfile()) {
- // If it's a managed profile, only return the current profile.
- final List result = new ArrayList(1);
+ if (mUserManager.isManagedProfile()
+ || (android.multiuser.Flags.enableLauncherAppsHiddenProfileChecks()
+ && android.os.Flags.allowPrivateProfile()
+ && mUserManager.isPrivateProfile())) {
+ // If it's a managed or private profile, only return the current profile.
+ final List result = new ArrayList(1);
result.add(android.os.Process.myUserHandle());
return result;
} else {
+ if (android.multiuser.Flags.enableLauncherAppsHiddenProfileChecks()) {
+ try {
+ return mService.getUserProfiles();
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
return mUserManager.getUserProfiles();
}
}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 2a974ed6045d..7a015cd187ca 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -3974,7 +3974,9 @@ public abstract class PackageManager {
* The device is capable of communicating with other devices via
* <a href="https://www.threadgroup.org">Thread</a> networking protocol.
*/
- @FlaggedApi(com.android.net.thread.flags.Flags.FLAG_THREAD_ENABLED_PLATFORM)
+ // TODO (b/325886480): update the flag to
+ // "com.android.net.thread.platform.flags.Flags.FLAG_THREAD_ENABLED_PLATFORM"
+ @FlaggedApi("com.android.net.thread.flags.thread_enabled_platform")
@SdkConstant(SdkConstantType.FEATURE)
public static final String FEATURE_THREAD_NETWORK = "android.hardware.thread_network";
diff --git a/core/java/android/content/pm/ShortcutServiceInternal.java b/core/java/android/content/pm/ShortcutServiceInternal.java
index 087a7952acd7..55d0bc1deedc 100644
--- a/core/java/android/content/pm/ShortcutServiceInternal.java
+++ b/core/java/android/content/pm/ShortcutServiceInternal.java
@@ -115,6 +115,11 @@ public abstract class ShortcutServiceInternal {
public abstract boolean hasShortcutHostPermission(int launcherUserId,
@NonNull String callingPackage, int callingPid, int callingUid);
+ /**
+ * Returns whether or not Shortcuts are supported on Launcher Home Screen.
+ */
+ public abstract boolean areShortcutsSupportedOnHomeScreen(@UserIdInt int userId);
+
public abstract void setShortcutHostPackage(@NonNull String type, @Nullable String packageName,
int userId);
diff --git a/core/java/android/content/pm/SignedPackage.java b/core/java/android/content/pm/SignedPackage.java
index 4d1b136915f2..7bffa7722142 100644
--- a/core/java/android/content/pm/SignedPackage.java
+++ b/core/java/android/content/pm/SignedPackage.java
@@ -35,9 +35,9 @@ public class SignedPackage {
private final SignedPackageParcel mData;
/** @hide */
- public SignedPackage(@NonNull String pkgName, @NonNull byte[] certificateDigest) {
+ public SignedPackage(@NonNull String packageName, @NonNull byte[] certificateDigest) {
SignedPackageParcel data = new SignedPackageParcel();
- data.pkgName = pkgName;
+ data.packageName = packageName;
data.certificateDigest = certificateDigest;
mData = data;
}
@@ -52,8 +52,8 @@ public class SignedPackage {
return mData;
}
- public @NonNull String getPkgName() {
- return mData.pkgName;
+ public @NonNull String getPackageName() {
+ return mData.packageName;
}
public @NonNull byte[] getCertificateDigest() {
@@ -64,12 +64,12 @@ public class SignedPackage {
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof SignedPackage that)) return false;
- return mData.pkgName.equals(that.mData.pkgName) && Arrays.equals(mData.certificateDigest,
- that.mData.certificateDigest);
+ return mData.packageName.equals(that.mData.packageName) && Arrays.equals(
+ mData.certificateDigest, that.mData.certificateDigest);
}
@Override
public int hashCode() {
- return Objects.hash(mData.pkgName, Arrays.hashCode(mData.certificateDigest));
+ return Objects.hash(mData.packageName, Arrays.hashCode(mData.certificateDigest));
}
}
diff --git a/core/java/android/content/pm/SignedPackageParcel.aidl b/core/java/android/content/pm/SignedPackageParcel.aidl
index 7957f7f99d4a..bb4dc8654108 100644
--- a/core/java/android/content/pm/SignedPackageParcel.aidl
+++ b/core/java/android/content/pm/SignedPackageParcel.aidl
@@ -20,6 +20,6 @@ import android.content.ComponentName;
/** @hide */
parcelable SignedPackageParcel {
- String pkgName;
+ String packageName;
byte[] certificateDigest;
}
diff --git a/core/java/android/content/pm/UserProperties.java b/core/java/android/content/pm/UserProperties.java
index d347a0e8ae63..915992904a5c 100644
--- a/core/java/android/content/pm/UserProperties.java
+++ b/core/java/android/content/pm/UserProperties.java
@@ -537,7 +537,6 @@ public final class UserProperties implements Parcelable {
setDeleteAppWithParent(orig.getDeleteAppWithParent());
setAlwaysVisible(orig.getAlwaysVisible());
setAllowStoppingUserWithDelayedLocking(orig.getAllowStoppingUserWithDelayedLocking());
- setItemsRestrictedOnHomeScreen(orig.areItemsRestrictedOnHomeScreen());
}
if (hasManagePermission) {
// Add items that require MANAGE_USERS or stronger.
@@ -557,6 +556,7 @@ public final class UserProperties implements Parcelable {
setShowInSharingSurfaces(orig.getShowInSharingSurfaces());
setCrossProfileContentSharingStrategy(orig.getCrossProfileContentSharingStrategy());
setProfileApiVisibility(orig.getProfileApiVisibility());
+ setItemsRestrictedOnHomeScreen(orig.areItemsRestrictedOnHomeScreen());
}
/**
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index 6696ba035f30..22e233aebf36 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -144,6 +144,13 @@ flag {
}
flag {
+ name: "show_set_screen_lock_dialog"
+ namespace: "profile_experiences"
+ description: "Display the dialog to set up screen lock when private space unlock operation is requested"
+ bug: "316129700"
+}
+
+flag {
name: "reorder_wallpaper_during_user_switch"
namespace: "multiuser"
description: "Reorder loading home and lock screen wallpapers during a user switch."
@@ -156,3 +163,17 @@ flag {
description: "Set power mode during a user switch."
bug: "325249845"
}
+
+flag {
+ name: "disable_private_space_items_on_home"
+ namespace: "profile_experiences"
+ description: "Disables adding items belonging to Private Space on Home Screen manually as well as automatically"
+ bug: "287975131"
+}
+
+flag {
+ name: "enable_ps_sensitive_notifications_toggle"
+ namespace: "profile_experiences"
+ description: "Enable the sensitive notifications toggle to be visible in the Private space settings page"
+ bug: "317067050"
+}
diff --git a/core/java/android/credentials/GetCredentialResponse.java b/core/java/android/credentials/GetCredentialResponse.java
index ea699b9a74e5..a1477ee1a43a 100644
--- a/core/java/android/credentials/GetCredentialResponse.java
+++ b/core/java/android/credentials/GetCredentialResponse.java
@@ -63,9 +63,6 @@ public final class GetCredentialResponse implements Parcelable {
}
/**
- *
- * @return
- *
* @hide
*/
public AutofillId getAutofillId() {
diff --git a/core/java/android/credentials/flags.aconfig b/core/java/android/credentials/flags.aconfig
index ef7b2594fd5d..09e59d315428 100644
--- a/core/java/android/credentials/flags.aconfig
+++ b/core/java/android/credentials/flags.aconfig
@@ -68,4 +68,18 @@ flag {
name: "new_framework_metrics"
description: "Enables new metrics fror 24Q3 / VIC"
bug: "324291187"
-} \ No newline at end of file
+}
+
+flag {
+ namespace: "credential_manager"
+ name: "clear_credentials_api_fix_enabled"
+ description: "Fixes bug in clearCredential API that causes indefinite suspension"
+ bug: "314926460"
+}
+
+flag {
+ namespace: "credential_manager"
+ name: "hybrid_filter_fix_enabled"
+ description: "Removes capability check from hybrid implementation"
+ bug: "323923403"
+}
diff --git a/core/java/android/credentials/selection/Constants.java b/core/java/android/credentials/selection/Constants.java
index f7fec237dd08..2229f258a3a4 100644
--- a/core/java/android/credentials/selection/Constants.java
+++ b/core/java/android/credentials/selection/Constants.java
@@ -30,13 +30,6 @@ public class Constants {
"android.credentials.selection.extra.RESULT_RECEIVER";
/**
- * The intent extra key for indicating whether the bottom sheet should be started directly
- * on the 'All Options' screen.
- */
- public static final String EXTRA_REQ_FOR_ALL_OPTIONS =
- "android.credentials.selection.extra.REQ_FOR_ALL_OPTIONS";
-
- /**
* The intent extra key for the final result receiver object
*/
public static final String EXTRA_FINAL_RESPONSE_RECEIVER =
diff --git a/core/java/android/credentials/selection/IntentFactory.java b/core/java/android/credentials/selection/IntentFactory.java
index ac2bae495219..4b0fa6d32af7 100644
--- a/core/java/android/credentials/selection/IntentFactory.java
+++ b/core/java/android/credentials/selection/IntentFactory.java
@@ -48,41 +48,28 @@ import java.util.ArrayList;
public class IntentFactory {
/**
- * Generate a new launch intent to the Credential Selector UI for auto-filling.
+ * Generate a new launch intent to the Credential Selector UI for auto-filling. This intent is
+ * invoked from the Autofill flow, when the user requests to bring up the 'All Options' page of
+ * the credential bottom-sheet. When the user clicks on the pinned entry, the intent will bring
+ * up the 'All Options' page of the bottom-sheet. The provider data list is processed by the
+ * credential autofill service for each autofill id and passed in as an auth extra.
*
* @hide
*/
@NonNull
- // TODO(b/323552850) - clean up method overloads
- public static Intent createCredentialSelectorIntent(
+ public static Intent createCredentialSelectorIntentForAutofill(
@NonNull Context context,
@NonNull RequestInfo requestInfo,
@SuppressLint("ConcreteCollection") // Concrete collection needed for marshalling.
- @Nullable
- ArrayList<ProviderData> enabledProviderDataList,
- @SuppressLint("ConcreteCollection") // Concrete collection needed for marshalling.
@NonNull
ArrayList<DisabledProviderData> disabledProviderDataList,
- @NonNull ResultReceiver resultReceiver,
- boolean isRequestForAllOptions) {
-
- Intent intent;
- if (enabledProviderDataList != null) {
- intent = createCredentialSelectorIntent(context, requestInfo, enabledProviderDataList,
- disabledProviderDataList, resultReceiver);
- } else {
- intent = createCredentialSelectorIntent(context, requestInfo,
- disabledProviderDataList, resultReceiver);
- }
- intent.putExtra(Constants.EXTRA_REQ_FOR_ALL_OPTIONS, isRequestForAllOptions);
-
- return intent;
+ @NonNull ResultReceiver resultReceiver) {
+ return createCredentialSelectorIntent(context, requestInfo,
+ disabledProviderDataList, resultReceiver);
}
/**
* Generate a new launch intent to the Credential Selector UI.
- *
- * @hide
*/
@NonNull
private static Intent createCredentialSelectorIntent(
diff --git a/core/java/android/hardware/SerialManager.java b/core/java/android/hardware/SerialManager.java
index 26e5129b1fb3..e2ce7230a796 100644
--- a/core/java/android/hardware/SerialManager.java
+++ b/core/java/android/hardware/SerialManager.java
@@ -29,6 +29,7 @@ import java.io.IOException;
* @hide
*/
@SystemService(Context.SERIAL_SERVICE)
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public class SerialManager {
private static final String TAG = "SerialManager";
@@ -69,6 +70,8 @@ public class SerialManager {
* @return the serial port
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ @android.ravenwood.annotation.RavenwoodThrow(blockedBy = ParcelFileDescriptor.class, reason =
+ "Needs socketpair() to offer accurate emulation")
public SerialPort openSerialPort(String name, int speed) throws IOException {
try {
ParcelFileDescriptor pfd = mService.openSerialPort(name);
diff --git a/core/java/android/hardware/SerialManagerInternal.java b/core/java/android/hardware/SerialManagerInternal.java
new file mode 100644
index 000000000000..9132da06aeac
--- /dev/null
+++ b/core/java/android/hardware/SerialManagerInternal.java
@@ -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 android.hardware;
+
+import android.annotation.NonNull;
+import android.os.ParcelFileDescriptor;
+
+import java.util.function.Supplier;
+
+/**
+ * Internal interactions with {@link SerialManager}.
+ *
+ * @hide
+ */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
+public abstract class SerialManagerInternal {
+ public abstract void addVirtualSerialPortForTest(@NonNull String name,
+ @NonNull Supplier<ParcelFileDescriptor> supplier);
+
+ public abstract void removeVirtualSerialPortForTest(@NonNull String name);
+}
diff --git a/core/java/android/hardware/biometrics/BiometricFaceConstants.java b/core/java/android/hardware/biometrics/BiometricFaceConstants.java
index 2b62b98529a9..2ba1d897fe34 100644
--- a/core/java/android/hardware/biometrics/BiometricFaceConstants.java
+++ b/core/java/android/hardware/biometrics/BiometricFaceConstants.java
@@ -19,6 +19,8 @@ package android.hardware.biometrics;
import android.annotation.IntDef;
import android.app.KeyguardManager;
import android.hardware.biometrics.BiometricManager.Authenticators;
+import android.hardware.face.FaceEnrollOptions;
+import android.hardware.face.FaceEnrollOptions.EnrollReason;
import android.hardware.face.FaceManager;
import java.lang.annotation.Retention;
@@ -432,4 +434,22 @@ public interface BiometricFaceConstants {
* vendor code.
*/
int FACE_ACQUIRED_VENDOR_BASE = 1000;
+
+
+ /**
+ * Converts FaceEnrollOptions.reason into BiometricsProtoEnums.enrollReason
+ */
+ public static int reasonToMetric(@EnrollReason int reason) {
+ switch (reason) {
+ case FaceEnrollOptions.ENROLL_REASON_RE_ENROLL_NOTIFICATION:
+ return BiometricsProtoEnums.ENROLLMENT_SOURCE_FRR_NOTIFICATION;
+ case FaceEnrollOptions.ENROLL_REASON_SETTINGS:
+ return BiometricsProtoEnums.ENROLLMENT_SOURCE_SETTINGS;
+ case FaceEnrollOptions.ENROLL_REASON_SUW:
+ return BiometricsProtoEnums.ENROLLMENT_SOURCE_SUW;
+ default:
+ return BiometricsProtoEnums.ENROLLMENT_SOURCE_UNKNOWN;
+ }
+
+ }
}
diff --git a/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java b/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java
index 5b24fb6860a2..770448bd594b 100644
--- a/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java
+++ b/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java
@@ -20,6 +20,8 @@ import android.annotation.IntDef;
import android.app.KeyguardManager;
import android.compat.annotation.UnsupportedAppUsage;
import android.hardware.biometrics.BiometricManager.Authenticators;
+import android.hardware.fingerprint.FingerprintEnrollOptions;
+import android.hardware.fingerprint.FingerprintEnrollOptions.EnrollReason;
import android.hardware.fingerprint.FingerprintManager;
import android.os.Build;
@@ -350,4 +352,22 @@ public interface BiometricFingerprintConstants {
return false;
}
}
+
+
+ /**
+ * Converts FaceEnrollOptions.reason into BiometricsProtoEnums.enrollReason
+ */
+ static int reasonToMetric(@EnrollReason int reason) {
+ switch(reason) {
+ case FingerprintEnrollOptions.ENROLL_REASON_RE_ENROLL_NOTIFICATION:
+ return BiometricsProtoEnums.ENROLLMENT_SOURCE_FRR_NOTIFICATION;
+ case FingerprintEnrollOptions.ENROLL_REASON_SETTINGS:
+ return BiometricsProtoEnums.ENROLLMENT_SOURCE_SETTINGS;
+ case FingerprintEnrollOptions.ENROLL_REASON_SUW:
+ return BiometricsProtoEnums.ENROLLMENT_SOURCE_SUW;
+ default:
+ return BiometricsProtoEnums.ENROLLMENT_SOURCE_UNKNOWN;
+ }
+
+ }
}
diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java
index d7d1d1a7c677..fdd8b04921f5 100644
--- a/core/java/android/hardware/biometrics/BiometricManager.java
+++ b/core/java/android/hardware/biometrics/BiometricManager.java
@@ -100,6 +100,13 @@ public class BiometricManager {
Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_STRONG;
/**
+ * Enroll reason extra that can be used by settings to understand where this request came
+ * from.
+ * @hide
+ */
+ public static final String EXTRA_ENROLL_REASON = "enroll_reason";
+
+ /**
* @hide
*/
@IntDef({BIOMETRIC_SUCCESS,
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index d4c58b239c84..0208fed6040f 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -22,8 +22,9 @@ import static android.Manifest.permission.USE_BIOMETRIC;
import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
import static android.hardware.biometrics.BiometricManager.Authenticators;
import static android.hardware.biometrics.Flags.FLAG_ADD_KEY_AGREEMENT_CRYPTO_OBJECT;
-import static android.hardware.biometrics.Flags.FLAG_GET_OP_ID_CRYPTO_OBJECT;
import static android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT;
+import static android.hardware.biometrics.Flags.FLAG_GET_OP_ID_CRYPTO_OBJECT;
+import static android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE;
import android.annotation.CallbackExecutor;
import android.annotation.DrawableRes;
@@ -501,6 +502,28 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
}
/**
+ * Remove {@link Builder#setAllowBackgroundAuthentication(boolean)} once
+ * FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE is enabled.
+ *
+ * @param allow If true, allows authentication when the calling package is not in the
+ * foreground. This is set to false by default.
+ * @param useParentProfileForDeviceCredential If true, uses parent profile for device
+ * credential IME request
+ * @return This builder
+ * @hide
+ */
+ @FlaggedApi(FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE)
+ @TestApi
+ @NonNull
+ @RequiresPermission(anyOf = {TEST_BIOMETRIC, USE_BIOMETRIC_INTERNAL})
+ public Builder setAllowBackgroundAuthentication(boolean allow,
+ boolean useParentProfileForDeviceCredential) {
+ mPromptInfo.setAllowBackgroundAuthentication(allow);
+ mPromptInfo.setUseParentProfileForDeviceCredential(useParentProfileForDeviceCredential);
+ return this;
+ }
+
+ /**
* If set check the Device Policy Manager for disabled biometrics.
*
* @param checkDevicePolicyManager
diff --git a/core/java/android/hardware/biometrics/PromptInfo.java b/core/java/android/hardware/biometrics/PromptInfo.java
index 2236660ee388..8bb958553673 100644
--- a/core/java/android/hardware/biometrics/PromptInfo.java
+++ b/core/java/android/hardware/biometrics/PromptInfo.java
@@ -55,6 +55,7 @@ public class PromptInfo implements Parcelable {
private boolean mIgnoreEnrollmentState;
private boolean mIsForLegacyFingerprintManager = false;
private boolean mShowEmergencyCallButton = false;
+ private boolean mUseParentProfileForDeviceCredential = false;
public PromptInfo() {
@@ -85,6 +86,7 @@ public class PromptInfo implements Parcelable {
mIgnoreEnrollmentState = in.readBoolean();
mIsForLegacyFingerprintManager = in.readBoolean();
mShowEmergencyCallButton = in.readBoolean();
+ mUseParentProfileForDeviceCredential = in.readBoolean();
}
public static final Creator<PromptInfo> CREATOR = new Creator<PromptInfo>() {
@@ -129,6 +131,7 @@ public class PromptInfo implements Parcelable {
dest.writeBoolean(mIgnoreEnrollmentState);
dest.writeBoolean(mIsForLegacyFingerprintManager);
dest.writeBoolean(mShowEmergencyCallButton);
+ dest.writeBoolean(mUseParentProfileForDeviceCredential);
}
// LINT.IfChange
@@ -181,6 +184,13 @@ public class PromptInfo implements Parcelable {
}
return false;
}
+
+ /**
+ * Returns if parent profile needs to be used for device credential.
+ */
+ public boolean shouldUseParentProfileForDeviceCredential() {
+ return mUseParentProfileForDeviceCredential;
+ }
// LINT.ThenChange(frameworks/base/core/java/android/hardware/biometrics/BiometricPrompt.java)
// Setters
@@ -281,6 +291,11 @@ public class PromptInfo implements Parcelable {
mShowEmergencyCallButton = showEmergencyCallButton;
}
+ public void setUseParentProfileForDeviceCredential(
+ boolean useParentProfileForDeviceCredential) {
+ mUseParentProfileForDeviceCredential = useParentProfileForDeviceCredential;
+ }
+
// Getters
@DrawableRes
public int getLogoRes() {
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 2add77e44dd3..57b437f4bacf 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -558,8 +558,11 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* on a particular SessionConfiguration.</p>
*
* @return List of CameraCharacteristic keys containing characterisitics specific to a session
- * configuration. For Android 15, this list only contains CONTROL_ZOOM_RATIO_RANGE and
- * SCALER_AVAILABLE_MAX_DIGITAL_ZOOM.
+ * configuration. If {@link #INFO_SESSION_CONFIGURATION_QUERY_VERSION} is
+ * {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, then this list will only contain
+ * CONTROL_ZOOM_RATIO_RANGE and SCALER_AVAILABLE_MAX_DIGITAL_ZOOM
+ *
+ * @see INFO_SESSION_CONFIGURATION_QUERY_VERSION
*/
@NonNull
@FlaggedApi(Flags.FLAG_FEATURE_COMBINATION_QUERY)
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index 1a0074f01087..991bade09a25 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -1718,7 +1718,7 @@ public abstract class CameraDevice implements AutoCloseable {
* <p>Other than that, the characteristics returned here can be used in the same way as
* those returned from {@link CameraManager#getCameraCharacteristics}.</p>
*
- * @param sessionConfig : The session configuration for which characteristics are fetched.
+ * @param sessionConfig The session configuration for which characteristics are fetched.
* @return CameraCharacteristics specific to a given session configuration.
*
* @throws IllegalArgumentException if the session configuration is invalid
diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
index 76c20ce2184a..749f218b0e6a 100644
--- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
@@ -874,8 +874,11 @@ public final class CameraExtensionCharacteristics {
Class<CameraCharacteristics.Key<?>> keyTyped =
(Class<CameraCharacteristics.Key<?>>) key;
+ // Do not include synthetic keys. Including synthetic keys leads to undefined
+ // behavior. This causes inclusion of capabilities that may not be supported in
+ // camera extensions.
ret.addAll(chars.getAvailableKeyList(CameraCharacteristics.class, keyTyped, keys,
- /*includeSynthetic*/ true));
+ /*includeSynthetic*/ false));
}
} catch (RemoteException e) {
Log.e(TAG, "Failed to query the extension for all available keys! Extension "
diff --git a/core/java/android/hardware/display/BrightnessInfo.java b/core/java/android/hardware/display/BrightnessInfo.java
index 53a9a75fbca0..c09106206c25 100644
--- a/core/java/android/hardware/display/BrightnessInfo.java
+++ b/core/java/android/hardware/display/BrightnessInfo.java
@@ -80,6 +80,11 @@ public final class BrightnessInfo implements Parcelable {
*/
public static final int BRIGHTNESS_MAX_REASON_POWER_IC = 2;
+ /**
+ * Maximum brightness is restricted due to the Wear bedtime mode.
+ */
+ public static final int BRIGHTNESS_MAX_REASON_WEAR_BEDTIME_MODE = 3;
+
/** Brightness */
public final float brightness;
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 8f0e0c911f56..eb26a768f2a6 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -28,6 +28,7 @@ import android.annotation.LongDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
@@ -367,6 +368,8 @@ public final class DisplayManager {
* @see #createVirtualDisplay
* @hide
*/
+ @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
+ @TestApi
public static final int VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH = 1 << 6;
/**
diff --git a/core/java/android/hardware/face/FaceEnrollOptions.aidl b/core/java/android/hardware/face/FaceEnrollOptions.aidl
new file mode 100644
index 000000000000..336de9db899c
--- /dev/null
+++ b/core/java/android/hardware/face/FaceEnrollOptions.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.hardware.face;
+
+parcelable FaceEnrollOptions;
diff --git a/core/java/android/hardware/face/FaceEnrollOptions.java b/core/java/android/hardware/face/FaceEnrollOptions.java
new file mode 100644
index 000000000000..440105584f74
--- /dev/null
+++ b/core/java/android/hardware/face/FaceEnrollOptions.java
@@ -0,0 +1,259 @@
+/*
+ * 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.hardware.face;
+
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+/**
+ * Additional options when requesting Face enrollment.
+ *
+ * @hide
+ */
+@DataClass(
+ genParcelable = true,
+ genAidl = true,
+ genBuilder = true,
+ genSetters = true,
+ genEqualsHashCode = true
+)
+public class FaceEnrollOptions implements Parcelable {
+ public static final int ENROLL_REASON_UNKNOWN = 0;
+ public static final int ENROLL_REASON_RE_ENROLL_NOTIFICATION = 1;
+ public static final int ENROLL_REASON_SETTINGS = 2;
+ public static final int ENROLL_REASON_SUW = 3;
+
+ /**
+ * The reason for enrollment.
+ */
+ @EnrollReason
+ private final int mEnrollReason;
+ private static int defaultEnrollReason() {
+ return ENROLL_REASON_UNKNOWN;
+ }
+
+
+
+
+ // 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/hardware/face/FaceEnrollOptions.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ @android.annotation.IntDef(prefix = "ENROLL_REASON_", value = {
+ ENROLL_REASON_UNKNOWN,
+ ENROLL_REASON_RE_ENROLL_NOTIFICATION,
+ ENROLL_REASON_SETTINGS,
+ ENROLL_REASON_SUW
+ })
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+ @DataClass.Generated.Member
+ public @interface EnrollReason {}
+
+ @DataClass.Generated.Member
+ public static String enrollReasonToString(@EnrollReason int value) {
+ switch (value) {
+ case ENROLL_REASON_UNKNOWN:
+ return "ENROLL_REASON_UNKNOWN";
+ case ENROLL_REASON_RE_ENROLL_NOTIFICATION:
+ return "ENROLL_REASON_RE_ENROLL_NOTIFICATION";
+ case ENROLL_REASON_SETTINGS:
+ return "ENROLL_REASON_SETTINGS";
+ case ENROLL_REASON_SUW:
+ return "ENROLL_REASON_SUW";
+ default: return Integer.toHexString(value);
+ }
+ }
+
+ @DataClass.Generated.Member
+ /* package-private */ FaceEnrollOptions(
+ @EnrollReason int enrollReason) {
+ this.mEnrollReason = enrollReason;
+
+ if (!(mEnrollReason == ENROLL_REASON_UNKNOWN)
+ && !(mEnrollReason == ENROLL_REASON_RE_ENROLL_NOTIFICATION)
+ && !(mEnrollReason == ENROLL_REASON_SETTINGS)
+ && !(mEnrollReason == ENROLL_REASON_SUW)) {
+ throw new java.lang.IllegalArgumentException(
+ "enrollReason was " + mEnrollReason + " but must be one of: "
+ + "ENROLL_REASON_UNKNOWN(" + ENROLL_REASON_UNKNOWN + "), "
+ + "ENROLL_REASON_RE_ENROLL_NOTIFICATION(" + ENROLL_REASON_RE_ENROLL_NOTIFICATION + "), "
+ + "ENROLL_REASON_SETTINGS(" + ENROLL_REASON_SETTINGS + "), "
+ + "ENROLL_REASON_SUW(" + ENROLL_REASON_SUW + ")");
+ }
+
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * The reason for enrollment.
+ */
+ @DataClass.Generated.Member
+ public @EnrollReason int getEnrollReason() {
+ return mEnrollReason;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public boolean equals(@android.annotation.Nullable Object o) {
+ // You can override field equality logic by defining either of the methods like:
+ // boolean fieldNameEquals(FaceEnrollOptions other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ FaceEnrollOptions that = (FaceEnrollOptions) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && mEnrollReason == that.mEnrollReason;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int hashCode() {
+ // You can override field hashCode logic by defining methods like:
+ // int fieldNameHashCode() { ... }
+
+ int _hash = 1;
+ _hash = 31 * _hash + mEnrollReason;
+ return _hash;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@android.annotation.NonNull android.os.Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ dest.writeInt(mEnrollReason);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ protected FaceEnrollOptions(@android.annotation.NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ int enrollReason = in.readInt();
+
+ this.mEnrollReason = enrollReason;
+
+ if (!(mEnrollReason == ENROLL_REASON_UNKNOWN)
+ && !(mEnrollReason == ENROLL_REASON_RE_ENROLL_NOTIFICATION)
+ && !(mEnrollReason == ENROLL_REASON_SETTINGS)
+ && !(mEnrollReason == ENROLL_REASON_SUW)) {
+ throw new java.lang.IllegalArgumentException(
+ "enrollReason was " + mEnrollReason + " but must be one of: "
+ + "ENROLL_REASON_UNKNOWN(" + ENROLL_REASON_UNKNOWN + "), "
+ + "ENROLL_REASON_RE_ENROLL_NOTIFICATION(" + ENROLL_REASON_RE_ENROLL_NOTIFICATION + "), "
+ + "ENROLL_REASON_SETTINGS(" + ENROLL_REASON_SETTINGS + "), "
+ + "ENROLL_REASON_SUW(" + ENROLL_REASON_SUW + ")");
+ }
+
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @android.annotation.NonNull Parcelable.Creator<FaceEnrollOptions> CREATOR
+ = new Parcelable.Creator<FaceEnrollOptions>() {
+ @Override
+ public FaceEnrollOptions[] newArray(int size) {
+ return new FaceEnrollOptions[size];
+ }
+
+ @Override
+ public FaceEnrollOptions createFromParcel(@android.annotation.NonNull android.os.Parcel in) {
+ return new FaceEnrollOptions(in);
+ }
+ };
+
+ /**
+ * A builder for {@link FaceEnrollOptions}
+ */
+ @SuppressWarnings("WeakerAccess")
+ @DataClass.Generated.Member
+ public static class Builder {
+
+ private @EnrollReason int mEnrollReason;
+
+ private long mBuilderFieldsSet = 0L;
+
+ public Builder() {
+ }
+
+ /**
+ * The reason for enrollment.
+ */
+ @DataClass.Generated.Member
+ public @android.annotation.NonNull Builder setEnrollReason(@EnrollReason int value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x1;
+ mEnrollReason = value;
+ return this;
+ }
+
+ /** Builds the instance. This builder should not be touched after calling this! */
+ public @android.annotation.NonNull FaceEnrollOptions build() {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x2; // Mark builder used
+
+ if ((mBuilderFieldsSet & 0x1) == 0) {
+ mEnrollReason = defaultEnrollReason();
+ }
+ FaceEnrollOptions o = new FaceEnrollOptions(
+ mEnrollReason);
+ return o;
+ }
+
+ private void checkNotUsed() {
+ if ((mBuilderFieldsSet & 0x2) != 0) {
+ throw new IllegalStateException(
+ "This Builder should not be reused. Use a new Builder instance instead");
+ }
+ }
+ }
+
+ @DataClass.Generated(
+ time = 1707949032303L,
+ codegenVersion = "1.0.23",
+ sourceFile = "frameworks/base/core/java/android/hardware/face/FaceEnrollOptions.java",
+ inputSignatures = "public static final int ENROLL_REASON_UNKNOWN\npublic static final int ENROLL_REASON_RE_ENROLL_NOTIFICATION\npublic static final int ENROLL_REASON_SETTINGS\npublic static final int ENROLL_REASON_SUW\nprivate final @android.hardware.face.FaceEnrollOptions.EnrollReason int mEnrollReason\nprivate static int defaultEnrollReason()\nclass FaceEnrollOptions extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genAidl=true, genBuilder=true, genSetters=true, genEqualsHashCode=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
index bae5e7f83569..066c45f03ec4 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -393,7 +393,9 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
public void enroll(int userId, byte[] hardwareAuthToken, CancellationSignal cancel,
EnrollmentCallback callback, int[] disabledFeatures) {
enroll(userId, hardwareAuthToken, cancel, callback, disabledFeatures,
- null /* previewSurface */, false /* debugConsent */);
+ null /* previewSurface */, false /* debugConsent */,
+ (new FaceEnrollOptions.Builder()).build());
+
}
/**
@@ -418,7 +420,7 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
@RequiresPermission(MANAGE_BIOMETRIC)
public void enroll(int userId, byte[] hardwareAuthToken, CancellationSignal cancel,
EnrollmentCallback callback, int[] disabledFeatures, @Nullable Surface previewSurface,
- boolean debugConsent) {
+ boolean debugConsent, FaceEnrollOptions options) {
if (callback == null) {
throw new IllegalArgumentException("Must supply an enrollment callback");
}
@@ -449,7 +451,7 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
Trace.beginSection("FaceManager#enroll");
final long enrollId = mService.enroll(userId, mToken, hardwareAuthToken,
mServiceReceiver, mContext.getOpPackageName(), disabledFeatures,
- previewSurface, debugConsent);
+ previewSurface, debugConsent, options);
if (cancel != null) {
cancel.setOnCancelListener(new OnEnrollCancelListener(enrollId));
}
diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl
index 8e234fa11866..b98c0cb41ac9 100644
--- a/core/java/android/hardware/face/IFaceService.aidl
+++ b/core/java/android/hardware/face/IFaceService.aidl
@@ -26,6 +26,7 @@ import android.hardware.face.IFaceAuthenticatorsRegisteredCallback;
import android.hardware.face.IFaceServiceReceiver;
import android.hardware.face.Face;
import android.hardware.face.FaceAuthenticateOptions;
+import android.hardware.face.FaceEnrollOptions;
import android.hardware.face.FaceSensorPropertiesInternal;
import android.hardware.face.FaceSensorConfigurations;
import android.view.Surface;
@@ -100,7 +101,7 @@ interface IFaceService {
@EnforcePermission("MANAGE_BIOMETRIC")
long enroll(int userId, IBinder token, in byte [] hardwareAuthToken, IFaceServiceReceiver receiver,
String opPackageName, in int [] disabledFeatures,
- in Surface previewSurface, boolean debugConsent);
+ in Surface previewSurface, boolean debugConsent, in FaceEnrollOptions options);
// Start remote face enrollment
@EnforcePermission("MANAGE_BIOMETRIC")
diff --git a/core/java/android/hardware/fingerprint/FingerprintEnrollOptions.aidl b/core/java/android/hardware/fingerprint/FingerprintEnrollOptions.aidl
new file mode 100644
index 000000000000..77803f3e3a9b
--- /dev/null
+++ b/core/java/android/hardware/fingerprint/FingerprintEnrollOptions.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.hardware.fingerprint;
+
+parcelable FingerprintEnrollOptions; \ No newline at end of file
diff --git a/core/java/android/hardware/fingerprint/FingerprintEnrollOptions.java b/core/java/android/hardware/fingerprint/FingerprintEnrollOptions.java
new file mode 100644
index 000000000000..9f9f322a8876
--- /dev/null
+++ b/core/java/android/hardware/fingerprint/FingerprintEnrollOptions.java
@@ -0,0 +1,260 @@
+/*
+ * 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.hardware.fingerprint;
+
+import android.os.Parcelable;
+
+
+import com.android.internal.util.DataClass;
+
+/**
+ * Additional options when requesting Fingerprint enrollment.
+ *
+ * @hide
+ */
+@DataClass(
+ genParcelable = true,
+ genAidl = true,
+ genBuilder = true,
+ genSetters = true,
+ genEqualsHashCode = true
+)
+public class FingerprintEnrollOptions implements Parcelable {
+ public static final int ENROLL_REASON_UNKNOWN = 0;
+ public static final int ENROLL_REASON_RE_ENROLL_NOTIFICATION = 1;
+ public static final int ENROLL_REASON_SETTINGS = 2;
+ public static final int ENROLL_REASON_SUW = 3;
+
+ /**
+ * The reason for enrollment.
+ */
+ @EnrollReason
+ private final int mEnrollReason;
+
+ private static int defaultEnrollReason() {
+ return ENROLL_REASON_UNKNOWN;
+ }
+
+
+
+ // 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/hardware/fingerprint/FingerprintEnrollOptions.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ @android.annotation.IntDef(prefix = "ENROLL_REASON_", value = {
+ ENROLL_REASON_UNKNOWN,
+ ENROLL_REASON_RE_ENROLL_NOTIFICATION,
+ ENROLL_REASON_SETTINGS,
+ ENROLL_REASON_SUW
+ })
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+ @DataClass.Generated.Member
+ public @interface EnrollReason {}
+
+ @DataClass.Generated.Member
+ public static String enrollReasonToString(@EnrollReason int value) {
+ switch (value) {
+ case ENROLL_REASON_UNKNOWN:
+ return "ENROLL_REASON_UNKNOWN";
+ case ENROLL_REASON_RE_ENROLL_NOTIFICATION:
+ return "ENROLL_REASON_RE_ENROLL_NOTIFICATION";
+ case ENROLL_REASON_SETTINGS:
+ return "ENROLL_REASON_SETTINGS";
+ case ENROLL_REASON_SUW:
+ return "ENROLL_REASON_SUW";
+ default: return Integer.toHexString(value);
+ }
+ }
+
+ @DataClass.Generated.Member
+ /* package-private */ FingerprintEnrollOptions(
+ @EnrollReason int enrollReason) {
+ this.mEnrollReason = enrollReason;
+
+ if (!(mEnrollReason == ENROLL_REASON_UNKNOWN)
+ && !(mEnrollReason == ENROLL_REASON_RE_ENROLL_NOTIFICATION)
+ && !(mEnrollReason == ENROLL_REASON_SETTINGS)
+ && !(mEnrollReason == ENROLL_REASON_SUW)) {
+ throw new java.lang.IllegalArgumentException(
+ "enrollReason was " + mEnrollReason + " but must be one of: "
+ + "ENROLL_REASON_UNKNOWN(" + ENROLL_REASON_UNKNOWN + "), "
+ + "ENROLL_REASON_RE_ENROLL_NOTIFICATION(" + ENROLL_REASON_RE_ENROLL_NOTIFICATION + "), "
+ + "ENROLL_REASON_SETTINGS(" + ENROLL_REASON_SETTINGS + "), "
+ + "ENROLL_REASON_SUW(" + ENROLL_REASON_SUW + ")");
+ }
+
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * The reason for enrollment.
+ */
+ @DataClass.Generated.Member
+ public @EnrollReason int getEnrollReason() {
+ return mEnrollReason;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public boolean equals(@android.annotation.Nullable Object o) {
+ // You can override field equality logic by defining either of the methods like:
+ // boolean fieldNameEquals(FingerprintEnrollOptions other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ FingerprintEnrollOptions that = (FingerprintEnrollOptions) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && mEnrollReason == that.mEnrollReason;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int hashCode() {
+ // You can override field hashCode logic by defining methods like:
+ // int fieldNameHashCode() { ... }
+
+ int _hash = 1;
+ _hash = 31 * _hash + mEnrollReason;
+ return _hash;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@android.annotation.NonNull android.os.Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ dest.writeInt(mEnrollReason);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ protected FingerprintEnrollOptions(@android.annotation.NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ int enrollReason = in.readInt();
+
+ this.mEnrollReason = enrollReason;
+
+ if (!(mEnrollReason == ENROLL_REASON_UNKNOWN)
+ && !(mEnrollReason == ENROLL_REASON_RE_ENROLL_NOTIFICATION)
+ && !(mEnrollReason == ENROLL_REASON_SETTINGS)
+ && !(mEnrollReason == ENROLL_REASON_SUW)) {
+ throw new java.lang.IllegalArgumentException(
+ "enrollReason was " + mEnrollReason + " but must be one of: "
+ + "ENROLL_REASON_UNKNOWN(" + ENROLL_REASON_UNKNOWN + "), "
+ + "ENROLL_REASON_RE_ENROLL_NOTIFICATION(" + ENROLL_REASON_RE_ENROLL_NOTIFICATION + "), "
+ + "ENROLL_REASON_SETTINGS(" + ENROLL_REASON_SETTINGS + "), "
+ + "ENROLL_REASON_SUW(" + ENROLL_REASON_SUW + ")");
+ }
+
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @android.annotation.NonNull Parcelable.Creator<FingerprintEnrollOptions> CREATOR
+ = new Parcelable.Creator<FingerprintEnrollOptions>() {
+ @Override
+ public FingerprintEnrollOptions[] newArray(int size) {
+ return new FingerprintEnrollOptions[size];
+ }
+
+ @Override
+ public FingerprintEnrollOptions createFromParcel(@android.annotation.NonNull android.os.Parcel in) {
+ return new FingerprintEnrollOptions(in);
+ }
+ };
+
+ /**
+ * A builder for {@link FingerprintEnrollOptions}
+ */
+ @SuppressWarnings("WeakerAccess")
+ @DataClass.Generated.Member
+ public static class Builder {
+
+ private @EnrollReason int mEnrollReason;
+
+ private long mBuilderFieldsSet = 0L;
+
+ public Builder() {
+ }
+
+ /**
+ * The reason for enrollment.
+ */
+ @DataClass.Generated.Member
+ public @android.annotation.NonNull Builder setEnrollReason(@EnrollReason int value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x1;
+ mEnrollReason = value;
+ return this;
+ }
+
+ /** Builds the instance. This builder should not be touched after calling this! */
+ public @android.annotation.NonNull FingerprintEnrollOptions build() {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x2; // Mark builder used
+
+ if ((mBuilderFieldsSet & 0x1) == 0) {
+ mEnrollReason = defaultEnrollReason();
+ }
+ FingerprintEnrollOptions o = new FingerprintEnrollOptions(
+ mEnrollReason);
+ return o;
+ }
+
+ private void checkNotUsed() {
+ if ((mBuilderFieldsSet & 0x2) != 0) {
+ throw new IllegalStateException(
+ "This Builder should not be reused. Use a new Builder instance instead");
+ }
+ }
+ }
+
+ @DataClass.Generated(
+ time = 1704407629121L,
+ codegenVersion = "1.0.23",
+ sourceFile = "frameworks/base/core/java/android/hardware/fingerprint/FingerprintEnrollOptions.java",
+ inputSignatures = "public static final int ENROLL_REASON_UNKNOWN\npublic static final int ENROLL_REASON_RE_ENROLL_NOTIFICATION\npublic static final int ENROLL_REASON_SETTINGS\npublic static final int ENROLL_REASON_SUW\nprivate final @android.hardware.fingerprint.FingerprintEnrollOptions.EnrollReason int mEnrollReason\nprivate static int defaultEnrollReason()\nclass FingerprintEnrollOptions extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genAidl=true, genBuilder=true, genSetters=true, genEqualsHashCode=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 5a38a34fde11..b0f69f56cba7 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -755,7 +755,8 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
*/
@RequiresPermission(MANAGE_FINGERPRINT)
public void enroll(byte [] hardwareAuthToken, CancellationSignal cancel, int userId,
- EnrollmentCallback callback, @EnrollReason int enrollReason) {
+ EnrollmentCallback callback, @EnrollReason int enrollReason,
+ FingerprintEnrollOptions options) {
if (userId == UserHandle.USER_CURRENT) {
userId = getCurrentUserId();
}
@@ -779,7 +780,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
try {
mEnrollmentCallback = callback;
final long enrollId = mService.enroll(mToken, hardwareAuthToken, userId,
- mServiceReceiver, mContext.getOpPackageName(), enrollReason);
+ mServiceReceiver, mContext.getOpPackageName(), enrollReason, options);
if (cancel != null) {
cancel.setOnCancelListener(new OnEnrollCancelListener(enrollId));
}
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index 606b171f36ba..8b37c24527d7 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -30,6 +30,7 @@ import android.hardware.fingerprint.IUdfpsOverlayController;
import android.hardware.fingerprint.ISidefpsController;
import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.FingerprintAuthenticateOptions;
+import android.hardware.fingerprint.FingerprintEnrollOptions;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.FingerprintSensorConfigurations;
import java.util.List;
@@ -98,7 +99,7 @@ interface IFingerprintService {
// Start fingerprint enrollment
@EnforcePermission("MANAGE_FINGERPRINT")
long enroll(IBinder token, in byte [] hardwareAuthToken, int userId, IFingerprintServiceReceiver receiver,
- String opPackageName, int enrollReason);
+ String opPackageName, int enrollReason, in FingerprintEnrollOptions options);
// Cancel enrollment in progress
@EnforcePermission("MANAGE_FINGERPRINT")
diff --git a/core/java/android/hardware/input/KeyboardLayoutPreviewDrawable.java b/core/java/android/hardware/input/KeyboardLayoutPreviewDrawable.java
index 1cc910c87b4f..e47a48d7aabc 100644
--- a/core/java/android/hardware/input/KeyboardLayoutPreviewDrawable.java
+++ b/core/java/android/hardware/input/KeyboardLayoutPreviewDrawable.java
@@ -48,14 +48,13 @@ final class KeyboardLayoutPreviewDrawable extends Drawable {
private static final int GRAVITY_RIGHT = 0x2;
private static final int GRAVITY_TOP = 0x4;
private static final int GRAVITY_BOTTOM = 0x8;
- private static final int GRAVITY_CENTER =
- GRAVITY_LEFT | GRAVITY_RIGHT | GRAVITY_TOP | GRAVITY_BOTTOM;
- private static final int GRAVITY_CENTER_HORIZONTAL = GRAVITY_LEFT | GRAVITY_RIGHT;
+ private static final int TEXT_PADDING_IN_DP = 1;
private static final int KEY_PADDING_IN_DP = 3;
private static final int KEYBOARD_PADDING_IN_DP = 10;
private static final int KEY_RADIUS_IN_DP = 5;
private static final int KEYBOARD_RADIUS_IN_DP = 10;
- private static final int GLYPH_TEXT_SIZE_IN_SP = 10;
+ private static final int MIN_GLYPH_TEXT_SIZE_IN_SP = 10;
+ private static final int MAX_GLYPH_TEXT_SIZE_IN_SP = 20;
private final List<KeyDrawable> mKeyDrawables = new ArrayList<>();
@@ -107,6 +106,8 @@ final class KeyboardLayoutPreviewDrawable extends Drawable {
}
int rowCount = keys.length;
float keyHeight = (float) (height - rowCount * 2 * keyPadding) / rowCount;
+ // Based on key height calculate the max text size that can fit for typing keys
+ mResourceProvider.calculateBestTextSizeForKey(keyHeight);
float isoEnterKeyLeft = 0;
float isoEnterKeyTop = 0;
float isoEnterWidthUnit = 0;
@@ -136,16 +137,19 @@ final class KeyboardLayoutPreviewDrawable extends Drawable {
}
if (PhysicalKeyLayout.isSpecialKey(row[j])) {
mKeyDrawables.add(new TypingKey(null, keyRect, keyRadius,
+ mResourceProvider.getTextPadding(),
mResourceProvider.getSpecialKeyPaint(),
mResourceProvider.getSpecialKeyPaint(),
mResourceProvider.getSpecialKeyPaint()));
} else if (PhysicalKeyLayout.isKeyPositionUnsure(row[j])) {
mKeyDrawables.add(new UnsureTypingKey(row[j].glyph(), keyRect,
- keyRadius, mResourceProvider.getTypingKeyPaint(),
+ keyRadius, mResourceProvider.getTextPadding(),
+ mResourceProvider.getTypingKeyPaint(),
mResourceProvider.getPrimaryGlyphPaint(),
mResourceProvider.getSecondaryGlyphPaint()));
} else {
mKeyDrawables.add(new TypingKey(row[j].glyph(), keyRect, keyRadius,
+ mResourceProvider.getTextPadding(),
mResourceProvider.getTypingKeyPaint(),
mResourceProvider.getPrimaryGlyphPaint(),
mResourceProvider.getSecondaryGlyphPaint()));
@@ -192,15 +196,18 @@ final class KeyboardLayoutPreviewDrawable extends Drawable {
private final RectF mKeyRect;
private final float mKeyRadius;
+ private final float mTextPadding;
private final Paint mKeyPaint;
private final Paint mBaseTextPaint;
private final Paint mModifierTextPaint;
private final List<GlyphDrawable> mGlyphDrawables = new ArrayList<>();
private TypingKey(@Nullable PhysicalKeyLayout.KeyGlyph glyphData, RectF keyRect,
- float keyRadius, Paint keyPaint, Paint baseTextPaint, Paint modifierTextPaint) {
+ float keyRadius, float textPadding, Paint keyPaint, Paint baseTextPaint,
+ Paint modifierTextPaint) {
mKeyRect = keyRect;
mKeyRadius = keyRadius;
+ mTextPadding = textPadding;
mKeyPaint = keyPaint;
mBaseTextPaint = baseTextPaint;
mModifierTextPaint = modifierTextPaint;
@@ -219,20 +226,17 @@ final class KeyboardLayoutPreviewDrawable extends Drawable {
if (!glyphData.hasBaseText()) {
return;
}
- boolean isCenter = !glyphData.hasValidAltGrText() && !glyphData.hasValidAltShiftText();
mGlyphDrawables.add(new GlyphDrawable(glyphData.getBaseText(), new RectF(),
- GRAVITY_BOTTOM | (isCenter ? GRAVITY_CENTER_HORIZONTAL : GRAVITY_LEFT),
- mBaseTextPaint));
+ GRAVITY_BOTTOM | GRAVITY_LEFT, mBaseTextPaint));
if (glyphData.hasValidShiftText()) {
mGlyphDrawables.add(new GlyphDrawable(glyphData.getShiftText(), new RectF(),
- GRAVITY_TOP | (isCenter ? GRAVITY_CENTER_HORIZONTAL : GRAVITY_LEFT),
- mModifierTextPaint));
+ GRAVITY_TOP | GRAVITY_LEFT, mModifierTextPaint));
}
if (glyphData.hasValidAltGrText()) {
mGlyphDrawables.add(new GlyphDrawable(glyphData.getAltGrText(), new RectF(),
GRAVITY_BOTTOM | GRAVITY_RIGHT, mModifierTextPaint));
}
- if (glyphData.hasValidAltShiftText()) {
+ if (glyphData.hasValidAltGrShiftText()) {
mGlyphDrawables.add(new GlyphDrawable(glyphData.getAltGrShiftText(), new RectF(),
GRAVITY_TOP | GRAVITY_RIGHT, mModifierTextPaint));
}
@@ -246,15 +250,19 @@ final class KeyboardLayoutPreviewDrawable extends Drawable {
float centerY = keyHeight / 2;
if ((glyph.gravity & GRAVITY_LEFT) != 0) {
centerX -= keyWidth / 4;
+ centerX += mTextPadding / 2;
}
if ((glyph.gravity & GRAVITY_RIGHT) != 0) {
centerX += keyWidth / 4;
+ centerX -= mTextPadding / 2;
}
if ((glyph.gravity & GRAVITY_TOP) != 0) {
centerY -= keyHeight / 4;
+ centerY += mTextPadding / 2;
}
if ((glyph.gravity & GRAVITY_BOTTOM) != 0) {
centerY += keyHeight / 4;
+ centerY -= mTextPadding / 2;
}
Rect textBounds = new Rect();
glyph.paint.getTextBounds(glyph.text, 0, glyph.text.length(), textBounds);
@@ -285,9 +293,9 @@ final class KeyboardLayoutPreviewDrawable extends Drawable {
private static class UnsureTypingKey extends TypingKey {
private UnsureTypingKey(@Nullable PhysicalKeyLayout.KeyGlyph glyphData,
- RectF keyRect, float keyRadius, Paint keyPaint, Paint baseTextPaint,
- Paint modifierTextPaint) {
- super(glyphData, keyRect, keyRadius, createGreyedOutPaint(keyPaint),
+ RectF keyRect, float keyRadius, float textPadding, Paint keyPaint,
+ Paint baseTextPaint, Paint modifierTextPaint) {
+ super(glyphData, keyRect, keyRadius, textPadding, createGreyedOutPaint(keyPaint),
createGreyedOutPaint(baseTextPaint), createGreyedOutPaint(modifierTextPaint));
}
}
@@ -402,8 +410,11 @@ final class KeyboardLayoutPreviewDrawable extends Drawable {
private final Paint mSecondaryGlyphPaint;
private final int mKeyPadding;
private final int mKeyboardPadding;
+ private final float mTextPadding;
private final float mKeyRadius;
private final float mBackgroundRadius;
+ private final float mSpToPxMultiplier;
+ private final Paint.FontMetrics mFontMetrics;
private ResourceProvider(Context context) {
mKeyPadding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
@@ -414,8 +425,10 @@ final class KeyboardLayoutPreviewDrawable extends Drawable {
KEY_RADIUS_IN_DP, context.getResources().getDisplayMetrics());
mBackgroundRadius = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
KEYBOARD_RADIUS_IN_DP, context.getResources().getDisplayMetrics());
- int textSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
- GLYPH_TEXT_SIZE_IN_SP, context.getResources().getDisplayMetrics());
+ mSpToPxMultiplier = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 1,
+ context.getResources().getDisplayMetrics());
+ mTextPadding = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+ TEXT_PADDING_IN_DP, context.getResources().getDisplayMetrics());
boolean isDark = (context.getResources().getConfiguration().uiMode
& Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;
int typingKeyColor = context.getColor(
@@ -430,15 +443,37 @@ final class KeyboardLayoutPreviewDrawable extends Drawable {
int backgroundColor = context.getColor(
isDark ? android.R.color.system_surface_container_dark
: android.R.color.system_surface_container_light);
- mPrimaryGlyphPaint = createTextPaint(primaryGlyphColor, textSize,
+ mPrimaryGlyphPaint = createTextPaint(primaryGlyphColor,
+ MIN_GLYPH_TEXT_SIZE_IN_SP * mSpToPxMultiplier,
Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD));
- mSecondaryGlyphPaint = createTextPaint(secondaryGlyphColor, textSize,
+ mSecondaryGlyphPaint = createTextPaint(secondaryGlyphColor,
+ MIN_GLYPH_TEXT_SIZE_IN_SP * mSpToPxMultiplier,
Typeface.create(Typeface.SANS_SERIF, Typeface.NORMAL));
+ mFontMetrics = mPrimaryGlyphPaint.getFontMetrics();
mTypingKeyPaint = createFillPaint(typingKeyColor);
mSpecialKeyPaint = createFillPaint(specialKeyColor);
mBackgroundPaint = createFillPaint(backgroundColor);
}
+ private void calculateBestTextSizeForKey(float keyHeight) {
+ int textSize = (int) (mSpToPxMultiplier * MIN_GLYPH_TEXT_SIZE_IN_SP) + 1;
+ while (textSize < mSpToPxMultiplier * MAX_GLYPH_TEXT_SIZE_IN_SP) {
+ updateTextSize(textSize);
+ if (mFontMetrics.bottom - mFontMetrics.top + 3 * mTextPadding > keyHeight / 2) {
+ textSize--;
+ break;
+ }
+ textSize++;
+ }
+ updateTextSize(textSize);
+ }
+
+ private void updateTextSize(float textSize) {
+ mPrimaryGlyphPaint.setTextSize(textSize);
+ mSecondaryGlyphPaint.setTextSize(textSize);
+ mPrimaryGlyphPaint.getFontMetrics(mFontMetrics);
+ }
+
private Paint getBackgroundPaint() {
return mBackgroundPaint;
}
@@ -467,6 +502,10 @@ final class KeyboardLayoutPreviewDrawable extends Drawable {
return mKeyboardPadding;
}
+ private float getTextPadding() {
+ return mTextPadding;
+ }
+
private float getKeyRadius() {
return mKeyRadius;
}
@@ -476,7 +515,8 @@ final class KeyboardLayoutPreviewDrawable extends Drawable {
}
}
- private static Paint createTextPaint(@ColorInt int textColor, int textSize, Typeface typeface) {
+ private static Paint createTextPaint(@ColorInt int textColor, float textSize,
+ Typeface typeface) {
Paint paint = new Paint();
paint.setColor(textColor);
paint.setStyle(Paint.Style.FILL);
diff --git a/core/java/android/hardware/input/PhysicalKeyLayout.java b/core/java/android/hardware/input/PhysicalKeyLayout.java
index 844e02f00777..cff444fc84bf 100644
--- a/core/java/android/hardware/input/PhysicalKeyLayout.java
+++ b/core/java/android/hardware/input/PhysicalKeyLayout.java
@@ -336,11 +336,13 @@ final class PhysicalKeyLayout {
return "";
}
int utf8Char = (kcm.get(keyCode, modifierState) & KeyCharacterMap.COMBINING_ACCENT_MASK);
+ if (utf8Char == 0) {
+ return "";
+ }
if (Character.isValidCodePoint(utf8Char)) {
return String.valueOf(Character.toChars(utf8Char));
- } else {
- return String.valueOf(kcm.getDisplayLabel(keyCode));
}
+ return "â–¡";
}
private static LayoutKey getKey(int keyCode, float keyWeight) {
@@ -434,10 +436,11 @@ final class PhysicalKeyLayout {
}
public boolean hasValidAltGrText() {
- return !TextUtils.isEmpty(mAltGrText) && !TextUtils.equals(mBaseText, mAltGrText);
+ return !TextUtils.isEmpty(mAltGrText) && !TextUtils.equals(mBaseText, mAltGrText)
+ && !TextUtils.equals(mShiftText, mAltGrText);
}
- public boolean hasValidAltShiftText() {
+ public boolean hasValidAltGrShiftText() {
return !TextUtils.isEmpty(mAltGrShiftText)
&& !TextUtils.equals(mBaseText, mAltGrShiftText)
&& !TextUtils.equals(mAltGrText, mAltGrShiftText)
diff --git a/core/java/android/hardware/input/VirtualKeyboard.java b/core/java/android/hardware/input/VirtualKeyboard.java
index 6eb2ae38ed82..6a7d19535ac9 100644
--- a/core/java/android/hardware/input/VirtualKeyboard.java
+++ b/core/java/android/hardware/input/VirtualKeyboard.java
@@ -18,7 +18,9 @@ package android.hardware.input;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.companion.virtual.IVirtualDevice;
import android.os.IBinder;
import android.os.RemoteException;
@@ -66,4 +68,15 @@ public class VirtualKeyboard extends VirtualInputDevice {
throw e.rethrowFromSystemServer();
}
}
+
+ /**
+ * @return The id of the {@link android.view.InputDevice} corresponding to this keyboard.
+ * @hide
+ */
+ @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
+ @TestApi
+ @Override
+ public int getInputDeviceId() {
+ return super.getInputDeviceId();
+ }
}
diff --git a/core/java/android/net/flags.aconfig b/core/java/android/net/flags.aconfig
index 311dc09eb516..6efb87225183 100644
--- a/core/java/android/net/flags.aconfig
+++ b/core/java/android/net/flags.aconfig
@@ -9,3 +9,11 @@ flag {
description: "The flag controls the access for getIpSecTransformState and IpSecTransformState"
bug: "308011229"
}
+
+flag {
+ name: "powered_off_finding_platform"
+ namespace: "nearby"
+ description: "Controls whether the Powered Off Finding feature is enabled"
+ bug: "307898240"
+}
+
diff --git a/core/java/android/net/thread/flags.aconfig b/core/java/android/net/thread/flags.aconfig
index ff762d78c412..d679f9c3acb8 100644
--- a/core/java/android/net/thread/flags.aconfig
+++ b/core/java/android/net/thread/flags.aconfig
@@ -1,4 +1,4 @@
-package: "com.android.net.thread.flags"
+package: "com.android.net.thread.platform.flags"
# This file contains aconfig flags used from platform code
# Flags used for module APIs must be in aconfig files under each modules
diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS
index 4b170f3786e9..b1c24a7fc64a 100644
--- a/core/java/android/os/OWNERS
+++ b/core/java/android/os/OWNERS
@@ -87,6 +87,7 @@ per-file DdmSyncStageUpdater.java = sanglardf@google.com, rpaquay@google.com
# PerformanceHintManager
per-file PerformanceHintManager.java = file:/ADPF_OWNERS
+per-file WorkDuration.java = file:/ADPF_OWNERS
# IThermal interfaces
per-file IThermal* = file:/THERMAL_OWNERS
diff --git a/core/java/android/os/PermissionEnforcer.java b/core/java/android/os/PermissionEnforcer.java
index 91d22698d7df..3cc6fb5ad0ef 100644
--- a/core/java/android/os/PermissionEnforcer.java
+++ b/core/java/android/os/PermissionEnforcer.java
@@ -24,6 +24,7 @@ import android.content.Context;
import android.content.PermissionChecker;
import android.content.pm.PackageManager;
import android.permission.PermissionCheckerManager;
+import android.permission.PermissionManager;
/**
* PermissionEnforcer check permissions for AIDL-generated services which use
@@ -71,6 +72,7 @@ import android.permission.PermissionCheckerManager;
* @hide
*/
@SystemService(Context.PERMISSION_ENFORCER_SERVICE)
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public class PermissionEnforcer {
private final Context mContext;
@@ -84,6 +86,8 @@ public class PermissionEnforcer {
}
/** Constructor, prefer using the fromContext static method when possible */
+ @android.ravenwood.annotation.RavenwoodThrow(blockedBy = PermissionManager.class,
+ reason = "Use subclass for unit tests, such as FakePermissionEnforcer")
public PermissionEnforcer(@NonNull Context context) {
mContext = context;
}
@@ -103,9 +107,19 @@ public class PermissionEnforcer {
return PermissionCheckerManager.PERMISSION_HARD_DENIED;
}
+ @android.ravenwood.annotation.RavenwoodReplace(blockedBy = AppOpsManager.class,
+ reason = "Blocked on Mainline dependencies")
+ private static int permissionToOpCode(String permission) {
+ return AppOpsManager.permissionToOpCode(permission);
+ }
+
+ private static int permissionToOpCode$ravenwood(String permission) {
+ return AppOpsManager.OP_NONE;
+ }
+
private boolean anyAppOps(@NonNull String[] permissions) {
for (String permission : permissions) {
- if (AppOpsManager.permissionToOpCode(permission) != AppOpsManager.OP_NONE) {
+ if (permissionToOpCode(permission) != AppOpsManager.OP_NONE) {
return true;
}
}
@@ -122,7 +136,7 @@ public class PermissionEnforcer {
public void enforcePermission(@NonNull String permission, int pid, int uid)
throws SecurityException {
- if (AppOpsManager.permissionToOpCode(permission) != AppOpsManager.OP_NONE) {
+ if (permissionToOpCode(permission) != AppOpsManager.OP_NONE) {
AttributionSource source = new AttributionSource(uid, null, null);
enforcePermission(permission, source);
return;
diff --git a/core/java/android/os/ServiceManager.java b/core/java/android/os/ServiceManager.java
index e96c24d677f1..0be2d3e30c33 100644
--- a/core/java/android/os/ServiceManager.java
+++ b/core/java/android/os/ServiceManager.java
@@ -25,6 +25,7 @@ import android.util.Log;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.BinderInternal;
+import com.android.internal.util.Preconditions;
import com.android.internal.util.StatLogger;
import java.util.Map;
@@ -38,6 +39,7 @@ import java.util.Map;
* @hide
**/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+@android.ravenwood.annotation.RavenwoodKeepPartialClass
public final class ServiceManager {
private static final String TAG = "ServiceManager";
private static final Object sLock = new Object();
@@ -48,9 +50,16 @@ public final class ServiceManager {
/**
* Cache for the "well known" services, such as WM and AM.
*/
+ // NOTE: this cache is designed to be populated exactly once at process
+ // start to avoid any overhead from locking
@UnsupportedAppUsage
private static Map<String, IBinder> sCache = new ArrayMap<String, IBinder>();
+ @GuardedBy("ServiceManager.class")
+ // NOTE: this cache is designed to support mutation by tests, so we require
+ // a lock to be held for all accesses
+ private static Map<String, IBinder> sCache$ravenwood;
+
/**
* We do the "slow log" at most once every this interval.
*/
@@ -115,9 +124,27 @@ public final class ServiceManager {
/** @hide */
@UnsupportedAppUsage
+ @android.ravenwood.annotation.RavenwoodKeep
public ServiceManager() {
}
+ /** @hide */
+ @android.ravenwood.annotation.RavenwoodKeep
+ public static void init$ravenwood() {
+ synchronized (ServiceManager.class) {
+ sCache$ravenwood = new ArrayMap<>();
+ }
+ }
+
+ /** @hide */
+ @android.ravenwood.annotation.RavenwoodKeep
+ public static void reset$ravenwood() {
+ synchronized (ServiceManager.class) {
+ sCache$ravenwood.clear();
+ sCache$ravenwood = null;
+ }
+ }
+
@UnsupportedAppUsage
private static IServiceManager getIServiceManager() {
if (sServiceManager != null) {
@@ -138,6 +165,7 @@ public final class ServiceManager {
* @hide
*/
@UnsupportedAppUsage
+ @android.ravenwood.annotation.RavenwoodReplace
public static IBinder getService(String name) {
try {
IBinder service = sCache.get(name);
@@ -152,12 +180,21 @@ public final class ServiceManager {
return null;
}
+ /** @hide */
+ public static IBinder getService$ravenwood(String name) {
+ synchronized (ServiceManager.class) {
+ // Ravenwood is a single-process environment, so it only needs to store locally
+ return Preconditions.requireNonNullViaRavenwoodRule(sCache$ravenwood).get(name);
+ }
+ }
+
/**
* Returns a reference to a service with the given name, or throws
* {@link ServiceNotFoundException} if none is found.
*
* @hide
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public static IBinder getServiceOrThrow(String name) throws ServiceNotFoundException {
final IBinder binder = getService(name);
if (binder != null) {
@@ -176,6 +213,7 @@ public final class ServiceManager {
* @hide
*/
@UnsupportedAppUsage
+ @android.ravenwood.annotation.RavenwoodKeep
public static void addService(String name, IBinder service) {
addService(name, service, false, IServiceManager.DUMP_FLAG_PRIORITY_DEFAULT);
}
@@ -191,6 +229,7 @@ public final class ServiceManager {
* @hide
*/
@UnsupportedAppUsage
+ @android.ravenwood.annotation.RavenwoodKeep
public static void addService(String name, IBinder service, boolean allowIsolated) {
addService(name, service, allowIsolated, IServiceManager.DUMP_FLAG_PRIORITY_DEFAULT);
}
@@ -207,6 +246,7 @@ public final class ServiceManager {
* @hide
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ @android.ravenwood.annotation.RavenwoodReplace
public static void addService(String name, IBinder service, boolean allowIsolated,
int dumpPriority) {
try {
@@ -216,6 +256,15 @@ public final class ServiceManager {
}
}
+ /** @hide */
+ public static void addService$ravenwood(String name, IBinder service, boolean allowIsolated,
+ int dumpPriority) {
+ synchronized (ServiceManager.class) {
+ // Ravenwood is a single-process environment, so it only needs to store locally
+ Preconditions.requireNonNullViaRavenwoodRule(sCache$ravenwood).put(name, service);
+ }
+ }
+
/**
* Retrieve an existing service called @a name from the
* service manager. Non-blocking.
@@ -366,6 +415,7 @@ public final class ServiceManager {
*
* @hide
*/
+ @android.ravenwood.annotation.RavenwoodKeepWholeClass
public static class ServiceNotFoundException extends Exception {
public ServiceNotFoundException(String name) {
super("No service published for: " + name);
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 7873a76b519d..be17d7c0f0db 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -1920,7 +1920,9 @@ public class UserManager {
* @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
*/
- @FlaggedApi(com.android.net.thread.flags.Flags.FLAG_THREAD_USER_RESTRICTION_ENABLED)
+ // TODO (b/325886480): update the flag to
+ // "com.android.net.thread.platform.flags.Flags.FLAG_THREAD_USER_RESTRICTION_ENABLED"
+ @FlaggedApi("com.android.net.thread.flags.thread_user_restriction_enabled")
public static final String DISALLOW_THREAD_NETWORK = "no_thread_network";
/**
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index c9c91fc49aeb..efbd96bc35cb 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -591,9 +591,14 @@ public abstract class VibrationEffect implements Parcelable {
/**
* Scale given vibration intensity by the given factor.
*
+ * <p> This scale is not necessarily linear and may apply a gamma correction to the scale
+ * factor before using it.
+ *
* @param intensity relative intensity of the effect, must be between 0 and 1
* @param scaleFactor scale factor to be applied to the intensity. Values within [0,1) will
* scale down the intensity, values larger than 1 will scale up
+ * @return the scaled intensity which will be values within [0, 1].
+ *
* @hide
*/
public static float scale(float intensity, float scaleFactor) {
@@ -624,6 +629,20 @@ public abstract class VibrationEffect implements Parcelable {
}
/**
+ * Performs a linear scaling on the given vibration intensity by the given factor.
+ *
+ * @param intensity relative intensity of the effect, must be between 0 and 1.
+ * @param scaleFactor scale factor to be applied to the intensity. Values within [0,1) will
+ * scale down the intensity, values larger than 1 will scale up.
+ * @return the scaled intensity which will be values within [0, 1].
+ *
+ * @hide
+ */
+ public static float scaleLinearly(float intensity, float scaleFactor) {
+ return MathUtils.constrain(intensity * scaleFactor, 0f, 1f);
+ }
+
+ /**
* Returns a compact version of the {@link #toString()} result for debugging purposes.
*
* @hide
diff --git a/core/java/android/os/vibrator/PrebakedSegment.java b/core/java/android/os/vibrator/PrebakedSegment.java
index a035092e314f..39f841226e4e 100644
--- a/core/java/android/os/vibrator/PrebakedSegment.java
+++ b/core/java/android/os/vibrator/PrebakedSegment.java
@@ -137,6 +137,14 @@ public final class PrebakedSegment extends VibrationEffectSegment {
/** @hide */
@NonNull
@Override
+ public PrebakedSegment scaleLinearly(float scaleFactor) {
+ // Prebaked effect strength cannot be scaled with this method.
+ return this;
+ }
+
+ /** @hide */
+ @NonNull
+ @Override
public PrebakedSegment applyEffectStrength(int effectStrength) {
if (effectStrength != mEffectStrength && isValidEffectStrength(effectStrength)) {
return new PrebakedSegment(mEffectId, mFallback, effectStrength);
diff --git a/core/java/android/os/vibrator/PrimitiveSegment.java b/core/java/android/os/vibrator/PrimitiveSegment.java
index 95d97bfe4ad1..3c84bcda639b 100644
--- a/core/java/android/os/vibrator/PrimitiveSegment.java
+++ b/core/java/android/os/vibrator/PrimitiveSegment.java
@@ -98,8 +98,24 @@ public final class PrimitiveSegment extends VibrationEffectSegment {
@NonNull
@Override
public PrimitiveSegment scale(float scaleFactor) {
- return new PrimitiveSegment(mPrimitiveId, VibrationEffect.scale(mScale, scaleFactor),
- mDelay);
+ float newScale = VibrationEffect.scale(mScale, scaleFactor);
+ if (Float.compare(mScale, newScale) == 0) {
+ return this;
+ }
+
+ return new PrimitiveSegment(mPrimitiveId, newScale, mDelay);
+ }
+
+ /** @hide */
+ @NonNull
+ @Override
+ public PrimitiveSegment scaleLinearly(float scaleFactor) {
+ float newScale = VibrationEffect.scaleLinearly(mScale, scaleFactor);
+ if (Float.compare(mScale, newScale) == 0) {
+ return this;
+ }
+
+ return new PrimitiveSegment(mPrimitiveId, newScale, mDelay);
}
/** @hide */
diff --git a/core/java/android/os/vibrator/RampSegment.java b/core/java/android/os/vibrator/RampSegment.java
index 5f9d1024d9a5..09d2e26be2a5 100644
--- a/core/java/android/os/vibrator/RampSegment.java
+++ b/core/java/android/os/vibrator/RampSegment.java
@@ -159,6 +159,21 @@ public final class RampSegment extends VibrationEffectSegment {
/** @hide */
@NonNull
@Override
+ public RampSegment scaleLinearly(float scaleFactor) {
+ float newStartAmplitude = VibrationEffect.scaleLinearly(mStartAmplitude, scaleFactor);
+ float newEndAmplitude = VibrationEffect.scaleLinearly(mEndAmplitude, scaleFactor);
+ if (Float.compare(mStartAmplitude, newStartAmplitude) == 0
+ && Float.compare(mEndAmplitude, newEndAmplitude) == 0) {
+ return this;
+ }
+ return new RampSegment(newStartAmplitude, newEndAmplitude, mStartFrequencyHz,
+ mEndFrequencyHz,
+ mDuration);
+ }
+
+ /** @hide */
+ @NonNull
+ @Override
public RampSegment applyEffectStrength(int effectStrength) {
return this;
}
diff --git a/core/java/android/os/vibrator/StepSegment.java b/core/java/android/os/vibrator/StepSegment.java
index 9576a5bba1f1..fa083c176a27 100644
--- a/core/java/android/os/vibrator/StepSegment.java
+++ b/core/java/android/os/vibrator/StepSegment.java
@@ -137,8 +137,25 @@ public final class StepSegment extends VibrationEffectSegment {
if (Float.compare(mAmplitude, VibrationEffect.DEFAULT_AMPLITUDE) == 0) {
return this;
}
- return new StepSegment(VibrationEffect.scale(mAmplitude, scaleFactor), mFrequencyHz,
- mDuration);
+ float newAmplitude = VibrationEffect.scale(mAmplitude, scaleFactor);
+ if (Float.compare(newAmplitude, mAmplitude) == 0) {
+ return this;
+ }
+ return new StepSegment(newAmplitude, mFrequencyHz, mDuration);
+ }
+
+ /** @hide */
+ @NonNull
+ @Override
+ public StepSegment scaleLinearly(float scaleFactor) {
+ if (Float.compare(mAmplitude, VibrationEffect.DEFAULT_AMPLITUDE) == 0) {
+ return this;
+ }
+ float newAmplitude = VibrationEffect.scaleLinearly(mAmplitude, scaleFactor);
+ if (Float.compare(newAmplitude, mAmplitude) == 0) {
+ return this;
+ }
+ return new StepSegment(newAmplitude, mFrequencyHz, mDuration);
}
/** @hide */
diff --git a/core/java/android/os/vibrator/VibrationEffectSegment.java b/core/java/android/os/vibrator/VibrationEffectSegment.java
index 17ac36f3ab37..e1fb4e361008 100644
--- a/core/java/android/os/vibrator/VibrationEffectSegment.java
+++ b/core/java/android/os/vibrator/VibrationEffectSegment.java
@@ -96,6 +96,9 @@ public abstract class VibrationEffectSegment implements Parcelable {
/**
* Scale the segment intensity with the given factor.
*
+ * <p> This scale is not necessarily linear and may apply a gamma correction to the scale
+ * factor before using it.
+ *
* @param scaleFactor scale factor to be applied to the intensity. Values within [0,1) will
* scale down the intensity, values larger than 1 will scale up
*
@@ -105,6 +108,17 @@ public abstract class VibrationEffectSegment implements Parcelable {
public abstract <T extends VibrationEffectSegment> T scale(float scaleFactor);
/**
+ * Performs a linear scaling on the segment intensity with the given factor.
+ *
+ * @param scaleFactor scale factor to be applied to the intensity. Values within [0,1) will
+ * scale down the intensity, values larger than 1 will scale up
+ *
+ * @hide
+ */
+ @NonNull
+ public abstract <T extends VibrationEffectSegment> T scaleLinearly(float scaleFactor);
+
+ /**
* Applies given effect strength to prebaked effects.
*
* @param effectStrength new effect strength to be applied, one of
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index 9218cb8f497d..de7008b19f54 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -130,3 +130,11 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "ignore_process_text"
+ namespace: "permissions"
+ description: "Ignore activities that handle PROCESS_TEXT in TextView"
+ bug: "325356776"
+}
+
diff --git a/core/java/android/print/pdf/TEST_MAPPING b/core/java/android/print/pdf/TEST_MAPPING
deleted file mode 100644
index d763598f5ba0..000000000000
--- a/core/java/android/print/pdf/TEST_MAPPING
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "presubmit": [
- {
- "name": "CtsPdfTestCases"
- }
- ]
-}
diff --git a/core/java/android/service/chooser/ChooserResult.java b/core/java/android/service/chooser/ChooserResult.java
index 4603be114508..2d56ec730836 100644
--- a/core/java/android/service/chooser/ChooserResult.java
+++ b/core/java/android/service/chooser/ChooserResult.java
@@ -20,6 +20,7 @@ import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.TestApi;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
import android.compat.annotation.Overridable;
@@ -91,6 +92,7 @@ public final class ChooserResult implements Parcelable {
}
/** @hide */
+ @TestApi
public ChooserResult(@ResultType int type, @Nullable ComponentName componentName,
boolean isShortcut) {
mType = type;
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 15fb6ccf82fa..d9ca935b35b3 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -216,7 +216,7 @@ public class ZenModeConfig implements Parcelable {
private static final String ALLOW_ATT_SCREEN_ON = "visualScreenOn";
private static final String ALLOW_ATT_CONV = "convos";
private static final String ALLOW_ATT_CONV_FROM = "convosFrom";
- private static final String ALLOW_ATT_CHANNELS = "priorityChannels";
+ private static final String ALLOW_ATT_CHANNELS = "priorityChannelsAllowed";
private static final String POLICY_USER_MODIFIED_FIELDS = "policyUserModifiedFields";
private static final String DISALLOW_TAG = "disallow";
private static final String DISALLOW_ATT_VISUAL_EFFECTS = "visualEffects";
diff --git a/core/java/android/service/notification/ZenPolicy.java b/core/java/android/service/notification/ZenPolicy.java
index aa47d3a5c2af..786d768bc55b 100644
--- a/core/java/android/service/notification/ZenPolicy.java
+++ b/core/java/android/service/notification/ZenPolicy.java
@@ -673,10 +673,6 @@ public final class ZenPolicy implements Parcelable {
mZenPolicy.mPriorityMessages = PEOPLE_TYPE_NONE;
mZenPolicy.mPriorityCalls = PEOPLE_TYPE_NONE;
mZenPolicy.mConversationSenders = CONVERSATION_SENDERS_NONE;
-
- if (Flags.modesApi()) {
- mZenPolicy.mAllowChannels = CHANNEL_POLICY_NONE;
- }
return this;
}
diff --git a/core/java/android/service/notification/flags.aconfig b/core/java/android/service/notification/flags.aconfig
index 446fe3de6482..c5acc2ceb968 100644
--- a/core/java/android/service/notification/flags.aconfig
+++ b/core/java/android/service/notification/flags.aconfig
@@ -1,4 +1,5 @@
package: "android.service.notification"
+container: "system"
flag {
name: "ranking_update_ashmem"
@@ -12,6 +13,7 @@ flag {
namespace: "systemui"
description: "This flag controls the redacting of sensitive notifications from untrusted NotificationListenerServices"
bug: "306271190"
+ is_exported: true
}
flag {
diff --git a/core/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl b/core/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl
new file mode 100644
index 000000000000..bbb4bc6c0272
--- /dev/null
+++ b/core/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.ondeviceintelligence;
+
+import android.os.PersistableBundle;
+import android.os.ParcelFileDescriptor;
+import android.os.ICancellationSignal;
+import android.os.RemoteCallback;
+import android.app.ondeviceintelligence.IDownloadCallback;
+import android.app.ondeviceintelligence.Feature;
+import android.app.ondeviceintelligence.IFeatureCallback;
+import android.app.ondeviceintelligence.IListFeaturesCallback;
+import android.app.ondeviceintelligence.IFeatureDetailsCallback;
+import com.android.internal.infra.AndroidFuture;
+
+
+/**
+ * Interface for a concrete implementation to provide on device intelligence services.
+ *
+ * @hide
+ */
+oneway interface IOnDeviceIntelligenceService {
+ void getVersion(in RemoteCallback remoteCallback);
+ void getFeature(in int featureId, in IFeatureCallback featureCallback);
+ void listFeatures(in IListFeaturesCallback listFeaturesCallback);
+ void getFeatureDetails(in Feature feature, in IFeatureDetailsCallback featureDetailsCallback);
+ void getReadOnlyFileDescriptor(in String fileName, in AndroidFuture<ParcelFileDescriptor> future);
+ void getReadOnlyFeatureFileDescriptorMap(in Feature feature, in RemoteCallback remoteCallback);
+ void requestFeatureDownload(in Feature feature, in ICancellationSignal cancellationSignal, in IDownloadCallback downloadCallback);
+} \ No newline at end of file
diff --git a/core/java/android/service/ondeviceintelligence/IOnDeviceTrustedInferenceService.aidl b/core/java/android/service/ondeviceintelligence/IOnDeviceTrustedInferenceService.aidl
new file mode 100644
index 000000000000..08eb9278fcc4
--- /dev/null
+++ b/core/java/android/service/ondeviceintelligence/IOnDeviceTrustedInferenceService.aidl
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.ondeviceintelligence;
+
+import android.app.ondeviceintelligence.IStreamingResponseCallback;
+import android.app.ondeviceintelligence.IResponseCallback;
+import android.app.ondeviceintelligence.ITokenCountCallback;
+import android.app.ondeviceintelligence.IProcessingSignal;
+import android.app.ondeviceintelligence.Content;
+import android.app.ondeviceintelligence.Feature;
+import android.os.ICancellationSignal;
+import android.service.ondeviceintelligence.IRemoteStorageService;
+
+
+/**
+ * Interface for a concrete implementation to provide on device trusted inference.
+ *
+ * @hide
+ */
+oneway interface IOnDeviceTrustedInferenceService {
+ void registerRemoteStorageService(in IRemoteStorageService storageService);
+ void requestTokenCount(in Feature feature, in Content request, in ICancellationSignal cancellationSignal,
+ in ITokenCountCallback tokenCountCallback);
+ void processRequest(in Feature feature, in Content request, in int requestType,
+ in ICancellationSignal cancellationSignal, in IProcessingSignal processingSignal,
+ in IResponseCallback callback);
+ void processRequestStreaming(in Feature feature, in Content request, in int requestType,
+ in ICancellationSignal cancellationSignal, in IProcessingSignal processingSignal,
+ in IStreamingResponseCallback callback);
+} \ No newline at end of file
diff --git a/core/java/android/service/ondeviceintelligence/IRemoteStorageService.aidl b/core/java/android/service/ondeviceintelligence/IRemoteStorageService.aidl
new file mode 100644
index 000000000000..a6f49e17d80e
--- /dev/null
+++ b/core/java/android/service/ondeviceintelligence/IRemoteStorageService.aidl
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.ondeviceintelligence;
+
+import android.app.ondeviceintelligence.Feature;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteCallback;
+
+import com.android.internal.infra.AndroidFuture;
+
+/**
+ * Interface for a concrete implementation to provide access to storage read access
+ * for the isolated process.
+ *
+ * @hide
+ */
+oneway interface IRemoteStorageService {
+ void getReadOnlyFileDescriptor(in String filePath, in AndroidFuture<ParcelFileDescriptor> future);
+ void getReadOnlyFeatureFileDescriptorMap(in Feature feature, in RemoteCallback remoteCallback);
+} \ No newline at end of file
diff --git a/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java b/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java
new file mode 100644
index 000000000000..0cba1d37721a
--- /dev/null
+++ b/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java
@@ -0,0 +1,383 @@
+/*
+ * 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.service.ondeviceintelligence;
+
+import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SdkConstant;
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.app.ondeviceintelligence.DownloadCallback;
+import android.app.ondeviceintelligence.Feature;
+import android.app.ondeviceintelligence.FeatureDetails;
+import android.app.ondeviceintelligence.IDownloadCallback;
+import android.app.ondeviceintelligence.IFeatureCallback;
+import android.app.ondeviceintelligence.IFeatureDetailsCallback;
+import android.app.ondeviceintelligence.IListFeaturesCallback;
+import android.app.ondeviceintelligence.OnDeviceIntelligenceManager;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.IBinder;
+import android.os.ICancellationSignal;
+import android.os.OutcomeReceiver;
+import android.os.ParcelFileDescriptor;
+import android.os.PersistableBundle;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.internal.infra.AndroidFuture;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Consumer;
+import java.util.function.LongConsumer;
+
+/**
+ * Abstract base class for performing setup for on-device inference and providing file access to
+ * the isolated counter part {@link OnDeviceTrustedInferenceService}.
+ *
+ * <p> A service that provides configuration and model files relevant to performing inference on
+ * device. The system's default OnDeviceIntelligenceService implementation is configured in
+ * {@code config_defaultOnDeviceIntelligenceService}. If this config has no value, a stub is
+ * returned.
+ *
+ * <pre>
+ * {@literal
+ * <service android:name=".SampleOnDeviceIntelligenceService"
+ * android:permission="android.permission.BIND_ON_DEVICE_INTELLIGENCE_SERVICE">
+ * </service>}
+ * </pre>
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
+public abstract class OnDeviceIntelligenceService extends Service {
+ private static final String TAG = OnDeviceIntelligenceService.class.getSimpleName();
+
+ /**
+ * The {@link Intent} that must be declared as handled by the service. To be supported, the
+ * service must also require the
+ * {@link android.Manifest.permission#BIND_ON_DEVICE_INTELLIGENCE_SERVICE}
+ * permission so that other applications can not abuse it.
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+ public static final String SERVICE_INTERFACE =
+ "android.service.ondeviceintelligence.OnDeviceIntelligenceService";
+
+ /**
+ * @hide
+ */
+ @Nullable
+ @Override
+ public final IBinder onBind(@NonNull Intent intent) {
+ if (SERVICE_INTERFACE.equals(intent.getAction())) {
+ return new IOnDeviceIntelligenceService.Stub() {
+ /** {@inheritDoc} */
+ @Override
+ public void getVersion(RemoteCallback remoteCallback) {
+ Objects.requireNonNull(remoteCallback);
+ OnDeviceIntelligenceService.this.onGetVersion(l -> {
+ Bundle b = new Bundle();
+ b.putLong(OnDeviceIntelligenceManager.API_VERSION_BUNDLE_KEY, l);
+ remoteCallback.sendResult(b);
+ });
+ }
+
+ @Override
+ public void listFeatures(IListFeaturesCallback listFeaturesCallback) {
+ Objects.requireNonNull(listFeaturesCallback);
+ OnDeviceIntelligenceService.this.onListFeatures(
+ wrapListFeaturesCallback(listFeaturesCallback));
+ }
+
+ @Override
+ public void getFeature(int id, IFeatureCallback featureCallback) {
+ Objects.requireNonNull(featureCallback);
+ OnDeviceIntelligenceService.this.onGetFeature(id,
+ wrapFeatureCallback(featureCallback));
+ }
+
+
+ @Override
+ public void getFeatureDetails(Feature feature,
+ IFeatureDetailsCallback featureDetailsCallback) {
+ Objects.requireNonNull(feature);
+ Objects.requireNonNull(featureDetailsCallback);
+
+ OnDeviceIntelligenceService.this.onGetFeatureDetails(feature,
+ wrapFeatureDetailsCallback(featureDetailsCallback));
+ }
+
+ @Override
+ public void requestFeatureDownload(Feature feature,
+ ICancellationSignal cancellationSignal,
+ IDownloadCallback downloadCallback) {
+ Objects.requireNonNull(feature);
+ Objects.requireNonNull(downloadCallback);
+
+ OnDeviceIntelligenceService.this.onDownloadFeature(feature,
+ CancellationSignal.fromTransport(cancellationSignal),
+ wrapDownloadCallback(downloadCallback));
+ }
+
+ @Override
+ public void getReadOnlyFileDescriptor(String fileName,
+ AndroidFuture<ParcelFileDescriptor> future) {
+ Objects.requireNonNull(fileName);
+ Objects.requireNonNull(future);
+
+ OnDeviceIntelligenceService.this.onGetReadOnlyFileDescriptor(fileName,
+ future);
+ }
+
+ @Override
+ public void getReadOnlyFeatureFileDescriptorMap(
+ Feature feature, RemoteCallback remoteCallback) {
+ Objects.requireNonNull(feature);
+ Objects.requireNonNull(remoteCallback);
+
+ OnDeviceIntelligenceService.this.onGetReadOnlyFeatureFileDescriptorMap(
+ feature, parcelFileDescriptorMap -> {
+ Bundle bundle = new Bundle();
+ parcelFileDescriptorMap.forEach(bundle::putParcelable);
+ remoteCallback.sendResult(bundle);
+ });
+ }
+ };
+ }
+ Slog.w(TAG, "Incorrect service interface, returning null.");
+ return null;
+ }
+
+ private OutcomeReceiver<Feature,
+ OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> wrapFeatureCallback(
+ IFeatureCallback featureCallback) {
+ return new OutcomeReceiver<>() {
+ @Override
+ public void onResult(@NonNull Feature feature) {
+ try {
+ featureCallback.onSuccess(feature);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending feature: " + e);
+ }
+ }
+
+ @Override
+ public void onError(
+ @NonNull OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException exception) {
+ try {
+ featureCallback.onFailure(exception.getErrorCode(), exception.getMessage(),
+ exception.getErrorParams());
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending download feature: " + e);
+ }
+ }
+ };
+
+ }
+
+ private OutcomeReceiver<List<Feature>,
+ OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> wrapListFeaturesCallback(
+ IListFeaturesCallback listFeaturesCallback) {
+ return new OutcomeReceiver<>() {
+ @Override
+ public void onResult(@NonNull List<Feature> features) {
+ try {
+ listFeaturesCallback.onSuccess(features);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending feature: " + e);
+ }
+ }
+
+ @Override
+ public void onError(
+ @NonNull OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException exception) {
+ try {
+ listFeaturesCallback.onFailure(exception.getErrorCode(), exception.getMessage(),
+ exception.getErrorParams());
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending download feature: " + e);
+ }
+ }
+ };
+ }
+
+ private OutcomeReceiver<FeatureDetails,
+ OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> wrapFeatureDetailsCallback(
+ IFeatureDetailsCallback featureStatusCallback) {
+ return new OutcomeReceiver<>() {
+ @Override
+ public void onResult(FeatureDetails result) {
+ try {
+ featureStatusCallback.onSuccess(result);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending feature status: " + e);
+ }
+ }
+
+ @Override
+ public void onError(
+ @NonNull OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException exception) {
+ try {
+ featureStatusCallback.onFailure(exception.getErrorCode(),
+ exception.getMessage(), exception.getErrorParams());
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending feature status: " + e);
+ }
+ }
+ };
+ }
+
+
+ private DownloadCallback wrapDownloadCallback(IDownloadCallback downloadCallback) {
+ return new DownloadCallback() {
+ @Override
+ public void onDownloadStarted(long bytesToDownload) {
+ try {
+ downloadCallback.onDownloadStarted(bytesToDownload);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending download status: " + e);
+ }
+ }
+
+ @Override
+ public void onDownloadFailed(int failureStatus,
+ String errorMessage, @NonNull PersistableBundle errorParams) {
+ try {
+ downloadCallback.onDownloadFailed(failureStatus, errorMessage, errorParams);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending download status: " + e);
+ }
+ }
+
+ @Override
+ public void onDownloadProgress(long totalBytesDownloaded) {
+ try {
+ downloadCallback.onDownloadProgress(totalBytesDownloaded);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending download status: " + e);
+ }
+ }
+
+ @Override
+ public void onDownloadCompleted(@NonNull PersistableBundle persistableBundle) {
+ try {
+ downloadCallback.onDownloadCompleted(persistableBundle);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending download status: " + e);
+ }
+ }
+ };
+ }
+
+ private void onGetReadOnlyFileDescriptor(@NonNull String fileName,
+ @NonNull AndroidFuture<ParcelFileDescriptor> future) {
+ Slog.v(TAG, "onGetReadOnlyFileDescriptor " + fileName);
+ Binder.withCleanCallingIdentity(() -> {
+ Slog.v(TAG,
+ "onGetReadOnlyFileDescriptor: " + fileName + " under internal app storage.");
+ File f = new File(getBaseContext().getFilesDir(), fileName);
+ ParcelFileDescriptor pfd = null;
+ try {
+ pfd = ParcelFileDescriptor.open(f, ParcelFileDescriptor.MODE_READ_ONLY);
+ Slog.d(TAG, "Successfully opened a file with ParcelFileDescriptor.");
+ } catch (FileNotFoundException e) {
+ Slog.e(TAG, "Cannot open file. No ParcelFileDescriptor returned.");
+ } finally {
+ future.complete(pfd);
+ }
+ });
+ }
+
+ /**
+ * Provide implementation for a scenario when caller wants to get all feature related
+ * file-descriptors that might be required for processing a request for the corresponding the
+ * feature.
+ *
+ * @param feature the feature for which files need to be opened.
+ * @param fileDescriptorMapConsumer callback to be populated with a map of file-path and
+ * corresponding ParcelDescriptor to be used in a remote
+ * service.
+ */
+ public abstract void onGetReadOnlyFeatureFileDescriptorMap(
+ @NonNull Feature feature,
+ @NonNull Consumer<Map<String, ParcelFileDescriptor>> fileDescriptorMapConsumer);
+
+ /**
+ * Request download for feature that is requested and listen to download progress updates. If
+ * the download completes successfully, success callback should be populated.
+ *
+ * @param feature the feature for which files need to be downlaoded.
+ * process.
+ * @param cancellationSignal signal to attach a listener to, and receive cancellation signals
+ * from thw client.
+ * @param downloadCallback callback to populate download updates for clients to listen on..
+ */
+ public abstract void onDownloadFeature(
+ @NonNull Feature feature,
+ @Nullable CancellationSignal cancellationSignal,
+ @NonNull DownloadCallback downloadCallback);
+
+ /**
+ * Provide feature details for the passed in feature. Usually the client and remote
+ * implementation use the {@link Feature#getFeatureParams()} as a hint to communicate what
+ * details the client is looking for.
+ *
+ * @param feature the feature for which status needs to be known.
+ * @param featureStatusCallback callback to populate the resulting feature status.
+ */
+ public abstract void onGetFeatureDetails(@NonNull Feature feature,
+ @NonNull OutcomeReceiver<FeatureDetails,
+ OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> featureStatusCallback);
+
+
+ /**
+ * Get feature using the provided identifier to the remote implementation.
+ *
+ * @param featureCallback callback to populate the features list.
+ */
+ public abstract void onGetFeature(int featureId,
+ @NonNull OutcomeReceiver<Feature,
+ OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> featureCallback);
+
+ /**
+ * List all features which are available in the remote implementation. The implementation might
+ * choose to provide only a certain list of features based on the caller.
+ *
+ * @param listFeaturesCallback callback to populate the features list.
+ */
+ public abstract void onListFeatures(@NonNull OutcomeReceiver<List<Feature>,
+ OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> listFeaturesCallback);
+
+ /**
+ * Provides a long value representing the version of the remote implementation processing
+ * requests.
+ *
+ * @param versionConsumer consumer to populate the version.
+ */
+ public abstract void onGetVersion(@NonNull LongConsumer versionConsumer);
+}
diff --git a/core/java/android/service/ondeviceintelligence/OnDeviceTrustedInferenceService.java b/core/java/android/service/ondeviceintelligence/OnDeviceTrustedInferenceService.java
new file mode 100644
index 000000000000..96982e3d7829
--- /dev/null
+++ b/core/java/android/service/ondeviceintelligence/OnDeviceTrustedInferenceService.java
@@ -0,0 +1,410 @@
+/*
+ * 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.service.ondeviceintelligence;
+
+import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SdkConstant;
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.app.ondeviceintelligence.Content;
+import android.app.ondeviceintelligence.Feature;
+import android.app.ondeviceintelligence.IProcessingSignal;
+import android.app.ondeviceintelligence.IResponseCallback;
+import android.app.ondeviceintelligence.IStreamingResponseCallback;
+import android.app.ondeviceintelligence.ITokenCountCallback;
+import android.app.ondeviceintelligence.OnDeviceIntelligenceManager;
+import android.app.ondeviceintelligence.ProcessingSignal;
+import android.app.ondeviceintelligence.StreamingResponseReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.CancellationSignal;
+import android.os.IBinder;
+import android.os.ICancellationSignal;
+import android.os.OutcomeReceiver;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.infra.AndroidFuture;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * Abstract base class for performing inference in a isolated process. This service exposes its
+ * methods via {@link android.app.ondeviceintelligence.OnDeviceIntelligenceManager}.
+ *
+ * <p> A service that provides methods to perform on-device inference both in streaming and
+ * non-streaming fashion. Also, provides a way to register a storage service that will be used to
+ * read-only access files from the {@link OnDeviceIntelligenceService} counterpart. </p>
+ *
+ * <pre>
+ * {@literal
+ * <service android:name=".SampleTrustedInferenceService"
+ * android:permission="android.permission.BIND_ONDEVICE_TRUSTED_INFERENCE_SERVICE"
+ * android:isolatedProcess="true">
+ * </service>}
+ * </pre>
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
+public abstract class OnDeviceTrustedInferenceService extends Service {
+ private static final String TAG = OnDeviceTrustedInferenceService.class.getSimpleName();
+
+ /**
+ * The {@link Intent} that must be declared as handled by the service. To be supported, the
+ * service must also require the
+ * {@link android.Manifest.permission#BIND_ON_DEVICE_TRUSTED_INFERENCE_SERVICE}
+ * permission so that other applications can not abuse it.
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+ public static final String SERVICE_INTERFACE =
+ "android.service.ondeviceintelligence.OnDeviceTrustedInferenceService";
+
+ private IRemoteStorageService mRemoteStorageService;
+
+ /**
+ * @hide
+ */
+ @Nullable
+ @Override
+ public final IBinder onBind(@NonNull Intent intent) {
+ if (SERVICE_INTERFACE.equals(intent.getAction())) {
+ return new IOnDeviceTrustedInferenceService.Stub() {
+ @Override
+ public void registerRemoteStorageService(IRemoteStorageService storageService) {
+ Objects.requireNonNull(storageService);
+ mRemoteStorageService = storageService;
+ }
+
+ @Override
+ public void requestTokenCount(Feature feature, Content request,
+ ICancellationSignal cancellationSignal,
+ ITokenCountCallback tokenCountCallback) {
+ Objects.requireNonNull(feature);
+ Objects.requireNonNull(tokenCountCallback);
+ OnDeviceTrustedInferenceService.this.onCountTokens(feature,
+ request,
+ CancellationSignal.fromTransport(cancellationSignal),
+ wrapTokenCountCallback(tokenCountCallback));
+ }
+
+ @Override
+ public void processRequestStreaming(Feature feature, Content request,
+ int requestType, ICancellationSignal cancellationSignal,
+ IProcessingSignal processingSignal,
+ IStreamingResponseCallback callback) {
+ Objects.requireNonNull(feature);
+ Objects.requireNonNull(request);
+ Objects.requireNonNull(callback);
+
+ OnDeviceTrustedInferenceService.this.onProcessRequestStreaming(feature,
+ request,
+ requestType,
+ CancellationSignal.fromTransport(cancellationSignal),
+ ProcessingSignal.fromTransport(processingSignal),
+ wrapStreamingResponseCallback(callback)
+ );
+ }
+
+ @Override
+ public void processRequest(Feature feature, Content request,
+ int requestType, ICancellationSignal cancellationSignal,
+ IProcessingSignal processingSignal,
+ IResponseCallback callback) {
+ Objects.requireNonNull(feature);
+ Objects.requireNonNull(request);
+ Objects.requireNonNull(callback);
+
+
+ OnDeviceTrustedInferenceService.this.onProcessRequest(feature, request,
+ requestType, CancellationSignal.fromTransport(cancellationSignal),
+ ProcessingSignal.fromTransport(processingSignal),
+ wrapResponseCallback(callback)
+ );
+ }
+ };
+ }
+ Slog.w(TAG, "Incorrect service interface, returning null.");
+ return null;
+ }
+
+ /**
+ * Invoked when caller wants to obtain a count of number of tokens present in the passed in
+ * Request associated with the provided feature.
+ * The expectation from the implementation is that when processing is complete, it
+ * should provide the token count in the {@link OutcomeReceiver#onResult}.
+ *
+ * @param feature feature which is associated with the request.
+ * @param request request that requires processing.
+ * @param cancellationSignal Cancellation Signal to receive cancellation events from client and
+ * configure a listener to.
+ * @param callback callback to populate failure and full response for the provided
+ * request.
+ */
+ @NonNull
+ public abstract void onCountTokens(
+ @NonNull Feature feature,
+ @NonNull Content request,
+ @Nullable CancellationSignal cancellationSignal,
+ @NonNull OutcomeReceiver<Long,
+ OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> callback);
+
+ /**
+ * Invoked when caller provides a request for a particular feature to be processed in a
+ * streaming manner. The expectation from the implementation is that when processing the
+ * request,
+ * it periodically populates the {@link StreamingResponseReceiver#onNewContent} to continuously
+ * provide partial Content results for the caller to utilize. Optionally the implementation can
+ * provide the complete response in the {@link StreamingResponseReceiver#onResult} upon
+ * processing completion.
+ *
+ * @param feature feature which is associated with the request.
+ * @param request request that requires processing.
+ * @param requestType identifier representing the type of request.
+ * @param cancellationSignal Cancellation Signal to receive cancellation events from client and
+ * configure a listener to.
+ * @param processingSignal Signal to receive custom action instructions from client.
+ * @param callback callback to populate the partial responses, failure and optionally
+ * full response for the provided request.
+ */
+ @NonNull
+ public abstract void onProcessRequestStreaming(
+ @NonNull Feature feature,
+ @NonNull Content request,
+ @OnDeviceIntelligenceManager.RequestType int requestType,
+ @Nullable CancellationSignal cancellationSignal,
+ @Nullable ProcessingSignal processingSignal,
+ @NonNull StreamingResponseReceiver<Content, Content,
+ OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> callback);
+
+ /**
+ * Invoked when caller provides a request for a particular feature to be processed in one shot
+ * completely.
+ * The expectation from the implementation is that when processing the request is complete, it
+ * should
+ * provide the complete response in the {@link OutcomeReceiver#onResult}.
+ *
+ * @param feature feature which is associated with the request.
+ * @param request request that requires processing.
+ * @param requestType identifier representing the type of request.
+ * @param cancellationSignal Cancellation Signal to receive cancellation events from client and
+ * configure a listener to.
+ * @param processingSignal Signal to receive custom action instructions from client.
+ * @param callback callback to populate failure and full response for the provided
+ * request.
+ */
+ @NonNull
+ public abstract void onProcessRequest(
+ @NonNull Feature feature,
+ @NonNull Content request,
+ @OnDeviceIntelligenceManager.RequestType int requestType,
+ @Nullable CancellationSignal cancellationSignal,
+ @Nullable ProcessingSignal processingSignal,
+ @NonNull OutcomeReceiver<Content,
+ OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> callback);
+
+ /**
+ * Overrides {@link Context#openFileInput} to read files with the given file names under the
+ * internal app storage of the {@link OnDeviceIntelligenceService}, i.e., only files stored in
+ * {@link Context#getFilesDir()} can be opened.
+ */
+ @Override
+ public final FileInputStream openFileInput(@NonNull String filename) throws
+ FileNotFoundException {
+ try {
+ AndroidFuture<ParcelFileDescriptor> future = new AndroidFuture<>();
+ mRemoteStorageService.getReadOnlyFileDescriptor(filename, future);
+ ParcelFileDescriptor pfd = future.get();
+ return new FileInputStream(pfd.getFileDescriptor());
+ } catch (RemoteException | ExecutionException | InterruptedException e) {
+ Log.w(TAG, "Cannot open file due to remote service failure");
+ throw new FileNotFoundException(e.getMessage());
+ }
+ }
+
+ /**
+ * Provides read-only access to the internal app storage via the
+ * {@link OnDeviceIntelligenceService}. This is an asynchronous implementation for
+ * {@link #openFileInput(String)}.
+ *
+ * @param fileName File name relative to the {@link Context#getFilesDir()}.
+ * @param resultConsumer Consumer to populate the corresponding file stream in.
+ */
+ public final void openFileInputAsync(@NonNull String fileName,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull Consumer<FileInputStream> resultConsumer) throws FileNotFoundException {
+ AndroidFuture<ParcelFileDescriptor> future = new AndroidFuture<>();
+ try {
+ mRemoteStorageService.getReadOnlyFileDescriptor(fileName, future);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Cannot open file due to remote service failure");
+ throw new FileNotFoundException(e.getMessage());
+ }
+ future.whenCompleteAsync((pfd, err) -> {
+ if (err != null) {
+ Log.e(TAG, "Failure when reading file: " + fileName + err);
+ executor.execute(() -> resultConsumer.accept(null));
+ } else {
+ executor.execute(
+ () -> resultConsumer.accept(new FileInputStream(pfd.getFileDescriptor())));
+ }
+ }, executor);
+ }
+
+ /**
+ * Provides access to all file streams required for feature via the
+ * {@link OnDeviceIntelligenceService}.
+ *
+ * @param feature Feature for which the associated files should be fetched.
+ * @param executor Executor to run the consumer callback on.
+ * @param resultConsumer Consumer to receive a map of filePath to the corresponding file input
+ * stream.
+ */
+ public final void fetchFeatureFileInputStreamMap(@NonNull Feature feature,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull Consumer<Map<String, FileInputStream>> resultConsumer) {
+ try {
+ mRemoteStorageService.getReadOnlyFeatureFileDescriptorMap(feature,
+ wrapResultReceiverAsReadOnly(resultConsumer, executor));
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private RemoteCallback wrapResultReceiverAsReadOnly(
+ @NonNull Consumer<Map<String, FileInputStream>> resultConsumer,
+ @NonNull Executor executor) {
+ return new RemoteCallback(result -> {
+ if (result == null) {
+ executor.execute(() -> resultConsumer.accept(new HashMap<>()));
+ } else {
+ Map<String, FileInputStream> bundleMap = new HashMap<>();
+ result.keySet().forEach(key -> {
+ ParcelFileDescriptor pfd = result.getParcelable(key,
+ ParcelFileDescriptor.class);
+ if (pfd != null) {
+ bundleMap.put(key, new FileInputStream(pfd.getFileDescriptor()));
+ }
+ });
+ executor.execute(() -> resultConsumer.accept(bundleMap));
+ }
+ });
+ }
+
+ private OutcomeReceiver<Content,
+ OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> wrapResponseCallback(
+ IResponseCallback callback) {
+ return new OutcomeReceiver<>() {
+ @Override
+ public void onResult(@androidx.annotation.NonNull Content response) {
+ try {
+ callback.onSuccess(response);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending result: " + e);
+ }
+ }
+
+ @Override
+ public void onError(
+ OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException exception) {
+ try {
+ callback.onFailure(exception.getErrorCode(), exception.getMessage(),
+ exception.getErrorParams());
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending result: " + e);
+ }
+ }
+ };
+ }
+
+ private StreamingResponseReceiver<Content, Content,
+ OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> wrapStreamingResponseCallback(
+ IStreamingResponseCallback callback) {
+ return new StreamingResponseReceiver<>() {
+ @Override
+ public void onNewContent(@androidx.annotation.NonNull Content content) {
+ try {
+ callback.onNewContent(content);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending result: " + e);
+ }
+ }
+
+ @Override
+ public void onResult(@androidx.annotation.NonNull Content response) {
+ try {
+ callback.onSuccess(response);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending result: " + e);
+ }
+ }
+
+ @Override
+ public void onError(
+ OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException exception) {
+ try {
+ callback.onFailure(exception.getErrorCode(), exception.getMessage(),
+ exception.getErrorParams());
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending result: " + e);
+ }
+ }
+ };
+ }
+
+ private OutcomeReceiver<Long,
+ OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> wrapTokenCountCallback(
+ ITokenCountCallback tokenCountCallback) {
+ return new OutcomeReceiver<>() {
+ @Override
+ public void onResult(Long tokenCount) {
+ try {
+ tokenCountCallback.onSuccess(tokenCount);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending result: " + e);
+ }
+ }
+
+ @Override
+ public void onError(
+ OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException exception) {
+ try {
+ tokenCountCallback.onFailure(exception.getErrorCode(), exception.getMessage(),
+ exception.getErrorParams());
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending failure: " + e);
+ }
+ }
+ };
+ }
+}
diff --git a/core/java/android/service/voice/flags/flags.aconfig b/core/java/android/service/voice/flags/flags.aconfig
index f870db5f95f0..22e8cddbfdb8 100644
--- a/core/java/android/service/voice/flags/flags.aconfig
+++ b/core/java/android/service/voice/flags/flags.aconfig
@@ -16,7 +16,7 @@ flag {
flag {
name: "allow_foreground_activities_in_on_show"
- namespace: "voice_interaction_session"
+ namespace: "machine_learning"
description: "This flag allows providing foreground app component along with onShow args."
bug: "319409708"
}
diff --git a/core/java/android/service/wearable/IWearableSensingService.aidl b/core/java/android/service/wearable/IWearableSensingService.aidl
index 22d8fda9cb8e..dffadf0cda70 100644
--- a/core/java/android/service/wearable/IWearableSensingService.aidl
+++ b/core/java/android/service/wearable/IWearableSensingService.aidl
@@ -28,7 +28,7 @@ import android.os.SharedMemory;
* @hide
*/
oneway interface IWearableSensingService {
- void provideSecureWearableConnection(in ParcelFileDescriptor parcelFileDescriptor, in RemoteCallback callback);
+ void provideSecureConnection(in ParcelFileDescriptor parcelFileDescriptor, in RemoteCallback callback);
void provideDataStream(in ParcelFileDescriptor parcelFileDescriptor, in RemoteCallback callback);
void provideData(in PersistableBundle data, in SharedMemory sharedMemory, in RemoteCallback callback);
void registerDataRequestObserver(int dataType, in RemoteCallback dataRequestCallback, int dataRequestObserverId, in String packageName, in RemoteCallback statusCallback);
diff --git a/core/java/android/service/wearable/WearableSensingService.java b/core/java/android/service/wearable/WearableSensingService.java
index 808c3ae7b6bc..a2770172d3ca 100644
--- a/core/java/android/service/wearable/WearableSensingService.java
+++ b/core/java/android/service/wearable/WearableSensingService.java
@@ -112,11 +112,11 @@ public abstract class WearableSensingService extends Service {
return new IWearableSensingService.Stub() {
/** {@inheritDoc} */
@Override
- public void provideSecureWearableConnection(
+ public void provideSecureConnection(
ParcelFileDescriptor secureWearableConnection, RemoteCallback callback) {
Objects.requireNonNull(secureWearableConnection);
Consumer<Integer> consumer = createWearableStatusConsumer(callback);
- WearableSensingService.this.onSecureWearableConnectionProvided(
+ WearableSensingService.this.onSecureConnectionProvided(
secureWearableConnection, consumer);
}
@@ -311,12 +311,12 @@ public abstract class WearableSensingService extends Service {
/**
* Called when a secure connection to the wearable is available. See {@link
- * WearableSensingManager#provideWearableConnection(ParcelFileDescriptor, Executor, Consumer)}
+ * WearableSensingManager#provideConnection(ParcelFileDescriptor, Executor, Consumer)}
* for details about the secure connection.
*
* <p>When the {@code secureWearableConnection} is closed, the system will send a {@link
* WearableSensingManager#STATUS_CHANNEL_ERROR} status code to the status consumer provided by
- * the caller of {@link WearableSensingManager#provideWearableConnection(ParcelFileDescriptor,
+ * the caller of {@link WearableSensingManager#provideConnection(ParcelFileDescriptor,
* Executor, Consumer)}.
*
* <p>The implementing class should override this method. It should return an appropriate status
@@ -327,7 +327,7 @@ public abstract class WearableSensingService extends Service {
*/
@FlaggedApi(Flags.FLAG_ENABLE_PROVIDE_WEARABLE_CONNECTION_API)
@BinderThread
- public void onSecureWearableConnectionProvided(
+ public void onSecureConnectionProvided(
@NonNull ParcelFileDescriptor secureWearableConnection,
@NonNull Consumer<Integer> statusConsumer) {
statusConsumer.accept(WearableSensingManager.STATUS_UNSUPPORTED_OPERATION);
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index 8e52af3fb57b..8dee4b19c6d3 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -16,6 +16,7 @@
package android.text;
+import static com.android.graphics.hwui.flags.Flags.highContrastTextLuminance;
import static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE;
import static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH;
import static com.android.text.flags.Flags.FLAG_LETTER_SPACING_JUSTIFICATION;
@@ -28,7 +29,9 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.compat.annotation.UnsupportedAppUsage;
+import android.graphics.BlendMode;
import android.graphics.Canvas;
+import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
@@ -46,7 +49,9 @@ import android.text.style.ReplacementSpan;
import android.text.style.TabStopSpan;
import android.widget.TextView;
+import com.android.graphics.hwui.flags.Flags;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.graphics.ColorUtils;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.GrowingArrayUtils;
@@ -480,9 +485,23 @@ public abstract class Layout {
int lastLine = TextUtils.unpackRangeEndFromLong(lineRange);
if (lastLine < 0) return;
- drawWithoutText(canvas, highlightPaths, highlightPaints, selectionPath, selectionPaint,
- cursorOffsetVertical, firstLine, lastLine);
+ if (shouldDrawHighlightsOnTop(canvas)) {
+ drawBackground(canvas, firstLine, lastLine);
+ } else {
+ drawWithoutText(canvas, highlightPaths, highlightPaints, selectionPath, selectionPaint,
+ cursorOffsetVertical, firstLine, lastLine);
+ }
+
drawText(canvas, firstLine, lastLine);
+
+ // Since high contrast text draws a solid rectangle background behind the text, it covers up
+ // the highlights and selections. In this case we draw over the top of the text with a
+ // blend mode that ensures the text stays high-contrast.
+ if (shouldDrawHighlightsOnTop(canvas)) {
+ drawHighlights(canvas, highlightPaths, highlightPaints, selectionPath, selectionPaint,
+ cursorOffsetVertical, firstLine, lastLine);
+ }
+
if (leftShift != 0) {
// Manually translate back to the original position because of b/324498002, using
// save/restore disappears the toggle switch drawables.
@@ -490,6 +509,19 @@ public abstract class Layout {
}
}
+ private static boolean shouldDrawHighlightsOnTop(Canvas canvas) {
+ return Flags.highContrastTextSmallTextRect() && canvas.isHighContrastTextEnabled();
+ }
+
+ private static Paint setToHighlightPaint(Paint p, BlendMode blendMode, Paint outPaint) {
+ if (p == null) return null;
+ outPaint.set(p);
+ outPaint.setBlendMode(blendMode);
+ // Yellow for maximum contrast
+ outPaint.setColor(Color.YELLOW);
+ return outPaint;
+ }
+
/**
* Draw text part of this layout.
*
@@ -542,11 +574,28 @@ public abstract class Layout {
int firstLine,
int lastLine) {
drawBackground(canvas, firstLine, lastLine);
+ drawHighlights(canvas, highlightPaths, highlightPaints, selectionPath, selectionPaint,
+ cursorOffsetVertical, firstLine, lastLine);
+ }
+
+ /**
+ * @hide public for Editor.java
+ */
+ public void drawHighlights(
+ @NonNull Canvas canvas,
+ @Nullable List<Path> highlightPaths,
+ @Nullable List<Paint> highlightPaints,
+ @Nullable Path selectionPath,
+ @Nullable Paint selectionPaint,
+ int cursorOffsetVertical,
+ int firstLine,
+ int lastLine) {
if (highlightPaths == null && highlightPaints == null) {
return;
}
if (cursorOffsetVertical != 0) canvas.translate(0, cursorOffsetVertical);
try {
+ BlendMode blendMode = determineHighContrastHighlightBlendMode(canvas);
if (highlightPaths != null) {
if (highlightPaints == null) {
throw new IllegalArgumentException(
@@ -559,7 +608,12 @@ public abstract class Layout {
}
for (int i = 0; i < highlightPaths.size(); ++i) {
final Path highlight = highlightPaths.get(i);
- final Paint highlightPaint = highlightPaints.get(i);
+ Paint highlightPaint = highlightPaints.get(i);
+ if (shouldDrawHighlightsOnTop(canvas)) {
+ highlightPaint = setToHighlightPaint(highlightPaint, blendMode,
+ mWorkPlainPaint);
+ }
+
if (highlight != null) {
canvas.drawPath(highlight, highlightPaint);
}
@@ -567,6 +621,10 @@ public abstract class Layout {
}
if (selectionPath != null) {
+ if (shouldDrawHighlightsOnTop(canvas)) {
+ selectionPaint = setToHighlightPaint(selectionPaint, blendMode,
+ mWorkPlainPaint);
+ }
canvas.drawPath(selectionPath, selectionPaint);
}
} finally {
@@ -574,6 +632,31 @@ public abstract class Layout {
}
}
+ @Nullable
+ private BlendMode determineHighContrastHighlightBlendMode(Canvas canvas) {
+ if (!shouldDrawHighlightsOnTop(canvas)) {
+ return null;
+ }
+
+ return isHighContrastTextDark() ? BlendMode.MULTIPLY : BlendMode.DIFFERENCE;
+ }
+
+ private boolean isHighContrastTextDark() {
+ // High-contrast text mode
+ // Determine if the text is black-on-white or white-on-black, so we know what blendmode will
+ // give the highest contrast and most realistic text color.
+ // This equation should match the one in libs/hwui/hwui/DrawTextFunctor.h
+ if (highContrastTextLuminance()) {
+ var lab = new double[3];
+ ColorUtils.colorToLAB(mPaint.getColor(), lab);
+ return lab[0] < 0.5;
+ } else {
+ var color = mPaint.getColor();
+ int channelSum = Color.red(color) + Color.green(color) + Color.blue(color);
+ return channelSum < (128 * 3);
+ }
+ }
+
private boolean isJustificationRequired(int lineNum) {
if (mJustificationMode == JUSTIFICATION_MODE_NONE) return false;
final int lineEnd = getLineEnd(lineNum);
@@ -3396,7 +3479,8 @@ public abstract class Layout {
private CharSequence mText;
@UnsupportedAppUsage
private TextPaint mPaint;
- private TextPaint mWorkPaint = new TextPaint();
+ private final TextPaint mWorkPaint = new TextPaint();
+ private final Paint mWorkPlainPaint = new Paint();
private int mWidth;
private Alignment mAlignment = Alignment.ALIGN_NORMAL;
private float mSpacingMult;
diff --git a/core/java/android/tracing/flags.aconfig b/core/java/android/tracing/flags.aconfig
index c6e8844bc47a..b1bca96c5c09 100644
--- a/core/java/android/tracing/flags.aconfig
+++ b/core/java/android/tracing/flags.aconfig
@@ -12,4 +12,5 @@ flag {
namespace: "windowing_tools"
description: "Migrate protolog to Perfetto"
bug: "276432490"
+ is_fixed_read_only: true
}
diff --git a/core/java/android/util/TimingsTraceLog.java b/core/java/android/util/TimingsTraceLog.java
index 48a5ceae1aef..b4f4729c82b2 100644
--- a/core/java/android/util/TimingsTraceLog.java
+++ b/core/java/android/util/TimingsTraceLog.java
@@ -34,6 +34,7 @@ import java.util.List;
*
* @hide
*/
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public class TimingsTraceLog {
// Debug boot time for every step if it's non-user build.
private static final boolean DEBUG_BOOT_TIME = !Build.IS_USER;
diff --git a/core/java/android/view/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java
index 676b903472c5..00657ea35e02 100644
--- a/core/java/android/view/HandwritingInitiator.java
+++ b/core/java/android/view/HandwritingInitiator.java
@@ -16,6 +16,7 @@
package android.view;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
@@ -38,6 +39,7 @@ import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
+import java.util.function.Consumer;
/**
* Initiates handwriting mode once it detects stylus movement in handwritable areas.
@@ -415,27 +417,55 @@ public class HandwritingInitiator {
*/
@VisibleForTesting
public boolean tryAcceptStylusHandwritingDelegation(@NonNull View view) {
+ if (Flags.useZeroJankProxy()) {
+ tryAcceptStylusHandwritingDelegationAsync(view);
+ } else {
+ return tryAcceptStylusHandwritingDelegationInternal(view);
+ }
+ return false;
+ }
+
+ private boolean tryAcceptStylusHandwritingDelegationInternal(@NonNull View view) {
String delegatorPackageName =
view.getAllowedHandwritingDelegatorPackageName();
if (delegatorPackageName == null) {
delegatorPackageName = view.getContext().getOpPackageName();
}
if (mImm.acceptStylusHandwritingDelegation(view, delegatorPackageName)) {
- if (mState != null) {
- mState.mHasInitiatedHandwriting = true;
- mState.mShouldInitHandwriting = false;
- }
- if (view instanceof TextView) {
- ((TextView) view).hideHint();
- }
- // A handwriting delegate view is accepted and handwriting starts; hide the
- // hover icon.
- mShowHoverIconForConnectedView = false;
+ onDelegationAccepted(view);
return true;
}
return false;
}
+ @FlaggedApi(Flags.FLAG_USE_ZERO_JANK_PROXY)
+ private void tryAcceptStylusHandwritingDelegationAsync(@NonNull View view) {
+ String delegatorPackageName =
+ view.getAllowedHandwritingDelegatorPackageName();
+ if (delegatorPackageName == null) {
+ delegatorPackageName = view.getContext().getOpPackageName();
+ }
+ Consumer<Boolean> consumer = delegationAccepted -> {
+ if (delegationAccepted) {
+ onDelegationAccepted(view);
+ }
+ };
+ mImm.acceptStylusHandwritingDelegation(view, delegatorPackageName, view::post, consumer);
+ }
+
+ private void onDelegationAccepted(View view) {
+ if (mState != null) {
+ mState.mHasInitiatedHandwriting = true;
+ mState.mShouldInitHandwriting = false;
+ }
+ if (view instanceof TextView) {
+ ((TextView) view).hideHint();
+ }
+ // A handwriting delegate view is accepted and handwriting starts; hide the
+ // hover icon.
+ mShowHoverIconForConnectedView = false;
+ }
+
/**
* Notify that the handwriting area for the given view might be updated.
* @param view the view whose handwriting area might be updated.
diff --git a/core/java/android/view/ISensitiveContentProtectionManager.aidl b/core/java/android/view/ISensitiveContentProtectionManager.aidl
new file mode 100644
index 000000000000..c135ae4b6a95
--- /dev/null
+++ b/core/java/android/view/ISensitiveContentProtectionManager.aidl
@@ -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 android.view;
+
+import android.os.IBinder;
+
+/**
+ * @hide
+ */
+oneway interface ISensitiveContentProtectionManager {
+ /**
+ * Block projection for a package's window when the window is showing sensitive content on
+ * the screen, the projection is unblocked when the window no more shows sensitive content.
+ *
+ * @param windowToken window where the content is shown.
+ * @param packageName package name.
+ * @param isShowingSensitiveContent whether the window is showing sensitive content.
+ */
+ void setSensitiveContentProtection(in IBinder windowToken, in String packageName,
+ in boolean isShowingSensitiveContent);
+}
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index c475f6babbf1..51d7caacd4b7 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -69,11 +69,11 @@ import android.view.SurfaceControl;
import android.view.displayhash.DisplayHash;
import android.view.displayhash.VerifiedDisplayHash;
import android.window.AddToSurfaceSyncGroupResult;
+import android.window.IGlobalDragListener;
import android.window.IScreenRecordingCallback;
import android.window.ISurfaceSyncGroupCompletedListener;
import android.window.ITaskFpsCallback;
import android.window.ITrustedPresentationListener;
-import android.window.IUnhandledDragListener;
import android.window.InputTransferToken;
import android.window.ScreenCapture;
import android.window.TrustedPresentationThresholds;
@@ -1094,10 +1094,9 @@ interface IWindowManager
void unregisterScreenRecordingCallback(IScreenRecordingCallback callback);
/**
- * Sets the listener to be called back when a cross-window drag and drop operation is unhandled
- * (ie. not handled by any window which can handle the drag).
+ * Sets the listener to be called back when a cross-window drag and drop operation happens.
*/
- void setUnhandledDragListener(IUnhandledDragListener listener);
+ void setGlobalDragListener(IGlobalDragListener listener);
boolean transferTouchGesture(in InputTransferToken transferFromToken,
in InputTransferToken transferToToken);
diff --git a/core/java/android/view/KeyboardShortcutGroup.java b/core/java/android/view/KeyboardShortcutGroup.java
index 763ca2621e93..c4c87ef595be 100644
--- a/core/java/android/view/KeyboardShortcutGroup.java
+++ b/core/java/android/view/KeyboardShortcutGroup.java
@@ -35,6 +35,8 @@ public final class KeyboardShortcutGroup implements Parcelable {
private final List<KeyboardShortcutInfo> mItems;
// The system group looks different UI wise.
private boolean mSystemGroup;
+ // The package name for the shortcut
+ private CharSequence mPackageName;
/**
* @param label The title to be used for this group, or null if there is none.
@@ -82,6 +84,7 @@ public final class KeyboardShortcutGroup implements Parcelable {
mLabel = source.readCharSequence();
source.readTypedList(mItems, KeyboardShortcutInfo.CREATOR);
mSystemGroup = source.readInt() == 1;
+ mPackageName = source.readCharSequence();
}
/**
@@ -105,6 +108,22 @@ public final class KeyboardShortcutGroup implements Parcelable {
}
/**
+ * @param packageName the name of the package associated with this shortcut.
+ * @hide
+ */
+ public void setPackageName(CharSequence packageName) {
+ mPackageName = packageName;
+ }
+
+ /**
+ * Return the package name of the app associated with this shortcut.
+ * @hide
+ */
+ public CharSequence getPackageName() {
+ return mPackageName;
+ }
+
+ /**
* Adds an item to the existing list.
*
* @param item The item to be added.
@@ -123,6 +142,7 @@ public final class KeyboardShortcutGroup implements Parcelable {
dest.writeCharSequence(mLabel);
dest.writeTypedList(mItems);
dest.writeInt(mSystemGroup ? 1 : 0);
+ dest.writeCharSequence(mPackageName);
}
public static final @android.annotation.NonNull Creator<KeyboardShortcutGroup> CREATOR =
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 8cbfdcb12846..028448ccf871 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -78,6 +78,7 @@ import android.content.ClipDescription;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
+import android.content.IntentSender;
import android.content.res.ColorStateList;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
@@ -1046,7 +1047,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
@Nullable
private ViewCredentialHandler mViewCredentialHandler;
-
/** Used to avoid computing the full strings each time when layout tracing is enabled. */
@Nullable
private ViewTraversalTracingStrings mTracingStrings;
@@ -5357,16 +5357,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
/**
* Flag indicating that an unhandled drag should be delegated to the system to be started if no
* visible window wishes to handle the drop. When using this flag, the caller must provide
- * ClipData with an Item that contains an immutable PendingIntent to an activity to be launched
+ * ClipData with an Item that contains an immutable IntentSender to an activity to be launched
* (not a broadcast, service, etc). See
- * {@link ClipData.Item.Builder#setPendingIntent(PendingIntent)}.
+ * {@link ClipData.Item.Builder#setIntentSender(IntentSender)}.
*
* The system can decide to launch the intent or not based on factors like the current screen
* size or windowing mode. If the system does not launch the intent, it will be canceled via the
* normal drag and drop flow.
*/
@FlaggedApi(FLAG_DELEGATE_UNHANDLED_DRAGS)
- public static final int DRAG_FLAG_START_PENDING_INTENT_ON_UNHANDLED_DRAG = 1 << 13;
+ public static final int DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG = 1 << 13;
/**
* Vertical scroll factor cached by {@link #getVerticalScrollFactor}.
@@ -7033,7 +7033,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
@NonNull OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback) {
Preconditions.checkNotNull(request, "request must not be null");
Preconditions.checkNotNull(callback, "request must not be null");
-
mViewCredentialHandler = new ViewCredentialHandler(request, callback);
}
@@ -9914,6 +9913,17 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
+ * @hide
+ */
+ public void onGetCredentialResponse(GetCredentialResponse response) {
+ if (getCredentialManagerCallback() == null) {
+ Log.w(AUTOFILL_LOG_TAG, "onGetCredentialResponse called but no callback found");
+ return;
+ }
+ getCredentialManagerCallback().onResult(response);
+ }
+
+ /**
* Gets the unique, logical identifier of this view in the activity, for autofill purposes.
*
* <p>The autofill id is created on demand, unless it is explicitly set by
@@ -28665,10 +28675,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
if (com.android.window.flags.Flags.delegateUnhandledDrags()) {
data.prepareToLeaveProcess(
(flags & (DRAG_FLAG_GLOBAL_SAME_APPLICATION | DRAG_FLAG_GLOBAL)) != 0);
- if ((flags & DRAG_FLAG_START_PENDING_INTENT_ON_UNHANDLED_DRAG) != 0) {
- if (!data.hasActivityPendingIntents()) {
+ if ((flags & DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG) != 0) {
+ if (!hasActivityPendingIntents(data)) {
// Reset the flag if there is no launchable activity intent
- flags &= ~DRAG_FLAG_START_PENDING_INTENT_ON_UNHANDLED_DRAG;
+ flags &= ~DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG;
Log.w(VIEW_LOG_TAG, "startDragAndDrop called with "
+ "DRAG_FLAG_START_INTENT_ON_UNHANDLED_DRAG but the clip data "
+ "contains non-activity PendingIntents");
@@ -28781,7 +28791,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
mAttachInfo.mDragSurface.release();
}
if (mAttachInfo.mDragData != null) {
- mAttachInfo.mDragData.cleanUpPendingIntents();
+ View.cleanUpPendingIntents(mAttachInfo.mDragData);
}
mAttachInfo.mDragSurface = surface;
mAttachInfo.mDragToken = token;
@@ -28806,6 +28816,39 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
}
+ /**
+ * Checks if this clip data has a pending intent that is an activity type.
+ * @hide
+ */
+ static boolean hasActivityPendingIntents(ClipData data) {
+ final int size = data.getItemCount();
+ for (int i = 0; i < size; i++) {
+ final ClipData.Item item = data.getItemAt(i);
+ if (item.getIntentSender() != null) {
+ final PendingIntent pi = new PendingIntent(item.getIntentSender().getTarget());
+ if (pi.isActivity()) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Cleans up all pending intents in the ClipData.
+ * @hide
+ */
+ static void cleanUpPendingIntents(ClipData data) {
+ final int size = data.getItemCount();
+ for (int i = 0; i < size; i++) {
+ final ClipData.Item item = data.getItemAt(i);
+ if (item.getIntentSender() != null) {
+ final PendingIntent pi = new PendingIntent(item.getIntentSender().getTarget());
+ pi.cancel();
+ }
+ }
+ }
+
void setAccessibilityDragStarted(boolean started) {
int pflags4 = mPrivateFlags4;
if (started) {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 75deceb5826f..1e79786d7554 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -8626,7 +8626,7 @@ public final class ViewRootImpl implements ViewParent,
mAttachInfo.mDragSurface = null;
}
if (mAttachInfo.mDragData != null) {
- mAttachInfo.mDragData.cleanUpPendingIntents();
+ View.cleanUpPendingIntents(mAttachInfo.mDragData);
mAttachInfo.mDragData = null;
}
}
@@ -8652,6 +8652,12 @@ public final class ViewRootImpl implements ViewParent,
if (mView != null) {
mView.requestKeyboardShortcuts(list, deviceId);
}
+ int numGroups = list.size();
+ for (int i = 0; i < numGroups; ++i) {
+ final KeyboardShortcutGroup group = list.get(i);
+ group.setPackageName(mBasePackageName);
+
+ }
data.putParcelableArrayList(WindowManager.PARCEL_KEY_SHORTCUTS_ARRAY, list);
try {
receiver.send(0, data);
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index bd9f5049321d..364c94f7f92f 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -50,6 +50,7 @@ import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.credentials.GetCredentialResponse;
import android.graphics.Rect;
import android.metrics.LogMaker;
import android.os.Build;
@@ -2364,6 +2365,7 @@ public final class AutofillManager {
synchronized (mLock) {
if (!isActiveLocked()) {
+ Log.w(TAG, "onAuthenticationResult(): sessionId=" + mSessionId + " not active");
return;
}
mState = STATE_ACTIVE;
@@ -2380,6 +2382,7 @@ public final class AutofillManager {
}
if (data == null) {
// data is set to null when result is not RESULT_OK
+ Log.i(TAG, "onAuthenticationResult(): empty intent");
return;
}
@@ -2923,6 +2926,65 @@ public final class AutofillManager {
}
}
+ private void onGetCredentialResponse(int sessionId, AutofillId id,
+ GetCredentialResponse response) {
+ synchronized (mLock) {
+ if (sessionId != mSessionId) {
+ Log.w(TAG, "onGetCredentialResponse afm sessionIds don't match");
+ return;
+ }
+
+ final AutofillClient client = getClient();
+ if (client == null) {
+ Log.w(TAG, "onGetCredentialResponse afm client id null");
+ return;
+ }
+ ArrayList<AutofillId> failedIds = new ArrayList<>();
+ final View[] views = client.autofillClientFindViewsByAutofillIdTraversal(
+ Helper.toArray(new ArrayList<>(Collections.singleton(id))));
+ if (views == null || views.length == 0) {
+ Log.w(TAG, "onGetCredentialResponse afm client view not found");
+ return;
+ }
+
+ final View view = views[0];
+ if (view == null) {
+ Log.i(TAG, "onGetCredentialResponse View is null");
+
+ // Most likely view has been removed after the initial request was sent to the
+ // the service; this is fine, but we need to update the view status in the
+ // server side so it can be triggered again.
+ Log.d(TAG, "onGetCredentialResponse(): no View with id " + id);
+ failedIds.add(id);
+ }
+ if (id.isVirtualInt()) {
+ Log.i(TAG, "onGetCredentialResponse afm client id is virtual");
+ // TODO(b/326314286): Handle virtual views
+ } else {
+ Log.i(TAG, "onGetCredentialResponse afm client id is NOT virtual");
+ view.onGetCredentialResponse(response);
+ }
+ handleFailedIdsLocked(failedIds);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void handleFailedIdsLocked(ArrayList<AutofillId> failedIds) {
+ if (failedIds != null && !failedIds.isEmpty()) {
+ if (sVerbose) {
+ Log.v(TAG, "autofill(): total failed views: " + failedIds);
+ }
+ try {
+ mService.setAutofillFailure(mSessionId, failedIds, mContext.getUserId());
+ } catch (RemoteException e) {
+ // In theory, we could ignore this error since it's not a big deal, but
+ // in reality, we rather crash the app anyways, as the failure could be
+ // a consequence of something going wrong on the server side...
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
private void autofill(int sessionId, List<AutofillId> ids, List<AutofillValue> values,
boolean hideHighlight) {
synchronized (mLock) {
@@ -2989,19 +3051,7 @@ public final class AutofillManager {
}
}
- if (failedIds != null) {
- if (sVerbose) {
- Log.v(TAG, "autofill(): total failed views: " + failedIds);
- }
- try {
- mService.setAutofillFailure(mSessionId, failedIds, mContext.getUserId());
- } catch (RemoteException e) {
- // In theory, we could ignore this error since it's not a big deal, but
- // in reality, we rather crash the app anyways, as the failure could be
- // a consequence of something going wrong on the server side...
- throw e.rethrowFromSystemServer();
- }
- }
+ handleFailedIdsLocked(failedIds);
if (virtualValues != null) {
for (int i = 0; i < virtualValues.size(); i++) {
@@ -3429,6 +3479,10 @@ public final class AutofillManager {
if (view == null) {
return false;
}
+ if (view.getViewCredentialHandler() != null) {
+ return true;
+ }
+
String[] hints = view.getAutofillHints();
if (hints == null) {
return false;
@@ -4319,6 +4373,15 @@ public final class AutofillManager {
}
@Override
+ public void onGetCredentialResponse(int sessionId, AutofillId id,
+ GetCredentialResponse response) {
+ final AutofillManager afm = mAfm.get();
+ if (afm != null) {
+ afm.post(() -> afm.onGetCredentialResponse(sessionId, id, response));
+ }
+ }
+
+ @Override
public void autofillContent(int sessionId, AutofillId id, ClipData content) {
final AutofillManager afm = mAfm.get();
if (afm != null) {
diff --git a/core/java/android/view/autofill/IAutoFillManagerClient.aidl b/core/java/android/view/autofill/IAutoFillManagerClient.aidl
index 917a974f992d..e838027a9ae1 100644
--- a/core/java/android/view/autofill/IAutoFillManagerClient.aidl
+++ b/core/java/android/view/autofill/IAutoFillManagerClient.aidl
@@ -22,6 +22,7 @@ import android.content.ClipData;
import android.content.ComponentName;
import android.content.Intent;
import android.content.IntentSender;
+import android.credentials.GetCredentialResponse;
import android.graphics.Rect;
import android.os.IBinder;
import android.view.autofill.AutofillId;
@@ -48,6 +49,9 @@ oneway interface IAutoFillManagerClient {
void autofill(int sessionId, in List<AutofillId> ids, in List<AutofillValue> values,
boolean hideHighlight);
+ void onGetCredentialResponse(int sessionId, in AutofillId id,
+ in GetCredentialResponse response);
+
/**
* Autofills the activity with rich content data (e.g. an image) from a dataset.
*/
diff --git a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
index dc5e0e5c62aa..491b0e349cde 100644
--- a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
+++ b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
@@ -34,6 +34,7 @@ import android.view.WindowManager;
import android.window.ImeOnBackInvokedDispatcher;
import com.android.internal.inputmethod.DirectBootAwareness;
+import com.android.internal.inputmethod.IBooleanListener;
import com.android.internal.inputmethod.IConnectionlessHandwritingCallback;
import com.android.internal.inputmethod.IImeTracker;
import com.android.internal.inputmethod.IInputMethodClient;
@@ -66,6 +67,7 @@ final class IInputMethodManagerGlobalInvoker {
@Nullable
private static volatile IImeTracker sTrackerServiceCache = null;
+ private static int sCurStartInputSeq = 0;
/**
* @return {@code true} if {@link IInputMethodManager} is available.
@@ -327,6 +329,7 @@ final class IInputMethodManagerGlobalInvoker {
}
}
+ // TODO(b/293640003): Remove method once Flags.useZeroJankProxy() is enabled.
@AnyThread
@NonNull
@RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
@@ -353,6 +356,41 @@ final class IInputMethodManagerGlobalInvoker {
}
}
+ /**
+ * Returns a sequence number for startInput.
+ */
+ @AnyThread
+ @NonNull
+ @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
+ static int startInputOrWindowGainedFocusAsync(@StartInputReason int startInputReason,
+ @NonNull IInputMethodClient client, @Nullable IBinder windowToken,
+ @StartInputFlags int startInputFlags,
+ @WindowManager.LayoutParams.SoftInputModeFlags int softInputMode,
+ @WindowManager.LayoutParams.Flags int windowFlags, @Nullable EditorInfo editorInfo,
+ @Nullable IRemoteInputConnection remoteInputConnection,
+ @Nullable IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
+ int unverifiedTargetSdkVersion, @UserIdInt int userId,
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
+ final IInputMethodManager service = getService();
+ if (service == null) {
+ return -1;
+ }
+ try {
+ service.startInputOrWindowGainedFocusAsync(startInputReason, client, windowToken,
+ startInputFlags, softInputMode, windowFlags, editorInfo, remoteInputConnection,
+ remoteAccessibilityInputConnection, unverifiedTargetSdkVersion, userId,
+ imeDispatcher, advanceAngGetStartInputSequenceNumber());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ return sCurStartInputSeq;
+ }
+
+ private static int advanceAngGetStartInputSequenceNumber() {
+ return ++sCurStartInputSeq;
+ }
+
+
@AnyThread
static void showInputMethodPickerFromClient(@NonNull IInputMethodClient client,
int auxiliarySubtypeMode) {
@@ -550,6 +588,28 @@ final class IInputMethodManagerGlobalInvoker {
}
}
+ /** Returns {@code true} if method is invoked */
+ @AnyThread
+ static boolean acceptStylusHandwritingDelegationAsync(
+ @NonNull IInputMethodClient client,
+ @UserIdInt int userId,
+ @NonNull String delegatePackageName,
+ @NonNull String delegatorPackageName,
+ @InputMethodManager.HandwritingDelegateFlags int flags,
+ @NonNull IBooleanListener callback) {
+ final IInputMethodManager service = getService();
+ if (service == null) {
+ return false;
+ }
+ try {
+ service.acceptStylusHandwritingDelegationAsync(
+ client, userId, delegatePackageName, delegatorPackageName, flags, callback);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ return true;
+ }
+
@AnyThread
@RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
static boolean isStylusHandwritingAvailableAsUser(
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index f4b09df35705..52384791f74b 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -109,6 +109,7 @@ import android.window.WindowOnBackInvokedDispatcher;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.inputmethod.DirectBootAwareness;
+import com.android.internal.inputmethod.IBooleanListener;
import com.android.internal.inputmethod.IConnectionlessHandwritingCallback;
import com.android.internal.inputmethod.IInputMethodClient;
import com.android.internal.inputmethod.IInputMethodSession;
@@ -321,6 +322,22 @@ public final class InputMethodManager {
};
/**
+ * A runnable that reports {@link InputConnection} opened event for calls to
+ * {@link IInputMethodManagerGlobalInvoker#startInputOrWindowGainedFocusAsync}.
+ */
+ private abstract static class ReportInputConnectionOpenedRunner implements Runnable {
+ /**
+ * Sequence number to track startInput requests to
+ * {@link IInputMethodManagerGlobalInvoker#startInputOrWindowGainedFocusAsync}
+ */
+ int mSequenceNum;
+ ReportInputConnectionOpenedRunner(int sequenceNum) {
+ this.mSequenceNum = sequenceNum;
+ }
+ }
+ private ReportInputConnectionOpenedRunner mReportInputConnectionOpenedRunner;
+
+ /**
* Ensures that {@link #sInstance} becomes non-{@code null} for application that have directly
* or indirectly relied on {@link #sInstance} via reflection or something like that.
*
@@ -691,6 +708,7 @@ public final class InputMethodManager {
private static final int MSG_UNBIND_ACCESSIBILITY_SERVICE = 12;
private static final int MSG_SET_INTERACTIVE = 13;
private static final int MSG_ON_SHOW_REQUESTED = 31;
+ private static final int MSG_START_INPUT_RESULT = 40;
/**
* Calling this will invalidate Local stylus handwriting availability Cache which
@@ -1045,7 +1063,7 @@ public final class InputMethodManager {
return;
}
case MSG_BIND: {
- final InputBindResult res = (InputBindResult)msg.obj;
+ final InputBindResult res = (InputBindResult) msg.obj;
if (DEBUG) {
Log.i(TAG, "handleMessage: MSG_BIND " + res.sequence + "," + res.id);
}
@@ -1071,6 +1089,60 @@ public final class InputMethodManager {
startInputInner(StartInputReason.BOUND_TO_IMMS, null, 0, 0, 0);
return;
}
+
+ case MSG_START_INPUT_RESULT: {
+ final InputBindResult res = (InputBindResult) msg.obj;
+ final int startInputSeq = msg.arg1;
+ if (res == null) {
+ // IMMS logs .wtf already.
+ return;
+ }
+ if (DEBUG) Log.v(TAG, "Starting input: Bind result=" + res);
+ synchronized (mH) {
+ if (res.id != null) {
+ updateInputChannelLocked(res.channel);
+ mCurMethod = res.method; // for @UnsupportedAppUsage
+ mCurBindState = new BindState(res);
+ mAccessibilityInputMethodSession.clear();
+ if (res.accessibilitySessions != null) {
+ for (int i = 0; i < res.accessibilitySessions.size(); i++) {
+ IAccessibilityInputMethodSessionInvoker wrapper =
+ IAccessibilityInputMethodSessionInvoker.createOrNull(
+ res.accessibilitySessions.valueAt(i));
+ if (wrapper != null) {
+ mAccessibilityInputMethodSession.append(
+ res.accessibilitySessions.keyAt(i), wrapper);
+ }
+ }
+ }
+ mCurId = res.id; // for @UnsupportedAppUsage
+ } else if (res.channel != null && res.channel != mCurChannel) {
+ res.channel.dispose();
+ }
+ switch (res.result) {
+ case InputBindResult.ResultCode.ERROR_NOT_IME_TARGET_WINDOW:
+ mRestartOnNextWindowFocus = true;
+ mServedView = null;
+ break;
+ }
+ if (mCompletions != null) {
+ if (isImeSessionAvailableLocked()) {
+ mCurBindState.mImeSession.displayCompletions(mCompletions);
+ }
+ }
+
+ if (res != null
+ && res.method != null
+ && mServedView != null
+ && mReportInputConnectionOpenedRunner != null
+ && mReportInputConnectionOpenedRunner.mSequenceNum
+ == startInputSeq) {
+ mReportInputConnectionOpenedRunner.run();
+ }
+ mReportInputConnectionOpenedRunner = null;
+ }
+ return;
+ }
case MSG_UNBIND: {
final int sequence = msg.arg1;
@UnbindReason
@@ -1322,6 +1394,12 @@ public final class InputMethodManager {
}
@Override
+ public void onStartInputResult(InputBindResult res, int startInputSeq) {
+ mH.obtainMessage(MSG_START_INPUT_RESULT, startInputSeq, -1 /* unused */, res)
+ .sendToTarget();
+ }
+
+ @Override
public void onBindAccessibilityService(InputBindResult res, int id) {
mH.obtainMessage(MSG_BIND_ACCESSIBILITY_SERVICE, id, 0, res).sendToTarget();
}
@@ -2010,6 +2088,7 @@ public final class InputMethodManager {
mServedConnecting = false;
clearConnectionLocked();
}
+ mReportInputConnectionOpenedRunner = null;
// Clear the back callbacks held by the ime dispatcher to avoid memory leaks.
mImeDispatcher.clear();
}
@@ -2440,16 +2519,46 @@ public final class InputMethodManager {
view, /* delegatorPackageName= */ null, /* handwritingDelegateFlags= */ 0);
}
+ private void startStylusHandwritingInternalAsync(
+ @NonNull View view, @Nullable String delegatorPackageName,
+ @HandwritingDelegateFlags int handwritingDelegateFlags,
+ @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<Boolean> callback) {
+ Objects.requireNonNull(view);
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+
+ startStylusHandwritingInternal(
+ view, delegatorPackageName, handwritingDelegateFlags, executor, callback);
+ }
+
+ private void sendFailureCallback(@NonNull @CallbackExecutor Executor executor,
+ @NonNull Consumer<Boolean> callback) {
+ if (executor == null || callback == null) {
+ return;
+ }
+ executor.execute(() -> callback.accept(false));
+ }
+
private boolean startStylusHandwritingInternal(
@NonNull View view, @Nullable String delegatorPackageName,
@HandwritingDelegateFlags int handwritingDelegateFlags) {
+ return startStylusHandwritingInternal(
+ view, delegatorPackageName, handwritingDelegateFlags,
+ null /* executor */, null /* callback */);
+ }
+
+ private boolean startStylusHandwritingInternal(
+ @NonNull View view, @Nullable String delegatorPackageName,
+ @HandwritingDelegateFlags int handwritingDelegateFlags, Executor executor,
+ Consumer<Boolean> callback) {
Objects.requireNonNull(view);
+ boolean useCallback = callback != null;
// Re-dispatch if there is a context mismatch.
final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view);
if (fallbackImm != null) {
fallbackImm.startStylusHandwritingInternal(
- view, delegatorPackageName, handwritingDelegateFlags);
+ view, delegatorPackageName, handwritingDelegateFlags, executor, callback);
}
boolean useDelegation = !TextUtils.isEmpty(delegatorPackageName);
@@ -2459,21 +2568,40 @@ public final class InputMethodManager {
if (!hasServedByInputMethodLocked(view)) {
Log.w(TAG,
"Ignoring startStylusHandwriting as view=" + view + " is not served.");
+ sendFailureCallback(executor, callback);
return false;
}
if (view.getViewRootImpl() != mCurRootView) {
Log.w(TAG,
"Ignoring startStylusHandwriting: View's window does not have focus.");
+ sendFailureCallback(executor, callback);
return false;
}
if (useDelegation) {
- return IInputMethodManagerGlobalInvoker.acceptStylusHandwritingDelegation(
- mClient, UserHandle.myUserId(), view.getContext().getOpPackageName(),
- delegatorPackageName, handwritingDelegateFlags);
+ if (useCallback) {
+ IBooleanListener listener = new IBooleanListener.Stub() {
+ @Override
+ public void onResult(boolean value) {
+ executor.execute(() -> {
+ callback.accept(value);
+ });
+ }
+ };
+ if (!IInputMethodManagerGlobalInvoker.acceptStylusHandwritingDelegationAsync(
+ mClient, UserHandle.myUserId(), view.getContext().getOpPackageName(),
+ delegatorPackageName, handwritingDelegateFlags, listener)) {
+ sendFailureCallback(executor, callback);
+ }
+ return true;
+ } else {
+ return IInputMethodManagerGlobalInvoker.acceptStylusHandwritingDelegation(
+ mClient, UserHandle.myUserId(), view.getContext().getOpPackageName(),
+ delegatorPackageName, handwritingDelegateFlags);
+ }
} else {
IInputMethodManagerGlobalInvoker.startStylusHandwriting(mClient);
+ return false;
}
- return false;
}
}
@@ -2710,6 +2838,7 @@ public final class InputMethodManager {
* #prepareStylusHandwritingDelegation(View, String)} and delegation is accepted
* @see #prepareStylusHandwritingDelegation(View, String)
* @see #acceptStylusHandwritingDelegation(View)
+ * TODO (b/293640003): deprecate this method once flag is enabled.
*/
// TODO(b/300979854): Once connectionless APIs are finalised, update documentation to add:
// <p>Otherwise, if the delegator view previously started delegation using {@link
@@ -2727,6 +2856,36 @@ public final class InputMethodManager {
/**
* Accepts and starts a stylus handwriting session on the delegate view, if handwriting
+ * initiation delegation was previously requested using
+ * {@link #prepareStylusHandwritingDelegation(View, String)} from the delegator and the view
+ * belongs to a specified delegate package.
+ *
+ * @param delegateView delegate view capable of receiving input via {@link InputConnection}
+ * on which {@link #startStylusHandwriting(View)} will be called.
+ * @param delegatorPackageName package name of the delegator that handled initial stylus stroke.
+ * @param executor The executor to run the callback on.
+ * @param callback Consumer callback that provides {@code true} if view belongs to allowed
+ * delegate package declared in
+ * {@link #prepareStylusHandwritingDelegation(View, String)} and handwriting
+ * session can start.
+ * @see #prepareStylusHandwritingDelegation(View, String)
+ * @see #acceptStylusHandwritingDelegation(View)
+ */
+ @FlaggedApi(Flags.FLAG_USE_ZERO_JANK_PROXY)
+ public void acceptStylusHandwritingDelegation(
+ @NonNull View delegateView, @NonNull String delegatorPackageName,
+ @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<Boolean> callback) {
+ Objects.requireNonNull(delegatorPackageName);
+ int flags = 0;
+ if (Flags.homeScreenHandwritingDelegator()) {
+ flags = delegateView.getHandwritingDelegateFlags();
+ }
+ startStylusHandwritingInternalAsync(
+ delegateView, delegatorPackageName, flags, executor, callback);
+ }
+
+ /**
+ * Accepts and starts a stylus handwriting session on the delegate view, if handwriting
* initiation delegation was previously requested using {@link
* #prepareStylusHandwritingDelegation(View, String)} from the delegator and the view belongs to
* a specified delegate package.
@@ -3080,14 +3239,52 @@ public final class InputMethodManager {
final int targetUserId = editorInfo.targetInputMethodUser != null
? editorInfo.targetInputMethodUser.getIdentifier() : UserHandle.myUserId();
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMM.startInputOrWindowGainedFocus");
- res = IInputMethodManagerGlobalInvoker.startInputOrWindowGainedFocus(
- startInputReason, mClient, windowGainingFocus, startInputFlags,
- softInputMode, windowFlags, editorInfo, servedInputConnection,
- servedInputConnection == null ? null
- : servedInputConnection.asIRemoteAccessibilityInputConnection(),
- view.getContext().getApplicationInfo().targetSdkVersion, targetUserId,
- mImeDispatcher);
+
+ int startInputSeq = -1;
+ if (Flags.useZeroJankProxy()) {
+ // async result delivered via MSG_START_INPUT_RESULT.
+ startInputSeq = IInputMethodManagerGlobalInvoker.startInputOrWindowGainedFocusAsync(
+ startInputReason, mClient, windowGainingFocus, startInputFlags,
+ softInputMode, windowFlags, editorInfo, servedInputConnection,
+ servedInputConnection == null ? null
+ : servedInputConnection.asIRemoteAccessibilityInputConnection(),
+ view.getContext().getApplicationInfo().targetSdkVersion, targetUserId,
+ mImeDispatcher);
+ } else {
+ res = IInputMethodManagerGlobalInvoker.startInputOrWindowGainedFocus(
+ startInputReason, mClient, windowGainingFocus, startInputFlags,
+ softInputMode, windowFlags, editorInfo, servedInputConnection,
+ servedInputConnection == null ? null
+ : servedInputConnection.asIRemoteAccessibilityInputConnection(),
+ view.getContext().getApplicationInfo().targetSdkVersion, targetUserId,
+ mImeDispatcher);
+ }
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+ if (Flags.useZeroJankProxy()) {
+ // Create a runnable for delayed notification to the app that the InputConnection is
+ // initialized and ready for use.
+ if (ic != null) {
+ final int seqId = startInputSeq;
+ mReportInputConnectionOpenedRunner =
+ new ReportInputConnectionOpenedRunner(startInputSeq) {
+ @Override
+ public void run() {
+ if (DEBUG) {
+ Log.v(TAG, "Calling View.onInputConnectionOpened: view= "
+ + view
+ + ", ic=" + ic + ", editorInfo=" + editorInfo
+ + ", handler="
+ + icHandler + ", startInputSeq=" + seqId);
+ }
+ reportInputConnectionOpened(ic, editorInfo, icHandler, view);
+ }
+ };
+ } else {
+ mReportInputConnectionOpenedRunner = null;
+ }
+ return true;
+ }
+
if (DEBUG) Log.v(TAG, "Starting input: Bind result=" + res);
if (res == null) {
Log.wtf(TAG, "startInputOrWindowGainedFocus must not return"
@@ -3118,6 +3315,7 @@ public final class InputMethodManager {
} else if (res.channel != null && res.channel != mCurChannel) {
res.channel.dispose();
}
+
switch (res.result) {
case InputBindResult.ResultCode.ERROR_NOT_IME_TARGET_WINDOW:
mRestartOnNextWindowFocus = true;
diff --git a/core/java/android/view/textclassifier/TextClassificationConstants.java b/core/java/android/view/textclassifier/TextClassificationConstants.java
index d0ed8eef7749..7dd77191cb08 100644
--- a/core/java/android/view/textclassifier/TextClassificationConstants.java
+++ b/core/java/android/view/textclassifier/TextClassificationConstants.java
@@ -137,10 +137,6 @@ public final class TextClassificationConstants {
properties.getBoolean(
LOCAL_TEXT_CLASSIFIER_ENABLED,
LOCAL_TEXT_CLASSIFIER_ENABLED_DEFAULT);
- sSystemTextClassifierEnabled =
- properties.getBoolean(
- SYSTEM_TEXT_CLASSIFIER_ENABLED,
- SYSTEM_TEXT_CLASSIFIER_ENABLED_DEFAULT);
sModelDarkLaunchEnabled =
properties.getBoolean(
MODEL_DARK_LAUNCH_ENABLED,
@@ -199,8 +195,11 @@ public final class TextClassificationConstants {
}
public boolean isSystemTextClassifierEnabled() {
- ensureMemoizedValues();
- return sSystemTextClassifierEnabled;
+ // Don't memoize this value because we want to be able to receive config
+ // updates at runtime.
+ return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_TEXTCLASSIFIER,
+ SYSTEM_TEXT_CLASSIFIER_ENABLED,
+ SYSTEM_TEXT_CLASSIFIER_ENABLED_DEFAULT);
}
public boolean isModelDarkLaunchEnabled() {
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 57d268ced6f4..139ebc38706e 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -19,6 +19,8 @@ package android.widget;
import static android.view.ContentInfo.SOURCE_DRAG_AND_DROP;
import static android.widget.TextView.ACCESSIBILITY_ACTION_SMART_START_ID;
+import static com.android.graphics.hwui.flags.Flags.highContrastTextSmallTextRect;
+
import android.R;
import android.animation.ValueAnimator;
import android.annotation.IntDef;
@@ -2151,8 +2153,15 @@ public class Editor {
int lastLine = TextUtils.unpackRangeEndFromLong(lineRange);
if (lastLine < 0) return;
- layout.drawWithoutText(canvas, highlightPaths, highlightPaints, selectionHighlight,
- selectionHighlightPaint, cursorOffsetVertical, firstLine, lastLine);
+ boolean shouldDrawHighlightsOnTop = highContrastTextSmallTextRect()
+ && canvas.isHighContrastTextEnabled();
+
+ if (!shouldDrawHighlightsOnTop) {
+ layout.drawWithoutText(canvas, highlightPaths, highlightPaints, selectionHighlight,
+ selectionHighlightPaint, cursorOffsetVertical, firstLine, lastLine);
+ } else {
+ layout.drawBackground(canvas, firstLine, lastLine);
+ }
if (layout instanceof DynamicLayout) {
if (mTextRenderNodes == null) {
@@ -2226,6 +2235,11 @@ public class Editor {
// Boring layout is used for empty and hint text
layout.drawText(canvas, firstLine, lastLine);
}
+
+ if (shouldDrawHighlightsOnTop) {
+ layout.drawHighlights(canvas, highlightPaths, highlightPaints, selectionHighlight,
+ selectionHighlightPaint, cursorOffsetVertical, firstLine, lastLine);
+ }
}
private int drawHardwareAcceleratedInner(Canvas canvas, Layout layout, Path highlight,
diff --git a/core/java/android/window/IUnhandledDragListener.aidl b/core/java/android/window/IGlobalDragListener.aidl
index 52e98952971d..8f2ca02b3c09 100644
--- a/core/java/android/window/IUnhandledDragListener.aidl
+++ b/core/java/android/window/IGlobalDragListener.aidl
@@ -16,14 +16,21 @@
package android.window;
+import android.app.ActivityManager;
import android.view.DragEvent;
import android.window.IUnhandledDragCallback;
/**
- * An interface to a handler for global drags that are not consumed (ie. not handled by any window).
+ * An interface to a handler for global drags.
* {@hide}
*/
-oneway interface IUnhandledDragListener {
+oneway interface IGlobalDragListener {
+ /**
+ * Called when a cross-window drag is handled by another window.
+ * @param taskInfo the task containing the window that consumed the drop
+ */
+ void onCrossWindowDrop(in ActivityManager.RunningTaskInfo taskInfo);
+
/**
* Called when the user finishes the drag gesture but no windows have reported handling the
* drop. The DragEvent is populated with the drag surface for the listener to animate. The
diff --git a/core/java/android/window/flags/OWNERS b/core/java/android/window/flags/OWNERS
index fa81ee3905c3..3fa376003123 100644
--- a/core/java/android/window/flags/OWNERS
+++ b/core/java/android/window/flags/OWNERS
@@ -1 +1,2 @@
-per-file responsible_apis.aconfig = file:/BAL_OWNERS \ No newline at end of file
+per-file responsible_apis.aconfig = file:/BAL_OWNERS
+per-file large_screen_experiences_app_compat.aconfig = file:/LSE_APP_COMPAT_OWNERS
diff --git a/core/java/com/android/internal/app/SetScreenLockDialogActivity.java b/core/java/com/android/internal/app/SetScreenLockDialogActivity.java
new file mode 100644
index 000000000000..93fe37c974b2
--- /dev/null
+++ b/core/java/com/android/internal/app/SetScreenLockDialogActivity.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.app;
+
+import static android.Manifest.permission.HIDE_OVERLAY_WINDOWS;
+import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.hardware.biometrics.BiometricManager.Authenticators.DEVICE_CREDENTIAL;
+import static android.provider.Settings.ACTION_BIOMETRIC_ENROLL;
+import static android.provider.Settings.EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED;
+
+import android.Manifest;
+import android.annotation.IntDef;
+import android.annotation.RequiresPermission;
+import android.app.AlertDialog;
+import android.app.KeyguardManager;
+import android.content.ComponentName;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.pm.UserInfo;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.Log;
+
+import com.android.internal.R;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * A dialog shown to the user that prompts them to set the screen lock for the current foreground
+ * user. Should be called from the context of foreground user.
+ */
+public class SetScreenLockDialogActivity extends AlertActivity
+ implements DialogInterface.OnClickListener, DialogInterface.OnDismissListener {
+ private static final String TAG = "SetScreenLockDialog";
+ public static final String EXTRA_LAUNCH_REASON = "launch_reason";
+ /**
+ * User id associated with the workflow that wants to launch the prompt to set up the
+ * screen lock
+ */
+ public static final String EXTRA_ORIGIN_USER_ID = "origin_user_id";
+ private static final String PACKAGE_NAME = "android";
+ @IntDef(prefix = "LAUNCH_REASON_", value = {
+ LAUNCH_REASON_PRIVATE_SPACE_SETTINGS_ACCESS,
+ LAUNCH_REASON_DISABLE_QUIET_MODE,
+ LAUNCH_REASON_UNKNOWN,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface LaunchReason {
+ }
+ public static final int LAUNCH_REASON_UNKNOWN = -1;
+ public static final int LAUNCH_REASON_DISABLE_QUIET_MODE = 1;
+ public static final int LAUNCH_REASON_PRIVATE_SPACE_SETTINGS_ACCESS = 2;
+ private @LaunchReason int mReason;
+ private int mOriginUserId;
+
+ @Override
+ @RequiresPermission(HIDE_OVERLAY_WINDOWS)
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (!(android.os.Flags.allowPrivateProfile()
+ && android.multiuser.Flags.showSetScreenLockDialog())) {
+ finish();
+ return;
+ }
+ Intent intent = getIntent();
+ mReason = intent.getIntExtra(EXTRA_LAUNCH_REASON, LAUNCH_REASON_UNKNOWN);
+ mOriginUserId = intent.getIntExtra(EXTRA_ORIGIN_USER_ID, UserHandle.USER_NULL);
+
+ if (mReason == LAUNCH_REASON_UNKNOWN) {
+ Log.e(TAG, "Invalid launch reason: " + mReason);
+ finish();
+ return;
+ }
+
+ final KeyguardManager km = getSystemService(KeyguardManager.class);
+ if (km == null) {
+ Log.e(TAG, "Error fetching keyguard manager");
+ return;
+ }
+ if (km.isDeviceSecure()) {
+ Log.w(TAG, "Closing the activity since screen lock is already set");
+ return;
+ }
+
+ Log.d(TAG, "Launching screen lock setup dialog due to " + mReason);
+ final AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle(R.string.set_up_screen_lock_title)
+ .setOnDismissListener(this)
+ .setPositiveButton(R.string.set_up_screen_lock_action_label, this)
+ .setNegativeButton(R.string.cancel, this);
+ setLaunchUserSpecificMessage(builder);
+ final AlertDialog dialog = builder.create();
+ dialog.create();
+ getWindow().setHideOverlayWindows(true);
+ dialog.getButton(DialogInterface.BUTTON_POSITIVE).setFilterTouchesWhenObscured(true);
+ dialog.show();
+ }
+
+ @Override
+ public void onDismiss(DialogInterface dialog) {
+ finish();
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ if (which == DialogInterface.BUTTON_POSITIVE) {
+ Intent setNewLockIntent = new Intent(ACTION_BIOMETRIC_ENROLL);
+ setNewLockIntent.putExtra(EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED, DEVICE_CREDENTIAL);
+ startActivity(setNewLockIntent);
+ } else {
+ finish();
+ }
+ }
+
+ @RequiresPermission(anyOf = {
+ Manifest.permission.MANAGE_USERS,
+ Manifest.permission.CREATE_USERS,
+ Manifest.permission.QUERY_USERS})
+ private void setLaunchUserSpecificMessage(AlertDialog.Builder builder) {
+ if (mReason == LAUNCH_REASON_PRIVATE_SPACE_SETTINGS_ACCESS) {
+ // Always set private space message if launch reason is specific to private space
+ builder.setMessage(R.string.private_space_set_up_screen_lock_message);
+ return;
+ }
+ final UserManager userManager = getApplicationContext().getSystemService(UserManager.class);
+ if (userManager != null) {
+ UserInfo userInfo = userManager.getUserInfo(mOriginUserId);
+ if (userInfo != null && userInfo.isPrivateProfile()) {
+ builder.setMessage(R.string.private_space_set_up_screen_lock_message);
+ }
+ }
+ }
+
+ /** Returns a basic intent to display the screen lock dialog */
+ public static Intent createBaseIntent(@LaunchReason int launchReason) {
+ Intent intent = new Intent();
+ intent.setComponent(new ComponentName(PACKAGE_NAME,
+ SetScreenLockDialogActivity.class.getName()));
+ intent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+ intent.putExtra(EXTRA_LAUNCH_REASON, launchReason);
+ return intent;
+ }
+}
diff --git a/core/java/com/android/internal/colorextraction/OWNERS b/core/java/com/android/internal/colorextraction/OWNERS
index ffade1ec4ebd..041559cd048d 100644
--- a/core/java/com/android/internal/colorextraction/OWNERS
+++ b/core/java/com/android/internal/colorextraction/OWNERS
@@ -1,3 +1,2 @@
-dupin@google.com
cinek@google.com
-jamesoleary@google.com
+arteiro@google.com
diff --git a/core/java/com/android/internal/inputmethod/IBooleanListener.aidl b/core/java/com/android/internal/inputmethod/IBooleanListener.aidl
new file mode 100644
index 000000000000..8830b1c2f21a
--- /dev/null
+++ b/core/java/com/android/internal/inputmethod/IBooleanListener.aidl
@@ -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.internal.inputmethod;
+
+/**
+ * Interface for providing a Boolean result.
+ */
+oneway interface IBooleanListener
+{
+ void onResult(boolean value);
+} \ No newline at end of file
diff --git a/core/java/com/android/internal/inputmethod/IInputMethodClient.aidl b/core/java/com/android/internal/inputmethod/IInputMethodClient.aidl
index 9251d2d5dc31..babd9a0950fd 100644
--- a/core/java/com/android/internal/inputmethod/IInputMethodClient.aidl
+++ b/core/java/com/android/internal/inputmethod/IInputMethodClient.aidl
@@ -24,6 +24,7 @@ import com.android.internal.inputmethod.InputBindResult;
*/
oneway interface IInputMethodClient {
void onBindMethod(in InputBindResult res);
+ void onStartInputResult(in InputBindResult res, int startInputSeq);
void onBindAccessibilityService(in InputBindResult res, int id);
void onUnbindMethod(int sequence, int unbindReason);
void onUnbindAccessibilityService(int sequence, int id);
diff --git a/core/java/com/android/internal/inputmethod/InputBindResult.java b/core/java/com/android/internal/inputmethod/InputBindResult.java
index b6eca07a0858..243b1031bd4b 100644
--- a/core/java/com/android/internal/inputmethod/InputBindResult.java
+++ b/core/java/com/android/internal/inputmethod/InputBindResult.java
@@ -271,6 +271,7 @@ public final class InputBindResult implements Parcelable {
public String toString() {
return "InputBindResult{result=" + getResultString() + " method=" + method + " id=" + id
+ " sequence=" + sequence
+ + " result=" + result
+ " isInputMethodSuppressingSpellChecker=" + isInputMethodSuppressingSpellChecker
+ "}";
}
diff --git a/core/java/com/android/internal/jank/Cuj.java b/core/java/com/android/internal/jank/Cuj.java
index 48c455aa70f2..3662d69e1974 100644
--- a/core/java/com/android/internal/jank/Cuj.java
+++ b/core/java/com/android/internal/jank/Cuj.java
@@ -120,7 +120,7 @@ public class Cuj {
public static final int CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY = 84;
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;
+ // 87 is reserved - previously assigned to deprecated CUJ_LAUNCHER_SEARCH_QSB_OPEN.
public static final int CUJ_BACK_PANEL_ARROW = 88;
public static final int CUJ_LAUNCHER_CLOSE_ALL_APPS_BACK = 89;
public static final int CUJ_LAUNCHER_SEARCH_QSB_WEB_SEARCH = 90;
@@ -209,7 +209,6 @@ public class Cuj {
CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY,
CUJ_PREDICTIVE_BACK_CROSS_TASK,
CUJ_PREDICTIVE_BACK_HOME,
- CUJ_LAUNCHER_SEARCH_QSB_OPEN,
CUJ_BACK_PANEL_ARROW,
CUJ_LAUNCHER_CLOSE_ALL_APPS_BACK,
CUJ_LAUNCHER_SEARCH_QSB_WEB_SEARCH,
@@ -304,7 +303,6 @@ 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_BACK_PANEL_ARROW] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__BACK_PANEL_ARROW;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_CLOSE_ALL_APPS_BACK] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_CLOSE_ALL_APPS_BACK;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_SEARCH_QSB_WEB_SEARCH] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_SEARCH_QSB_WEB_SEARCH;
@@ -480,8 +478,6 @@ public class Cuj {
return "PREDICTIVE_BACK_CROSS_TASK";
case CUJ_PREDICTIVE_BACK_HOME:
return "PREDICTIVE_BACK_HOME";
- case CUJ_LAUNCHER_SEARCH_QSB_OPEN:
- return "LAUNCHER_SEARCH_QSB_OPEN";
case CUJ_BACK_PANEL_ARROW:
return "BACK_PANEL_ARROW";
case CUJ_LAUNCHER_CLOSE_ALL_APPS_BACK:
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index ed43b8190f93..d463b62e62a3 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -376,4 +376,10 @@ oneway interface IStatusBar
* @param packageName of the session for which the output switcher is shown.
*/
void showMediaOutputSwitcher(String packageName);
+
+ /** Enters desktop mode.
+ *
+ * @param displayId the id of the current display.
+ */
+ void enterDesktop(int displayId);
}
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index e95127be8543..1f4503a69428 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -24,6 +24,7 @@ import android.view.inputmethod.InputMethodSubtype;
import android.view.inputmethod.EditorInfo;
import android.window.ImeOnBackInvokedDispatcher;
+import com.android.internal.inputmethod.IBooleanListener;
import com.android.internal.inputmethod.IConnectionlessHandwritingCallback;
import com.android.internal.inputmethod.IImeTracker;
import com.android.internal.inputmethod.IInputMethodClient;
@@ -69,6 +70,8 @@ interface IInputMethodManager {
boolean hideSoftInput(in IInputMethodClient client, @nullable IBinder windowToken,
in @nullable ImeTracker.Token statsToken, int flags,
in @nullable ResultReceiver resultReceiver, int reason);
+
+ // TODO(b/293640003): Remove method once Flags.useZeroJankProxy() is enabled.
// If windowToken is null, this just does startInput(). Otherwise this reports that a window
// has gained focus, and if 'editorInfo' is non-null then also does startInput.
// @NonNull
@@ -85,6 +88,21 @@ interface IInputMethodManager {
int unverifiedTargetSdkVersion, int userId,
in ImeOnBackInvokedDispatcher imeDispatcher);
+ // If windowToken is null, this just does startInput(). Otherwise this reports that a window
+ // has gained focus, and if 'editorInfo' is non-null then also does startInput.
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+ + "android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)")
+ void startInputOrWindowGainedFocusAsync(
+ /* @StartInputReason */ int startInputReason,
+ in IInputMethodClient client, in @nullable IBinder windowToken,
+ /* @StartInputFlags */ int startInputFlags,
+ /* @android.view.WindowManager.LayoutParams.SoftInputModeFlags */ int softInputMode,
+ /* @android.view.WindowManager.LayoutParams.Flags */ int windowFlags,
+ in @nullable EditorInfo editorInfo, in @nullable IRemoteInputConnection inputConnection,
+ in @nullable IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
+ int unverifiedTargetSdkVersion, int userId,
+ in ImeOnBackInvokedDispatcher imeDispatcher, int startInputSeq);
+
void showInputMethodPickerFromClient(in IInputMethodClient client,
int auxiliarySubtypeMode);
@@ -160,6 +178,12 @@ interface IInputMethodManager {
boolean acceptStylusHandwritingDelegation(in IInputMethodClient client, in int userId,
in String delegatePackageName, in String delegatorPackageName, int flags);
+ /** Accepts and starts a stylus handwriting session for the delegate view and provides result
+ * async **/
+ oneway void acceptStylusHandwritingDelegationAsync(in IInputMethodClient client, in int userId,
+ in String delegatePackageName, in String delegatorPackageName, int flags,
+ in IBooleanListener callback);
+
/** Returns {@code true} if currently selected IME supports Stylus handwriting. */
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+ "android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)")
diff --git a/core/java/com/android/internal/widget/CallLayout.java b/core/java/com/android/internal/widget/CallLayout.java
index 89f46599322e..c85257578492 100644
--- a/core/java/com/android/internal/widget/CallLayout.java
+++ b/core/java/com/android/internal/widget/CallLayout.java
@@ -31,6 +31,7 @@ import android.view.RemotableViewMethod;
import android.widget.FrameLayout;
import android.widget.RemoteViews;
import android.widget.TextView;
+import android.widget.flags.Flags;
import com.android.internal.R;
@@ -41,7 +42,17 @@ import com.android.internal.R;
public class CallLayout extends FrameLayout {
private final PeopleHelper mPeopleHelper = new PeopleHelper();
+ /**
+ * Layout Color is used for creating CallLayout person avatar.
+ * It will be set on the background thread during CallLayout's inflation
+ * when call_style_set_data_async is enabled.
+ */
private int mLayoutColor;
+ /**
+ * LargeIcon is used for creating CallLayout person avatar.
+ * It will be set on the background thread during CallLayout's inflation
+ * when call_style_set_data_async is enabled.
+ */
private Icon mLargeIcon;
private Person mUser;
@@ -49,7 +60,6 @@ public class CallLayout extends FrameLayout {
private CachingIconView mIcon;
private CachingIconView mConversationIconBadgeBg;
private TextView mConversationText;
- private boolean mSetDataAsyncEnabled = false;
public CallLayout(@NonNull Context context) {
super(context);
@@ -103,7 +113,19 @@ public class CallLayout extends FrameLayout {
return icon;
}
- @RemotableViewMethod
+ /**
+ * async version of {@link CallLayout#setLayoutColor}
+ */
+ public Runnable setLayoutColorAsync(int color) {
+ if (!Flags.callStyleSetDataAsync()) {
+ return () -> setLayoutColor(color);
+ }
+
+ mLayoutColor = color;
+ return () -> {};
+ }
+
+ @RemotableViewMethod(asyncImpl = "setLayoutColorAsync")
public void setLayoutColor(int color) {
mLayoutColor = color;
}
@@ -116,7 +138,19 @@ public class CallLayout extends FrameLayout {
mConversationIconBadgeBg.setImageTintList(ColorStateList.valueOf(color));
}
- @RemotableViewMethod
+ /**
+ * async version of {@link CallLayout#setLargeIcon}
+ */
+ public Runnable setLargeIconAsync(Icon largeIcon) {
+ if (!Flags.callStyleSetDataAsync()) {
+ return () -> setLargeIcon(largeIcon);
+ }
+
+ mLargeIcon = largeIcon;
+ return () -> {};
+ }
+
+ @RemotableViewMethod(asyncImpl = "setLargeIconAsync")
public void setLargeIcon(Icon largeIcon) {
mLargeIcon = largeIcon;
}
@@ -133,16 +167,11 @@ public class CallLayout extends FrameLayout {
mConversationIconView.setImageIcon(icon);
}
-
- public void setSetDataAsyncEnabled(boolean setDataAsyncEnabled) {
- mSetDataAsyncEnabled = setDataAsyncEnabled;
- }
-
/**
* Async implementation for setData
*/
public Runnable setDataAsync(Bundle extras) {
- if (!mSetDataAsyncEnabled) {
+ if (!Flags.callStyleSetDataAsync()) {
return () -> setData(extras);
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
index a7260bb7c14b..c34730fdef04 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
@@ -49,8 +49,13 @@ public class RemoteComposeBuffer {
this.mRemoteComposeState = remoteComposeState;
}
- public void reset() {
- mBuffer.reset();
+ /**
+ * Reset the internal buffers
+ *
+ * @param expectedSize provided hint for the main buffer size
+ */
+ public void reset(int expectedSize) {
+ mBuffer.reset(expectedSize);
mRemoteComposeState.reset();
}
@@ -288,8 +293,7 @@ public class RemoteComposeBuffer {
public static void read(InputStream fd, RemoteComposeBuffer buffer) {
try {
byte[] bytes = readAllBytes(fd);
- buffer.reset();
- buffer.mBuffer.resize(bytes.length);
+ buffer.reset(bytes.length);
System.arraycopy(bytes, 0, buffer.mBuffer.mBuffer, 0, bytes.length);
buffer.mBuffer.mSize = bytes.length;
} catch (Exception e) {
diff --git a/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java b/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java
index 4518d94498d6..b7cb3926d936 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java
@@ -83,10 +83,18 @@ public class WireBuffer {
mIndex = currentIndex;
}
- public void reset() {
+ /**
+ * Reset the internal buffer
+ *
+ * @param expectedSize provided hint for the buffer size
+ */
+ public void reset(int expectedSize) {
mIndex = 0;
mStartingIndex = 0;
mSize = 0;
+ if (expectedSize > mMaxSize) {
+ resize(expectedSize);
+ }
}
public int size() {
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/BitmapData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/BitmapData.java
index 4bfdc59aad3a..76b714443990 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/BitmapData.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/BitmapData.java
@@ -33,7 +33,7 @@ public class BitmapData implements Operation {
int mImageWidth;
int mImageHeight;
byte[] mBitmap;
- public static final int MAX_IMAGE_DIMENSION = 6000;
+ public static final int MAX_IMAGE_DIMENSION = 8000;
public static final Companion COMPANION = new Companion();
diff --git a/core/jni/android_view_InputEventReceiver.cpp b/core/jni/android_view_InputEventReceiver.cpp
index f93b3068a229..f1b93db3e731 100644
--- a/core/jni/android_view_InputEventReceiver.cpp
+++ b/core/jni/android_view_InputEventReceiver.cpp
@@ -371,15 +371,15 @@ status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,
}
}
- jobject inputEventObj;
+ ScopedLocalRef<jobject> inputEventObj(env);
switch (inputEvent->getType()) {
case InputEventType::KEY:
if (kDebugDispatchCycle) {
ALOGD("channel '%s' ~ Received key event.", getInputChannelName().c_str());
}
inputEventObj =
- android_view_KeyEvent_fromNative(env,
- static_cast<KeyEvent&>(*inputEvent));
+ android_view_KeyEvent_obtainAsCopy(env,
+ static_cast<KeyEvent&>(*inputEvent));
break;
case InputEventType::MOTION: {
@@ -447,20 +447,19 @@ status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,
default:
assert(false); // InputConsumer should prevent this from ever happening
- inputEventObj = nullptr;
}
- if (inputEventObj) {
+ if (inputEventObj.get()) {
if (kDebugDispatchCycle) {
ALOGD("channel '%s' ~ Dispatching input event.", getInputChannelName().c_str());
}
env->CallVoidMethod(receiverObj.get(),
- gInputEventReceiverClassInfo.dispatchInputEvent, seq, inputEventObj);
+ gInputEventReceiverClassInfo.dispatchInputEvent, seq,
+ inputEventObj.get());
if (env->ExceptionCheck()) {
ALOGE("Exception dispatching input event.");
skipCallbacks = true;
}
- env->DeleteLocalRef(inputEventObj);
} else {
ALOGW("channel '%s' ~ Failed to obtain event object.",
getInputChannelName().c_str());
diff --git a/core/jni/android_view_InputEventSender.cpp b/core/jni/android_view_InputEventSender.cpp
index b5fbb22215b4..88b02baab924 100644
--- a/core/jni/android_view_InputEventSender.cpp
+++ b/core/jni/android_view_InputEventSender.cpp
@@ -359,7 +359,7 @@ static jboolean nativeSendKeyEvent(JNIEnv* env, jclass clazz, jlong senderPtr,
jint seq, jobject eventObj) {
sp<NativeInputEventSender> sender =
reinterpret_cast<NativeInputEventSender*>(senderPtr);
- const KeyEvent event = android_view_KeyEvent_toNative(env, eventObj);
+ const KeyEvent event = android_view_KeyEvent_obtainAsCopy(env, eventObj);
status_t status = sender->sendKeyEvent(seq, &event);
return !status;
}
diff --git a/core/jni/android_view_InputQueue.cpp b/core/jni/android_view_InputQueue.cpp
index a0d081d2cd26..50d2cbe2ce74 100644
--- a/core/jni/android_view_InputQueue.cpp
+++ b/core/jni/android_view_InputQueue.cpp
@@ -221,7 +221,7 @@ static jlong nativeSendKeyEvent(JNIEnv* env, jobject clazz, jlong ptr, jobject e
jboolean predispatch) {
InputQueue* queue = reinterpret_cast<InputQueue*>(ptr);
KeyEvent* event = queue->createKeyEvent();
- *event = android_view_KeyEvent_toNative(env, eventObj);
+ *event = android_view_KeyEvent_obtainAsCopy(env, eventObj);
if (predispatch) {
event->setFlags(event->getFlags() | AKEY_EVENT_FLAG_PREDISPATCH);
diff --git a/core/jni/android_view_KeyCharacterMap.cpp b/core/jni/android_view_KeyCharacterMap.cpp
index a79e37afd4cd..2b19ddfed5eb 100644
--- a/core/jni/android_view_KeyCharacterMap.cpp
+++ b/core/jni/android_view_KeyCharacterMap.cpp
@@ -219,10 +219,10 @@ static jobjectArray nativeGetEvents(JNIEnv *env, jobject clazz, jlong ptr,
result = env->NewObjectArray(jsize(events.size()), gKeyEventClassInfo.clazz, NULL);
if (result) {
for (size_t i = 0; i < events.size(); i++) {
- jobject keyEventObj = android_view_KeyEvent_fromNative(env, events.itemAt(i));
- if (!keyEventObj) break; // threw OOM exception
- env->SetObjectArrayElement(result, jsize(i), keyEventObj);
- env->DeleteLocalRef(keyEventObj);
+ ScopedLocalRef<jobject> keyEventObj =
+ android_view_KeyEvent_obtainAsCopy(env, events.itemAt(i));
+ if (!keyEventObj.get()) break; // threw OOM exception
+ env->SetObjectArrayElement(result, jsize(i), keyEventObj.get());
}
}
}
diff --git a/core/jni/android_view_KeyEvent.cpp b/core/jni/android_view_KeyEvent.cpp
index a9c991919361..ca8752f93e11 100644
--- a/core/jni/android_view_KeyEvent.cpp
+++ b/core/jni/android_view_KeyEvent.cpp
@@ -94,26 +94,28 @@ static struct {
// ----------------------------------------------------------------------------
-jobject android_view_KeyEvent_fromNative(JNIEnv* env, const KeyEvent& event) {
+ScopedLocalRef<jobject> android_view_KeyEvent_obtainAsCopy(JNIEnv* env, const KeyEvent& event) {
ScopedLocalRef<jbyteArray> hmac = toJbyteArray(env, event.getHmac());
- jobject eventObj =
- env->CallStaticObjectMethod(gKeyEventClassInfo.clazz, gKeyEventClassInfo.obtain,
- event.getId(), event.getDownTime(), event.getEventTime(),
- event.getAction(), event.getKeyCode(),
- event.getRepeatCount(), event.getMetaState(),
- event.getDeviceId(), event.getScanCode(), event.getFlags(),
- event.getSource(), event.getDisplayId(), hmac.get(),
- nullptr);
+ ScopedLocalRef<jobject>
+ eventObj(env,
+ env->CallStaticObjectMethod(gKeyEventClassInfo.clazz,
+ gKeyEventClassInfo.obtain, event.getId(),
+ event.getDownTime(), event.getEventTime(),
+ event.getAction(), event.getKeyCode(),
+ event.getRepeatCount(), event.getMetaState(),
+ event.getDeviceId(), event.getScanCode(),
+ event.getFlags(), event.getSource(),
+ event.getDisplayId(), hmac.get(), nullptr));
if (env->ExceptionCheck()) {
ALOGE("An exception occurred while obtaining a key event.");
LOGE_EX(env);
env->ExceptionClear();
- return NULL;
+ return ScopedLocalRef<jobject>(env);
}
return eventObj;
}
-KeyEvent android_view_KeyEvent_toNative(JNIEnv* env, jobject eventObj) {
+KeyEvent android_view_KeyEvent_obtainAsCopy(JNIEnv* env, jobject eventObj) {
jint id = env->GetIntField(eventObj, gKeyEventClassInfo.mId);
jint deviceId = env->GetIntField(eventObj, gKeyEventClassInfo.mDeviceId);
jint source = env->GetIntField(eventObj, gKeyEventClassInfo.mSource);
diff --git a/core/jni/android_view_KeyEvent.h b/core/jni/android_view_KeyEvent.h
index bc4876a7a835..838f0130bd40 100644
--- a/core/jni/android_view_KeyEvent.h
+++ b/core/jni/android_view_KeyEvent.h
@@ -17,21 +17,24 @@
#ifndef _ANDROID_VIEW_KEYEVENT_H
#define _ANDROID_VIEW_KEYEVENT_H
-#include "jni.h"
+#include <nativehelper/scoped_local_ref.h>
#include <utils/Errors.h>
#include <utils/threads.h>
+#include "jni.h"
+
namespace android {
class KeyEvent;
/* Obtains an instance of a DVM KeyEvent object as a copy of a native KeyEvent instance.
* Returns NULL on error. */
-extern jobject android_view_KeyEvent_fromNative(JNIEnv* env, const KeyEvent& event);
+extern ScopedLocalRef<jobject> android_view_KeyEvent_obtainAsCopy(JNIEnv* env,
+ const KeyEvent& event);
/* Copies the contents of a DVM KeyEvent object to a native KeyEvent instance.
* Returns non-zero on error. */
-extern KeyEvent android_view_KeyEvent_toNative(JNIEnv* env, jobject eventObj);
+extern KeyEvent android_view_KeyEvent_obtainAsCopy(JNIEnv* env, jobject eventObj);
/* Recycles a DVM KeyEvent object.
* Key events should only be recycled if they are owned by the system since user
diff --git a/core/jni/android_view_MotionEvent.cpp b/core/jni/android_view_MotionEvent.cpp
index 2e9f1790a4a5..285dee364bb0 100644
--- a/core/jni/android_view_MotionEvent.cpp
+++ b/core/jni/android_view_MotionEvent.cpp
@@ -77,39 +77,28 @@ MotionEvent* android_view_MotionEvent_getNativePtr(JNIEnv* env, jobject eventObj
env->GetLongField(eventObj, gMotionEventClassInfo.mNativePtr));
}
-static void android_view_MotionEvent_setNativePtr(JNIEnv* env, jobject eventObj,
- MotionEvent* event) {
- env->SetLongField(eventObj, gMotionEventClassInfo.mNativePtr,
- reinterpret_cast<jlong>(event));
+static void android_view_MotionEvent_setNativePtr(JNIEnv* env, ScopedLocalRef<jobject>& eventObj,
+ MotionEvent* event) {
+ env->SetLongField(eventObj.get(), gMotionEventClassInfo.mNativePtr,
+ reinterpret_cast<jlong>(event));
}
-jobject android_view_MotionEvent_obtainAsCopy(JNIEnv* env, const MotionEvent& event) {
- jobject eventObj = env->CallStaticObjectMethod(gMotionEventClassInfo.clazz,
- gMotionEventClassInfo.obtain);
- if (env->ExceptionCheck() || !eventObj) {
- ALOGE("An exception occurred while obtaining a motion event.");
- LOGE_EX(env);
- env->ExceptionClear();
- return NULL;
- }
-
- MotionEvent* destEvent = android_view_MotionEvent_getNativePtr(env, eventObj);
- if (!destEvent) {
- destEvent = new MotionEvent();
- android_view_MotionEvent_setNativePtr(env, eventObj, destEvent);
- }
-
+ScopedLocalRef<jobject> android_view_MotionEvent_obtainAsCopy(JNIEnv* env,
+ const MotionEvent& event) {
+ std::unique_ptr<MotionEvent> destEvent = std::make_unique<MotionEvent>();
destEvent->copyFrom(&event, true);
- return eventObj;
+ return android_view_MotionEvent_obtainFromNative(env, std::move(destEvent));
}
-jobject android_view_MotionEvent_obtainFromNative(JNIEnv* env, std::unique_ptr<MotionEvent> event) {
+ScopedLocalRef<jobject> android_view_MotionEvent_obtainFromNative(
+ JNIEnv* env, std::unique_ptr<MotionEvent> event) {
if (event == nullptr) {
- return nullptr;
+ return ScopedLocalRef<jobject>(env);
}
- jobject eventObj =
- env->CallStaticObjectMethod(gMotionEventClassInfo.clazz, gMotionEventClassInfo.obtain);
- if (env->ExceptionCheck() || !eventObj) {
+ ScopedLocalRef<jobject> eventObj(env,
+ env->CallStaticObjectMethod(gMotionEventClassInfo.clazz,
+ gMotionEventClassInfo.obtain));
+ if (env->ExceptionCheck() || !eventObj.get()) {
LOGE_EX(env);
LOG_ALWAYS_FATAL("An exception occurred while obtaining a Java motion event.");
}
diff --git a/core/jni/android_view_MotionEvent.h b/core/jni/android_view_MotionEvent.h
index e81213608d68..b1bf1c435e26 100644
--- a/core/jni/android_view_MotionEvent.h
+++ b/core/jni/android_view_MotionEvent.h
@@ -17,21 +17,24 @@
#ifndef _ANDROID_VIEW_MOTIONEVENT_H
#define _ANDROID_VIEW_MOTIONEVENT_H
-#include "jni.h"
+#include <nativehelper/scoped_local_ref.h>
#include <utils/Errors.h>
+#include "jni.h"
+
namespace android {
class MotionEvent;
/* Obtains an instance of a DVM MotionEvent object as a copy of a native MotionEvent instance.
* Returns NULL on error. */
-extern jobject android_view_MotionEvent_obtainAsCopy(JNIEnv* env, const MotionEvent& event);
+extern ScopedLocalRef<jobject> android_view_MotionEvent_obtainAsCopy(JNIEnv* env,
+ const MotionEvent& event);
/* Obtains an instance of a Java MotionEvent object, taking over the ownership of the provided
* native MotionEvent instance. Crashes on error. */
-extern jobject android_view_MotionEvent_obtainFromNative(JNIEnv* env,
- std::unique_ptr<MotionEvent> event);
+extern ScopedLocalRef<jobject> android_view_MotionEvent_obtainFromNative(
+ JNIEnv* env, std::unique_ptr<MotionEvent> event);
/* Gets the underlying native MotionEvent instance within a DVM MotionEvent object.
* Returns NULL if the event is NULL or if it is uninitialized. */
diff --git a/core/jni/android_view_MotionPredictor.cpp b/core/jni/android_view_MotionPredictor.cpp
index de3e81c7088b..0707e99205aa 100644
--- a/core/jni/android_view_MotionPredictor.cpp
+++ b/core/jni/android_view_MotionPredictor.cpp
@@ -61,7 +61,8 @@ static jobject android_view_MotionPredictor_nativePredict(JNIEnv* env, jclass cl
MotionPredictor* predictor = reinterpret_cast<MotionPredictor*>(ptr);
return android_view_MotionEvent_obtainFromNative(env,
predictor->predict(static_cast<nsecs_t>(
- predictionTimeNanos)));
+ predictionTimeNanos)))
+ .release();
}
static jboolean android_view_MotionPredictor_nativeIsPredictionAvailable(JNIEnv* env, jclass clazz,
diff --git a/core/proto/android/content/intent.proto b/core/proto/android/content/intent.proto
index 1d1f88b01838..3607865cc591 100644
--- a/core/proto/android/content/intent.proto
+++ b/core/proto/android/content/intent.proto
@@ -55,6 +55,7 @@ message IntentProto {
optional string data = 3 [ (.android.privacy).dest = DEST_EXPLICIT ];
optional string type = 4;
optional string flag = 5;
+ optional string extended_flag = 14;
optional string package = 6;
optional ComponentNameProto component = 7;
optional string source_bounds = 8;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 0b3a06545648..8720f947bb8d 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -7182,10 +7182,10 @@
<permission android:name="android.permission.MANAGE_SPEECH_RECOGNITION"
android:protectionLevel="signature" />
- <!-- @SystemApi Allows an application to manage the content suggestions service.
+ <!-- @SystemApi Allows an application to interact with the content suggestions service.
@hide <p>Not for use by third-party applications.</p> -->
<permission android:name="android.permission.MANAGE_CONTENT_SUGGESTIONS"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature|role" />
<!-- @SystemApi Allows an application to manage the app predictions service.
@hide <p>Not for use by third-party applications.</p> -->
@@ -8370,6 +8370,12 @@
android:process=":ui">
</activity>
+ <activity android:name="com.android.internal.app.SetScreenLockDialogActivity"
+ android:theme="@style/Theme.Dialog.Confirmation"
+ android:excludeFromRecents="true"
+ android:process=":ui">
+ </activity>
+
<activity android:name="com.android.internal.app.BlockedAppActivity"
android:theme="@style/Theme.Dialog.Confirmation"
android:excludeFromRecents="true"
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index c6bc589cffcb..1f06b0b7c62b 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4670,6 +4670,13 @@
-->
<string name="config_defaultWearableSensingService" translatable="false"></string>
+
+ <!-- The component name for the default system on-device intelligence service, -->
+ <string name="config_defaultOnDeviceIntelligenceService" translatable="false"></string>
+
+ <!-- The component name for the default system on-device trusted inference service. -->
+ <string name="config_defaultOnDeviceTrustedInferenceService" translatable="false"></string>
+
<!-- Component name that accepts ACTION_SEND intents for requesting ambient context consent for
wearable sensing. -->
<string translatable="false" name="config_defaultWearableSensingConsentComponent"></string>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index e9996955a9c0..59066eb83f1c 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5448,6 +5448,13 @@
<!-- Title for button to launch the personal safety app to make an emergency call -->
<string name="work_mode_emergency_call_button">Emergency</string>
+ <!-- Title of the alert dialog prompting the user to set up a screen lock [CHAR LIMIT=30] -->
+ <string name="set_up_screen_lock_title">Set a screen lock</string>
+ <!-- Action label for the dialog prompting the user to set up a screen lock [CHAR LIMIT=30] -->
+ <string name="set_up_screen_lock_action_label">Set screen lock</string>
+ <!-- Message shown in the dialog prompting the user to set up a screen lock to access private space [CHAR LIMIT=30] -->
+ <string name="private_space_set_up_screen_lock_message">To use your private space, set a screen lock on this device</string>
+
<!-- Title of the dialog that is shown when the user tries to launch a blocked application [CHAR LIMIT=50] -->
<string name="app_blocked_title">App is not available</string>
<!-- Default message shown in the dialog that is shown when the user tries to launch a blocked application [CHAR LIMIT=NONE] -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 9d7acffee68b..3284791ef384 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3247,6 +3247,12 @@
<java-symbol type="string" name="work_mode_off_title" />
<java-symbol type="string" name="work_mode_turn_on" />
+ <!-- Alert dialog prompting the user to set up a screen lock -->
+ <java-symbol type="string" name="set_up_screen_lock_title" />
+ <java-symbol type="string" name="set_up_screen_lock_action_label" />
+ <!-- Message for the alert dialog prompting the user to set up a screen lock to access private space -->
+ <java-symbol type="string" name="private_space_set_up_screen_lock_message" />
+
<java-symbol type="string" name="deprecated_target_sdk_message" />
<java-symbol type="string" name="deprecated_target_sdk_app_store" />
@@ -3909,6 +3915,8 @@
<java-symbol type="string" name="config_ambientContextPackageNameExtraKey" />
<java-symbol type="string" name="config_ambientContextEventArrayExtraKey" />
<java-symbol type="string" name="config_defaultWearableSensingService" />
+ <java-symbol type="string" name="config_defaultOnDeviceIntelligenceService" />
+ <java-symbol type="string" name="config_defaultOnDeviceTrustedInferenceService" />
<java-symbol type="string" name="config_retailDemoPackage" />
<java-symbol type="string" name="config_retailDemoPackageSignature" />
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index 861f71992f54..37e6780a8109 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -211,6 +211,7 @@ android_ravenwood_test {
],
srcs: [
"src/android/app/ActivityManagerTest.java",
+ "src/android/content/ContextTest.java",
"src/android/content/pm/PackageManagerTest.java",
"src/android/content/pm/UserInfoTest.java",
"src/android/database/CursorWindowTest.java",
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index 48ef7e6f11a8..a7d083cbc399 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -43,7 +43,6 @@ import android.app.PictureInPictureParams;
import android.app.PictureInPictureUiState;
import android.app.ResourcesManager;
import android.app.servertransaction.ActivityConfigurationChangeItem;
-import android.app.servertransaction.ActivityLifecycleItem;
import android.app.servertransaction.ActivityRelaunchItem;
import android.app.servertransaction.ClientTransaction;
import android.app.servertransaction.ClientTransactionItem;
@@ -75,7 +74,6 @@ import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.content.ReferrerIntent;
-import com.android.window.flags.Flags;
import org.junit.After;
import org.junit.Before;
@@ -230,7 +228,7 @@ public class ActivityThreadTest {
try {
// Send process level config change.
ClientTransaction transaction = newTransaction(activityThread);
- addClientTransactionItem(transaction, ConfigurationChangeItem.obtain(
+ transaction.addTransactionItem(ConfigurationChangeItem.obtain(
newConfig, DEVICE_ID_INVALID));
appThread.scheduleTransaction(transaction);
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
@@ -247,7 +245,7 @@ public class ActivityThreadTest {
newConfig.seq++;
newConfig.smallestScreenWidthDp++;
transaction = newTransaction(activityThread);
- addClientTransactionItem(transaction, ActivityConfigurationChangeItem.obtain(
+ transaction.addTransactionItem(ActivityConfigurationChangeItem.obtain(
activity.getActivityToken(), newConfig));
appThread.scheduleTransaction(transaction);
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
@@ -448,16 +446,16 @@ public class ActivityThreadTest {
activity.mTestLatch = new CountDownLatch(1);
ClientTransaction transaction = newTransaction(activityThread);
- addClientTransactionItem(transaction, ConfigurationChangeItem.obtain(
+ transaction.addTransactionItem(ConfigurationChangeItem.obtain(
processConfigLandscape, DEVICE_ID_INVALID));
appThread.scheduleTransaction(transaction);
transaction = newTransaction(activityThread);
- addClientTransactionItem(transaction, ActivityConfigurationChangeItem.obtain(
+ transaction.addTransactionItem(ActivityConfigurationChangeItem.obtain(
activity.getActivityToken(), activityConfigLandscape));
- addClientTransactionItem(transaction, ConfigurationChangeItem.obtain(
+ transaction.addTransactionItem(ConfigurationChangeItem.obtain(
processConfigPortrait, DEVICE_ID_INVALID));
- addClientTransactionItem(transaction, ActivityConfigurationChangeItem.obtain(
+ transaction.addTransactionItem(ActivityConfigurationChangeItem.obtain(
activity.getActivityToken(), activityConfigPortrait));
appThread.scheduleTransaction(transaction);
@@ -847,8 +845,8 @@ public class ActivityThreadTest {
false /* shouldSendCompatFakeFocus*/);
final ClientTransaction transaction = newTransaction(activity);
- addClientTransactionItem(transaction, callbackItem);
- addClientTransactionItem(transaction, resumeStateRequest);
+ transaction.addTransactionItem(callbackItem);
+ transaction.addTransactionItem(resumeStateRequest);
return transaction;
}
@@ -860,7 +858,7 @@ public class ActivityThreadTest {
false /* shouldSendCompatFakeFocus */);
final ClientTransaction transaction = newTransaction(activity);
- addClientTransactionItem(transaction, resumeStateRequest);
+ transaction.addTransactionItem(resumeStateRequest);
return transaction;
}
@@ -871,7 +869,7 @@ public class ActivityThreadTest {
activity.getActivityToken(), 0 /* configChanges */);
final ClientTransaction transaction = newTransaction(activity);
- addClientTransactionItem(transaction, stopStateRequest);
+ transaction.addTransactionItem(stopStateRequest);
return transaction;
}
@@ -883,7 +881,7 @@ public class ActivityThreadTest {
activity.getActivityToken(), config);
final ClientTransaction transaction = newTransaction(activity);
- addClientTransactionItem(transaction, item);
+ transaction.addTransactionItem(item);
return transaction;
}
@@ -895,7 +893,7 @@ public class ActivityThreadTest {
resume);
final ClientTransaction transaction = newTransaction(activity);
- addClientTransactionItem(transaction, item);
+ transaction.addTransactionItem(item);
return transaction;
}
@@ -910,17 +908,6 @@ public class ActivityThreadTest {
return ClientTransaction.obtain(activityThread.getApplicationThread());
}
- private static void addClientTransactionItem(@NonNull ClientTransaction transaction,
- @NonNull ClientTransactionItem item) {
- if (Flags.bundleClientTransactionFlag()) {
- transaction.addTransactionItem(item);
- } else if (item.isActivityLifecycleItem()) {
- transaction.setLifecycleStateRequest((ActivityLifecycleItem) item);
- } else {
- transaction.addCallback(item);
- }
- }
-
// Test activity
public static class TestActivity extends Activity {
static final String PIP_REQUESTED_OVERRIDE_ENTER = "pip_requested_override_enter";
@@ -1001,7 +988,7 @@ public class ActivityThreadTest {
@Override
public void onPictureInPictureUiStateChanged(PictureInPictureUiState pipState) {
- if (mPipUiStateLatch != null && pipState.isEnteringPip()) {
+ if (mPipUiStateLatch != null && pipState.isTransitioningToPip()) {
mPipUiStateLatch.countDown();
}
}
diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
index 95d50499b92f..213fd7bd494d 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
@@ -16,10 +16,13 @@
package android.app.servertransaction;
+import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
+
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+import static com.android.window.flags.Flags.FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG;
+
import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import android.hardware.display.DisplayManager;
@@ -28,12 +31,14 @@ import android.hardware.display.IDisplayManager;
import android.os.Handler;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.view.DisplayInfo;
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;
import org.mockito.Mock;
@@ -49,6 +54,10 @@ import org.mockito.MockitoAnnotations;
@SmallTest
@Presubmit
public class ClientTransactionListenerControllerTest {
+
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
+
@Mock
private IDisplayManager mIDisplayManager;
@Mock
@@ -60,12 +69,12 @@ public class ClientTransactionListenerControllerTest {
@Before
public void setup() {
+ mSetFlagsRule.enableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG);
+
MockitoAnnotations.initMocks(this);
mDisplayManager = new DisplayManagerGlobal(mIDisplayManager);
mHandler = getInstrumentation().getContext().getMainThreadHandler();
- mController = spy(ClientTransactionListenerController.createInstanceForTesting(
- mDisplayManager));
- doReturn(true).when(mController).isBundleClientTransactionFlagEnabled();
+ mController = ClientTransactionListenerController.createInstanceForTesting(mDisplayManager);
}
@Test
diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionTests.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionTests.java
index d10cf1691408..527241613a7a 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionTests.java
@@ -16,16 +16,22 @@
package android.app.servertransaction;
+import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
+
+import static com.android.window.flags.Flags.FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG;
+
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.app.ClientTransactionHandler;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -43,31 +49,28 @@ import org.junit.runner.RunWith;
@Presubmit
public class ClientTransactionTests {
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
+
@Test
public void testPreExecute() {
- final ClientTransactionItem callback1 = mock(ClientTransactionItem.class);
- final ClientTransactionItem callback2 = mock(ClientTransactionItem.class);
- final ActivityLifecycleItem stateRequest = mock(ActivityLifecycleItem.class);
- final ClientTransactionHandler clientTransactionHandler =
- mock(ClientTransactionHandler.class);
+ mSetFlagsRule.disableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG);
- final ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
- transaction.addCallback(callback1);
- transaction.addCallback(callback2);
- transaction.setLifecycleStateRequest(stateRequest);
+ testPreExecuteInner();
+ }
- transaction.preExecute(clientTransactionHandler);
+ @Test
+ public void testPreExecute_bundleClientTransaction() {
+ mSetFlagsRule.enableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG);
- verify(callback1, times(1)).preExecute(clientTransactionHandler);
- verify(callback2, times(1)).preExecute(clientTransactionHandler);
- verify(stateRequest, times(1)).preExecute(clientTransactionHandler);
+ testPreExecuteInner();
}
- @Test
- public void testPreExecuteTransactionItems() {
+ private void testPreExecuteInner() {
final ClientTransactionItem callback1 = mock(ClientTransactionItem.class);
final ClientTransactionItem callback2 = mock(ClientTransactionItem.class);
final ActivityLifecycleItem stateRequest = mock(ActivityLifecycleItem.class);
+ doReturn(true).when(stateRequest).isActivityLifecycleItem();
final ClientTransactionHandler clientTransactionHandler =
mock(ClientTransactionHandler.class);
@@ -78,8 +81,8 @@ public class ClientTransactionTests {
transaction.preExecute(clientTransactionHandler);
- verify(callback1, times(1)).preExecute(clientTransactionHandler);
- verify(callback2, times(1)).preExecute(clientTransactionHandler);
- verify(stateRequest, times(1)).preExecute(clientTransactionHandler);
+ verify(callback1).preExecute(clientTransactionHandler);
+ verify(callback2).preExecute(clientTransactionHandler);
+ verify(stateRequest).preExecute(clientTransactionHandler);
}
}
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
index 2315a58eb487..adb6f2a23847 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
@@ -25,6 +25,9 @@ import static android.app.servertransaction.ActivityLifecycleItem.ON_START;
import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP;
import static android.app.servertransaction.ActivityLifecycleItem.PRE_ON_CREATE;
import static android.app.servertransaction.ActivityLifecycleItem.UNDEFINED;
+import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
+
+import static com.android.window.flags.Flags.FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
@@ -51,12 +54,14 @@ import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.util.ArrayMap;
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;
import org.mockito.InOrder;
@@ -83,6 +88,9 @@ import java.util.stream.Collectors;
@Presubmit
public class TransactionExecutorTests {
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
+
@Mock
private ClientTransactionHandler mTransactionHandler;
@Mock
@@ -240,29 +248,19 @@ public class TransactionExecutorTests {
@Test
public void testTransactionResolution() {
- ClientTransactionItem callback1 = mock(ClientTransactionItem.class);
- when(callback1.getPostExecutionState()).thenReturn(UNDEFINED);
- ClientTransactionItem callback2 = mock(ClientTransactionItem.class);
- when(callback2.getPostExecutionState()).thenReturn(UNDEFINED);
+ mSetFlagsRule.disableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG);
- ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
- transaction.addCallback(callback1);
- transaction.addCallback(callback2);
- transaction.setLifecycleStateRequest(mActivityLifecycleItem);
+ testTransactionResolutionInner();
+ }
- transaction.preExecute(mTransactionHandler);
- mExecutor.execute(transaction);
+ @Test
+ public void testTransactionResolution_bundleClientTransaction() {
+ mSetFlagsRule.enableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG);
- InOrder inOrder = inOrder(mTransactionHandler, callback1, callback2,
- mActivityLifecycleItem);
- inOrder.verify(callback1).execute(eq(mTransactionHandler), any());
- inOrder.verify(callback2).execute(eq(mTransactionHandler), any());
- inOrder.verify(mActivityLifecycleItem).execute(eq(mTransactionHandler), eq(mClientRecord),
- any());
+ testTransactionResolutionInner();
}
- @Test
- public void testExecuteTransactionItems_transactionResolution() {
+ private void testTransactionResolutionInner() {
ClientTransactionItem callback1 = mock(ClientTransactionItem.class);
when(callback1.getPostExecutionState()).thenReturn(UNDEFINED);
ClientTransactionItem callback2 = mock(ClientTransactionItem.class);
@@ -286,38 +284,19 @@ public class TransactionExecutorTests {
@Test
public void testDoNotLaunchDestroyedActivity() {
- final Map<IBinder, DestroyActivityItem> activitiesToBeDestroyed = new ArrayMap<>();
- when(mTransactionHandler.getActivitiesToBeDestroyed()).thenReturn(activitiesToBeDestroyed);
- // Assume launch transaction is still in queue, so there is no client record.
- when(mTransactionHandler.getActivityClient(any())).thenReturn(null);
-
- // An incoming destroy transaction enters binder thread (preExecute).
- final IBinder token = mock(IBinder.class);
- final ClientTransaction destroyTransaction = ClientTransaction.obtain(null /* client */);
- destroyTransaction.setLifecycleStateRequest(
- DestroyActivityItem.obtain(token, false /* finished */, 0 /* configChanges */));
- destroyTransaction.preExecute(mTransactionHandler);
- // The activity should be added to to-be-destroyed container.
- assertEquals(1, activitiesToBeDestroyed.size());
+ mSetFlagsRule.disableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG);
- // A previous queued launch transaction runs on main thread (execute).
- final ClientTransaction launchTransaction = ClientTransaction.obtain(null /* client */);
- final LaunchActivityItem launchItem =
- spy(new LaunchActivityItemBuilder(token, new Intent(), new ActivityInfo()).build());
- launchTransaction.addCallback(launchItem);
- mExecutor.execute(launchTransaction);
+ testDoNotLaunchDestroyedActivityInner();
+ }
- // The launch transaction should not be executed because its token is in the
- // to-be-destroyed container.
- verify(launchItem, never()).execute(any(), any());
+ @Test
+ public void testDoNotLaunchDestroyedActivity_bundleClientTransaction() {
+ mSetFlagsRule.enableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG);
- // After the destroy transaction has been executed, the token should be removed.
- mExecutor.execute(destroyTransaction);
- assertTrue(activitiesToBeDestroyed.isEmpty());
+ testDoNotLaunchDestroyedActivityInner();
}
- @Test
- public void testExecuteTransactionItems_doNotLaunchDestroyedActivity() {
+ private void testDoNotLaunchDestroyedActivityInner() {
final Map<IBinder, DestroyActivityItem> activitiesToBeDestroyed = new ArrayMap<>();
when(mTransactionHandler.getActivitiesToBeDestroyed()).thenReturn(activitiesToBeDestroyed);
// Assume launch transaction is still in queue, so there is no client record.
@@ -350,26 +329,19 @@ public class TransactionExecutorTests {
@Test
public void testActivityResultRequiredStateResolution() {
- when(mTransactionHandler.getActivity(any())).thenReturn(mock(Activity.class));
-
- PostExecItem postExecItem = new PostExecItem(ON_RESUME);
+ mSetFlagsRule.disableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG);
- ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
- transaction.addCallback(postExecItem);
+ testActivityResultRequiredStateResolutionInner();
+ }
- // Verify resolution that should get to onPause
- mClientRecord.setState(ON_RESUME);
- mExecutor.executeCallbacks(transaction);
- verify(mExecutor).cycleToPath(eq(mClientRecord), eq(ON_PAUSE), eq(transaction));
+ @Test
+ public void testActivityResultRequiredStateResolution_bundleClientTransaction() {
+ mSetFlagsRule.enableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG);
- // Verify resolution that should get to onStart
- mClientRecord.setState(ON_STOP);
- mExecutor.executeCallbacks(transaction);
- verify(mExecutor).cycleToPath(eq(mClientRecord), eq(ON_START), eq(transaction));
+ testActivityResultRequiredStateResolutionInner();
}
- @Test
- public void testExecuteTransactionItems_activityResultRequiredStateResolution() {
+ private void testActivityResultRequiredStateResolutionInner() {
when(mTransactionHandler.getActivity(any())).thenReturn(mock(Activity.class));
PostExecItem postExecItem = new PostExecItem(ON_RESUME);
@@ -379,12 +351,12 @@ public class TransactionExecutorTests {
// Verify resolution that should get to onPause
mClientRecord.setState(ON_RESUME);
- mExecutor.executeTransactionItems(transaction);
+ mExecutor.execute(transaction);
verify(mExecutor).cycleToPath(eq(mClientRecord), eq(ON_PAUSE), eq(transaction));
// Verify resolution that should get to onStart
mClientRecord.setState(ON_STOP);
- mExecutor.executeTransactionItems(transaction);
+ mExecutor.execute(transaction);
verify(mExecutor).cycleToPath(eq(mClientRecord), eq(ON_START), eq(transaction));
}
@@ -523,18 +495,19 @@ public class TransactionExecutorTests {
@Test(expected = IllegalArgumentException.class)
public void testActivityItemNullRecordThrowsException() {
- final ActivityTransactionItem activityItem = mock(ActivityTransactionItem.class);
- when(activityItem.getPostExecutionState()).thenReturn(UNDEFINED);
- final IBinder token = mock(IBinder.class);
- final ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
- transaction.addCallback(activityItem);
- when(mTransactionHandler.getActivityClient(token)).thenReturn(null);
+ mSetFlagsRule.disableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG);
- mExecutor.executeCallbacks(transaction);
+ testActivityItemNullRecordThrowsExceptionInner();
}
@Test(expected = IllegalArgumentException.class)
- public void testExecuteTransactionItems_activityItemNullRecordThrowsException() {
+ public void testActivityItemNullRecordThrowsException_bundleClientTransaction() {
+ mSetFlagsRule.enableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG);
+
+ testActivityItemNullRecordThrowsExceptionInner();
+ }
+
+ private void testActivityItemNullRecordThrowsExceptionInner() {
final ActivityTransactionItem activityItem = mock(ActivityTransactionItem.class);
when(activityItem.getPostExecutionState()).thenReturn(UNDEFINED);
final IBinder token = mock(IBinder.class);
@@ -542,28 +515,24 @@ public class TransactionExecutorTests {
transaction.addTransactionItem(activityItem);
when(mTransactionHandler.getActivityClient(token)).thenReturn(null);
- mExecutor.executeTransactionItems(transaction);
+ mExecutor.execute(transaction);
}
@Test
public void testActivityItemExecute() {
- final ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
- final ActivityTransactionItem activityItem = mock(ActivityTransactionItem.class);
- when(activityItem.getPostExecutionState()).thenReturn(UNDEFINED);
- when(activityItem.getActivityToken()).thenReturn(mActivityToken);
- transaction.addCallback(activityItem);
- transaction.setLifecycleStateRequest(mActivityLifecycleItem);
-
- mExecutor.execute(transaction);
+ mSetFlagsRule.disableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG);
- final InOrder inOrder = inOrder(activityItem, mActivityLifecycleItem);
- inOrder.verify(activityItem).execute(eq(mTransactionHandler), eq(mClientRecord), any());
- inOrder.verify(mActivityLifecycleItem).execute(eq(mTransactionHandler), eq(mClientRecord),
- any());
+ testActivityItemExecuteInner();
}
@Test
- public void testExecuteTransactionItems_activityItemExecute() {
+ public void testActivityItemExecute_bundleClientTransaction() {
+ mSetFlagsRule.enableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG);
+
+ testActivityItemExecuteInner();
+ }
+
+ private void testActivityItemExecuteInner() {
final ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
final ActivityTransactionItem activityItem = mock(ActivityTransactionItem.class);
when(activityItem.getPostExecutionState()).thenReturn(UNDEFINED);
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
index c30d216208f9..aa8001310008 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -20,6 +20,9 @@ import static android.app.servertransaction.TestUtils.config;
import static android.app.servertransaction.TestUtils.mergedConfig;
import static android.app.servertransaction.TestUtils.referrerIntentList;
import static android.app.servertransaction.TestUtils.resultInfoList;
+import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
+
+import static com.android.window.flags.Flags.FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG;
import static org.junit.Assert.assertEquals;
@@ -36,11 +39,13 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.os.PersistableBundle;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
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;
@@ -60,6 +65,9 @@ import java.util.ArrayList;
@Presubmit
public class TransactionParcelTests {
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
+
private Parcel mParcel;
private IBinder mActivityToken;
@@ -275,6 +283,8 @@ public class TransactionParcelTests {
@Test
public void testClientTransaction() {
+ mSetFlagsRule.enableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG);
+
// Write to parcel
NewIntentItem callback1 = NewIntentItem.obtain(mActivityToken, new ArrayList<>(), true);
ActivityConfigurationChangeItem callback2 = ActivityConfigurationChangeItem.obtain(
@@ -300,14 +310,16 @@ public class TransactionParcelTests {
@Test
public void testClientTransactionCallbacksOnly() {
+ mSetFlagsRule.disableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG);
+
// Write to parcel
NewIntentItem callback1 = NewIntentItem.obtain(mActivityToken, new ArrayList<>(), true);
ActivityConfigurationChangeItem callback2 = ActivityConfigurationChangeItem.obtain(
mActivityToken, config());
ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
- transaction.addCallback(callback1);
- transaction.addCallback(callback2);
+ transaction.addTransactionItem(callback1);
+ transaction.addTransactionItem(callback2);
writeAndPrepareForReading(transaction);
@@ -321,12 +333,14 @@ public class TransactionParcelTests {
@Test
public void testClientTransactionLifecycleOnly() {
+ mSetFlagsRule.disableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG);
+
// Write to parcel
StopActivityItem lifecycleRequest = StopActivityItem.obtain(mActivityToken,
78 /* configChanges */);
ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
- transaction.setLifecycleStateRequest(lifecycleRequest);
+ transaction.addTransactionItem(lifecycleRequest);
writeAndPrepareForReading(transaction);
diff --git a/core/tests/coretests/src/android/content/ContextTest.java b/core/tests/coretests/src/android/content/ContextTest.java
index d4784374745d..a02af788d496 100644
--- a/core/tests/coretests/src/android/content/ContextTest.java
+++ b/core/tests/coretests/src/android/content/ContextTest.java
@@ -26,6 +26,7 @@ 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.assertNotNull;
import static org.junit.Assert.assertTrue;
import android.app.ActivityThread;
@@ -35,7 +36,9 @@ import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
import android.media.ImageReader;
import android.os.UserHandle;
+import android.platform.test.annotations.DisabledOnRavenwood;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.ravenwood.RavenwoodRule;
import android.view.Display;
import androidx.test.core.app.ApplicationProvider;
@@ -43,6 +46,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -54,7 +58,23 @@ import org.junit.runner.RunWith;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class ContextTest {
+ @Rule
+ public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder().build();
+
+ @Test
+ public void testInstrumentationContext() {
+ // Confirm that we have a valid Context
+ assertNotNull(InstrumentationRegistry.getInstrumentation().getContext());
+ }
+
+ @Test
+ public void testInstrumentationTargetContext() {
+ // Confirm that we have a valid Context
+ assertNotNull(InstrumentationRegistry.getInstrumentation().getTargetContext());
+ }
+
@Test
+ @DisabledOnRavenwood(blockedBy = Context.class)
public void testDisplayIdForSystemContext() {
final Context systemContext =
ActivityThread.currentActivityThread().getSystemContext();
@@ -63,6 +83,7 @@ public class ContextTest {
}
@Test
+ @DisabledOnRavenwood(blockedBy = Context.class)
public void testDisplayIdForSystemUiContext() {
final Context systemUiContext =
ActivityThread.currentActivityThread().getSystemUiContext();
@@ -71,6 +92,7 @@ public class ContextTest {
}
@Test
+ @DisabledOnRavenwood(blockedBy = Context.class)
public void testDisplayIdForTestContext() {
final Context testContext =
InstrumentationRegistry.getInstrumentation().getTargetContext();
@@ -79,6 +101,7 @@ public class ContextTest {
}
@Test
+ @DisabledOnRavenwood(blockedBy = Context.class)
public void testDisplayIdForDefaultDisplayContext() {
final Context testContext =
InstrumentationRegistry.getInstrumentation().getTargetContext();
@@ -91,6 +114,7 @@ public class ContextTest {
}
@Test(expected = NullPointerException.class)
+ @DisabledOnRavenwood(blockedBy = Context.class)
public void testStartActivityAsUserNullIntentNullUser() {
final Context testContext =
InstrumentationRegistry.getInstrumentation().getTargetContext();
@@ -98,6 +122,7 @@ public class ContextTest {
}
@Test(expected = NullPointerException.class)
+ @DisabledOnRavenwood(blockedBy = Context.class)
public void testStartActivityAsUserNullIntentNonNullUser() {
final Context testContext =
InstrumentationRegistry.getInstrumentation().getTargetContext();
@@ -105,6 +130,7 @@ public class ContextTest {
}
@Test(expected = NullPointerException.class)
+ @DisabledOnRavenwood(blockedBy = Context.class)
public void testStartActivityAsUserNonNullIntentNullUser() {
final Context testContext =
InstrumentationRegistry.getInstrumentation().getTargetContext();
@@ -112,6 +138,7 @@ public class ContextTest {
}
@Test(expected = RuntimeException.class)
+ @DisabledOnRavenwood(blockedBy = Context.class)
public void testStartActivityAsUserNonNullIntentNonNullUser() {
final Context testContext =
InstrumentationRegistry.getInstrumentation().getTargetContext();
@@ -119,6 +146,7 @@ public class ContextTest {
}
@Test
+ @DisabledOnRavenwood(blockedBy = Context.class)
public void testIsUiContext_appContext_returnsFalse() {
final Context appContext = ApplicationProvider.getApplicationContext();
@@ -126,6 +154,7 @@ public class ContextTest {
}
@Test
+ @DisabledOnRavenwood(blockedBy = Context.class)
public void testIsUiContext_systemContext_returnsTrue() {
final Context systemContext =
ActivityThread.currentActivityThread().getSystemContext();
@@ -134,6 +163,7 @@ public class ContextTest {
}
@Test
+ @DisabledOnRavenwood(blockedBy = Context.class)
public void testIsUiContext_systemUiContext_returnsTrue() {
final Context systemUiContext =
ActivityThread.currentActivityThread().getSystemUiContext();
@@ -142,11 +172,13 @@ public class ContextTest {
}
@Test
+ @DisabledOnRavenwood(blockedBy = Context.class)
public void testGetDisplayFromDisplayContextDerivedContextOnPrimaryDisplay() {
verifyGetDisplayFromDisplayContextDerivedContext(false /* onSecondaryDisplay */);
}
@Test
+ @DisabledOnRavenwood(blockedBy = Context.class)
public void testGetDisplayFromDisplayContextDerivedContextOnSecondaryDisplay() {
verifyGetDisplayFromDisplayContextDerivedContext(true /* onSecondaryDisplay */);
}
@@ -179,6 +211,7 @@ public class ContextTest {
}
@Test
+ @DisabledOnRavenwood(blockedBy = Context.class)
public void testIsUiContext_ContextWrapper() {
ContextWrapper wrapper = new ContextWrapper(null /* base */);
@@ -190,6 +223,7 @@ public class ContextTest {
}
@Test
+ @DisabledOnRavenwood(blockedBy = Context.class)
public void testIsUiContext_UiContextDerivedContext() {
final Context uiContext = createUiContext();
Context context = uiContext.createAttributionContext(null /* attributionTag */);
@@ -202,6 +236,7 @@ public class ContextTest {
}
@Test
+ @DisabledOnRavenwood(blockedBy = Context.class)
public void testIsUiContext_UiContextDerivedDisplayContext() {
final Context uiContext = createUiContext();
final Display secondaryDisplay =
@@ -212,6 +247,7 @@ public class ContextTest {
}
@Test
+ @DisabledOnRavenwood(blockedBy = Context.class)
public void testDeviceIdForSystemContext() {
final Context systemContext =
ActivityThread.currentActivityThread().getSystemContext();
@@ -220,6 +256,7 @@ public class ContextTest {
}
@Test
+ @DisabledOnRavenwood(blockedBy = Context.class)
public void testDeviceIdForSystemUiContext() {
final Context systemUiContext =
ActivityThread.currentActivityThread().getSystemUiContext();
@@ -228,6 +265,7 @@ public class ContextTest {
}
@Test
+ @DisabledOnRavenwood(blockedBy = Context.class)
public void testDeviceIdForTestContext() {
final Context testContext =
InstrumentationRegistry.getInstrumentation().getTargetContext();
diff --git a/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java b/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java
index d816d0853ab6..34f5841aef72 100644
--- a/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java
+++ b/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java
@@ -194,13 +194,13 @@ public class FaceManagerTest {
new CancellationSignal(), mEnrollmentCallback, null /* disabledFeatures */);
verify(mService).enroll(eq(USER_ID), any(), any(), any(), anyString(), any(), any(),
- anyBoolean());
+ anyBoolean(), any());
mFaceManager.enroll(USER_ID, new byte[]{},
new CancellationSignal(), mEnrollmentCallback, null /* disabledFeatures */);
verify(mService, atMost(1 /* maxNumberOfInvocations */)).enroll(eq(USER_ID), any(), any(),
- any(), anyString(), any(), any(), anyBoolean());
+ any(), anyString(), any(), any(), anyBoolean(), any());
verify(mEnrollmentCallback).onEnrollmentError(eq(FACE_ERROR_HW_UNAVAILABLE), anyString());
}
@@ -213,7 +213,7 @@ public class FaceManagerTest {
verify(mEnrollmentCallback).onEnrollmentError(eq(FACE_ERROR_UNABLE_TO_PROCESS),
anyString());
verify(mService, never()).enroll(eq(USER_ID), any(), any(),
- any(), anyString(), any(), any(), anyBoolean());
+ any(), anyString(), any(), any(), anyBoolean(), any());
}
@Test
diff --git a/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java b/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java
index 70313b8c9ea7..ce7d6a95c2f4 100644
--- a/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java
+++ b/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java
@@ -162,11 +162,13 @@ public class FingerprintManagerTest {
mCaptor.getValue().onAllAuthenticatorsRegistered(mProps);
mFingerprintManager.enroll(null, new CancellationSignal(), USER_ID,
- mEnrollCallback, FingerprintManager.ENROLL_ENROLL);
+ mEnrollCallback, FingerprintManager.ENROLL_ENROLL,
+ (new FingerprintEnrollOptions.Builder()).build());
verify(mEnrollCallback).onEnrollmentError(eq(FINGERPRINT_ERROR_UNABLE_TO_PROCESS),
anyString());
- verify(mService, never()).enroll(any(), any(), anyInt(), any(), anyString(), anyInt());
+ verify(mService, never()).enroll(any(), any(), anyInt(), any(), anyString(), anyInt(),
+ any());
}
@Test
diff --git a/core/tests/coretests/src/android/text/LayoutTest.java b/core/tests/coretests/src/android/text/LayoutTest.java
index 5649e7118fd5..f60eff69690a 100644
--- a/core/tests/coretests/src/android/text/LayoutTest.java
+++ b/core/tests/coretests/src/android/text/LayoutTest.java
@@ -16,6 +16,11 @@
package android.text;
+import static com.android.graphics.hwui.flags.Flags.FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -26,11 +31,16 @@ import static org.junit.Assert.fail;
import android.graphics.Bitmap;
import android.graphics.Canvas;
+import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.RectF;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.text.Layout.Alignment;
import android.text.style.StrikethroughSpan;
@@ -38,6 +48,7 @@ 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;
@@ -49,6 +60,9 @@ import java.util.Locale;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class LayoutTest {
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
private static final int LINE_COUNT = 5;
private static final int LINE_HEIGHT = 12;
private static final int LINE_DESCENT = 4;
@@ -638,22 +652,268 @@ public class LayoutTest {
}
}
- private final class MockCanvas extends Canvas {
+ @Test
+ @RequiresFlagsEnabled(FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT)
+ public void highContrastTextEnabled_testDrawSelectionAndHighlight_drawsHighContrastSelectionAndHighlight() {
+ Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth,
+ mAlign, mSpacingMult, mSpacingAdd);
+
+ List<Path> highlightPaths = new ArrayList<>();
+ List<Paint> highlightPaints = new ArrayList<>();
+
+ Path selectionPath = new Path();
+ RectF selectionRect = new RectF(0f, 0f, mWidth / 2f, LINE_HEIGHT);
+ selectionPath.addRect(selectionRect, Path.Direction.CW);
+ highlightPaths.add(selectionPath);
+
+ Paint selectionPaint = new Paint();
+ selectionPaint.setColor(Color.CYAN);
+ highlightPaints.add(selectionPaint);
+
+ final int width = 256;
+ final int height = 256;
+ MockCanvas c = new MockCanvas(width, height);
+ c.setHighContrastTextEnabled(true);
+ layout.draw(c, highlightPaths, highlightPaints, selectionPath, selectionPaint,
+ /* cursorOffsetVertical= */ 0);
+ List<MockCanvas.DrawCommand> drawCommands = c.getDrawCommands();
+ var textsDrawn = LINE_COUNT;
+ var highlightsDrawn = 2;
+ assertThat(drawCommands.size()).isEqualTo(textsDrawn + highlightsDrawn);
+
+ var highlightsFound = 0;
+ var curLineIndex = 0;
+ for (int i = 0; i < drawCommands.size(); i++) {
+ MockCanvas.DrawCommand drawCommand = drawCommands.get(i);
+
+ if (drawCommand.path != null) {
+ assertThat(drawCommand.path).isEqualTo(selectionPath);
+ assertThat(drawCommand.paint.getColor()).isEqualTo(Color.YELLOW);
+ assertThat(drawCommand.paint.getBlendMode()).isNotNull();
+ highlightsFound++;
+ } else if (drawCommand.text != null) {
+ int start = layout.getLineStart(curLineIndex);
+ int end = layout.getLineEnd(curLineIndex);
+ assertEquals(LAYOUT_TEXT.toString().substring(start, end), drawCommand.text);
+ curLineIndex++;
+
+ assertWithMessage("highlight is drawn on top of text")
+ .that(highlightsFound).isEqualTo(0);
+ }
+ }
+
+ assertThat(highlightsFound).isEqualTo(2);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT)
+ public void highContrastTextEnabled_testDrawHighlight_drawsHighContrastHighlight() {
+ Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth,
+ mAlign, mSpacingMult, mSpacingAdd);
+
+ List<Path> highlightPaths = new ArrayList<>();
+ List<Paint> highlightPaints = new ArrayList<>();
+
+ Path selectionPath = new Path();
+ RectF selectionRect = new RectF(0f, 0f, mWidth / 2f, LINE_HEIGHT);
+ selectionPath.addRect(selectionRect, Path.Direction.CW);
+ highlightPaths.add(selectionPath);
+
+ Paint selectionPaint = new Paint();
+ selectionPaint.setColor(Color.CYAN);
+ highlightPaints.add(selectionPaint);
+
+ final int width = 256;
+ final int height = 256;
+ MockCanvas c = new MockCanvas(width, height);
+ c.setHighContrastTextEnabled(true);
+ layout.draw(c, highlightPaths, highlightPaints, /* selectionPath= */ null,
+ /* selectionPaint= */ null, /* cursorOffsetVertical= */ 0);
+ List<MockCanvas.DrawCommand> drawCommands = c.getDrawCommands();
+ var textsDrawn = LINE_COUNT;
+ var highlightsDrawn = 1;
+ assertThat(drawCommands.size()).isEqualTo(textsDrawn + highlightsDrawn);
+
+ var highlightsFound = 0;
+ var curLineIndex = 0;
+ for (int i = 0; i < drawCommands.size(); i++) {
+ MockCanvas.DrawCommand drawCommand = drawCommands.get(i);
+
+ if (drawCommand.path != null) {
+ assertThat(drawCommand.path).isEqualTo(selectionPath);
+ assertThat(drawCommand.paint.getColor()).isEqualTo(Color.YELLOW);
+ assertThat(drawCommand.paint.getBlendMode()).isNotNull();
+ highlightsFound++;
+ } else if (drawCommand.text != null) {
+ int start = layout.getLineStart(curLineIndex);
+ int end = layout.getLineEnd(curLineIndex);
+ assertEquals(LAYOUT_TEXT.toString().substring(start, end), drawCommand.text);
+ curLineIndex++;
+
+ assertWithMessage("highlight is drawn on top of text")
+ .that(highlightsFound).isEqualTo(0);
+ }
+ }
+
+ assertThat(highlightsFound).isEqualTo(1);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT)
+ public void highContrastTextDisabledByDefault_testDrawHighlight_drawsNormalHighlightBehind() {
+ Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth,
+ mAlign, mSpacingMult, mSpacingAdd);
+
+ List<Path> highlightPaths = new ArrayList<>();
+ List<Paint> highlightPaints = new ArrayList<>();
+
+ Path selectionPath = new Path();
+ RectF selectionRect = new RectF(0f, 0f, mWidth / 2f, LINE_HEIGHT);
+ selectionPath.addRect(selectionRect, Path.Direction.CW);
+ highlightPaths.add(selectionPath);
+
+ Paint selectionPaint = new Paint();
+ selectionPaint.setColor(Color.CYAN);
+ highlightPaints.add(selectionPaint);
+
+ final int width = 256;
+ final int height = 256;
+ MockCanvas c = new MockCanvas(width, height);
+ layout.draw(c, highlightPaths, highlightPaints, /* selectionPath= */ null,
+ /* selectionPaint= */ null, /* cursorOffsetVertical= */ 0);
+ List<MockCanvas.DrawCommand> drawCommands = c.getDrawCommands();
+ var textsDrawn = LINE_COUNT;
+ var highlightsDrawn = 1;
+ assertThat(drawCommands.size()).isEqualTo(textsDrawn + highlightsDrawn);
+
+ var highlightsFound = 0;
+ var curLineIndex = 0;
+ for (int i = 0; i < drawCommands.size(); i++) {
+ MockCanvas.DrawCommand drawCommand = drawCommands.get(i);
+
+ if (drawCommand.path != null) {
+ assertThat(drawCommand.path).isEqualTo(selectionPath);
+ assertThat(drawCommand.paint.getColor()).isEqualTo(Color.CYAN);
+ assertThat(drawCommand.paint.getBlendMode()).isNull();
+ highlightsFound++;
+ } else if (drawCommand.text != null) {
+ int start = layout.getLineStart(curLineIndex);
+ int end = layout.getLineEnd(curLineIndex);
+ assertEquals(LAYOUT_TEXT.toString().substring(start, end), drawCommand.text);
+ curLineIndex++;
+
+ assertWithMessage("highlight is drawn behind text")
+ .that(highlightsFound).isGreaterThan(0);
+ }
+ }
+
+ assertThat(highlightsFound).isEqualTo(1);
+ }
+
+ @Test
+ @RequiresFlagsDisabled(FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT)
+ public void highContrastTextEnabledButFlagOff_testDrawHighlight_drawsNormalHighlightBehind() {
+ Layout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth,
+ mAlign, mSpacingMult, mSpacingAdd);
+
+ List<Path> highlightPaths = new ArrayList<>();
+ List<Paint> highlightPaints = new ArrayList<>();
+
+ Path selectionPath = new Path();
+ RectF selectionRect = new RectF(0f, 0f, mWidth / 2f, LINE_HEIGHT);
+ selectionPath.addRect(selectionRect, Path.Direction.CW);
+ highlightPaths.add(selectionPath);
+
+ Paint selectionPaint = new Paint();
+ selectionPaint.setColor(Color.CYAN);
+ highlightPaints.add(selectionPaint);
+
+ final int width = 256;
+ final int height = 256;
+ MockCanvas c = new MockCanvas(width, height);
+ c.setHighContrastTextEnabled(true);
+ layout.draw(c, highlightPaths, highlightPaints, /* selectionPath= */ null,
+ /* selectionPaint= */ null, /* cursorOffsetVertical= */ 0);
+ List<MockCanvas.DrawCommand> drawCommands = c.getDrawCommands();
+ var textsDrawn = LINE_COUNT;
+ var highlightsDrawn = 1;
+ assertThat(drawCommands.size()).isEqualTo(textsDrawn + highlightsDrawn);
- class DrawCommand {
+ var highlightsFound = 0;
+ var curLineIndex = 0;
+ for (int i = 0; i < drawCommands.size(); i++) {
+ MockCanvas.DrawCommand drawCommand = drawCommands.get(i);
+
+ if (drawCommand.path != null) {
+ assertThat(drawCommand.path).isEqualTo(selectionPath);
+ assertThat(drawCommand.paint.getColor()).isEqualTo(Color.CYAN);
+ assertThat(drawCommand.paint.getBlendMode()).isNull();
+ highlightsFound++;
+ } else if (drawCommand.text != null) {
+ int start = layout.getLineStart(curLineIndex);
+ int end = layout.getLineEnd(curLineIndex);
+ assertEquals(LAYOUT_TEXT.toString().substring(start, end), drawCommand.text);
+ curLineIndex++;
+
+ assertWithMessage("highlight is drawn behind text")
+ .that(highlightsFound).isGreaterThan(0);
+ }
+ }
+
+ assertThat(highlightsFound).isEqualTo(1);
+ }
+
+ @Test
+ public void mockCanvasHighContrastOverridesCorrectly() {
+ var canvas = new MockCanvas(100, 100);
+
+ assertThat(canvas.isHighContrastTextEnabled()).isFalse();
+ canvas.setHighContrastTextEnabled(true);
+ assertThat(canvas.isHighContrastTextEnabled()).isTrue();
+ canvas.setHighContrastTextEnabled(false);
+ assertThat(canvas.isHighContrastTextEnabled()).isFalse();
+ }
+
+ private static final class MockCanvas extends Canvas {
+
+ static class DrawCommand {
public final String text;
public final float x;
public final float y;
+ public final Path path;
+ public final Paint paint;
- DrawCommand(String text, float x, float y) {
+ DrawCommand(String text, float x, float y, Paint paint) {
this.text = text;
this.x = x;
this.y = y;
+ this.paint = paint;
+ path = null;
+ }
+
+ DrawCommand(Path path, Paint paint) {
+ this.path = path;
+ this.paint = paint;
+ y = 0;
+ x = 0;
+ text = null;
}
}
List<DrawCommand> mDrawCommands;
+ private Boolean mIsHighContrastTextOverride = null;
+
+ public void setHighContrastTextEnabled(boolean enabled) {
+ mIsHighContrastTextOverride = enabled;
+ }
+
+ @Override
+ public boolean isHighContrastTextEnabled() {
+ return mIsHighContrastTextOverride == null ? super.isHighContrastTextEnabled()
+ : mIsHighContrastTextOverride;
+ }
+
MockCanvas(int width, int height) {
super();
mDrawCommands = new ArrayList<>();
@@ -666,7 +926,7 @@ public class LayoutTest {
@Override
public void drawText(String text, int start, int end, float x, float y, Paint p) {
- mDrawCommands.add(new DrawCommand(text.substring(start, end), x, y));
+ mDrawCommands.add(new DrawCommand(text.substring(start, end), x, y, p));
}
@Override
@@ -676,7 +936,7 @@ public class LayoutTest {
@Override
public void drawText(char[] text, int index, int count, float x, float y, Paint p) {
- mDrawCommands.add(new DrawCommand(new String(text, index, count), x, y));
+ mDrawCommands.add(new DrawCommand(new String(text, index, count), x, y, p));
}
@Override
@@ -691,6 +951,11 @@ public class LayoutTest {
drawText(text, index, count, x, y, paint);
}
+ @Override
+ public void drawPath(Path path, Paint p) {
+ mDrawCommands.add(new DrawCommand(path, p));
+ }
+
List<DrawCommand> getDrawCommands() {
return mDrawCommands;
}
diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassificationConstantsTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassificationConstantsTest.java
index a567b4bd5a48..20a876809ba0 100644
--- a/core/tests/coretests/src/android/view/textclassifier/TextClassificationConstantsTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/TextClassificationConstantsTest.java
@@ -28,6 +28,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.function.Consumer;
+import java.util.function.Predicate;
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -74,6 +75,13 @@ public class TextClassificationConstantsTest {
.isEqualTo(1));
}
+ @Test
+ public void runtimeMutableSettings() {
+ assertOverride(
+ TextClassificationConstants.SYSTEM_TEXT_CLASSIFIER_ENABLED,
+ settings -> settings.isSystemTextClassifierEnabled());
+ }
+
private static void assertSettings(
String key, String value, Consumer<TextClassificationConstants> settingsConsumer) {
final String originalValue =
@@ -87,6 +95,21 @@ public class TextClassificationConstantsTest {
}
}
+ private static void assertOverride(
+ String key, Predicate<TextClassificationConstants> settingsPredicate) {
+ final String originalValue =
+ DeviceConfig.getProperty(DeviceConfig.NAMESPACE_TEXTCLASSIFIER, key);
+ TextClassificationConstants settings = new TextClassificationConstants();
+ try {
+ setDeviceConfig(key, "true");
+ assertThat(settingsPredicate.test(settings)).isTrue();
+ setDeviceConfig(key, "false");
+ assertThat(settingsPredicate.test(settings)).isFalse();
+ } finally {
+ setDeviceConfig(key, originalValue);
+ }
+ }
+
private static void setDeviceConfig(String key, String value) {
DeviceConfig.setProperty(
DeviceConfig.NAMESPACE_TEXTCLASSIFIER, key, value, /* makeDefault */ false);
diff --git a/core/tests/vibrator/src/android/os/vibrator/PrebakedSegmentTest.java b/core/tests/vibrator/src/android/os/vibrator/PrebakedSegmentTest.java
index 4f5f3c0ddeaf..7dd9e55f8f3e 100644
--- a/core/tests/vibrator/src/android/os/vibrator/PrebakedSegmentTest.java
+++ b/core/tests/vibrator/src/android/os/vibrator/PrebakedSegmentTest.java
@@ -106,6 +106,13 @@ public class PrebakedSegmentTest {
}
@Test
+ public void testScaleLinearly_ignoresAndReturnsSameEffect() {
+ PrebakedSegment prebaked = new PrebakedSegment(
+ VibrationEffect.EFFECT_CLICK, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM);
+ assertSame(prebaked, prebaked.scaleLinearly(0.5f));
+ }
+
+ @Test
public void testDuration() {
assertEquals(-1, new PrebakedSegment(
VibrationEffect.EFFECT_CLICK, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM)
diff --git a/core/tests/vibrator/src/android/os/vibrator/PrimitiveSegmentTest.java b/core/tests/vibrator/src/android/os/vibrator/PrimitiveSegmentTest.java
index ec5a084c2ddb..e9a08aef4856 100644
--- a/core/tests/vibrator/src/android/os/vibrator/PrimitiveSegmentTest.java
+++ b/core/tests/vibrator/src/android/os/vibrator/PrimitiveSegmentTest.java
@@ -129,6 +129,27 @@ public class PrimitiveSegmentTest {
}
@Test
+ public void testScaleLinearly() {
+ PrimitiveSegment initial = new PrimitiveSegment(
+ VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0);
+
+ assertEquals(1f, initial.scaleLinearly(1).getScale(), TOLERANCE);
+ assertEquals(0.5f, initial.scaleLinearly(0.5f).getScale(), TOLERANCE);
+ assertEquals(1f, initial.scaleLinearly(1.5f).getScale(), TOLERANCE);
+ assertEquals(0.8f, initial.scaleLinearly(0.8f).getScale(), TOLERANCE);
+ // Restores back to the exact original value since this is a linear scaling.
+ assertEquals(1f, initial.scaleLinearly(0.8f).scaleLinearly(1.25f).getScale(), TOLERANCE);
+
+ initial = new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_CLICK, 0, 0);
+
+ assertEquals(0f, initial.scaleLinearly(1).getScale(), TOLERANCE);
+ assertEquals(0f, initial.scaleLinearly(0.5f).getScale(), TOLERANCE);
+ assertEquals(0f, initial.scaleLinearly(1.5f).getScale(), TOLERANCE);
+ assertEquals(0f, initial.scaleLinearly(1.5f).scaleLinearly(2 / 3f).getScale(), TOLERANCE);
+ assertEquals(0f, initial.scaleLinearly(0.8f).scaleLinearly(1.25f).getScale(), TOLERANCE);
+ }
+
+ @Test
public void testDuration() {
assertEquals(-1, new PrimitiveSegment(
VibrationEffect.Composition.PRIMITIVE_NOOP, 1, 10).getDuration());
diff --git a/core/tests/vibrator/src/android/os/vibrator/RampSegmentTest.java b/core/tests/vibrator/src/android/os/vibrator/RampSegmentTest.java
index 5caa86bb9fb5..01013ab69f85 100644
--- a/core/tests/vibrator/src/android/os/vibrator/RampSegmentTest.java
+++ b/core/tests/vibrator/src/android/os/vibrator/RampSegmentTest.java
@@ -131,6 +131,37 @@ public class RampSegmentTest {
}
@Test
+ public void testScaleLinearly() {
+ RampSegment initial = new RampSegment(0, 1, 0, 0, 0);
+
+ assertEquals(0f, initial.scaleLinearly(1f).getStartAmplitude(), TOLERANCE);
+ assertEquals(0f, initial.scaleLinearly(0.5f).getStartAmplitude(), TOLERANCE);
+ assertEquals(0f, initial.scaleLinearly(1.5f).getStartAmplitude(), TOLERANCE);
+ assertEquals(0f, initial.scaleLinearly(1.5f).scaleLinearly(2 / 3f).getStartAmplitude(),
+ TOLERANCE);
+ assertEquals(0f, initial.scaleLinearly(0.8f).scaleLinearly(1.25f).getStartAmplitude(),
+ TOLERANCE);
+
+ assertEquals(1f, initial.scaleLinearly(1f).getEndAmplitude(), TOLERANCE);
+ assertEquals(0.5f, initial.scaleLinearly(0.5f).getEndAmplitude(), TOLERANCE);
+ assertEquals(1f, initial.scaleLinearly(1.5f).getEndAmplitude(), TOLERANCE);
+ assertEquals(0.8f, initial.scaleLinearly(0.8f).getEndAmplitude(), TOLERANCE);
+ // Restores back to the exact original value since this is a linear scaling.
+ assertEquals(0.8f, initial.scaleLinearly(1.5f).scaleLinearly(0.8f).getEndAmplitude(),
+ TOLERANCE);
+
+ initial = new RampSegment(0.5f, 1, 0, 0, 0);
+
+ assertEquals(0.5f, initial.scaleLinearly(1).getStartAmplitude(), TOLERANCE);
+ assertEquals(0.25f, initial.scaleLinearly(0.5f).getStartAmplitude(), TOLERANCE);
+ assertEquals(0.75f, initial.scaleLinearly(1.5f).getStartAmplitude(), TOLERANCE);
+ assertEquals(0.4f, initial.scaleLinearly(0.8f).getStartAmplitude(), TOLERANCE);
+ // Restores back to the exact original value since this is a linear scaling.
+ assertEquals(0.5f, initial.scaleLinearly(0.8f).scaleLinearly(1.25f).getStartAmplitude(),
+ TOLERANCE);
+ }
+
+ @Test
public void testDuration() {
assertEquals(10, new RampSegment(0.5f, 1, 0, 0, 10).getDuration());
}
diff --git a/core/tests/vibrator/src/android/os/vibrator/StepSegmentTest.java b/core/tests/vibrator/src/android/os/vibrator/StepSegmentTest.java
index 44db30603089..40776abc0291 100644
--- a/core/tests/vibrator/src/android/os/vibrator/StepSegmentTest.java
+++ b/core/tests/vibrator/src/android/os/vibrator/StepSegmentTest.java
@@ -141,6 +141,37 @@ public class StepSegmentTest {
}
@Test
+ public void testScaleLinearly_fullAmplitude() {
+ StepSegment initial = new StepSegment(1f, 0, 0);
+
+ assertEquals(1f, initial.scaleLinearly(1).getAmplitude(), TOLERANCE);
+ assertEquals(0.5f, initial.scaleLinearly(0.5f).getAmplitude(), TOLERANCE);
+ assertEquals(1f, initial.scaleLinearly(1.5f).getAmplitude(), TOLERANCE);
+ assertEquals(0.8f, initial.scaleLinearly(0.8f).getAmplitude(), TOLERANCE);
+ // Restores back to the exact original value since this is a linear scaling.
+ assertEquals(1f, initial.scaleLinearly(0.8f).scaleLinearly(1.25f).getAmplitude(),
+ TOLERANCE);
+
+ initial = new StepSegment(0, 0, 0);
+
+ assertEquals(0f, initial.scaleLinearly(1).getAmplitude(), TOLERANCE);
+ assertEquals(0f, initial.scaleLinearly(0.5f).getAmplitude(), TOLERANCE);
+ assertEquals(0f, initial.scaleLinearly(1.5f).getAmplitude(), TOLERANCE);
+ }
+
+ @Test
+ public void testScaleLinearly_defaultAmplitude() {
+ StepSegment initial = new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE, 0, 0);
+
+ assertEquals(VibrationEffect.DEFAULT_AMPLITUDE, initial.scaleLinearly(1).getAmplitude(),
+ TOLERANCE);
+ assertEquals(VibrationEffect.DEFAULT_AMPLITUDE, initial.scaleLinearly(0.5f).getAmplitude(),
+ TOLERANCE);
+ assertEquals(VibrationEffect.DEFAULT_AMPLITUDE, initial.scaleLinearly(1.5f).getAmplitude(),
+ TOLERANCE);
+ }
+
+ @Test
public void testDuration() {
assertEquals(5, new StepSegment(0, 0, 5).getDuration());
}
diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java
index f3bb21719890..b6ce9b64323b 100644
--- a/graphics/java/android/graphics/Canvas.java
+++ b/graphics/java/android/graphics/Canvas.java
@@ -153,6 +153,18 @@ public class Canvas extends BaseCanvas {
}
/**
+ * Indicates whether this Canvas is drawing high contrast text.
+ *
+ * @see android.view.accessibility.AccessibilityManager#isHighTextContrastEnabled()
+ * @return True if high contrast text is enabled, false otherwise.
+ *
+ * @hide
+ */
+ public boolean isHighContrastTextEnabled() {
+ return nIsHighContrastText(mNativeCanvasWrapper);
+ }
+
+ /**
* Specify a bitmap for the canvas to draw into. All canvas state such as
* layers, filters, and the save/restore stack are reset. Additionally,
* the canvas' target density is updated to match that of the bitmap.
@@ -1452,6 +1464,8 @@ public class Canvas extends BaseCanvas {
@CriticalNative
private static native boolean nIsOpaque(long canvasHandle);
@CriticalNative
+ private static native boolean nIsHighContrastText(long canvasHandle);
+ @CriticalNative
private static native int nGetWidth(long canvasHandle);
@CriticalNative
private static native int nGetHeight(long canvasHandle);
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index 310300d2f32a..d66c925de376 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -206,6 +206,8 @@ android_robolectric_test {
srcs: [
"multivalentTests/src/**/*.kt",
],
+ // TODO(b/323188766): Include BubbleStackViewTest once the robolectric issue is fixed.
+ exclude_srcs: ["multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt"],
static_libs: [
"junit",
"androidx.test.runner",
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt
index 5825bbfbfefa..9cd14fca6a9d 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt
@@ -466,6 +466,26 @@ class BubblePositionerTest {
.isEqualTo(expectedExpandedViewY)
}
+ @Test
+ fun testGetTaskViewContentWidth_onLeft() {
+ positioner.update(defaultDeviceConfig.copy(insets = Insets.of(100, 0, 200, 0)))
+ val taskViewWidth = positioner.getTaskViewContentWidth(true /* onLeft */)
+ val paddings = positioner.getExpandedViewContainerPadding(true /* onLeft */,
+ false /* isOverflow */)
+ assertThat(taskViewWidth).isEqualTo(
+ positioner.screenRect.width() - paddings[0] - paddings[2])
+ }
+
+ @Test
+ fun testGetTaskViewContentWidth_onRight() {
+ positioner.update(defaultDeviceConfig.copy(insets = Insets.of(100, 0, 200, 0)))
+ val taskViewWidth = positioner.getTaskViewContentWidth(false /* onLeft */)
+ val paddings = positioner.getExpandedViewContainerPadding(false /* onLeft */,
+ false /* isOverflow */)
+ assertThat(taskViewWidth).isEqualTo(
+ positioner.screenRect.width() - paddings[0] - paddings[2])
+ }
+
private val defaultYPosition: Float
/**
* Calculates the Y position bubbles should be placed based on the config. Based on the
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
new file mode 100644
index 000000000000..8989fc543044
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.bubbles
+
+import android.content.Context
+import android.content.Intent
+import android.graphics.Color
+import android.graphics.drawable.Icon
+import android.os.UserHandle
+import android.view.IWindowManager
+import android.view.WindowManager
+import android.view.WindowManagerGlobal
+import androidx.test.annotation.UiThreadTest
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.internal.protolog.common.ProtoLog
+import com.android.launcher3.icons.BubbleIconFactory
+import com.android.wm.shell.R
+import com.android.wm.shell.bubbles.Bubbles.SysuiProxy
+import com.android.wm.shell.common.FloatingContentCoordinator
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.taskview.TaskView
+import com.android.wm.shell.taskview.TaskViewTaskController
+import com.google.common.truth.Truth.assertThat
+import com.google.common.util.concurrent.MoreExecutors.directExecutor
+import java.util.concurrent.Semaphore
+import java.util.concurrent.TimeUnit
+import java.util.function.Consumer
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+
+/** Unit tests for [BubbleStackView]. */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BubbleStackViewTest {
+
+ private val context = ApplicationProvider.getApplicationContext<Context>()
+ private lateinit var positioner: BubblePositioner
+ private lateinit var iconFactory: BubbleIconFactory
+ private lateinit var expandedViewManager: FakeBubbleExpandedViewManager
+ private lateinit var bubbleStackView: BubbleStackView
+ private lateinit var shellExecutor: ShellExecutor
+ private lateinit var windowManager: IWindowManager
+ private lateinit var bubbleTaskViewFactory: BubbleTaskViewFactory
+ private lateinit var bubbleData: BubbleData
+
+ @Before
+ fun setUp() {
+ // Disable protolog tool when running the tests from studio
+ ProtoLog.REQUIRE_PROTOLOGTOOL = false
+ windowManager = WindowManagerGlobal.getWindowManagerService()!!
+ shellExecutor = TestShellExecutor()
+ val windowManager = context.getSystemService(WindowManager::class.java)
+ iconFactory =
+ BubbleIconFactory(
+ context,
+ context.resources.getDimensionPixelSize(R.dimen.bubble_size),
+ context.resources.getDimensionPixelSize(R.dimen.bubble_badge_size),
+ Color.BLACK,
+ context.resources.getDimensionPixelSize(
+ com.android.internal.R.dimen.importance_ring_stroke_width
+ )
+ )
+ positioner = BubblePositioner(context, windowManager)
+ val bubbleStackViewManager = FakeBubbleStackViewManager()
+ bubbleData =
+ BubbleData(
+ context,
+ BubbleLogger(UiEventLoggerFake()),
+ positioner,
+ BubbleEducationController(context),
+ shellExecutor
+ )
+
+ val sysuiProxy = mock<SysuiProxy>()
+ expandedViewManager = FakeBubbleExpandedViewManager()
+ bubbleTaskViewFactory = FakeBubbleTaskViewFactory()
+ bubbleStackView =
+ BubbleStackView(
+ context,
+ bubbleStackViewManager,
+ positioner,
+ bubbleData,
+ null,
+ FloatingContentCoordinator(),
+ { sysuiProxy },
+ shellExecutor
+ )
+ }
+
+ @UiThreadTest
+ @Test
+ fun addBubble() {
+ val bubble = createAndInflateBubble()
+ bubbleStackView.addBubble(bubble)
+ assertThat(bubbleStackView.bubbleCount).isEqualTo(1)
+ }
+
+ @UiThreadTest
+ @Test
+ fun tapBubbleToExpand() {
+ val bubble = createAndInflateBubble()
+ bubbleStackView.addBubble(bubble)
+ assertThat(bubbleStackView.bubbleCount).isEqualTo(1)
+
+ bubble.iconView!!.performClick()
+ // we're checking the expanded state in BubbleData because that's the source of truth. This
+ // will eventually propagate an update back to the stack view, but setting the entire
+ // pipeline is outside the scope of a unit test.
+ assertThat(bubbleData.isExpanded).isTrue()
+ }
+
+ private fun createAndInflateBubble(): Bubble {
+ val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName)
+ val icon = Icon.createWithResource(context.resources, R.drawable.bubble_ic_overflow_button)
+ val bubble = Bubble.createAppBubble(intent, UserHandle(1), icon, directExecutor())
+ bubble.setInflateSynchronously(true)
+ bubbleData.notificationEntryUpdated(bubble, true, false)
+
+ val semaphore = Semaphore(0)
+ val callback: BubbleViewInfoTask.Callback =
+ BubbleViewInfoTask.Callback { semaphore.release() }
+ bubble.inflate(
+ callback,
+ context,
+ expandedViewManager,
+ bubbleTaskViewFactory,
+ positioner,
+ bubbleStackView,
+ null,
+ iconFactory,
+ false
+ )
+
+ assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
+ assertThat(bubble.isInflated).isTrue()
+ return bubble
+ }
+
+ private class FakeBubbleStackViewManager : BubbleStackViewManager {
+
+ override fun onAllBubblesAnimatedOut() {}
+
+ override fun updateWindowFlagsForBackpress(interceptBack: Boolean) {}
+
+ override fun checkNotificationPanelExpandedState(callback: Consumer<Boolean>) {}
+
+ override fun hideCurrentInputMethod() {}
+ }
+
+ private class TestShellExecutor : ShellExecutor {
+
+ override fun execute(runnable: Runnable) {
+ runnable.run()
+ }
+
+ override fun executeDelayed(r: Runnable, delayMillis: Long) {
+ r.run()
+ }
+
+ override fun removeCallbacks(r: Runnable) {}
+
+ override fun hasCallback(r: Runnable): Boolean = false
+ }
+
+ private inner class FakeBubbleTaskViewFactory : BubbleTaskViewFactory {
+ override fun create(): BubbleTaskView {
+ val taskViewTaskController = mock<TaskViewTaskController>()
+ val taskView = TaskView(context, taskViewTaskController)
+ return BubbleTaskView(taskView, shellExecutor)
+ }
+ }
+
+ private inner class FakeBubbleExpandedViewManager : BubbleExpandedViewManager {
+
+ override val overflowBubbles: List<Bubble>
+ get() = emptyList()
+
+ override fun setOverflowListener(listener: BubbleData.Listener) {}
+
+ override fun collapseStack() {}
+
+ override fun updateWindowFlagsForBackpress(intercept: Boolean) {}
+
+ override fun promoteBubbleFromOverflow(bubble: Bubble) {}
+
+ override fun removeBubble(key: String, reason: Int) {}
+
+ override fun dismissBubble(bubble: Bubble, reason: Int) {}
+
+ override fun setAppBubbleTaskId(key: String, taskId: Int) {}
+
+ override fun isStackExpanded(): Boolean = false
+
+ override fun isShowingAsBubbleBar(): Boolean = false
+ }
+}
diff --git a/libs/WindowManager/Shell/res/layout/bubble_bar_expanded_view.xml b/libs/WindowManager/Shell/res/layout/bubble_bar_expanded_view.xml
index e04ab817215c..34f03c2f226b 100644
--- a/libs/WindowManager/Shell/res/layout/bubble_bar_expanded_view.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_bar_expanded_view.xml
@@ -23,7 +23,8 @@
<com.android.wm.shell.bubbles.bar.BubbleBarHandleView
android:id="@+id/bubble_bar_handle_view"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content" />
+ android:layout_height="@dimen/bubble_bar_expanded_view_caption_height"
+ android:layout_width="@dimen/bubble_bar_expanded_view_caption_width"
+ android:layout_gravity="top|center_horizontal" />
</com.android.wm.shell.bubbles.bar.BubbleBarExpandedView>
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index f73775becac9..cbfa74e9332b 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -244,6 +244,8 @@
<dimen name="bubble_popup_padding">24dp</dimen>
<!-- The size of the caption bar inset at the top of bubble bar expanded view. -->
<dimen name="bubble_bar_expanded_view_caption_height">32dp</dimen>
+ <!-- The width of the caption bar at the top of bubble bar expanded view. -->
+ <dimen name="bubble_bar_expanded_view_caption_width">128dp</dimen>
<!-- The height of the dots shown for the caption menu in the bubble bar expanded view.. -->
<dimen name="bubble_bar_expanded_view_caption_dot_size">4dp</dimen>
<!-- The spacing between the dots for the caption menu in the bubble bar expanded view.. -->
@@ -501,6 +503,17 @@
fullscreen if dragged until the top bound of the task is within the area. -->
<dimen name="desktop_mode_transition_area_height">16dp</dimen>
+ <!-- The width of the area where a desktop task will transition to fullscreen. -->
+ <dimen name="desktop_mode_fullscreen_from_desktop_width">80dp</dimen>
+
+ <!-- The height of the area where a desktop task will transition to fullscreen. -->
+ <dimen name="desktop_mode_fullscreen_from_desktop_height">40dp</dimen>
+
+ <!-- The height on the screen where drag to the left or right edge will result in a
+ desktop task snapping to split size. The empty space between this and the top is to allow
+ for corner drags without transition. -->
+ <dimen name="desktop_mode_split_from_desktop_height">100dp</dimen>
+
<!-- The acceptable area ratio of fg icon area/bg icon area, i.e. (72 x 72) / (108 x 108) -->
<item type="dimen" format="float" name="splash_icon_enlarge_foreground_threshold">0.44</item>
<!-- Scaling factor applied to splash icons without provided background i.e. (192 / 160) -->
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index fe65fdd30e48..d8d0d876b4f2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -643,19 +643,6 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
}
}
- /** Helper to set int metadata on the Surface corresponding to the task id. */
- public void setSurfaceMetadata(int taskId, int key, int value) {
- synchronized (mLock) {
- final TaskAppearedInfo info = mTasks.get(taskId);
- if (info == null || info.getLeash() == null) {
- return;
- }
- SurfaceControl.Transaction t = new SurfaceControl.Transaction();
- t.setMetadata(info.getLeash(), key, value);
- t.apply();
- }
- }
-
private boolean updateTaskListenerIfNeeded(RunningTaskInfo taskInfo, SurfaceControl leash,
TaskListener oldListener, TaskListener newListener) {
if (oldListener == newListener) return false;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java
index 2ec9e8b12fc6..19963675ff86 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java
@@ -71,6 +71,13 @@ public class Interpolators {
*/
public static final Interpolator EMPHASIZED_DECELERATE = new PathInterpolator(
0.05f, 0.7f, 0.1f, 1f);
+
+ /**
+ * The standard decelerating interpolator that should be used on every regular movement of
+ * content that is appearing e.g. when coming from off screen.
+ */
+ public static final Interpolator STANDARD_DECELERATE = new PathInterpolator(0f, 0f, 0f, 1f);
+
/**
* Interpolator to be used when animating a move based on a click. Pair with enough duration.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index e7f6f0d61847..34be9b097e81 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -404,10 +404,10 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
@VisibleForTesting
void onPilferPointers() {
- mCurrentTracker.updateStartLocation();
// Dispatch onBackStarted, only to app callbacks.
// System callbacks will receive onBackStarted when the remote animation starts.
if (!shouldDispatchToAnimator() && mActiveCallback != null) {
+ mCurrentTracker.updateStartLocation();
tryDispatchAppOnBackStarted(mActiveCallback, mCurrentTracker.createStartEvent(null));
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java
index 55982dca79b3..d6f7c367f772 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.java
@@ -40,7 +40,6 @@ import android.view.IRemoteAnimationFinishedCallback;
import android.view.IRemoteAnimationRunner;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
-import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.window.BackEvent;
import android.window.BackMotionEvent;
@@ -51,6 +50,7 @@ import com.android.internal.dynamicanimation.animation.SpringAnimation;
import com.android.internal.dynamicanimation.animation.SpringForce;
import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.animation.Interpolators;
import com.android.wm.shell.common.annotations.ShellMainThread;
import javax.inject.Inject;
@@ -65,7 +65,7 @@ public class CrossActivityBackAnimation extends ShellBackAnimation {
/** Duration of post animation after gesture committed. */
private static final int POST_ANIMATION_DURATION = 350;
- private static final Interpolator INTERPOLATOR = new DecelerateInterpolator();
+ private static final Interpolator INTERPOLATOR = Interpolators.STANDARD_DECELERATE;
private static final FloatProperty<CrossActivityBackAnimation> ENTER_PROGRESS_PROP =
new FloatProperty<>("enter-alpha") {
@Override
@@ -285,7 +285,7 @@ public class CrossActivityBackAnimation extends ShellBackAnimation {
ValueAnimator valueAnimator =
ValueAnimator.ofFloat(1f, 0f).setDuration(POST_ANIMATION_DURATION);
- valueAnimator.setInterpolator(new DecelerateInterpolator());
+ valueAnimator.setInterpolator(INTERPOLATOR);
valueAnimator.addUpdateListener(animation -> {
float progress = animation.getAnimatedFraction();
updatePostCommitEnteringAnimation(progress);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
index adc78391f033..4b3154190910 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
@@ -91,7 +91,8 @@ public class CrossTaskBackAnimation extends ShellBackAnimation {
private final PointF mInitialTouchPos = new PointF();
private final Interpolator mPostAnimationInterpolator = Interpolators.EMPHASIZED;
- private final Interpolator mProgressInterpolator = new DecelerateInterpolator();
+ private final Interpolator mProgressInterpolator = Interpolators.STANDARD_DECELERATE;
+ private final Interpolator mVerticalMoveInterpolator = new DecelerateInterpolator();
private final Matrix mTransformMatrix = new Matrix();
private final float[] mTmpFloat9 = new float[9];
@@ -169,7 +170,7 @@ public class CrossTaskBackAnimation extends ShellBackAnimation {
float yDirection = rawYDelta < 0 ? -1 : 1;
// limit yDelta interpretation to 1/2 of screen height in either direction
float deltaYRatio = Math.min(height / 2f, Math.abs(rawYDelta)) / (height / 2f);
- float interpolatedYRatio = mProgressInterpolator.getInterpolation(deltaYRatio);
+ float interpolatedYRatio = mVerticalMoveInterpolator.getInterpolation(deltaYRatio);
// limit y-shift so surface never passes 8dp screen margin
float deltaY = yDirection * interpolatedYRatio * Math.max(0f,
(height - scaledHeight) / 2f - mVerticalMargin);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 5c6f73f0a4a2..e0f0556d03f0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -481,7 +481,12 @@ public class BubbleController implements ConfigurationChangeListener,
});
mOneHandedOptional.ifPresent(this::registerOneHandedState);
- mDragAndDropController.addListener(this::collapseStack);
+ mDragAndDropController.addListener(new DragAndDropController.DragAndDropListener() {
+ @Override
+ public void onDragStarted() {
+ collapseStack();
+ }
+ });
// Clear out any persisted bubbles on disk that no longer have a valid user.
List<UserInfo> users = mUserManager.getAliveUsers();
@@ -1838,7 +1843,7 @@ public class BubbleController implements ConfigurationChangeListener,
+ " expanded=%b selectionChanged=%b selected=%s"
+ " suppressed=%s unsupressed=%s shouldShowEducation=%b",
update.addedBubble != null ? update.addedBubble.getKey() : "null",
- update.removedBubbles.isEmpty(),
+ !update.removedBubbles.isEmpty(),
update.updatedBubble != null ? update.updatedBubble.getKey() : "null",
update.orderChanged, update.expandedChanged, update.expanded,
update.selectionChanged,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
index cda29c95281d..a5853d621cb5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
@@ -395,7 +395,7 @@ public class BubblePositioner {
public int getTaskViewContentWidth(boolean onLeft) {
int[] paddings = getExpandedViewContainerPadding(onLeft, /* isOverflow = */ false);
int pointerOffset = showBubblesVertically() ? getPointerSize() : 0;
- return mPositionRect.width() - paddings[0] - paddings[2] - pointerOffset;
+ return mScreenRect.width() - paddings[0] - paddings[2] - pointerOffset;
}
/** Gets the y position of the expanded view if it was top-aligned. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
index eddd43f263d9..271fb9abce6a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
@@ -143,6 +143,8 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), mCurrentCornerRadius);
}
});
+ // Set a touch sink to ensure that clicks on the caption area do not propagate to the parent
+ setOnTouchListener((v, event) -> true);
}
@Override
@@ -245,12 +247,8 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- int height = MeasureSpec.getSize(heightMeasureSpec);
- int menuViewHeight = Math.min(mCaptionHeight, height);
- measureChild(mHandleView, widthMeasureSpec, MeasureSpec.makeMeasureSpec(menuViewHeight,
- MeasureSpec.getMode(heightMeasureSpec)));
-
if (mTaskView != null) {
+ int height = MeasureSpec.getSize(heightMeasureSpec);
measureChild(mTaskView, widthMeasureSpec, MeasureSpec.makeMeasureSpec(height,
MeasureSpec.getMode(heightMeasureSpec)));
}
@@ -259,14 +257,11 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
- final int captionBottom = t + mCaptionHeight;
if (mTaskView != null) {
mTaskView.layout(l, t, r,
t + mTaskView.getMeasuredHeight());
mTaskView.setCaptionInsets(Insets.of(0, mCaptionHeight, 0, 0));
}
- // Handle draws on top of task view in the caption area.
- mHandleView.layout(l, t, r, captionBottom);
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index 62f2726ad9bd..78a41f759d96 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -231,6 +231,7 @@ public class BubbleBarLayerView extends FrameLayout
// Touch delegate for the menu
BubbleBarHandleView view = mExpandedView.getHandleView();
view.getBoundsOnScreen(mHandleTouchBounds);
+ // Move top value up to ensure touch target is large enough
mHandleTouchBounds.top -= mPositioner.getBubblePaddingTop();
mHandleTouchDelegate = new TouchDelegate(mHandleTouchBounds,
mExpandedView.getHandleView());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsAlgorithm.java
index a9f687fc9b2d..6ffeb97f50fa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsAlgorithm.java
@@ -125,11 +125,15 @@ public class PipBoundsAlgorithm {
public Rect getEntryDestinationBoundsIgnoringKeepClearAreas() {
final PipBoundsState.PipReentryState reentryState = mPipBoundsState.getReentryState();
- final Rect destinationBounds = reentryState != null
- ? getDefaultBounds(reentryState.getSnapFraction(), reentryState.getSize())
- : getDefaultBounds();
+ final Rect destinationBounds = getDefaultBounds();
+ if (reentryState != null) {
+ final Size scaledBounds = new Size(
+ Math.round(mPipBoundsState.getMaxSize().x * reentryState.getBoundsScale()),
+ Math.round(mPipBoundsState.getMaxSize().y * reentryState.getBoundsScale()));
+ destinationBounds.set(getDefaultBounds(reentryState.getSnapFraction(), scaledBounds));
+ }
- final boolean useCurrentSize = reentryState != null && reentryState.getSize() != null;
+ final boolean useCurrentSize = reentryState != null;
Rect aspectRatioBounds = transformBoundsToAspectRatioIfValid(destinationBounds,
mPipBoundsState.getAspectRatio(), false /* useCurrentMinEdgeSize */,
useCurrentSize);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java
index df589df8b227..b57e2d2c6ac2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsState.java
@@ -298,8 +298,8 @@ public class PipBoundsState {
}
/** Save the reentry state to restore to when re-entering PIP mode. */
- public void saveReentryState(Size size, float fraction) {
- mPipReentryState = new PipReentryState(size, fraction);
+ public void saveReentryState(float fraction) {
+ mPipReentryState = new PipReentryState(mBoundsScale, fraction);
}
/** Returns the saved reentry state. */
@@ -601,17 +601,16 @@ public class PipBoundsState {
public static final class PipReentryState {
private static final String TAG = PipReentryState.class.getSimpleName();
- private final @Nullable Size mSize;
private final float mSnapFraction;
+ private final float mBoundsScale;
- PipReentryState(@Nullable Size size, float snapFraction) {
- mSize = size;
+ PipReentryState(float boundsScale, float snapFraction) {
+ mBoundsScale = boundsScale;
mSnapFraction = snapFraction;
}
- @Nullable
- public Size getSize() {
- return mSize;
+ public float getBoundsScale() {
+ return mBoundsScale;
}
public float getSnapFraction() {
@@ -621,7 +620,7 @@ public class PipBoundsState {
void dump(PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
pw.println(prefix + TAG);
- pw.println(innerPrefix + "mSize=" + mSize);
+ pw.println(innerPrefix + "mBoundsScale=" + mBoundsScale);
pw.println(innerPrefix + "mSnapFraction=" + mSnapFraction);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java
index 81d13999e73c..7c280994042b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java
@@ -20,6 +20,7 @@ import static android.window.TaskConstants.TASK_CHILD_LAYER_COMPAT_UI;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.AppCompatTaskInfo;
import android.app.TaskInfo;
import android.content.Context;
import android.content.Intent;
@@ -88,7 +89,8 @@ class UserAspectRatioSettingsWindowManager extends CompatUIWindowManagerAbstract
mShellExecutor = shellExecutor;
mUserAspectRatioButtonShownChecker = userAspectRatioButtonStateChecker;
mUserAspectRatioButtonStateConsumer = userAspectRatioButtonShownConsumer;
- mHasUserAspectRatioSettingsButton = getHasUserAspectRatioSettingsButton(taskInfo);
+ mHasUserAspectRatioSettingsButton = shouldShowUserAspectRatioSettingsButton(
+ taskInfo.appCompatTaskInfo, taskInfo.baseIntent);
mCompatUIHintsState = compatUIHintsState;
mOnButtonClicked = onButtonClicked;
mDisappearTimeSupplier = disappearTimeSupplier;
@@ -134,7 +136,8 @@ class UserAspectRatioSettingsWindowManager extends CompatUIWindowManagerAbstract
public boolean updateCompatInfo(@NonNull TaskInfo taskInfo,
@NonNull ShellTaskOrganizer.TaskListener taskListener, boolean canShow) {
final boolean prevHasUserAspectRatioSettingsButton = mHasUserAspectRatioSettingsButton;
- mHasUserAspectRatioSettingsButton = getHasUserAspectRatioSettingsButton(taskInfo);
+ mHasUserAspectRatioSettingsButton = shouldShowUserAspectRatioSettingsButton(
+ taskInfo.appCompatTaskInfo, taskInfo.baseIntent);
if (!super.updateCompatInfo(taskInfo, taskListener, canShow)) {
return false;
@@ -227,12 +230,21 @@ class UserAspectRatioSettingsWindowManager extends CompatUIWindowManagerAbstract
return SystemClock.uptimeMillis() + hideDelay;
}
- private boolean getHasUserAspectRatioSettingsButton(@NonNull TaskInfo taskInfo) {
- final Intent intent = taskInfo.baseIntent;
- return taskInfo.appCompatTaskInfo.topActivityEligibleForUserAspectRatioButton
- && (taskInfo.appCompatTaskInfo.topActivityBoundsLetterboxed
- || taskInfo.appCompatTaskInfo.isUserFullscreenOverrideEnabled)
- && !taskInfo.appCompatTaskInfo.isSystemFullscreenOverrideEnabled
+ private boolean shouldShowUserAspectRatioSettingsButton(@NonNull AppCompatTaskInfo taskInfo,
+ @NonNull Intent intent) {
+ final Rect stableBounds = getTaskStableBounds();
+ final int letterboxHeight = taskInfo.topActivityLetterboxHeight;
+ final int letterboxWidth = taskInfo.topActivityLetterboxWidth;
+ // App is not visibly letterboxed if it covers status bar/bottom insets or matches the
+ // stable bounds, so don't show the button
+ if (stableBounds.height() <= letterboxHeight && stableBounds.width() <= letterboxWidth) {
+ return false;
+ }
+
+ return taskInfo.topActivityEligibleForUserAspectRatioButton
+ && (taskInfo.topActivityBoundsLetterboxed
+ || taskInfo.isUserFullscreenOverrideEnabled)
+ && !taskInfo.isSystemFullscreenOverrideEnabled
&& Intent.ACTION_MAIN.equals(intent.getAction())
&& intent.hasCategory(Intent.CATEGORY_LAUNCHER)
&& (!mUserAspectRatioButtonShownChecker.get() || isShowingButton());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 4fe79c13e722..f757e1c88cb8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -65,7 +65,7 @@ import com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler;
import com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler;
import com.android.wm.shell.desktopmode.ToggleResizeDesktopTaskTransitionHandler;
import com.android.wm.shell.draganddrop.DragAndDropController;
-import com.android.wm.shell.draganddrop.UnhandledDragController;
+import com.android.wm.shell.draganddrop.GlobalDragListener;
import com.android.wm.shell.freeform.FreeformComponents;
import com.android.wm.shell.freeform.FreeformTaskListener;
import com.android.wm.shell.freeform.FreeformTaskTransitionHandler;
@@ -498,6 +498,7 @@ public abstract class WMShellModule {
ShellTaskOrganizer shellTaskOrganizer,
SyncTransactionQueue syncQueue,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
+ DragAndDropController dragAndDropController,
Transitions transitions,
EnterDesktopTaskTransitionHandler enterDesktopTransitionHandler,
ExitDesktopTaskTransitionHandler exitDesktopTransitionHandler,
@@ -506,14 +507,15 @@ public abstract class WMShellModule {
@DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository,
LaunchAdjacentController launchAdjacentController,
RecentsTransitionHandler recentsTransitionHandler,
+ MultiInstanceHelper multiInstanceHelper,
@ShellMainThread ShellExecutor mainExecutor
) {
return new DesktopTasksController(context, shellInit, shellCommandHandler, shellController,
displayController, shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer,
- transitions, enterDesktopTransitionHandler, exitDesktopTransitionHandler,
- toggleResizeDesktopTaskTransitionHandler, dragToDesktopTransitionHandler,
- desktopModeTaskRepository, launchAdjacentController, recentsTransitionHandler,
- mainExecutor);
+ dragAndDropController, transitions, enterDesktopTransitionHandler,
+ exitDesktopTransitionHandler, toggleResizeDesktopTaskTransitionHandler,
+ dragToDesktopTransitionHandler, desktopModeTaskRepository, launchAdjacentController,
+ recentsTransitionHandler, multiInstanceHelper, mainExecutor);
}
@WMSingleton
@@ -562,10 +564,10 @@ public abstract class WMShellModule {
@WMSingleton
@Provides
- static UnhandledDragController provideUnhandledDragController(
+ static GlobalDragListener provideGlobalDragListener(
IWindowManager wmService,
@ShellMainThread ShellExecutor mainExecutor) {
- return new UnhandledDragController(wmService, mainExecutor);
+ return new GlobalDragListener(wmService, mainExecutor);
}
@WMSingleton
@@ -577,9 +579,12 @@ public abstract class WMShellModule {
DisplayController displayController,
UiEventLogger uiEventLogger,
IconProvider iconProvider,
+ GlobalDragListener globalDragListener,
+ Transitions transitions,
@ShellMainThread ShellExecutor mainExecutor) {
- return new DragAndDropController(context, shellInit, shellController,
- shellCommandHandler, displayController, uiEventLogger, iconProvider, mainExecutor);
+ return new DragAndDropController(context, shellInit, shellController, shellCommandHandler,
+ displayController, uiEventLogger, iconProvider, globalDragListener, transitions,
+ mainExecutor);
}
//
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 8eecf1c58db0..458ea05e620d 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
@@ -74,13 +74,18 @@ public abstract class Pip2Module {
ShellController shellController,
DisplayController displayController,
DisplayInsetsController displayInsetsController,
- PipDisplayLayoutState pipDisplayLayoutState) {
+ PipBoundsState pipBoundsState,
+ PipBoundsAlgorithm pipBoundsAlgorithm,
+ PipDisplayLayoutState pipDisplayLayoutState,
+ PipScheduler pipScheduler,
+ @ShellMainThread ShellExecutor mainExecutor) {
if (!PipUtils.isPip2ExperimentEnabled()) {
return Optional.empty();
} else {
return Optional.ofNullable(PipController.create(
context, shellInit, shellController, displayController, displayInsetsController,
- pipDisplayLayoutState));
+ pipBoundsState, pipBoundsAlgorithm, pipDisplayLayoutState, pipScheduler,
+ mainExecutor));
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
index e732a0354806..8305fa6b0fbf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
@@ -47,4 +47,8 @@ public interface DesktopMode {
default void addDesktopGestureExclusionRegionListener(Consumer<Region> listener,
Executor callbackExecutor) { }
+
+ /** Called when requested to go to desktop mode from the current focused app. */
+ void enterDesktop(int displayId);
+
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
index 6250fc5820aa..405341803a46 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
@@ -19,6 +19,8 @@ package com.android.wm.shell.desktopmode;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.FINAL_FREEFORM_SCALE;
@@ -28,11 +30,13 @@ import android.animation.RectEvaluator;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.app.ActivityManager;
+import android.app.WindowConfiguration;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.PixelFormat;
import android.graphics.PointF;
import android.graphics.Rect;
+import android.graphics.Region;
import android.util.DisplayMetrics;
import android.view.SurfaceControl;
import android.view.SurfaceControlViewHost;
@@ -41,6 +45,8 @@ import android.view.WindowManager;
import android.view.WindowlessWindowManager;
import android.view.animation.DecelerateInterpolator;
+import androidx.annotation.VisibleForTesting;
+
import com.android.wm.shell.R;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.common.DisplayController;
@@ -93,28 +99,114 @@ public class DesktopModeVisualIndicator {
/**
* Based on the coordinates of the current drag event, determine which indicator type we should
* display, including no visible indicator.
- * TODO(b/280828642): Update drag zones per starting windowing mode.
*/
- IndicatorType updateIndicatorType(PointF inputCoordinates) {
+ IndicatorType updateIndicatorType(PointF inputCoordinates, int windowingMode) {
final DisplayLayout layout = mDisplayController.getDisplayLayout(mTaskInfo.displayId);
// If we are in freeform, we don't want a visible indicator in the "freeform" drag zone.
- IndicatorType result = mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
- ? IndicatorType.NO_INDICATOR : IndicatorType.TO_DESKTOP_INDICATOR;
- int transitionAreaHeight = mContext.getResources().getDimensionPixelSize(
- com.android.wm.shell.R.dimen.desktop_mode_transition_area_height);
- int transitionAreaWidth = mContext.getResources().getDimensionPixelSize(
+ IndicatorType result = IndicatorType.NO_INDICATOR;
+ final int transitionAreaWidth = mContext.getResources().getDimensionPixelSize(
com.android.wm.shell.R.dimen.desktop_mode_transition_area_width);
- if (inputCoordinates.y <= transitionAreaHeight) {
+ // Because drags in freeform use task position for indicator calculation, we need to
+ // account for the possibility of the task going off the top of the screen by captionHeight
+ final int captionHeight = mContext.getResources().getDimensionPixelSize(
+ com.android.wm.shell.R.dimen.desktop_mode_freeform_decor_caption_height);
+ final Region fullscreenRegion = calculateFullscreenRegion(layout, windowingMode,
+ captionHeight);
+ final Region splitLeftRegion = calculateSplitLeftRegion(layout, windowingMode,
+ transitionAreaWidth, captionHeight);
+ final Region splitRightRegion = calculateSplitRightRegion(layout, windowingMode,
+ transitionAreaWidth, captionHeight);
+ final Region toDesktopRegion = calculateToDesktopRegion(layout, windowingMode,
+ splitLeftRegion, splitRightRegion, fullscreenRegion);
+ if (fullscreenRegion.contains((int) inputCoordinates.x, (int) inputCoordinates.y)) {
result = IndicatorType.TO_FULLSCREEN_INDICATOR;
- } else if (inputCoordinates.x <= transitionAreaWidth) {
+ }
+ if (splitLeftRegion.contains((int) inputCoordinates.x, (int) inputCoordinates.y)) {
result = IndicatorType.TO_SPLIT_LEFT_INDICATOR;
- } else if (inputCoordinates.x >= layout.width() - transitionAreaWidth) {
+ }
+ if (splitRightRegion.contains((int) inputCoordinates.x, (int) inputCoordinates.y)) {
result = IndicatorType.TO_SPLIT_RIGHT_INDICATOR;
}
+ if (toDesktopRegion.contains((int) inputCoordinates.x, (int) inputCoordinates.y)) {
+ result = IndicatorType.TO_DESKTOP_INDICATOR;
+ }
transitionIndicator(result);
return result;
}
+ @VisibleForTesting
+ Region calculateFullscreenRegion(DisplayLayout layout,
+ @WindowConfiguration.WindowingMode int windowingMode, int captionHeight) {
+ final Region region = new Region();
+ int edgeTransitionHeight = mContext.getResources().getDimensionPixelSize(
+ com.android.wm.shell.R.dimen.desktop_mode_transition_area_height);
+ // A thin, short Rect at the top of the screen.
+ if (windowingMode == WINDOWING_MODE_FREEFORM) {
+ int fromFreeformWidth = mContext.getResources().getDimensionPixelSize(
+ com.android.wm.shell.R.dimen.desktop_mode_fullscreen_from_desktop_width);
+ int fromFreeformHeight = mContext.getResources().getDimensionPixelSize(
+ com.android.wm.shell.R.dimen.desktop_mode_fullscreen_from_desktop_height);
+ region.union(new Rect((layout.width() / 2) - (fromFreeformWidth / 2),
+ -captionHeight,
+ (layout.width() / 2) + (fromFreeformWidth / 2),
+ fromFreeformHeight));
+ }
+ // A screen-wide, shorter Rect if the task is in fullscreen or split.
+ if (windowingMode == WINDOWING_MODE_FULLSCREEN
+ || windowingMode == WINDOWING_MODE_MULTI_WINDOW) {
+ region.union(new Rect(0,
+ -captionHeight,
+ layout.width(),
+ edgeTransitionHeight));
+ }
+ return region;
+ }
+
+ @VisibleForTesting
+ Region calculateToDesktopRegion(DisplayLayout layout,
+ @WindowConfiguration.WindowingMode int windowingMode,
+ Region splitLeftRegion, Region splitRightRegion,
+ Region toFullscreenRegion) {
+ final Region region = new Region();
+ // If in desktop, we need no region. Otherwise it's the same for all windowing modes.
+ if (windowingMode != WINDOWING_MODE_FREEFORM) {
+ region.union(new Rect(0, 0, layout.width(), layout.height()));
+ region.op(splitLeftRegion, Region.Op.DIFFERENCE);
+ region.op(splitRightRegion, Region.Op.DIFFERENCE);
+ region.op(toFullscreenRegion, Region.Op.DIFFERENCE);
+ }
+ return region;
+ }
+
+ @VisibleForTesting
+ Region calculateSplitLeftRegion(DisplayLayout layout,
+ @WindowConfiguration.WindowingMode int windowingMode,
+ int transitionEdgeWidth, int captionHeight) {
+ final Region region = new Region();
+ // In freeform, keep the top corners clear.
+ int transitionHeight = windowingMode == WINDOWING_MODE_FREEFORM
+ ? mContext.getResources().getDimensionPixelSize(
+ com.android.wm.shell.R.dimen.desktop_mode_split_from_desktop_height) :
+ -captionHeight;
+ region.union(new Rect(0, transitionHeight, transitionEdgeWidth, layout.height()));
+ return region;
+ }
+
+ @VisibleForTesting
+ Region calculateSplitRightRegion(DisplayLayout layout,
+ @WindowConfiguration.WindowingMode int windowingMode,
+ int transitionEdgeWidth, int captionHeight) {
+ final Region region = new Region();
+ // In freeform, keep the top corners clear.
+ int transitionHeight = windowingMode == WINDOWING_MODE_FREEFORM
+ ? mContext.getResources().getDimensionPixelSize(
+ com.android.wm.shell.R.dimen.desktop_mode_split_from_desktop_height) :
+ -captionHeight;
+ region.union(new Rect(layout.width() - transitionEdgeWidth, transitionHeight,
+ layout.width(), layout.height()));
+ return region;
+ }
+
/**
* Create a fullscreen indicator with no animation
*/
@@ -175,7 +267,6 @@ public class DesktopModeVisualIndicator {
mDisplayController.getDisplayLayout(mTaskInfo.displayId));
animator.start();
mCurrentType = IndicatorType.NO_INDICATOR;
-
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 11304ec587e7..98f9988eaabb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -16,14 +16,19 @@
package com.android.wm.shell.desktopmode
+import android.app.ActivityManager
import android.app.ActivityManager.RunningTaskInfo
+import android.app.ActivityOptions
+import android.app.PendingIntent
import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
import android.app.WindowConfiguration.WindowingMode
import android.content.Context
+import android.content.Intent
import android.graphics.Point
import android.graphics.PointF
import android.graphics.Rect
@@ -31,6 +36,7 @@ import android.graphics.Region
import android.os.IBinder
import android.os.SystemProperties
import android.util.DisplayMetrics.DENSITY_DEFAULT
+import android.view.Display.DEFAULT_DISPLAY
import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_CHANGE
import android.view.WindowManager.TRANSIT_NONE
@@ -48,6 +54,8 @@ import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.ExecutorUtils
import com.android.wm.shell.common.ExternalInterfaceBinder
import com.android.wm.shell.common.LaunchAdjacentController
+import com.android.wm.shell.common.MultiInstanceHelper
+import com.android.wm.shell.common.MultiInstanceHelper.Companion.getComponent
import com.android.wm.shell.common.RemoteCallable
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SingleInstanceRemoteListener
@@ -58,7 +66,9 @@ import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOT
import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksListener
import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler.DragToDesktopStateListener
+import com.android.wm.shell.draganddrop.DragAndDropController
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
+import com.android.wm.shell.recents.RecentTasksController
import com.android.wm.shell.recents.RecentsTransitionHandler
import com.android.wm.shell.recents.RecentsTransitionStateListener
import com.android.wm.shell.splitscreen.SplitScreenController
@@ -75,6 +85,7 @@ import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener
import java.io.PrintWriter
import java.util.concurrent.Executor
import java.util.function.Consumer
+import java.util.function.Function
/** Handles moving tasks in and out of desktop */
class DesktopTasksController(
@@ -86,6 +97,7 @@ class DesktopTasksController(
private val shellTaskOrganizer: ShellTaskOrganizer,
private val syncQueue: SyncTransactionQueue,
private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
+ private val dragAndDropController: DragAndDropController,
private val transitions: Transitions,
private val enterDesktopTaskTransitionHandler: EnterDesktopTaskTransitionHandler,
private val exitDesktopTaskTransitionHandler: ExitDesktopTaskTransitionHandler,
@@ -95,8 +107,10 @@ class DesktopTasksController(
private val desktopModeTaskRepository: DesktopModeTaskRepository,
private val launchAdjacentController: LaunchAdjacentController,
private val recentsTransitionHandler: RecentsTransitionHandler,
+ private val multiInstanceHelper: MultiInstanceHelper,
@ShellMainThread private val mainExecutor: ShellExecutor
-) : RemoteCallable<DesktopTasksController>, Transitions.TransitionHandler {
+) : RemoteCallable<DesktopTasksController>, Transitions.TransitionHandler,
+ DragAndDropController.DragAndDropListener {
private val desktopMode: DesktopModeImpl
private var visualIndicator: DesktopModeVisualIndicator? = null
@@ -173,6 +187,7 @@ class DesktopTasksController(
}
}
)
+ dragAndDropController.addListener(this)
}
fun setOnTaskResizeAnimationListener(listener: OnTaskResizeAnimationListener) {
@@ -240,6 +255,42 @@ class DesktopTasksController(
return desktopModeTaskRepository.getVisibleTaskCount(displayId)
}
+ /** Enter desktop by using the focused task in given `displayId` */
+ fun enterDesktop(displayId: Int) {
+ val allFocusedTasks =
+ shellTaskOrganizer.getRunningTasks(displayId).filter { taskInfo ->
+ taskInfo.isFocused &&
+ (taskInfo.windowingMode == WINDOWING_MODE_FULLSCREEN ||
+ taskInfo.windowingMode == WINDOWING_MODE_MULTI_WINDOW) &&
+ taskInfo.activityType != ACTIVITY_TYPE_HOME
+ }
+ if (allFocusedTasks.isNotEmpty()) {
+ when (allFocusedTasks.size) {
+ 2 -> {
+ // Split-screen case where there are two focused tasks, then we find the child
+ // task to move to desktop.
+ val splitFocusedTask =
+ if (allFocusedTasks[0].taskId == allFocusedTasks[1].parentTaskId)
+ allFocusedTasks[1]
+ else allFocusedTasks[0]
+ moveToDesktop(splitFocusedTask)
+ }
+ 1 -> {
+ // Fullscreen case where we move the current focused task.
+ moveToDesktop(allFocusedTasks[0].taskId)
+ }
+ else -> {
+ KtProtoLog.w(
+ WM_SHELL_DESKTOP_MODE,
+ "DesktopTasksController: Cannot enter desktop, expected less " +
+ "than 3 focused tasks but found %d",
+ allFocusedTasks.size
+ )
+ }
+ }
+ }
+ }
+
/** Move a task with given `taskId` to desktop */
fun moveToDesktop(
taskId: Int,
@@ -893,7 +944,7 @@ class DesktopTasksController(
}
// Then, update the indicator type.
val indicator = visualIndicator ?: return
- indicator.updateIndicatorType(PointF(inputX, taskTop))
+ indicator.updateIndicatorType(PointF(inputX, taskTop), taskInfo.windowingMode)
}
/**
@@ -986,6 +1037,50 @@ class DesktopTasksController(
desktopModeTaskRepository.setExclusionRegionListener(listener, callbackExecutor)
}
+ override fun onUnhandledDrag(
+ launchIntent: PendingIntent,
+ dragSurface: SurfaceControl,
+ onFinishCallback: Consumer<Boolean>
+ ): Boolean {
+ // TODO(b/320797628): Pass through which display we are dropping onto
+ val activeTasks = desktopModeTaskRepository.getActiveTasks(DEFAULT_DISPLAY)
+ if (!activeTasks.any { desktopModeTaskRepository.isVisibleTask(it) }) {
+ // Not currently in desktop mode, ignore the drop
+ return false
+ }
+
+ val launchComponent = getComponent(launchIntent)
+ if (!multiInstanceHelper.supportsMultiInstanceSplit(launchComponent)) {
+ // TODO(b/320797628): Should only return early if there is an existing running task, and
+ // notify the user as well. But for now, just ignore the drop.
+ KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "Dropped intent does not support multi-instance")
+ return false
+ }
+
+ // Start a new transition to launch the app
+ val opts = ActivityOptions.makeBasic().apply {
+ launchWindowingMode = WINDOWING_MODE_FREEFORM
+ pendingIntentLaunchFlags =
+ Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK
+ setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
+ isPendingIntentBackgroundActivityLaunchAllowedByPermission = true
+ }
+ val wct = WindowContainerTransaction()
+ wct.sendPendingIntent(launchIntent, null, opts.toBundle())
+ transitions.startTransition(TRANSIT_OPEN, wct, null /* handler */)
+
+ // Report that this is handled by the listener
+ onFinishCallback.accept(true)
+
+ // We've assumed responsibility of cleaning up the drag surface, so do that now
+ // TODO(b/320797628): Do an actual animation here for the drag surface
+ val t = SurfaceControl.Transaction()
+ t.remove(dragSurface)
+ t.apply()
+ return true
+ }
+
private fun dump(pw: PrintWriter, prefix: String) {
val innerPrefix = "$prefix "
pw.println("${prefix}DesktopTasksController")
@@ -1012,6 +1107,12 @@ class DesktopTasksController(
this@DesktopTasksController.setTaskRegionListener(listener, callbackExecutor)
}
}
+
+ override fun enterDesktop(displayId: Int) {
+ mainExecutor.execute {
+ this@DesktopTasksController.enterDesktop(displayId)
+ }
+ }
}
/** The interface for calls from outside the host process. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
index 269c3699ac0a..1afbdf90eac0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
@@ -35,7 +35,9 @@ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_DRAG_AND_DROP;
+import android.app.ActivityManager;
import android.app.ActivityTaskManager;
+import android.app.PendingIntent;
import android.content.ClipDescription;
import android.content.ComponentCallbacks2;
import android.content.Context;
@@ -51,6 +53,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.FrameLayout;
+import android.window.WindowContainerTransaction;
import androidx.annotation.BinderThread;
import androidx.annotation.NonNull;
@@ -71,14 +74,18 @@ import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.Transitions;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.function.Consumer;
+import java.util.function.Function;
/**
* Handles the global drag and drop handling for the Shell.
*/
public class DragAndDropController implements RemoteCallable<DragAndDropController>,
+ GlobalDragListener.GlobalDragListenerCallback,
DisplayController.OnDisplaysChangedListener,
View.OnDragListener, ComponentCallbacks2 {
@@ -90,6 +97,8 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
private final DisplayController mDisplayController;
private final DragAndDropEventLogger mLogger;
private final IconProvider mIconProvider;
+ private final GlobalDragListener mGlobalDragListener;
+ private final Transitions mTransitions;
private SplitScreenController mSplitScreen;
private ShellExecutor mMainExecutor;
private ArrayList<DragAndDropListener> mListeners = new ArrayList<>();
@@ -97,12 +106,29 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
// Map of displayId -> per-display info
private final SparseArray<PerDisplay> mDisplayDropTargets = new SparseArray<>();
+ // The current display if a drag is in progress
+ private int mActiveDragDisplay = -1;
+
/**
- * Listener called during drag events, currently just onDragStarted.
+ * Listener called during drag events.
*/
public interface DragAndDropListener {
/** Called when a drag has started. */
- void onDragStarted();
+ default void onDragStarted() {}
+
+ /** Called when a drag has ended. */
+ default void onDragEnded() {}
+
+ /**
+ * Called when an unhandled drag has occurred. The impl must return true if it decides to
+ * handled the unhandled drag, and it must also call `onFinishCallback` to complete the
+ * drag.
+ */
+ default boolean onUnhandledDrag(@NonNull PendingIntent launchIntent,
+ @NonNull SurfaceControl dragSurface,
+ @NonNull Consumer<Boolean> onFinishCallback) {
+ return false;
+ }
}
public DragAndDropController(Context context,
@@ -112,6 +138,8 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
DisplayController displayController,
UiEventLogger uiEventLogger,
IconProvider iconProvider,
+ GlobalDragListener globalDragListener,
+ Transitions transitions,
ShellExecutor mainExecutor) {
mContext = context;
mShellController = shellController;
@@ -119,6 +147,8 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
mDisplayController = displayController;
mLogger = new DragAndDropEventLogger(uiEventLogger);
mIconProvider = iconProvider;
+ mGlobalDragListener = globalDragListener;
+ mTransitions = transitions;
mMainExecutor = mainExecutor;
shellInit.addInitCallback(this::onInit, this);
}
@@ -136,6 +166,7 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
mShellController.addExternalInterface(KEY_EXTRA_SHELL_DRAG_AND_DROP,
this::createExternalInterface, this);
mShellCommandHandler.addDumpCallback(this::dump, this);
+ mGlobalDragListener.setListener(this);
}
private ExternalInterfaceBinder createExternalInterface() {
@@ -169,10 +200,18 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
mListeners.remove(listener);
}
- private void notifyDragStarted() {
+ /**
+ * Notifies all listeners and returns whether any listener handled the callback.
+ */
+ private boolean notifyListeners(Function<DragAndDropListener, Boolean> callback) {
for (int i = 0; i < mListeners.size(); i++) {
- mListeners.get(i).onDragStarted();
+ boolean handled = callback.apply(mListeners.get(i));
+ if (handled) {
+ // Return once the callback reports it has handled it
+ return true;
+ }
}
+ return false;
}
@Override
@@ -258,6 +297,7 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
}
if (event.getAction() == ACTION_DRAG_STARTED) {
+ mActiveDragDisplay = displayId;
pd.isHandlingDrag = DragUtils.canHandleDrag(event);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
"Clip description: handlingDrag=%b itemCount=%d mimeTypes=%s",
@@ -283,7 +323,11 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
pd.dragSession.update();
pd.dragLayout.prepare(pd.dragSession, loggerSessionId);
setDropTargetWindowVisibility(pd, View.VISIBLE);
- notifyDragStarted();
+ notifyListeners(l -> {
+ l.onDragStarted();
+ // Return false to continue dispatch to next listener
+ return false;
+ });
break;
case ACTION_DRAG_ENTERED:
pd.dragLayout.show();
@@ -317,11 +361,43 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
});
}
mLogger.logEnd();
+ mActiveDragDisplay = -1;
+ notifyListeners(l -> {
+ l.onDragEnded();
+ // Return false to continue dispatch to next listener
+ return false;
+ });
break;
}
return true;
}
+ @Override
+ public void onCrossWindowDrop(@NonNull ActivityManager.RunningTaskInfo taskInfo) {
+ // Bring the task forward when an item is dropped on it
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.reorder(taskInfo.token, true /* onTop */);
+ mTransitions.startTransition(WindowManager.TRANSIT_TO_FRONT, wct, null);
+ }
+
+ @Override
+ public void onUnhandledDrop(@NonNull DragEvent dragEvent,
+ @NonNull Consumer<Boolean> onFinishCallback) {
+ final PendingIntent launchIntent = DragUtils.getLaunchIntent(dragEvent);
+ if (launchIntent == null) {
+ // No intent to launch, report that this is unhandled by the listener
+ onFinishCallback.accept(false);
+ return;
+ }
+
+ final boolean handled = notifyListeners(
+ l -> l.onUnhandledDrag(launchIntent, dragEvent.getDragSurface(), onFinishCallback));
+ if (!handled) {
+ // Nobody handled this, we still have to notify WM
+ onFinishCallback.accept(false);
+ }
+ }
+
/**
* Handles dropping on the drop target.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java
index 7c0883d2538f..f7bcc9477aa1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java
@@ -20,9 +20,14 @@ import static android.content.ClipDescription.MIMETYPE_APPLICATION_ACTIVITY;
import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT;
import static android.content.ClipDescription.MIMETYPE_APPLICATION_TASK;
+import android.app.PendingIntent;
+import android.content.ClipData;
import android.content.ClipDescription;
import android.view.DragEvent;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
/** Collection of utility classes for handling drag and drop. */
public class DragUtils {
private static final String TAG = "DragUtils";
@@ -45,6 +50,31 @@ public class DragUtils {
}
/**
+ * Returns a launchable intent in the given `DragEvent` or `null` if there is none.
+ */
+ @Nullable
+ public static PendingIntent getLaunchIntent(@NonNull DragEvent dragEvent) {
+ return getLaunchIntent(dragEvent.getClipData());
+ }
+
+ /**
+ * Returns a launchable intent in the given `ClipData` or `null` if there is none.
+ */
+ @Nullable
+ public static PendingIntent getLaunchIntent(@NonNull ClipData data) {
+ for (int i = 0; i < data.getItemCount(); i++) {
+ final ClipData.Item item = data.getItemAt(i);
+ if (item.getIntentSender() != null) {
+ final PendingIntent intent = new PendingIntent(item.getIntentSender().getTarget());
+ if (intent != null && intent.isActivity()) {
+ return intent;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
* Returns a list of the mime types provided in the clip description.
*/
public static String getMimeTypesConcatenated(ClipDescription description) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/UnhandledDragController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/GlobalDragListener.kt
index ccf48d0de9ed..8826141fb406 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/UnhandledDragController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/GlobalDragListener.kt
@@ -15,12 +15,13 @@
*/
package com.android.wm.shell.draganddrop
+import android.app.ActivityManager
import android.os.RemoteException
import android.util.Log
import android.view.DragEvent
import android.view.IWindowManager
+import android.window.IGlobalDragListener
import android.window.IUnhandledDragCallback
-import android.window.IUnhandledDragListener
import androidx.annotation.VisibleForTesting
import com.android.internal.protolog.common.ProtoLog
import com.android.wm.shell.common.ShellExecutor
@@ -29,26 +30,38 @@ import java.util.function.Consumer
/**
* Manages the listener and callbacks for unhandled global drags.
+ * This is only used by DragAndDropController and should not be used directly by other classes.
*/
-class UnhandledDragController(
- val wmService: IWindowManager,
- mainExecutor: ShellExecutor
+class GlobalDragListener(
+ private val wmService: IWindowManager,
+ private val mainExecutor: ShellExecutor
) {
- private var callback: UnhandledDragAndDropCallback? = null
+ private var callback: GlobalDragListenerCallback? = null
+
+ private val globalDragListener: IGlobalDragListener =
+ object : IGlobalDragListener.Stub() {
+ override fun onCrossWindowDrop(taskInfo: ActivityManager.RunningTaskInfo) {
+ mainExecutor.execute() {
+ this@GlobalDragListener.onCrossWindowDrop(taskInfo)
+ }
+ }
- private val unhandledDragListener: IUnhandledDragListener =
- object : IUnhandledDragListener.Stub() {
override fun onUnhandledDrop(event: DragEvent, callback: IUnhandledDragCallback) {
mainExecutor.execute() {
- this@UnhandledDragController.onUnhandledDrop(event, callback)
+ this@GlobalDragListener.onUnhandledDrop(event, callback)
}
}
}
/**
- * Listener called when an unhandled drag is started.
+ * Callbacks for global drag events.
*/
- interface UnhandledDragAndDropCallback {
+ interface GlobalDragListenerCallback {
+ /**
+ * Called when a global drag is successfully handled by another window.
+ */
+ fun onCrossWindowDrop(taskInfo: ActivityManager.RunningTaskInfo) {}
+
/**
* Called when a global drag is unhandled (ie. dropped outside of all visible windows, or
* dropped on a window that does not want to handle it).
@@ -62,7 +75,7 @@ class UnhandledDragController(
/**
* Sets a listener for callbacks when an unhandled drag happens.
*/
- fun setListener(listener: UnhandledDragAndDropCallback?) {
+ fun setListener(listener: GlobalDragListenerCallback?) {
val updateWm = (callback == null && listener != null)
|| (callback != null && listener == null)
callback = listener
@@ -71,8 +84,8 @@ class UnhandledDragController(
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
"%s unhandled drag listener",
if (callback != null) "Registering" else "Unregistering")
- wmService.setUnhandledDragListener(
- if (callback != null) unhandledDragListener else null)
+ wmService.setGlobalDragListener(
+ if (callback != null) globalDragListener else null)
} catch (e: RemoteException) {
Log.e(TAG, "Failed to set unhandled drag listener")
}
@@ -80,11 +93,19 @@ class UnhandledDragController(
}
@VisibleForTesting
+ fun onCrossWindowDrop(taskInfo: ActivityManager.RunningTaskInfo) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+ "onCrossWindowDrop: %s", taskInfo)
+ callback?.onCrossWindowDrop(taskInfo)
+ }
+
+ @VisibleForTesting
fun onUnhandledDrop(dragEvent: DragEvent, wmCallback: IUnhandledDragCallback) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
"onUnhandledDrop: %s", dragEvent)
if (callback == null) {
wmCallback.notifyUnhandledDropComplete(false)
+ return
}
callback?.onUnhandledDrop(dragEvent) {
@@ -95,6 +116,6 @@ class UnhandledDragController(
}
companion object {
- private val TAG = UnhandledDragController::class.java.simpleName
+ private val TAG = GlobalDragListener::class.java.simpleName
}
}
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 d17317522694..32442f740a52 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
@@ -191,7 +191,7 @@ public abstract class PipTransitionController implements Transitions.TransitionH
try {
ActivityTaskManager.getService().onPictureInPictureUiStateChanged(
new PictureInPictureUiState.Builder()
- .setEnteringPip(true)
+ .setTransitioningToPip(true)
.build());
} catch (RemoteException | IllegalStateException e) {
ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
@@ -210,7 +210,7 @@ public abstract class PipTransitionController implements Transitions.TransitionH
try {
ActivityTaskManager.getService().onPictureInPictureUiStateChanged(
new PictureInPictureUiState.Builder()
- .setEnteringPip(false)
+ .setTransitioningToPip(false)
.build());
} catch (RemoteException | IllegalStateException e) {
ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 4b12134cb377..46840773cfc6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -48,7 +48,6 @@ import android.graphics.Rect;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.util.Pair;
-import android.util.Size;
import android.view.DisplayInfo;
import android.view.InsetsState;
import android.view.SurfaceControl;
@@ -1042,22 +1041,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
/** Save the state to restore to on re-entry. */
public void saveReentryState(Rect pipBounds) {
float snapFraction = mPipBoundsAlgorithm.getSnapFraction(pipBounds);
-
- if (!mPipBoundsState.hasUserResizedPip()) {
- mPipBoundsState.saveReentryState(null /* bounds */, snapFraction);
- return;
- }
-
- Size reentrySize = new Size(pipBounds.width(), pipBounds.height());
-
- // TODO: b/279937014 Investigate why userResizeBounds are empty with shell transitions on
- // fallback to using the userResizeBounds if userResizeBounds are not empty
- if (!mTouchHandler.getUserResizeBounds().isEmpty()) {
- Rect userResizeBounds = mTouchHandler.getUserResizeBounds();
- reentrySize = new Size(userResizeBounds.width(), userResizeBounds.height());
- }
-
- mPipBoundsState.saveReentryState(reentrySize, snapFraction);
+ mPipBoundsState.saveReentryState(snapFraction);
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
index 186cb615f4ec..e73a85003881 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
@@ -18,14 +18,31 @@ package com.android.wm.shell.pip2.phone;
import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
+import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_PIP;
+
+import android.app.PictureInPictureParams;
+import android.content.ComponentName;
import android.content.Context;
+import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
+import android.graphics.Rect;
import android.view.InsetsState;
+import android.view.SurfaceControl;
+
+import androidx.annotation.BinderThread;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
+import com.android.wm.shell.common.RemoteCallable;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.pip.IPip;
+import com.android.wm.shell.common.pip.IPipAnimationListener;
+import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
@@ -37,32 +54,54 @@ import com.android.wm.shell.sysui.ShellInit;
* Manages the picture-in-picture (PIP) UI and states for Phones.
*/
public class PipController implements ConfigurationChangeListener,
- DisplayController.OnDisplaysChangedListener {
+ DisplayController.OnDisplaysChangedListener, RemoteCallable<PipController> {
private static final String TAG = PipController.class.getSimpleName();
private Context mContext;
private ShellController mShellController;
private DisplayController mDisplayController;
private DisplayInsetsController mDisplayInsetsController;
+ private PipBoundsState mPipBoundsState;
+ private PipBoundsAlgorithm mPipBoundsAlgorithm;
private PipDisplayLayoutState mPipDisplayLayoutState;
+ private PipScheduler mPipScheduler;
+ private ShellExecutor mMainExecutor;
private PipController(Context context,
ShellInit shellInit,
ShellController shellController,
DisplayController displayController,
DisplayInsetsController displayInsetsController,
- PipDisplayLayoutState pipDisplayLayoutState) {
+ PipBoundsState pipBoundsState,
+ PipBoundsAlgorithm pipBoundsAlgorithm,
+ PipDisplayLayoutState pipDisplayLayoutState,
+ PipScheduler pipScheduler,
+ ShellExecutor mainExecutor) {
mContext = context;
mShellController = shellController;
mDisplayController = displayController;
mDisplayInsetsController = displayInsetsController;
+ mPipBoundsState = pipBoundsState;
+ mPipBoundsAlgorithm = pipBoundsAlgorithm;
mPipDisplayLayoutState = pipDisplayLayoutState;
+ mPipScheduler = pipScheduler;
+ mMainExecutor = mainExecutor;
if (PipUtils.isPip2ExperimentEnabled()) {
shellInit.addInitCallback(this::onInit, this);
}
}
+ @Override
+ public Context getContext() {
+ return mContext;
+ }
+
+ @Override
+ public ShellExecutor getRemoteCallExecutor() {
+ return mMainExecutor;
+ }
+
private void onInit() {
// Ensure that we have the display info in case we get calls to update the bounds before the
// listener calls back
@@ -80,6 +119,10 @@ public class PipController implements ConfigurationChangeListener,
.getDisplayLayout(mPipDisplayLayoutState.getDisplayId()));
}
});
+
+ // Allow other outside processes to bind to PiP controller using the key below.
+ mShellController.addExternalInterface(KEY_EXTRA_SHELL_PIP,
+ this::createExternalInterface, this);
}
/**
@@ -90,16 +133,24 @@ public class PipController implements ConfigurationChangeListener,
ShellController shellController,
DisplayController displayController,
DisplayInsetsController displayInsetsController,
- PipDisplayLayoutState pipDisplayLayoutState) {
+ PipBoundsState pipBoundsState,
+ PipBoundsAlgorithm pipBoundsAlgorithm,
+ PipDisplayLayoutState pipDisplayLayoutState,
+ PipScheduler pipScheduler,
+ ShellExecutor mainExecutor) {
if (!context.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: Device doesn't support Pip feature", TAG);
return null;
}
return new PipController(context, shellInit, shellController, displayController,
- displayInsetsController, pipDisplayLayoutState);
+ displayInsetsController, pipBoundsState, pipBoundsAlgorithm, pipDisplayLayoutState,
+ pipScheduler, mainExecutor);
}
+ private ExternalInterfaceBinder createExternalInterface() {
+ return new IPipImpl(this);
+ }
@Override
public void onConfigurationChanged(Configuration newConfiguration) {
@@ -130,4 +181,86 @@ public class PipController implements ConfigurationChangeListener,
private void onDisplayChanged(DisplayLayout layout) {
mPipDisplayLayoutState.setDisplayLayout(layout);
}
+
+ private Rect getSwipePipToHomeBounds(ComponentName componentName, ActivityInfo activityInfo,
+ PictureInPictureParams pictureInPictureParams,
+ int launcherRotation, Rect hotseatKeepClearArea) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "getSwipePipToHomeBounds: %s", componentName);
+ mPipBoundsState.setBoundsStateForEntry(componentName, activityInfo, pictureInPictureParams,
+ mPipBoundsAlgorithm);
+ return mPipBoundsAlgorithm.getEntryDestinationBounds();
+ }
+
+ private void onSwipePipToHomeAnimationStart(int taskId, ComponentName componentName,
+ Rect destinationBounds, SurfaceControl overlay, Rect appBounds) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "onSwipePipToHomeAnimationStart: %s", componentName);
+ mPipScheduler.setInSwipePipToHomeTransition(true);
+ // TODO: cache the overlay if provided for reparenting later.
+ }
+
+ /**
+ * The interface for calls from outside the host process.
+ */
+ @BinderThread
+ private static class IPipImpl extends IPip.Stub implements ExternalInterfaceBinder {
+ private PipController mController;
+
+ IPipImpl(PipController controller) {
+ mController = controller;
+ }
+
+ /**
+ * Invalidates this instance, preventing future calls from updating the controller.
+ */
+ @Override
+ public void invalidate() {
+ mController = null;
+ }
+
+ @Override
+ public Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo,
+ PictureInPictureParams pictureInPictureParams, int launcherRotation,
+ Rect keepClearArea) {
+ Rect[] result = new Rect[1];
+ executeRemoteCallWithTaskPermission(mController, "startSwipePipToHome",
+ (controller) -> {
+ result[0] = controller.getSwipePipToHomeBounds(componentName, activityInfo,
+ pictureInPictureParams, launcherRotation, keepClearArea);
+ }, true /* blocking */);
+ return result[0];
+ }
+
+ @Override
+ public void stopSwipePipToHome(int taskId, ComponentName componentName,
+ Rect destinationBounds, SurfaceControl overlay, Rect appBounds) {
+ if (overlay != null) {
+ overlay.setUnreleasedWarningCallSite("PipController.stopSwipePipToHome");
+ }
+ executeRemoteCallWithTaskPermission(mController, "stopSwipePipToHome",
+ (controller) -> controller.onSwipePipToHomeAnimationStart(
+ taskId, componentName, destinationBounds, overlay, appBounds));
+ }
+
+ @Override
+ public void abortSwipePipToHome(int taskId, ComponentName componentName) {}
+
+ @Override
+ public void setShelfHeight(boolean visible, int height) {}
+
+ @Override
+ public void setLauncherKeepClearAreaHeight(boolean visible, int height) {}
+
+ @Override
+ public void setLauncherAppIconSize(int iconSizePx) {}
+
+ @Override
+ public void setPipAnimationListener(IPipAnimationListener listener) {
+ // TODO: set a proper animation listener to update the Launcher state as needed.
+ }
+
+ @Override
+ public void setPipAnimationTypeToAlpha() {}
+ }
}
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 57b73b3019f4..895c793007a5 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
@@ -63,6 +63,9 @@ public class PipScheduler {
@Nullable
private SurfaceControl mPinnedTaskLeash;
+ // true if Launcher has started swipe PiP to home animation
+ private boolean mInSwipePipToHomeTransition;
+
/**
* 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
@@ -168,6 +171,14 @@ public class PipScheduler {
mPipTransitionController.startResizeTransition(wct, onFinishResizeCallback);
}
+ void setInSwipePipToHomeTransition(boolean inSwipePipToHome) {
+ mInSwipePipToHomeTransition = true;
+ }
+
+ boolean isInSwipePipToHomeTransition() {
+ return mInSwipePipToHomeTransition;
+ }
+
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 fbf4d13a0c19..dfb04758c851 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
@@ -152,6 +152,12 @@ public class PipTransition extends PipTransitionController {
@NonNull Transitions.TransitionFinishCallback finishCallback) {
if (transition == mEnterTransition) {
mEnterTransition = null;
+ if (mPipScheduler.isInSwipePipToHomeTransition()) {
+ // If this is the second transition as a part of swipe PiP to home cuj,
+ // handle this transition as a special case with no-op animation.
+ return handleSwipePipToHomeTransition(info, startTransaction, finishTransaction,
+ finishCallback);
+ }
if (isLegacyEnter(info)) {
// If this is a legacy-enter-pip (auto-enter is off and PiP activity went to pause),
// then we should run an ALPHA type (cross-fade) animation.
@@ -207,6 +213,25 @@ public class PipTransition extends PipTransitionController {
return true;
}
+ private boolean handleSwipePipToHomeTransition(@NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ TransitionInfo.Change pipChange = getPipChange(info);
+ if (pipChange == null) {
+ return false;
+ }
+ mPipScheduler.setInSwipePipToHomeTransition(false);
+ mPipTaskToken = pipChange.getContainer();
+
+ // cache the PiP task token and leash
+ mPipScheduler.setPipTaskToken(mPipTaskToken);
+
+ startTransaction.apply();
+ finishCallback.onTransitionFinished(null);
+ return true;
+ }
+
private boolean startBoundsTypeEnterAnimation(@NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
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 5e79681e060b..b8a0f6703b97 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
@@ -493,11 +493,9 @@ public class Transitions implements RemoteCallable<Transitions>,
final SurfaceControl leash = change.getLeash();
final int mode = info.getChanges().get(i).getMode();
- if (mode == TRANSIT_TO_FRONT
- && ((change.getStartAbsBounds().height() != change.getEndAbsBounds().height()
- || change.getStartAbsBounds().width() != change.getEndAbsBounds().width()))) {
- // When the window is moved to front with a different size, make sure the crop is
- // updated to prevent it from using the old crop.
+ if (mode == TRANSIT_TO_FRONT) {
+ // When the window is moved to front, make sure the crop is updated to prevent it
+ // from using the old crop.
t.setWindowCrop(leash, change.getEndAbsBounds().width(),
change.getEndAbsBounds().height());
}
@@ -1170,7 +1168,11 @@ public class Transitions implements RemoteCallable<Transitions>,
mPendingTransitions.add(0, active);
}
- /** Start a new transition directly. */
+ /**
+ * Start a new transition directly.
+ * @param handler if null, the transition will be dispatched to the registered set of transition
+ * handlers to be handled
+ */
public IBinder startTransition(@WindowManager.TransitionType int type,
@NonNull WindowContainerTransaction wct, @Nullable TransitionHandler handler) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Directly starting a new transition "
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 c713a2ed24b0..a2d962d065c8 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
@@ -728,7 +728,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
mTransitionDragActive = false;
final int statusBarHeight = getStatusBarHeight(
relevantDecor.mTaskInfo.displayId);
- if (ev.getY() > 2 * statusBarHeight) {
+ if (ev.getRawY() > 2 * statusBarHeight) {
if (DesktopModeStatus.isEnabled()) {
animateToDesktop(relevantDecor, ev);
}
@@ -753,10 +753,10 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
mDesktopTasksController.ifPresent(
c -> c.updateVisualIndicator(
relevantDecor.mTaskInfo,
- relevantDecor.mTaskSurface, ev.getX(), ev.getY()));
+ relevantDecor.mTaskSurface, ev.getRawX(), ev.getRawY()));
final int statusBarHeight = getStatusBarHeight(
relevantDecor.mTaskInfo.displayId);
- if (ev.getY() > statusBarHeight) {
+ if (ev.getRawY() > statusBarHeight) {
if (mMoveToDesktopAnimator == null) {
mMoveToDesktopAnimator = new MoveToDesktopAnimator(
mContext, mDragToDesktopAnimationStartBounds,
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipWithSourceRectHintTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipWithSourceRectHintTest.kt
new file mode 100644
index 000000000000..8c0a524cda1e
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/AutoEnterPipWithSourceRectHintTest.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.pip
+
+import android.platform.test.annotations.Presubmit
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test auto entering pip using a source rect hint.
+ *
+ * To run this test: `atest AutoEnterPipWithSourceRectHintTest`
+ *
+ * Actions:
+ * ```
+ * Launch an app in full screen
+ * Select "Auto-enter PiP" radio button
+ * Press "Set SourceRectHint" to create a temporary view that is used as the source rect hint
+ * Press Home button or swipe up to go Home and put [pipApp] in pip mode
+ * ```
+ *
+ * Notes:
+ * ```
+ * 1. All assertions are inherited from [AutoEnterPipOnGoToHomeTest]
+ * 2. Part of the test setup occurs automatically via
+ * [android.tools.device.flicker.legacy.runner.TransitionRunner],
+ * including configuring navigation mode, initial orientation and ensuring no
+ * apps are running before setup
+ * ```
+ */
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class AutoEnterPipWithSourceRectHintTest(flicker: LegacyFlickerTest) :
+ AutoEnterPipOnGoToHomeTest(flicker) {
+ override val defaultEnterPip: FlickerBuilder.() -> Unit = {
+ setup {
+ pipApp.launchViaIntent(wmHelper)
+ pipApp.setSourceRectHint()
+ pipApp.enableAutoEnterForPipActivity()
+ }
+ }
+
+ @Presubmit
+ @Test
+ fun pipOverlayNotShown() {
+ val overlay = ComponentNameMatcher.PIP_CONTENT_OVERLAY
+ flicker.assertLayers {
+ this.notContains(overlay)
+ }
+ }
+ @Presubmit
+ @Test
+ override fun pipOverlayLayerAppearThenDisappear() {
+ // we don't use overlay when entering with sourceRectHint
+ }
+
+ @Presubmit
+ @Test
+ override fun pipLayerOrOverlayRemainInsideVisibleBounds() {
+ // TODO (b/323511194): Looks like there is some bounciness with pip when using
+ // auto enter and sourceRectHint that causes the app to move outside of the display
+ // bounds during the transition.
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/AndroidManifest.xml b/libs/WindowManager/Shell/tests/unittest/AndroidManifest.xml
index 47a116be1b66..2ef425cf3d41 100644
--- a/libs/WindowManager/Shell/tests/unittest/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/tests/unittest/AndroidManifest.xml
@@ -21,6 +21,7 @@
<uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
<uses-permission android:name="android.permission.VIBRATE"/>
+ <uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS"/>
<application android:debuggable="true" android:largeHeap="true">
<uses-library android:name="android.test.mock" />
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java
index 9fe2cb11e804..81ba4b37d13b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java
@@ -113,8 +113,22 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase {
mExecutor = new TestShellExecutor();
mTaskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */
false, /* topActivityBoundsLetterboxed */ true, ACTION_MAIN, CATEGORY_LAUNCHER);
+ final DisplayInfo displayInfo = new DisplayInfo();
+ final int displayWidth = 1000;
+ final int displayHeight = 1200;
+ displayInfo.logicalWidth = displayWidth;
+ displayInfo.logicalHeight = displayHeight;
+ final DisplayLayout displayLayout = new DisplayLayout(displayInfo,
+ mContext.getResources(), /* hasNavigationBar= */ true, /* hasStatusBar= */ false);
+ InsetsState insetsState = new InsetsState();
+ insetsState.setDisplayFrame(new Rect(0, 0, displayWidth, displayHeight));
+ InsetsSource insetsSource = new InsetsSource(
+ InsetsSource.createId(null, 0, navigationBars()), navigationBars());
+ insetsSource.setFrame(0, displayHeight - 200, displayWidth, displayHeight);
+ insetsState.addSource(insetsSource);
+ displayLayout.setInsets(mContext.getResources(), insetsState);
mWindowManager = new UserAspectRatioSettingsWindowManager(mContext, mTaskInfo,
- mSyncTransactionQueue, mTaskListener, new DisplayLayout(), new CompatUIHintsState(),
+ mSyncTransactionQueue, mTaskListener, displayLayout, new CompatUIHintsState(),
mOnUserAspectRatioSettingsButtonClicked, mExecutor, flags -> 0,
mUserAspectRatioButtonShownChecker, s -> {});
spyOn(mWindowManager);
@@ -253,6 +267,31 @@ public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase {
}
@Test
+ public void testEligibleButtonHiddenIfLetterboxBoundsEqualToStableBounds() {
+ TaskInfo taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */
+ true, /* topActivityBoundsLetterboxed */ true, ACTION_MAIN, CATEGORY_LAUNCHER);
+
+ final Rect stableBounds = mWindowManager.getTaskStableBounds();
+ final int stableHeight = stableBounds.height();
+
+ // Letterboxed activity bounds equal to stable bounds, layout shouldn't be inflated
+ taskInfo.appCompatTaskInfo.topActivityLetterboxHeight = stableHeight;
+ taskInfo.appCompatTaskInfo.topActivityLetterboxWidth = stableBounds.width();
+
+ mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true);
+
+ verify(mWindowManager, never()).inflateLayout();
+
+ // Letterboxed activity bounds smaller than stable bounds, layout should be inflated
+ taskInfo.appCompatTaskInfo.topActivityLetterboxHeight = stableHeight - 100;
+
+ clearInvocations(mWindowManager);
+ mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true);
+
+ verify(mWindowManager).inflateLayout();
+ }
+
+ @Test
public void testUpdateDisplayLayout() {
final DisplayInfo displayInfo = new DisplayInfo();
displayInfo.logicalWidth = 1000;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
new file mode 100644
index 000000000000..f8ce4ee8e1ce
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
+import android.graphics.Rect
+import android.graphics.Region
+import android.testing.AndroidTestingRunner
+import android.view.SurfaceControl
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.R
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.DisplayLayout
+import com.android.wm.shell.common.SyncTransactionQueue
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class DesktopModeVisualIndicatorTest : ShellTestCase() {
+ @Mock private lateinit var taskInfo: RunningTaskInfo
+ @Mock private lateinit var syncQueue: SyncTransactionQueue
+ @Mock private lateinit var displayController: DisplayController
+ @Mock private lateinit var taskSurface: SurfaceControl
+ @Mock private lateinit var taskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
+ @Mock private lateinit var displayLayout: DisplayLayout
+
+ private lateinit var visualIndicator: DesktopModeVisualIndicator
+
+ @Before
+ fun setUp() {
+ visualIndicator = DesktopModeVisualIndicator(syncQueue, taskInfo, displayController,
+ context, taskSurface, taskDisplayAreaOrganizer)
+ whenever(displayLayout.width()).thenReturn(DISPLAY_BOUNDS.width())
+ whenever(displayLayout.height()).thenReturn(DISPLAY_BOUNDS.height())
+ }
+
+ @Test
+ fun testFullscreenRegionCalculation() {
+ val transitionHeight = context.resources.getDimensionPixelSize(
+ R.dimen.desktop_mode_transition_area_height)
+ val fromFreeformWidth = mContext.resources.getDimensionPixelSize(
+ R.dimen.desktop_mode_fullscreen_from_desktop_width
+ )
+ val fromFreeformHeight = mContext.resources.getDimensionPixelSize(
+ R.dimen.desktop_mode_fullscreen_from_desktop_height
+ )
+ var testRegion = visualIndicator.calculateFullscreenRegion(displayLayout,
+ WINDOWING_MODE_FULLSCREEN, CAPTION_HEIGHT)
+ assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 2400, transitionHeight))
+ testRegion = visualIndicator.calculateFullscreenRegion(displayLayout,
+ WINDOWING_MODE_FREEFORM, CAPTION_HEIGHT)
+ assertThat(testRegion.bounds).isEqualTo(Rect(
+ DISPLAY_BOUNDS.width() / 2 - fromFreeformWidth / 2,
+ -50,
+ DISPLAY_BOUNDS.width() / 2 + fromFreeformWidth / 2,
+ fromFreeformHeight))
+ testRegion = visualIndicator.calculateFullscreenRegion(displayLayout,
+ WINDOWING_MODE_MULTI_WINDOW, CAPTION_HEIGHT)
+ assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 2400, transitionHeight))
+ }
+
+ @Test
+ fun testSplitLeftRegionCalculation() {
+ val transitionHeight = context.resources.getDimensionPixelSize(
+ R.dimen.desktop_mode_split_from_desktop_height)
+ var testRegion = visualIndicator.calculateSplitLeftRegion(displayLayout,
+ WINDOWING_MODE_FULLSCREEN, TRANSITION_AREA_WIDTH, CAPTION_HEIGHT)
+ assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 32, 1600))
+ testRegion = visualIndicator.calculateSplitLeftRegion(displayLayout,
+ WINDOWING_MODE_FREEFORM, TRANSITION_AREA_WIDTH, CAPTION_HEIGHT)
+ assertThat(testRegion.bounds).isEqualTo(Rect(0, transitionHeight, 32, 1600))
+ testRegion = visualIndicator.calculateSplitLeftRegion(displayLayout,
+ WINDOWING_MODE_MULTI_WINDOW, TRANSITION_AREA_WIDTH, CAPTION_HEIGHT)
+ assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 32, 1600))
+ }
+
+ @Test
+ fun testSplitRightRegionCalculation() {
+ val transitionHeight = context.resources.getDimensionPixelSize(
+ R.dimen.desktop_mode_split_from_desktop_height)
+ var testRegion = visualIndicator.calculateSplitRightRegion(displayLayout,
+ WINDOWING_MODE_FULLSCREEN, TRANSITION_AREA_WIDTH, CAPTION_HEIGHT)
+ assertThat(testRegion.bounds).isEqualTo(Rect(2368, -50, 2400, 1600))
+ testRegion = visualIndicator.calculateSplitRightRegion(displayLayout,
+ WINDOWING_MODE_FREEFORM, TRANSITION_AREA_WIDTH, CAPTION_HEIGHT)
+ assertThat(testRegion.bounds).isEqualTo(Rect(2368, transitionHeight, 2400, 1600))
+ testRegion = visualIndicator.calculateSplitRightRegion(displayLayout,
+ WINDOWING_MODE_MULTI_WINDOW, TRANSITION_AREA_WIDTH, CAPTION_HEIGHT)
+ assertThat(testRegion.bounds).isEqualTo(Rect(2368, -50, 2400, 1600))
+ }
+
+ @Test
+ fun testToDesktopRegionCalculation() {
+ val fullscreenRegion = visualIndicator.calculateFullscreenRegion(displayLayout,
+ WINDOWING_MODE_FULLSCREEN, CAPTION_HEIGHT)
+ val splitLeftRegion = visualIndicator.calculateSplitLeftRegion(displayLayout,
+ WINDOWING_MODE_FULLSCREEN, TRANSITION_AREA_WIDTH, CAPTION_HEIGHT)
+ val splitRightRegion = visualIndicator.calculateSplitRightRegion(displayLayout,
+ WINDOWING_MODE_FULLSCREEN, TRANSITION_AREA_WIDTH, CAPTION_HEIGHT)
+ val desktopRegion = visualIndicator.calculateToDesktopRegion(displayLayout,
+ WINDOWING_MODE_FULLSCREEN, splitLeftRegion, splitRightRegion, fullscreenRegion)
+ var testRegion = Region()
+ testRegion.union(DISPLAY_BOUNDS)
+ testRegion.op(splitLeftRegion, Region.Op.DIFFERENCE)
+ testRegion.op(splitRightRegion, Region.Op.DIFFERENCE)
+ testRegion.op(fullscreenRegion, Region.Op.DIFFERENCE)
+ assertThat(desktopRegion).isEqualTo(testRegion)
+ }
+
+ companion object {
+ private const val TRANSITION_AREA_WIDTH = 32
+ private const val CAPTION_HEIGHT = 50
+ private val DISPLAY_BOUNDS = Rect(0, 0, 2400, 1600)
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 63618f4d0673..383621beca22 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -48,12 +48,14 @@ import com.android.wm.shell.TestRunningTaskInfoBuilder
import com.android.wm.shell.TestShellExecutor
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.LaunchAdjacentController
+import com.android.wm.shell.common.MultiInstanceHelper
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFullscreenTask
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createHomeTask
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createSplitScreenTask
+import com.android.wm.shell.draganddrop.DragAndDropController
import com.android.wm.shell.recents.RecentsTransitionHandler
import com.android.wm.shell.recents.RecentsTransitionStateListener
import com.android.wm.shell.splitscreen.SplitScreenController
@@ -106,6 +108,8 @@ class DesktopTasksControllerTest : ShellTestCase() {
@Mock lateinit var desktopModeWindowDecoration: DesktopModeWindowDecoration
@Mock lateinit var splitScreenController: SplitScreenController
@Mock lateinit var recentsTransitionHandler: RecentsTransitionHandler
+ @Mock lateinit var dragAndDropController: DragAndDropController
+ @Mock lateinit var multiInstanceHelper: MultiInstanceHelper
private lateinit var mockitoSession: StaticMockitoSession
private lateinit var controller: DesktopTasksController
@@ -148,6 +152,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
shellTaskOrganizer,
syncQueue,
rootTaskDisplayAreaOrganizer,
+ dragAndDropController,
transitions,
enterDesktopTransitionHandler,
exitDesktopTransitionHandler,
@@ -156,6 +161,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
desktopModeTaskRepository,
launchAdjacentController,
recentsTransitionHandler,
+ multiInstanceHelper,
shellExecutor
)
}
@@ -734,6 +740,46 @@ class DesktopTasksControllerTest : ShellTestCase() {
shellExecutor.flushAll()
verify(launchAdjacentController).launchAdjacentEnabled = true
}
+ @Test
+ fun enterDesktop_fullscreenTaskIsMovedToDesktop() {
+ val task1 = setUpFullscreenTask()
+ val task2 = setUpFullscreenTask()
+ val task3 = setUpFullscreenTask()
+
+ task1.isFocused = true
+ task2.isFocused = false
+ task3.isFocused = false
+
+ controller.enterDesktop(DEFAULT_DISPLAY)
+
+ val wct = getLatestMoveToDesktopWct()
+ assertThat(wct.changes[task1.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ }
+
+ @Test
+ fun enterDesktop_splitScreenTaskIsMovedToDesktop() {
+ val task1 = setUpSplitScreenTask()
+ val task2 = setUpFullscreenTask()
+ val task3 = setUpFullscreenTask()
+ val task4 = setUpSplitScreenTask()
+
+ task1.isFocused = true
+ task2.isFocused = false
+ task3.isFocused = false
+ task4.isFocused = true
+
+ task4.parentTaskId = task1.taskId
+
+ controller.enterDesktop(DEFAULT_DISPLAY)
+
+ val wct = getLatestMoveToDesktopWct()
+ assertThat(wct.changes[task4.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ verify(splitScreenController).prepareExitSplitScreen(any(), anyInt(),
+ eq(SplitScreenController.EXIT_REASON_DESKTOP_MODE)
+ )
+ }
private fun setUpFreeformTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
val task = createFreeformTask(displayId)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java
index 54f36f61859d..a64ebd301c00 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java
@@ -45,12 +45,14 @@ import androidx.test.filters.SmallTest;
import com.android.internal.logging.UiEventLogger;
import com.android.launcher3.icons.IconProvider;
+import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.Transitions;
import org.junit.Before;
import org.junit.Test;
@@ -84,7 +86,9 @@ public class DragAndDropControllerTest extends ShellTestCase {
@Mock
private ShellExecutor mMainExecutor;
@Mock
- private WindowManager mWindowManager;
+ private Transitions mTransitions;
+ @Mock
+ private GlobalDragListener mGlobalDragListener;
private DragAndDropController mController;
@@ -93,7 +97,7 @@ public class DragAndDropControllerTest extends ShellTestCase {
MockitoAnnotations.initMocks(this);
mController = new DragAndDropController(mContext, mShellInit, mShellController,
mShellCommandHandler, mDisplayController, mUiEventLogger, mIconProvider,
- mMainExecutor);
+ mGlobalDragListener, mTransitions, mMainExecutor);
mController.onInit();
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/UnhandledDragControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/GlobalDragListenerTest.kt
index 522f05233f3a..e731b06c0947 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/UnhandledDragControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/GlobalDragListenerTest.kt
@@ -15,6 +15,7 @@
*/
package com.android.wm.shell.draganddrop
+import android.app.ActivityManager.RunningTaskInfo
import android.os.RemoteException
import android.view.DragEvent
import android.view.DragEvent.ACTION_DROP
@@ -25,19 +26,17 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.common.ShellExecutor
-import com.android.wm.shell.draganddrop.UnhandledDragController.UnhandledDragAndDropCallback
+import com.android.wm.shell.draganddrop.GlobalDragListener.GlobalDragListenerCallback
import java.util.function.Consumer
import junit.framework.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers
-import org.mockito.Mock
import org.mockito.Mockito
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.reset
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.reset
+import org.mockito.kotlin.verify
/**
* Tests for the unhandled drag controller.
@@ -45,35 +44,31 @@ import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidJUnit4::class)
class UnhandledDragControllerTest : ShellTestCase() {
- @Mock
- private lateinit var mIWindowManager: IWindowManager
+ private val mIWindowManager = mock<IWindowManager>()
+ private val mMainExecutor = mock<ShellExecutor>()
- @Mock
- private lateinit var mMainExecutor: ShellExecutor
-
- private lateinit var mController: UnhandledDragController
+ private lateinit var mController: GlobalDragListener
@Before
@Throws(RemoteException::class)
fun setUp() {
- MockitoAnnotations.initMocks(this)
- mController = UnhandledDragController(mIWindowManager, mMainExecutor)
+ mController = GlobalDragListener(mIWindowManager, mMainExecutor)
}
@Test
fun setListener_registersUnregistersWithWM() {
- mController.setListener(object : UnhandledDragAndDropCallback {})
- mController.setListener(object : UnhandledDragAndDropCallback {})
- mController.setListener(object : UnhandledDragAndDropCallback {})
+ mController.setListener(object : GlobalDragListenerCallback {})
+ mController.setListener(object : GlobalDragListenerCallback {})
+ mController.setListener(object : GlobalDragListenerCallback {})
verify(mIWindowManager, Mockito.times(1))
- .setUnhandledDragListener(ArgumentMatchers.any())
+ .setGlobalDragListener(ArgumentMatchers.any())
reset(mIWindowManager)
mController.setListener(null)
mController.setListener(null)
mController.setListener(null)
verify(mIWindowManager, Mockito.times(1))
- .setUnhandledDragListener(ArgumentMatchers.isNull())
+ .setGlobalDragListener(ArgumentMatchers.isNull())
}
@Test
@@ -81,7 +76,7 @@ class UnhandledDragControllerTest : ShellTestCase() {
// Simulate an unhandled drop
val dropEvent = DragEvent.obtain(ACTION_DROP, 0f, 0f, 0f, 0f, null, null, null,
null, null, false)
- val wmCallback = mock(IUnhandledDragCallback::class.java)
+ val wmCallback = mock<IUnhandledDragCallback>()
mController.onUnhandledDrop(dropEvent, wmCallback)
verify(wmCallback).notifyUnhandledDropComplete(ArgumentMatchers.eq(false))
@@ -92,7 +87,7 @@ class UnhandledDragControllerTest : ShellTestCase() {
val lastDragEvent = arrayOfNulls<DragEvent>(1)
// Set a listener to listen for unhandled drops
- mController.setListener(object : UnhandledDragAndDropCallback {
+ mController.setListener(object : GlobalDragListenerCallback {
override fun onUnhandledDrop(dragEvent: DragEvent,
onFinishedCallback: Consumer<Boolean>) {
lastDragEvent[0] = dragEvent
@@ -102,14 +97,31 @@ class UnhandledDragControllerTest : ShellTestCase() {
})
// Simulate an unhandled drop
- val dragSurface = mock(SurfaceControl::class.java)
+ val dragSurface = mock<SurfaceControl>()
val dropEvent = DragEvent.obtain(ACTION_DROP, 0f, 0f, 0f, 0f, null, null, null,
dragSurface, null, false)
- val wmCallback = mock(IUnhandledDragCallback::class.java)
+ val wmCallback = mock<IUnhandledDragCallback>()
mController.onUnhandledDrop(dropEvent, wmCallback)
verify(wmCallback).notifyUnhandledDropComplete(ArgumentMatchers.eq(true))
verify(dragSurface).release()
assertEquals(lastDragEvent.get(0), dropEvent)
}
+
+ @Test
+ fun onCrossWindowDrop() {
+ val lastTaskInfo = arrayOfNulls<RunningTaskInfo>(1)
+
+ // Set a listener to listen for unhandled drops
+ mController.setListener(object : GlobalDragListenerCallback {
+ override fun onCrossWindowDrop(taskInfo: RunningTaskInfo) {
+ lastTaskInfo[0] = taskInfo
+ }
+ })
+
+ // Simulate a cross-window drop
+ val taskInfo = mock<RunningTaskInfo>()
+ mController.onCrossWindowDrop(taskInfo)
+ assertEquals(lastTaskInfo.get(0), taskInfo)
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
index 46259a8b177f..080b0ae006ea 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
@@ -354,14 +354,17 @@ public class PipBoundsAlgorithmTest extends ShellTestCase {
}
@Test
- public void getEntryDestinationBounds_reentryStateExists_restoreLastSize() {
+ public void getEntryDestinationBounds_reentryStateExists_restoreProportionalSize() {
mPipBoundsState.setAspectRatio(DEFAULT_ASPECT_RATIO);
+ final Size maxSize = mSizeSpecSource.getMaxSize(DEFAULT_ASPECT_RATIO);
+ mPipBoundsState.setMaxSize(maxSize.getWidth(), maxSize.getHeight());
final Rect reentryBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
reentryBounds.scale(1.25f);
+ mPipBoundsState.setBounds(reentryBounds); // this updates the bounds scale used in reentry
+
final float reentrySnapFraction = mPipBoundsAlgorithm.getSnapFraction(reentryBounds);
- mPipBoundsState.saveReentryState(
- new Size(reentryBounds.width(), reentryBounds.height()), reentrySnapFraction);
+ mPipBoundsState.saveReentryState(reentrySnapFraction);
final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
assertEquals(reentryBounds.width(), destinationBounds.width());
@@ -375,8 +378,7 @@ public class PipBoundsAlgorithmTest extends ShellTestCase {
reentryBounds.offset(0, -100);
final float reentrySnapFraction = mPipBoundsAlgorithm.getSnapFraction(reentryBounds);
- mPipBoundsState.saveReentryState(
- new Size(reentryBounds.width(), reentryBounds.height()), reentrySnapFraction);
+ mPipBoundsState.saveReentryState(reentrySnapFraction);
final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
index db98abbbcbf1..304da75f870c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
@@ -114,22 +114,19 @@ public class PipBoundsStateTest extends ShellTestCase {
@Test
public void testSetReentryState() {
- final Size size = new Size(100, 100);
final float snapFraction = 0.5f;
- mPipBoundsState.saveReentryState(size, snapFraction);
+ mPipBoundsState.saveReentryState(snapFraction);
final PipBoundsState.PipReentryState state = mPipBoundsState.getReentryState();
- assertEquals(size, state.getSize());
assertEquals(snapFraction, state.getSnapFraction(), 0.01);
}
@Test
public void testClearReentryState() {
- final Size size = new Size(100, 100);
final float snapFraction = 0.5f;
- mPipBoundsState.saveReentryState(size, snapFraction);
+ mPipBoundsState.saveReentryState(snapFraction);
mPipBoundsState.clearReentryState();
assertNull(mPipBoundsState.getReentryState());
@@ -138,20 +135,19 @@ public class PipBoundsStateTest extends ShellTestCase {
@Test
public void testSetLastPipComponentName_notChanged_doesNotClearReentryState() {
mPipBoundsState.setLastPipComponentName(mTestComponentName1);
- mPipBoundsState.saveReentryState(DEFAULT_SIZE, DEFAULT_SNAP_FRACTION);
+ mPipBoundsState.saveReentryState(DEFAULT_SNAP_FRACTION);
mPipBoundsState.setLastPipComponentName(mTestComponentName1);
final PipBoundsState.PipReentryState state = mPipBoundsState.getReentryState();
assertNotNull(state);
- assertEquals(DEFAULT_SIZE, state.getSize());
assertEquals(DEFAULT_SNAP_FRACTION, state.getSnapFraction(), 0.01);
}
@Test
public void testSetLastPipComponentName_changed_clearReentryState() {
mPipBoundsState.setLastPipComponentName(mTestComponentName1);
- mPipBoundsState.saveReentryState(DEFAULT_SIZE, DEFAULT_SNAP_FRACTION);
+ mPipBoundsState.saveReentryState(DEFAULT_SNAP_FRACTION);
mPipBoundsState.setLastPipComponentName(mTestComponentName2);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index 4eb519334e12..5d968d3360ab 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -45,7 +45,6 @@ import android.os.RemoteException;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
-import android.util.Size;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.WindowManagerShellWrapper;
@@ -256,40 +255,13 @@ public class PipControllerTest extends ShellTestCase {
}
@Test
- public void saveReentryState_noUserResize_doesNotSaveSize() {
+ public void saveReentryState_savesPipBoundsState() {
final Rect bounds = new Rect(0, 0, 10, 10);
when(mMockPipBoundsAlgorithm.getSnapFraction(bounds)).thenReturn(1.0f);
- when(mMockPipBoundsState.hasUserResizedPip()).thenReturn(false);
mPipController.saveReentryState(bounds);
- verify(mMockPipBoundsState).saveReentryState(null, 1.0f);
- }
-
- @Test
- public void saveReentryState_nonEmptyUserResizeBounds_savesSize() {
- final Rect bounds = new Rect(0, 0, 10, 10);
- final Rect resizedBounds = new Rect(0, 0, 30, 30);
- when(mMockPipBoundsAlgorithm.getSnapFraction(bounds)).thenReturn(1.0f);
- when(mMockPipTouchHandler.getUserResizeBounds()).thenReturn(resizedBounds);
- when(mMockPipBoundsState.hasUserResizedPip()).thenReturn(true);
-
- mPipController.saveReentryState(bounds);
-
- verify(mMockPipBoundsState).saveReentryState(new Size(30, 30), 1.0f);
- }
-
- @Test
- public void saveReentryState_emptyUserResizeBounds_savesSize() {
- final Rect bounds = new Rect(0, 0, 10, 10);
- final Rect resizedBounds = new Rect(0, 0, 0, 0);
- when(mMockPipBoundsAlgorithm.getSnapFraction(bounds)).thenReturn(1.0f);
- when(mMockPipTouchHandler.getUserResizeBounds()).thenReturn(resizedBounds);
- when(mMockPipBoundsState.hasUserResizedPip()).thenReturn(true);
-
- mPipController.saveReentryState(bounds);
-
- verify(mMockPipBoundsState).saveReentryState(new Size(10, 10), 1.0f);
+ verify(mMockPipBoundsState).saveReentryState(1.0f);
}
@Test
diff --git a/libs/hwui/CanvasTransform.cpp b/libs/hwui/CanvasTransform.cpp
index b667daf9c850..30e7a628f1f6 100644
--- a/libs/hwui/CanvasTransform.cpp
+++ b/libs/hwui/CanvasTransform.cpp
@@ -137,9 +137,10 @@ static BitmapPalette filterPalette(const SkPaint* paint, BitmapPalette palette)
return palette;
}
- SkColor color = palette == BitmapPalette::Light ? SK_ColorWHITE : SK_ColorBLACK;
- color = paint->getColorFilter()->filterColor(color);
- return paletteForColorHSV(color);
+ SkColor4f color = palette == BitmapPalette::Light ? SkColors::kWhite : SkColors::kBlack;
+ sk_sp<SkColorSpace> srgb = SkColorSpace::MakeSRGB();
+ color = paint->getColorFilter()->filterColor4f(color, srgb.get(), srgb.get());
+ return paletteForColorHSV(color.toSkColor());
}
bool transformPaint(ColorTransform transform, SkPaint* paint) {
diff --git a/libs/hwui/DamageAccumulator.cpp b/libs/hwui/DamageAccumulator.cpp
index fd276419f5e5..28d85bd860df 100644
--- a/libs/hwui/DamageAccumulator.cpp
+++ b/libs/hwui/DamageAccumulator.cpp
@@ -218,7 +218,7 @@ void DamageAccumulator::applyRenderNodeTransform(DirtyStack* frame) {
}
// Perform clipping
- if (props.getClipDamageToBounds() && !frame->pendingDirty.isEmpty()) {
+ if (props.getClipDamageToBounds()) {
if (!frame->pendingDirty.intersect(SkRect::MakeIWH(props.getWidth(), props.getHeight()))) {
frame->pendingDirty.setEmpty();
}
diff --git a/libs/hwui/hwui/DrawTextFunctor.h b/libs/hwui/hwui/DrawTextFunctor.h
index 02bf0d8d5e95..1fcb6920db14 100644
--- a/libs/hwui/hwui/DrawTextFunctor.h
+++ b/libs/hwui/hwui/DrawTextFunctor.h
@@ -92,6 +92,7 @@ public:
// high contrast draw path
int color = paint.getColor();
bool darken;
+ // This equation should match the one in core/java/android/text/Layout.java
if (flags::high_contrast_text_luminance()) {
uirenderer::Lab lab = uirenderer::sRGBToLab(color);
darken = lab.L <= 50;
diff --git a/libs/hwui/jni/android_graphics_Canvas.cpp b/libs/hwui/jni/android_graphics_Canvas.cpp
index 1fc34d633370..9b63a46822ac 100644
--- a/libs/hwui/jni/android_graphics_Canvas.cpp
+++ b/libs/hwui/jni/android_graphics_Canvas.cpp
@@ -88,6 +88,10 @@ static void setBitmap(JNIEnv* env, jobject, jlong canvasHandle, jlong bitmapHand
get_canvas(canvasHandle)->setBitmap(bitmap);
}
+static jboolean isHighContrastText(CRITICAL_JNI_PARAMS_COMMA jlong canvasHandle) {
+ return get_canvas(canvasHandle)->isHighContrastText() ? JNI_TRUE : JNI_FALSE;
+}
+
static jboolean isOpaque(CRITICAL_JNI_PARAMS_COMMA jlong canvasHandle) {
return get_canvas(canvasHandle)->isOpaque() ? JNI_TRUE : JNI_FALSE;
}
@@ -792,6 +796,7 @@ static const JNINativeMethod gMethods[] = {
// ------------ @CriticalNative ----------------
{"nIsOpaque", "(J)Z", (void*)CanvasJNI::isOpaque},
+ {"nIsHighContrastText", "(J)Z", (void*)CanvasJNI::isHighContrastText},
{"nGetWidth", "(J)I", (void*)CanvasJNI::getWidth},
{"nGetHeight", "(J)I", (void*)CanvasJNI::getHeight},
{"nSave", "(JI)I", (void*)CanvasJNI::save},
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 9c7f7cc24266..1d0330185b1c 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -1067,6 +1067,7 @@ SkRect CanvasContext::computeDirtyRect(const Frame& frame, SkRect* dirty) {
if (dirty->isEmpty()) {
dirty->setIWH(frame.width(), frame.height());
+ return *dirty;
}
// At this point dirty is the area of the window to update. However,
diff --git a/media/java/android/media/browse/MediaBrowser.java b/media/java/android/media/browse/MediaBrowser.java
index b662901176e6..022278298875 100644
--- a/media/java/android/media/browse/MediaBrowser.java
+++ b/media/java/android/media/browse/MediaBrowser.java
@@ -697,6 +697,19 @@ public final class MediaBrowser {
});
}
+ private void onDisconnectRequested(ServiceCallbacks callback) {
+ mHandler.post(
+ () -> {
+ Log.i(TAG, "onDisconnectRequest for " + mServiceComponent);
+
+ if (!isCurrent(callback, "onDisconnectRequest")) {
+ return;
+ }
+ forceCloseConnection();
+ mCallback.onDisconnected();
+ });
+ }
+
/**
* Return true if {@code callback} is the current ServiceCallbacks. Also logs if it's not.
*/
@@ -880,6 +893,19 @@ public final class MediaBrowser {
*/
public void onConnectionFailed() {
}
+
+ /**
+ * Invoked after disconnecting by request of the {@link MediaBrowserService}.
+ *
+ * <p>The default implementation of this method calls {@link #onConnectionFailed()}.
+ *
+ * @hide
+ */
+ // TODO: b/185136506 - Consider publishing this API in the next window for API changes, if
+ // the need arises.
+ public void onDisconnected() {
+ onConnectionFailed();
+ }
}
/**
@@ -1112,6 +1138,14 @@ public final class MediaBrowser {
mediaBrowser.onLoadChildren(this, parentId, list, options);
}
}
+
+ @Override
+ public void onDisconnect() {
+ MediaBrowser mediaBrowser = mMediaBrowser.get();
+ if (mediaBrowser != null) {
+ mediaBrowser.onDisconnectRequested(this);
+ }
+ }
}
private static class Subscription {
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig
index 8dba04066ad9..6cf9c6fa7616 100644
--- a/media/java/android/media/flags/media_better_together.aconfig
+++ b/media/java/android/media/flags/media_better_together.aconfig
@@ -104,3 +104,10 @@ flag {
description: "Enable new MediaRouter2 API to enable watch companion apps to scan while the phone screen is off."
bug: "281072508"
}
+
+flag {
+ name: "enable_null_session_in_media_browser_service"
+ namespace: "media_solutions"
+ description: "Enables apps owning a MediaBrowserService to disconnect all connected browsers."
+ bug: "263520343"
+}
diff --git a/media/java/android/media/midi/package.html b/media/java/android/media/midi/package.html
index ae0c2ab0e811..45b4370d6cc9 100644
--- a/media/java/android/media/midi/package.html
+++ b/media/java/android/media/midi/package.html
@@ -478,7 +478,7 @@ MIDI 1.0 virtual devices, android.media.midi.MidiUmpDeviceService is used</p>
&lt;intent-filter>
&lt;action android:name="android.media.midi.MidiUmpDeviceService" />
&lt;/intent-filter>
- &lt;meta-data android:name="android.media.midi.MidiUmpDeviceService"
+ &lt;property android:name="android.media.midi.MidiUmpDeviceService"
android:resource="@xml/<strong>echo_device_info</strong>" />
&lt;/service>
</pre>
diff --git a/media/java/android/media/tv/SignalingDataResponse.java b/media/java/android/media/tv/SignalingDataResponse.java
index be172ec62773..51fa6a23bdf6 100644
--- a/media/java/android/media/tv/SignalingDataResponse.java
+++ b/media/java/android/media/tv/SignalingDataResponse.java
@@ -73,6 +73,10 @@ public final class SignalingDataResponse extends BroadcastInfoResponse implement
/**
* Gets a list of types of metadata that are contained in this response.
*
+ * <p> This list correlates to all the available types that can be found within
+ * {@link #getSignalingDataInfoList()}. This list is determined by the types specified in
+ * {@link SignalingDataRequest#getSignalingDataTypes()}.
+ *
* <p> A list of types available are defined in {@link SignalingDataRequest}.
* For more information about these types, see A/344:2023-5 9.2.10 - Query Signaling Data API.
*
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
index f332f8102013..84d08db1b9c8 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
@@ -964,7 +964,11 @@ public abstract class TvInteractiveAppService extends Service {
/**
* Called when the TV App sends the selected track info as a response to
- * {@link #requestSelectedTrackInfo()}
+ * {@link #requestSelectedTrackInfo()}.
+ *
+ * <p> When a selected track changes as a result of a new selection,
+ * {@link #onTrackSelected(int, String)} should be used instead to communicate the specific
+ * track selection.
*
* @param tracks A list of {@link TvTrackInfo} that are currently selected
*/
@@ -1383,6 +1387,8 @@ public abstract class TvInteractiveAppService extends Service {
* <p> Normally, track info cannot be synchronized until the channel has
* been changed. This is used when the session of the {@link TvInteractiveAppService}
* is newly created and the normal synchronization has not happened yet.
+ *
+ * <p> The track info will be returned in {@link #onSelectedTrackInfo(List)}
*/
@FlaggedApi(Flags.FLAG_TIAF_V_APIS)
@CallSuper
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppView.java b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
index 29a3b98073fe..635572d12cc5 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppView.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
@@ -585,7 +585,8 @@ public class TvInteractiveAppView extends ViewGroup {
}
/**
- * Sends the currently selected track info to the TV Interactive App.
+ * Sends the currently selected track info to the TV Interactive App in response to a
+ * {@link TvInteractiveAppCallback#onRequestSelectedTrackInfo(String)} request.
*
* @param tracks list of {@link TvTrackInfo} of the currently selected track(s)
*/
diff --git a/media/java/android/service/media/IMediaBrowserServiceCallbacks.aidl b/media/java/android/service/media/IMediaBrowserServiceCallbacks.aidl
index a8772076af97..fbb7cfd5ded1 100644
--- a/media/java/android/service/media/IMediaBrowserServiceCallbacks.aidl
+++ b/media/java/android/service/media/IMediaBrowserServiceCallbacks.aidl
@@ -24,4 +24,11 @@ oneway interface IMediaBrowserServiceCallbacks {
@UnsupportedAppUsage
void onConnectFailed();
void onLoadChildren(String mediaId, in ParceledListSlice list, in Bundle options);
+ /**
+ * Invoked when the browser service cuts off the connection with the browser.
+ *
+ * <p>The browser must also clean up any state associated with this connection, as if the
+ * service had been destroyed.
+ */
+ void onDisconnect();
}
diff --git a/media/java/android/service/media/MediaBrowserService.java b/media/java/android/service/media/MediaBrowserService.java
index d17b341782ce..39ef528fb69b 100644
--- a/media/java/android/service/media/MediaBrowserService.java
+++ b/media/java/android/service/media/MediaBrowserService.java
@@ -38,10 +38,13 @@ import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ResultReceiver;
+import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Pair;
+import com.android.media.flags.Flags;
+
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
@@ -51,6 +54,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
/**
* Base class for media browser services.
@@ -96,6 +100,7 @@ public abstract class MediaBrowserService extends Service {
private static final int RESULT_ERROR = -1;
private static final int RESULT_OK = 0;
+ private final ServiceBinder mBinder;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@@ -105,7 +110,7 @@ public abstract class MediaBrowserService extends Service {
private final Handler mHandler = new Handler();
- private final ServiceState mServiceState = new ServiceState();
+ private final AtomicReference<ServiceState> mServiceState;
// Holds the connection record associated with the currently executing callback operation, if
// any. See getCurrentBrowserInfo for an example. Must only be accessed on mHandler.
@@ -216,16 +221,21 @@ public abstract class MediaBrowserService extends Service {
}
private static class ServiceBinder extends IMediaBrowserService.Stub {
- private WeakReference<ServiceState> mServiceState;
+ private final AtomicReference<WeakReference<ServiceState>> mServiceState;
private ServiceBinder(ServiceState serviceState) {
- mServiceState = new WeakReference(serviceState);
+ mServiceState = new AtomicReference<>();
+ setServiceState(serviceState);
+ }
+
+ public void setServiceState(ServiceState serviceState) {
+ mServiceState.set(new WeakReference<>(serviceState));
}
@Override
public void connect(final String pkg, final Bundle rootHints,
final IMediaBrowserServiceCallbacks callbacks) {
- ServiceState serviceState = mServiceState.get();
+ ServiceState serviceState = mServiceState.get().get();
if (serviceState == null) {
return;
}
@@ -243,7 +253,7 @@ public abstract class MediaBrowserService extends Service {
@Override
public void disconnect(final IMediaBrowserServiceCallbacks callbacks) {
- ServiceState serviceState = mServiceState.get();
+ ServiceState serviceState = mServiceState.get().get();
if (serviceState == null) {
return;
}
@@ -260,7 +270,7 @@ public abstract class MediaBrowserService extends Service {
@Override
public void addSubscription(final String id, final IBinder token, final Bundle options,
final IMediaBrowserServiceCallbacks callbacks) {
- ServiceState serviceState = mServiceState.get();
+ ServiceState serviceState = mServiceState.get().get();
if (serviceState == null) {
return;
}
@@ -278,7 +288,7 @@ public abstract class MediaBrowserService extends Service {
@Override
public void removeSubscription(final String id, final IBinder token,
final IMediaBrowserServiceCallbacks callbacks) {
- ServiceState serviceState = mServiceState.get();
+ ServiceState serviceState = mServiceState.get().get();
if (serviceState == null) {
return;
}
@@ -294,7 +304,7 @@ public abstract class MediaBrowserService extends Service {
@Override
public void getMediaItem(final String mediaId, final ResultReceiver receiver,
final IMediaBrowserServiceCallbacks callbacks) {
- ServiceState serviceState = mServiceState.get();
+ ServiceState serviceState = mServiceState.get().get();
if (serviceState == null) {
return;
}
@@ -304,17 +314,23 @@ public abstract class MediaBrowserService extends Service {
}
}
+ /** Default constructor. */
+ public MediaBrowserService() {
+ mServiceState = new AtomicReference<>(new ServiceState());
+ mBinder = new ServiceBinder(mServiceState.get());
+ }
+
@Override
public void onCreate() {
super.onCreate();
- mServiceState.mBinder = new ServiceBinder(mServiceState);
}
@Override
public IBinder onBind(Intent intent) {
if (SERVICE_INTERFACE.equals(intent.getAction())) {
- return mServiceState.mBinder;
+ return mBinder;
}
+
return null;
}
@@ -428,21 +444,33 @@ public abstract class MediaBrowserService extends Service {
/**
* Call to set the media session.
- * <p>
- * This should be called as soon as possible during the service's startup.
- * It may only be called once.
+ *
+ * <p>This should be called as soon as possible during the service's startup. It may only be
+ * called once.
*
* @param token The token for the service's {@link MediaSession}.
*/
+ // TODO: b/185136506 - Update the javadoc to reflect API changes when
+ // enableNullSessionInMediaBrowserService makes it to nextfood.
public void setSessionToken(final MediaSession.Token token) {
+ ServiceState serviceState = mServiceState.get();
if (token == null) {
- throw new IllegalArgumentException("Session token may not be null.");
- }
- if (mServiceState.mSession != null) {
+ if (!Flags.enableNullSessionInMediaBrowserService()) {
+ throw new IllegalArgumentException("Session token may not be null.");
+ } else if (serviceState.mSession != null) {
+ ServiceState newServiceState = new ServiceState();
+ mBinder.setServiceState(newServiceState);
+ mServiceState.set(newServiceState);
+ serviceState.release();
+ } else {
+ // Nothing to do. The session is already null.
+ }
+ } else if (serviceState.mSession != null) {
throw new IllegalStateException("The session token has already been set.");
+ } else {
+ serviceState.mSession = token;
+ mHandler.post(() -> serviceState.notifySessionTokenInitializedOnHandler(token));
}
- mServiceState.mSession = token;
- mHandler.post(() -> mServiceState.notifySessionTokenInitializedOnHandler(token));
}
/**
@@ -450,7 +478,7 @@ public abstract class MediaBrowserService extends Service {
* or if it has been destroyed.
*/
public @Nullable MediaSession.Token getSessionToken() {
- return mServiceState.mSession;
+ return mServiceState.get().mSession;
}
/**
@@ -521,7 +549,7 @@ public abstract class MediaBrowserService extends Service {
if (parentId == null) {
throw new IllegalArgumentException("parentId cannot be null in notifyChildrenChanged");
}
- mHandler.post(() -> mServiceState.notifyChildrenChangeOnHandler(parentId, options));
+ mHandler.post(() -> mServiceState.get().notifyChildrenChangeOnHandler(parentId, options));
}
/**
@@ -623,15 +651,38 @@ public abstract class MediaBrowserService extends Service {
// Fields accessed from any caller thread.
@Nullable private MediaSession.Token mSession;
- @Nullable private ServiceBinder mBinder;
// Fields accessed from mHandler only.
@NonNull private final ArrayMap<IBinder, ConnectionRecord> mConnections = new ArrayMap<>();
+ public ServiceBinder getBinder() {
+ return mBinder;
+ }
+
public void postOnHandler(Runnable runnable) {
mHandler.post(runnable);
}
+ public void release() {
+ mHandler.postAtFrontOfQueue(this::clearConnectionsOnHandler);
+ }
+
+ private void clearConnectionsOnHandler() {
+ Iterator<ConnectionRecord> iterator = mConnections.values().iterator();
+ while (iterator.hasNext()) {
+ ConnectionRecord record = iterator.next();
+ iterator.remove();
+ try {
+ record.callbacks.onDisconnect();
+ } catch (RemoteException exception) {
+ Log.w(
+ TAG,
+ TextUtils.formatSimple("onDisconnectRequest for %s failed", record.pkg),
+ exception);
+ }
+ }
+ }
+
public void removeConnectionRecordOnHandler(IMediaBrowserServiceCallbacks callbacks) {
IBinder b = callbacks.asBinder();
// Clear out the old subscriptions. We are getting new ones.
@@ -796,8 +847,7 @@ public abstract class MediaBrowserService extends Service {
@Override
void onResultSent(
List<MediaBrowser.MediaItem> list, @ResultFlags int flag) {
- if (mServiceState.mConnections.get(connection.callbacks.asBinder())
- != connection) {
+ if (mConnections.get(connection.callbacks.asBinder()) != connection) {
if (DBG) {
Log.d(
TAG,
diff --git a/native/android/input.cpp b/native/android/input.cpp
index 6efb0280ac02..53699bc706ea 100644
--- a/native/android/input.cpp
+++ b/native/android/input.cpp
@@ -87,7 +87,7 @@ int64_t AKeyEvent_getDownTime(const AInputEvent* key_event) {
const AInputEvent* AKeyEvent_fromJava(JNIEnv* env, jobject keyEvent) {
std::unique_ptr<KeyEvent> event = std::make_unique<KeyEvent>();
- *event = android::android_view_KeyEvent_toNative(env, keyEvent);
+ *event = android::android_view_KeyEvent_obtainAsCopy(env, keyEvent);
return event.release();
}
@@ -321,11 +321,13 @@ jobject AInputEvent_toJava(JNIEnv* env, const AInputEvent* aInputEvent) {
case AINPUT_EVENT_TYPE_MOTION:
return android::android_view_MotionEvent_obtainAsCopy(env,
static_cast<const MotionEvent&>(
- *aInputEvent));
+ *aInputEvent))
+ .release();
case AINPUT_EVENT_TYPE_KEY:
- return android::android_view_KeyEvent_fromNative(env,
- static_cast<const KeyEvent&>(
- *aInputEvent));
+ return android::android_view_KeyEvent_obtainAsCopy(env,
+ static_cast<const KeyEvent&>(
+ *aInputEvent))
+ .release();
default:
LOG_ALWAYS_FATAL("Unexpected event type %d in AInputEvent_toJava.", eventType);
}
diff --git a/nfc/api/current.txt b/nfc/api/current.txt
index 845a8f97db10..7b53ca6ea7e9 100644
--- a/nfc/api/current.txt
+++ b/nfc/api/current.txt
@@ -82,7 +82,7 @@ package android.nfc {
method @FlaggedApi("android.nfc.enable_nfc_charging") public boolean isWlcEnabled();
method @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public void resetDiscoveryTechnology(@NonNull android.app.Activity);
method @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public void setDiscoveryTechnology(@NonNull android.app.Activity, int, int);
- method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean setTransactionAllowed(boolean);
+ method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean setObserveModeEnabled(boolean);
field public static final String ACTION_ADAPTER_STATE_CHANGED = "android.nfc.action.ADAPTER_STATE_CHANGED";
field public static final String ACTION_NDEF_DISCOVERED = "android.nfc.action.NDEF_DISCOVERED";
field @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public static final String ACTION_PREFERRED_PAYMENT_CHANGED = "android.nfc.action.PREFERRED_PAYMENT_CHANGED";
@@ -206,9 +206,9 @@ package android.nfc.cardemulation {
method public boolean registerAidsForService(android.content.ComponentName, String, java.util.List<java.lang.String>);
method @FlaggedApi("android.nfc.nfc_read_polling_loop") public boolean registerPollingLoopFilterForService(@NonNull android.content.ComponentName, @NonNull String);
method public boolean removeAidsForService(android.content.ComponentName, String);
+ method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean setDefaultToObserveModeForService(@NonNull android.content.ComponentName, boolean);
method @NonNull @RequiresPermission(android.Manifest.permission.NFC) public boolean setOffHostForService(@NonNull android.content.ComponentName, @NonNull String);
method public boolean setPreferredService(android.app.Activity, android.content.ComponentName);
- method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean setServiceObserveModeDefault(@NonNull android.content.ComponentName, boolean);
method public boolean supportsAidPrefixRegistration();
method @NonNull @RequiresPermission(android.Manifest.permission.NFC) public boolean unsetOffHostForService(@NonNull android.content.ComponentName);
method public boolean unsetPreferredService(android.app.Activity);
@@ -228,20 +228,10 @@ package android.nfc.cardemulation {
method public final android.os.IBinder onBind(android.content.Intent);
method public abstract void onDeactivated(int);
method public abstract byte[] processCommandApdu(byte[], android.os.Bundle);
- method @FlaggedApi("android.nfc.nfc_read_polling_loop") public void processPollingFrames(@NonNull java.util.List<android.os.Bundle>);
+ method @FlaggedApi("android.nfc.nfc_read_polling_loop") public void processPollingFrames(@NonNull java.util.List<android.nfc.cardemulation.PollingFrame>);
method public final void sendResponseApdu(byte[]);
field public static final int DEACTIVATION_DESELECTED = 1; // 0x1
field public static final int DEACTIVATION_LINK_LOSS = 0; // 0x0
- field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String KEY_POLLING_LOOP_DATA = "android.nfc.cardemulation.DATA";
- field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String KEY_POLLING_LOOP_GAIN = "android.nfc.cardemulation.GAIN";
- field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String KEY_POLLING_LOOP_TIMESTAMP = "android.nfc.cardemulation.TIMESTAMP";
- field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String KEY_POLLING_LOOP_TYPE = "android.nfc.cardemulation.TYPE";
- field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_A = 65; // 0x0041 'A'
- field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_B = 66; // 0x0042 'B'
- field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_F = 70; // 0x0046 'F'
- field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_OFF = 88; // 0x0058 'X'
- field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_ON = 79; // 0x004f 'O'
- field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_UNKNOWN = 85; // 0x0055 'U'
field public static final String SERVICE_INTERFACE = "android.nfc.cardemulation.action.HOST_APDU_SERVICE";
field public static final String SERVICE_META_DATA = "android.nfc.cardemulation.host_apdu_service";
}
@@ -274,6 +264,23 @@ package android.nfc.cardemulation {
field public static final String SERVICE_META_DATA = "android.nfc.cardemulation.off_host_apdu_service";
}
+ @FlaggedApi("android.nfc.nfc_read_polling_loop") public final class PollingFrame implements android.os.Parcelable {
+ ctor public PollingFrame(int, @Nullable byte[], int, int);
+ method public int describeContents();
+ method @NonNull public byte[] getData();
+ method public int getGain();
+ method public int getTimestamp();
+ method public int getType();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.nfc.cardemulation.PollingFrame> CREATOR;
+ field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final int POLLING_LOOP_TYPE_A = 65; // 0x41
+ field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final int POLLING_LOOP_TYPE_B = 66; // 0x42
+ field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final int POLLING_LOOP_TYPE_F = 70; // 0x46
+ field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final int POLLING_LOOP_TYPE_OFF = 88; // 0x58
+ field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final int POLLING_LOOP_TYPE_ON = 79; // 0x4f
+ field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final int POLLING_LOOP_TYPE_UNKNOWN = 85; // 0x55
+ }
+
}
package android.nfc.tech {
diff --git a/nfc/java/android/nfc/INfcCardEmulation.aidl b/nfc/java/android/nfc/INfcCardEmulation.aidl
index 791bd8c9e6f4..65d0625f251e 100644
--- a/nfc/java/android/nfc/INfcCardEmulation.aidl
+++ b/nfc/java/android/nfc/INfcCardEmulation.aidl
@@ -30,7 +30,7 @@ interface INfcCardEmulation
boolean isDefaultServiceForAid(int userHandle, in ComponentName service, String aid);
boolean setDefaultServiceForCategory(int userHandle, in ComponentName service, String category);
boolean setDefaultForNextTap(int userHandle, in ComponentName service);
- boolean setServiceObserveModeDefault(int userId, in android.content.ComponentName service, boolean enable);
+ boolean setDefaultToObserveModeForService(int userId, in android.content.ComponentName service, boolean enable);
boolean registerAidGroupForService(int userHandle, in ComponentName service, in AidGroup aidGroup);
boolean registerPollingLoopFilterForService(int userHandle, in ComponentName service, in String pollingLoopFilter);
boolean setOffHostForService(int userHandle, in ComponentName service, in String offHostSecureElement);
diff --git a/nfc/java/android/nfc/NfcAdapter.java b/nfc/java/android/nfc/NfcAdapter.java
index c5b758207603..0ebc3f5178e0 100644
--- a/nfc/java/android/nfc/NfcAdapter.java
+++ b/nfc/java/android/nfc/NfcAdapter.java
@@ -38,6 +38,7 @@ import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
+import android.nfc.cardemulation.PollingFrame;
import android.nfc.tech.MifareClassic;
import android.nfc.tech.Ndef;
import android.nfc.tech.NfcA;
@@ -1249,16 +1250,16 @@ public final class NfcAdapter {
* and simply observe and notify the APDU service of polling loop frames. See
* {@link #isObserveModeSupported()} for a description of observe mode.
*
- * @param allowed true disables observe mode to allow the transaction to proceed while false
+ * @param enabled false disables observe mode to allow the transaction to proceed while true
* enables observe mode and does not allow transactions to proceed.
*
* @return boolean indicating success or failure.
*/
@FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE)
- public boolean setTransactionAllowed(boolean allowed) {
+ public boolean setObserveModeEnabled(boolean enabled) {
try {
- return sService.setObserveMode(!allowed);
+ return sService.setObserveMode(enabled);
} catch (RemoteException e) {
attemptDeadServiceRecovery(e);
return false;
@@ -2799,7 +2800,8 @@ public final class NfcAdapter {
*/
@TestApi
@FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
- public void notifyPollingLoop(@NonNull Bundle frame) {
+ public void notifyPollingLoop(@NonNull PollingFrame pollingFrame) {
+ Bundle frame = pollingFrame.toBundle();
try {
if (sService == null) {
attemptDeadServiceRecovery(null);
diff --git a/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java b/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
index f264b16347f9..5242a7d32930 100644
--- a/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
+++ b/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
@@ -105,7 +105,6 @@ public final class ApduServiceInfo implements Parcelable {
*/
private final HashMap<String, AidGroup> mDynamicAidGroups;
- private final ArrayList<String> mPollingLoopFilters;
private final Map<String, Boolean> mAutoTransact;
@@ -181,7 +180,6 @@ public final class ApduServiceInfo implements Parcelable {
this.mDescription = description;
this.mStaticAidGroups = new HashMap<String, AidGroup>();
this.mDynamicAidGroups = new HashMap<String, AidGroup>();
- this.mPollingLoopFilters = new ArrayList<String>();
this.mAutoTransact = new HashMap<String, Boolean>();
this.mOffHostName = offHost;
this.mStaticOffHostName = staticOffHost;
@@ -302,7 +300,6 @@ public final class ApduServiceInfo implements Parcelable {
mStaticAidGroups = new HashMap<String, AidGroup>();
mDynamicAidGroups = new HashMap<String, AidGroup>();
- mPollingLoopFilters = new ArrayList<String>();
mAutoTransact = new HashMap<String, Boolean>();
mOnHost = onHost;
@@ -393,7 +390,6 @@ public final class ApduServiceInfo implements Parcelable {
String plf =
a.getString(com.android.internal.R.styleable.PollingLoopFilter_name)
.toUpperCase(Locale.ROOT);
- mPollingLoopFilters.add(plf);
boolean autoTransact = a.getBoolean(
com.android.internal.R.styleable.PollingLoopFilter_autoTransact,
false);
@@ -461,7 +457,7 @@ public final class ApduServiceInfo implements Parcelable {
@FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
@NonNull
public List<String> getPollingLoopFilters() {
- return mPollingLoopFilters;
+ return new ArrayList<>(mAutoTransact.keySet());
}
/**
@@ -672,26 +668,30 @@ public final class ApduServiceInfo implements Parcelable {
/**
* Add a Polling Loop Filter. Custom NFC polling frames that match this filter will be
- * delivered to {@link HostApduService#processPollingFrames(List)}.
- * @param pollingLoopFilter this polling loop filter to add.
+ * delivered to {@link HostApduService#processPollingFrames(List)}. Adding a key with this or
+ * {@link ApduServiceInfo#addPollingLoopFilterToAutoTransact(String)} multiple times will
+ * cause the value to be overwritten each time.
+ * @param pollingLoopFilter the polling loop filter to add, must be a valide hexadecimal string
*/
@FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
public void addPollingLoopFilter(@NonNull String pollingLoopFilter) {
- mPollingLoopFilters.add(pollingLoopFilter.toUpperCase(Locale.ROOT));
+ mAutoTransact.put(pollingLoopFilter.toUpperCase(Locale.ROOT), false);
+
}
/**
* Add a Polling Loop Filter. Custom NFC polling frames that match this filter will cause the
* device to exit observe mode, just as if
- * {@link android.nfc.NfcAdapter#setTransactionAllowed(boolean)} had been called with true,
+ * {@link android.nfc.NfcAdapter#setObserveModeEnabled(boolean)} had been called with true,
* allowing transactions to proceed. The matching frame will also be delivered to
- * {@link HostApduService#processPollingFrames(List)}.
+ * {@link HostApduService#processPollingFrames(List)}. Adding a key with this or
+ * {@link ApduServiceInfo#addPollingLoopFilter(String)} multiple times will
+ * cause the value to be overwritten each time.
*
- * @param pollingLoopFilter this polling loop filter to add.
+ * @param pollingLoopFilter the polling loop filter to add, must be a valide hexadecimal string
*/
@FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
public void addPollingLoopFilterToAutoTransact(@NonNull String pollingLoopFilter) {
- mPollingLoopFilters.add(pollingLoopFilter.toUpperCase(Locale.ROOT));
mAutoTransact.put(pollingLoopFilter.toUpperCase(Locale.ROOT), true);
}
@@ -702,7 +702,7 @@ public final class ApduServiceInfo implements Parcelable {
*/
@FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
public void removePollingLoopFilter(@NonNull String pollingLoopFilter) {
- mPollingLoopFilters.remove(pollingLoopFilter.toUpperCase(Locale.ROOT));
+ mAutoTransact.remove(pollingLoopFilter.toUpperCase(Locale.ROOT));
}
/**
diff --git a/nfc/java/android/nfc/cardemulation/CardEmulation.java b/nfc/java/android/nfc/cardemulation/CardEmulation.java
index 53288cc032a8..e681a8568300 100644
--- a/nfc/java/android/nfc/cardemulation/CardEmulation.java
+++ b/nfc/java/android/nfc/cardemulation/CardEmulation.java
@@ -338,19 +338,20 @@ public final class CardEmulation {
}
}
/**
- * Sets whether the system should default to observe mode or not when the service is in the
- * foreground or the default payment service. The default is to not enable observe mode when
- * a service either the foreground default service or the default payment service so not
- * calling this method will preserve that behavior.
+ * Sets whether when this service becomes the preferred service, if the NFC stack
+ * should enable observe mode or disable observe mode. The default is to not enable observe
+ * mode when a service either the foreground default service or the default payment service so
+ * not calling this method will preserve that behavior.
*
* @param service The component name of the service
- * @param enable Whether the servic should default to observe mode or not
+ * @param enable Whether the service should default to observe mode or not
* @return whether the change was successful.
*/
@FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE)
- public boolean setServiceObserveModeDefault(@NonNull ComponentName service, boolean enable) {
+ public boolean setDefaultToObserveModeForService(@NonNull ComponentName service,
+ boolean enable) {
try {
- return sService.setServiceObserveModeDefault(mContext.getUser().getIdentifier(),
+ return sService.setDefaultToObserveModeForService(mContext.getUser().getIdentifier(),
service, enable);
} catch (RemoteException e) {
Log.e(TAG, "Failed to reach CardEmulationService.");
diff --git a/nfc/java/android/nfc/cardemulation/HostApduService.java b/nfc/java/android/nfc/cardemulation/HostApduService.java
index 363788eb5979..61037a2e7e9e 100644
--- a/nfc/java/android/nfc/cardemulation/HostApduService.java
+++ b/nfc/java/android/nfc/cardemulation/HostApduService.java
@@ -244,85 +244,6 @@ public abstract class HostApduService extends Service {
public static final String KEY_DATA = "data";
/**
- * KEY_POLLING_LOOP_TYPE is the Bundle key for the type of
- * polling loop frame in the Bundle passed to {@link #processPollingFrames(List)}
- */
- @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
- public static final String KEY_POLLING_LOOP_TYPE = "android.nfc.cardemulation.TYPE";
-
- /**
- * POLLING_LOOP_TYPE_A is the value associated with the key
- * POLLING_LOOP_TYPE in the Bundle passed to {@link #processPollingFrames(List)}
- * when the polling loop is for NFC-A.
- */
- @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
- public static final char POLLING_LOOP_TYPE_A = 'A';
-
- /**
- * POLLING_LOOP_TYPE_B is the value associated with the key
- * POLLING_LOOP_TYPE in the Bundle passed to {@link #processPollingFrames(List)}
- * when the polling loop is for NFC-B.
- */
- @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
- public static final char POLLING_LOOP_TYPE_B = 'B';
-
- /**
- * POLLING_LOOP_TYPE_F is the value associated with the key
- * POLLING_LOOP_TYPE in the Bundle passed to {@link #processPollingFrames(List)}
- * when the polling loop is for NFC-F.
- */
- @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
- public static final char POLLING_LOOP_TYPE_F = 'F';
-
- /**
- * POLLING_LOOP_TYPE_ON is the value associated with the key
- * POLLING_LOOP_TYPE in the Bundle passed to {@link #processPollingFrames(List)}
- * when the polling loop turns on.
- */
- @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
- public static final char POLLING_LOOP_TYPE_ON = 'O';
-
- /**
- * POLLING_LOOP_TYPE_OFF is the value associated with the key
- * POLLING_LOOP_TYPE in the Bundle passed to {@link #processPollingFrames(List)}
- * when the polling loop turns off.
- */
- @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
- public static final char POLLING_LOOP_TYPE_OFF = 'X';
-
- /**
- * POLLING_LOOP_TYPE_UNKNOWN is the value associated with the key
- * POLLING_LOOP_TYPE in the Bundle passed to {@link #processPollingFrames(List)}
- * when the polling loop frame isn't recognized.
- */
- @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
- public static final char POLLING_LOOP_TYPE_UNKNOWN = 'U';
-
- /**
- * KEY_POLLING_LOOP_DATA is the Bundle key for the raw data of captured from
- * the polling loop frame in the Bundle passed to {@link #processPollingFrames(List)}
- * when the frame type isn't recognized.
- */
- @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
- public static final String KEY_POLLING_LOOP_DATA = "android.nfc.cardemulation.DATA";
-
- /**
- * KEY_POLLING_LOOP_GAIN is the Bundle key for the field strength of
- * the polling loop frame in the Bundle passed to {@link #processPollingFrames(List)}
- * when the frame type isn't recognized.
- */
- @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
- public static final String KEY_POLLING_LOOP_GAIN = "android.nfc.cardemulation.GAIN";
-
- /**
- * KEY_POLLING_LOOP_TIMESTAMP is the Bundle key for the timestamp of
- * the polling loop frame in the Bundle passed to {@link #processPollingFrames(List)}
- * when the frame type isn't recognized.
- */
- @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
- public static final String KEY_POLLING_LOOP_TIMESTAMP = "android.nfc.cardemulation.TIMESTAMP";
-
- /**
* @hide
*/
public static final String KEY_POLLING_LOOP_FRAMES_BUNDLE =
@@ -407,7 +328,12 @@ public abstract class HostApduService extends Service {
ArrayList<Bundle> frames =
msg.getData().getParcelableArrayList(KEY_POLLING_LOOP_FRAMES_BUNDLE,
Bundle.class);
- processPollingFrames(frames);
+ ArrayList<PollingFrame> pollingFrames =
+ new ArrayList<PollingFrame>(frames.size());
+ for (Bundle frame : frames) {
+ pollingFrames.add(new PollingFrame(frame));
+ }
+ processPollingFrames(pollingFrames);
break;
default:
super.handleMessage(msg);
@@ -482,7 +408,7 @@ public abstract class HostApduService extends Service {
* @param frame A description of the polling frame.
*/
@FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
- public void processPollingFrames(@NonNull List<Bundle> frame) {
+ public void processPollingFrames(@NonNull List<PollingFrame> frame) {
}
/**
diff --git a/nfc/java/android/nfc/cardemulation/PollingFrame.java b/nfc/java/android/nfc/cardemulation/PollingFrame.java
new file mode 100644
index 000000000000..3383f3bd4e7a
--- /dev/null
+++ b/nfc/java/android/nfc/cardemulation/PollingFrame.java
@@ -0,0 +1,243 @@
+/*
+ * 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.nfc.cardemulation;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.HexFormat;
+import java.util.List;
+
+/**
+ * Polling Frames represent data about individual frames of an NFC polling loop. These frames will
+ * be deliverd to subclasses of {@link HostApduService} that have registered filters with
+ * {@link CardEmulation#registerPollingLoopFilterForService(ComponentName, String)} that match a
+ * given frame in a loop and will be delivered through calls to
+ * {@link HostApduService#processPollingFrames(List)}.
+ */
+@FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+public final class PollingFrame implements Parcelable{
+
+ /**
+ * @hide
+ */
+ @IntDef(prefix = { "POLLING_LOOP_TYPE_"}, value = { POLLING_LOOP_TYPE_A, POLLING_LOOP_TYPE_B,
+ POLLING_LOOP_TYPE_F, POLLING_LOOP_TYPE_OFF, POLLING_LOOP_TYPE_ON })
+ @Retention(RetentionPolicy.SOURCE)
+ @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+ public @interface PollingFrameType {}
+
+ /**
+ * POLLING_LOOP_TYPE_A is the value associated with the key
+ * POLLING_LOOP_TYPE in the Bundle passed to {@link HostApduService#processPollingFrames(List)}
+ * when the polling loop is for NFC-A.
+ */
+ @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+ public static final int POLLING_LOOP_TYPE_A = 'A';
+
+ /**
+ * POLLING_LOOP_TYPE_B is the value associated with the key
+ * POLLING_LOOP_TYPE in the Bundle passed to {@link HostApduService#processPollingFrames(List)}
+ * when the polling loop is for NFC-B.
+ */
+ @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+ public static final int POLLING_LOOP_TYPE_B = 'B';
+
+ /**
+ * POLLING_LOOP_TYPE_F is the value associated with the key
+ * POLLING_LOOP_TYPE in the Bundle passed to {@link HostApduService#processPollingFrames(List)}
+ * when the polling loop is for NFC-F.
+ */
+ @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+ public static final int POLLING_LOOP_TYPE_F = 'F';
+
+ /**
+ * POLLING_LOOP_TYPE_ON is the value associated with the key
+ * POLLING_LOOP_TYPE in the Bundle passed to {@link HostApduService#processPollingFrames(List)}
+ * when the polling loop turns on.
+ */
+ @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+ public static final int POLLING_LOOP_TYPE_ON = 'O';
+
+ /**
+ * POLLING_LOOP_TYPE_OFF is the value associated with the key
+ * POLLING_LOOP_TYPE in the Bundle passed to {@link HostApduService#processPollingFrames(List)}
+ * when the polling loop turns off.
+ */
+ @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+ public static final int POLLING_LOOP_TYPE_OFF = 'X';
+
+ /**
+ * POLLING_LOOP_TYPE_UNKNOWN is the value associated with the key
+ * POLLING_LOOP_TYPE in the Bundle passed to {@link HostApduService#processPollingFrames(List)}
+ * when the polling loop frame isn't recognized.
+ */
+ @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+ public static final int POLLING_LOOP_TYPE_UNKNOWN = 'U';
+
+ /**
+ * KEY_POLLING_LOOP_TYPE is the Bundle key for the type of
+ * polling loop frame in the Bundle included in MSG_POLLING_LOOP.
+ *
+ * @hide
+ */
+ @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+ public static final String KEY_POLLING_LOOP_TYPE = "android.nfc.cardemulation.TYPE";
+
+ /**
+ * KEY_POLLING_LOOP_DATA is the Bundle key for the raw data of captured from
+ * the polling loop frame in the Bundle included in MSG_POLLING_LOOP.
+ *
+ * @hide
+ */
+ @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+ public static final String KEY_POLLING_LOOP_DATA = "android.nfc.cardemulation.DATA";
+
+ /**
+ * KEY_POLLING_LOOP_GAIN is the Bundle key for the field strength of
+ * the polling loop frame in the Bundle included in MSG_POLLING_LOOP.
+ *
+ * @hide
+ */
+ @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+ public static final String KEY_POLLING_LOOP_GAIN = "android.nfc.cardemulation.GAIN";
+
+ /**
+ * KEY_POLLING_LOOP_TIMESTAMP is the Bundle key for the timestamp of
+ * the polling loop frame in the Bundle included in MSG_POLLING_LOOP.
+ *
+ * @hide
+ */
+ @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+ public static final String KEY_POLLING_LOOP_TIMESTAMP = "android.nfc.cardemulation.TIMESTAMP";
+
+
+ @PollingFrameType
+ private final int mType;
+ private final byte[] mData;
+ private final int mGain;
+ private final int mTimestamp;
+
+ public static final @NonNull Parcelable.Creator<PollingFrame> CREATOR =
+ new Parcelable.Creator<>() {
+ @Override
+ public PollingFrame createFromParcel(Parcel source) {
+ return new PollingFrame(source.readBundle());
+ }
+
+ @Override
+ public PollingFrame[] newArray(int size) {
+ return new PollingFrame[size];
+ }
+ };
+
+ PollingFrame(Bundle frame) {
+ mType = frame.getInt(KEY_POLLING_LOOP_TYPE);
+ byte[] data = frame.getByteArray(KEY_POLLING_LOOP_DATA);
+ mData = (data == null) ? new byte[0] : data;
+ mGain = frame.getByte(KEY_POLLING_LOOP_GAIN);
+ mTimestamp = frame.getInt(KEY_POLLING_LOOP_TIMESTAMP);
+ }
+
+ public PollingFrame(@PollingFrameType int type, @Nullable byte[] data,
+ int gain, int timestamp) {
+ mType = type;
+ mData = data == null ? new byte[0] : data;
+ mGain = gain;
+ mTimestamp = timestamp;
+ }
+
+ /**
+ * Returns the type of frame for this polling loop frame.
+ * The possible return values are:
+ * <ul>
+ * <li>{@link POLLING_LOOP_TYPE_ON}</li>
+ * <li>{@link POLLING_LOOP_TYPE_OFF}</li>
+ * <li>{@link POLLING_LOOP_TYPE_A}</li>
+ * <li>{@link POLLING_LOOP_TYPE_B}</li>
+ * <li>{@link POLLING_LOOP_TYPE_F}</li>
+ * </ul>
+ */
+ public @PollingFrameType int getType() {
+ return mType;
+ }
+
+ /**
+ * Returns the raw data from the polling type frame.
+ */
+ public @NonNull byte[] getData() {
+ return mData;
+ }
+
+ /**
+ * Returns the gain representing the field strength of the NFC field when this polling loop
+ * frame was observed.
+ */
+ public int getGain() {
+ return mGain;
+ }
+
+ /**
+ * Returns the timestamp of when the polling loop frame was observed in milliseconds. These
+ * timestamps are relative and not absolute and should only be used fro comparing the timing of
+ * frames relative to each other.
+ * @return the timestamp in milliseconds
+ */
+ public int getTimestamp() {
+ return mTimestamp;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeBundle(toBundle());
+ }
+
+ /**
+ *
+ * @hide
+ * @return a Bundle representing this frame
+ */
+ public Bundle toBundle() {
+ Bundle frame = new Bundle();
+ frame.putInt(KEY_POLLING_LOOP_TYPE, getType());
+ frame.putByte(KEY_POLLING_LOOP_GAIN, (byte) getGain());
+ frame.putByteArray(KEY_POLLING_LOOP_DATA, getData());
+ frame.putInt(KEY_POLLING_LOOP_TIMESTAMP, getTimestamp());
+ return frame;
+ }
+
+ @Override
+ public String toString() {
+ return "PollingFrame { Type: " + (char) getType()
+ + ", gain: " + getGain()
+ + ", timestamp: " + Integer.toUnsignedString(getTimestamp())
+ + ", data: [" + HexFormat.ofDelimiter(" ").formatHex(getData()) + "] }";
+ }
+}
diff --git a/packages/CompanionDeviceManager/res/values/styles.xml b/packages/CompanionDeviceManager/res/values/styles.xml
index 88f1204641ff..0af108052137 100644
--- a/packages/CompanionDeviceManager/res/values/styles.xml
+++ b/packages/CompanionDeviceManager/res/values/styles.xml
@@ -59,8 +59,10 @@
<style name="VendorHelperBackButton"
parent="@android:style/Widget.Material.Button.Borderless.Colored">
- <item name="android:layout_width">70dp</item>
+ <item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">48dp</item>
+ <item name="android:layout_marginStart">12dp</item>
+ <item name="android:layout_marginEnd">12dp</item>
<item name="android:textAllCaps">false</item>
<item name="android:textSize">14sp</item>
<item name="android:textColor">@android:color/system_neutral1_900</item>
diff --git a/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java b/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java
index b5cf011c32a6..ce8fb6568bd5 100644
--- a/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java
+++ b/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java
@@ -100,13 +100,15 @@ public class PackageWatchdog {
public static final int FAILURE_REASON_EXPLICIT_HEALTH_CHECK = 2;
public static final int FAILURE_REASON_APP_CRASH = 3;
public static final int FAILURE_REASON_APP_NOT_RESPONDING = 4;
+ public static final int FAILURE_REASON_BOOT_LOOP = 5;
@IntDef(prefix = { "FAILURE_REASON_" }, value = {
FAILURE_REASON_UNKNOWN,
FAILURE_REASON_NATIVE_CRASH,
FAILURE_REASON_EXPLICIT_HEALTH_CHECK,
FAILURE_REASON_APP_CRASH,
- FAILURE_REASON_APP_NOT_RESPONDING
+ FAILURE_REASON_APP_NOT_RESPONDING,
+ FAILURE_REASON_BOOT_LOOP
})
@Retention(RetentionPolicy.SOURCE)
public @interface FailureReasons {}
@@ -542,7 +544,7 @@ public class PackageWatchdog {
mNumberOfNativeCrashPollsRemaining--;
// Check if native watchdog reported a crash
if ("1".equals(SystemProperties.get("sys.init.updatable_crashing"))) {
- // We rollback everything available when crash is unattributable
+ // We rollback all available low impact rollbacks when crash is unattributable
onPackageFailure(Collections.EMPTY_LIST, FAILURE_REASON_NATIVE_CRASH);
// we stop polling after an attempt to execute rollback, regardless of whether the
// attempt succeeds or not
@@ -572,6 +574,7 @@ public class PackageWatchdog {
PackageHealthObserverImpact.USER_IMPACT_LEVEL_30,
PackageHealthObserverImpact.USER_IMPACT_LEVEL_50,
PackageHealthObserverImpact.USER_IMPACT_LEVEL_70,
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_90,
PackageHealthObserverImpact.USER_IMPACT_LEVEL_100})
public @interface PackageHealthObserverImpact {
/** No action to take. */
@@ -582,6 +585,7 @@ public class PackageWatchdog {
int USER_IMPACT_LEVEL_30 = 30;
int USER_IMPACT_LEVEL_50 = 50;
int USER_IMPACT_LEVEL_70 = 70;
+ int USER_IMPACT_LEVEL_90 = 90;
/* Action has high user impact, a last resort, user of a device will be very frustrated. */
int USER_IMPACT_LEVEL_100 = 100;
}
diff --git a/packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index dd74a2a978b2..5fb47dd9b95a 100644
--- a/packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -28,6 +28,7 @@ import android.content.pm.VersionedPackage;
import android.content.rollback.PackageRollbackInfo;
import android.content.rollback.RollbackInfo;
import android.content.rollback.RollbackManager;
+import android.crashrecovery.flags.Flags;
import android.os.Environment;
import android.os.FileUtils;
import android.os.Handler;
@@ -45,7 +46,6 @@ import com.android.server.PackageWatchdog;
import com.android.server.PackageWatchdog.FailureReasons;
import com.android.server.PackageWatchdog.PackageHealthObserver;
import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
-import com.android.server.SystemConfig;
import com.android.server.crashrecovery.proto.CrashRecoveryStatsLog;
import com.android.server.pm.ApexManager;
@@ -57,6 +57,7 @@ import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Collections;
+import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
@@ -84,7 +85,8 @@ final class RollbackPackageHealthObserver implements PackageHealthObserver {
// True if needing to roll back only rebootless apexes when native crash happens
private boolean mTwoPhaseRollbackEnabled;
- RollbackPackageHealthObserver(Context context) {
+ @VisibleForTesting
+ RollbackPackageHealthObserver(Context context, ApexManager apexManager) {
mContext = context;
HandlerThread handlerThread = new HandlerThread("RollbackPackageHealthObserver");
handlerThread.start();
@@ -94,7 +96,7 @@ final class RollbackPackageHealthObserver implements PackageHealthObserver {
mLastStagedRollbackIdsFile = new File(dataDir, "last-staged-rollback-ids");
mTwoPhaseRollbackEnabledFile = new File(dataDir, "two-phase-rollback-enabled");
PackageWatchdog.getInstance(mContext).registerHealthObserver(this);
- mApexManager = ApexManager.getInstance();
+ mApexManager = apexManager;
if (SystemProperties.getBoolean("sys.boot_completed", false)) {
// Load the value from the file if system server has crashed and restarted
@@ -107,24 +109,46 @@ final class RollbackPackageHealthObserver implements PackageHealthObserver {
}
}
+ RollbackPackageHealthObserver(Context context) {
+ this(context, ApexManager.getInstance());
+ }
+
@Override
public int onHealthCheckFailed(@Nullable VersionedPackage failedPackage,
@FailureReasons int failureReason, int mitigationCount) {
- boolean anyRollbackAvailable = !mContext.getSystemService(RollbackManager.class)
- .getAvailableRollbacks().isEmpty();
int impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
-
- if (failureReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH
- && anyRollbackAvailable) {
- // For native crashes, we will directly roll back any available rollbacks
- // Note: For non-native crashes the rollback-all step has higher impact
- impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_30;
- } else if (getAvailableRollback(failedPackage) != null) {
- // Rollback is available, we may get a callback into #execute
- impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_30;
- } else if (anyRollbackAvailable) {
- // If any rollbacks are available, we will commit them
- impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_70;
+ if (Flags.recoverabilityDetection()) {
+ List<RollbackInfo> availableRollbacks = getAvailableRollbacks();
+ List<RollbackInfo> lowImpactRollbacks = getRollbacksAvailableForImpactLevel(
+ availableRollbacks, PackageManager.ROLLBACK_USER_IMPACT_LOW);
+ if (!lowImpactRollbacks.isEmpty()) {
+ if (failureReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH) {
+ // For native crashes, we will directly roll back any available rollbacks at low
+ // impact level
+ impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_30;
+ } else if (getRollbackForPackage(failedPackage, lowImpactRollbacks) != null) {
+ // Rollback is available for crashing low impact package
+ impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_30;
+ } else {
+ impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_70;
+ }
+ }
+ } else {
+ boolean anyRollbackAvailable = !mContext.getSystemService(RollbackManager.class)
+ .getAvailableRollbacks().isEmpty();
+
+ if (failureReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH
+ && anyRollbackAvailable) {
+ // For native crashes, we will directly roll back any available rollbacks
+ // Note: For non-native crashes the rollback-all step has higher impact
+ impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_30;
+ } else if (getAvailableRollback(failedPackage) != null) {
+ // Rollback is available, we may get a callback into #execute
+ impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_30;
+ } else if (anyRollbackAvailable) {
+ // If any rollbacks are available, we will commit them
+ impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_70;
+ }
}
return impact;
@@ -133,16 +157,34 @@ final class RollbackPackageHealthObserver implements PackageHealthObserver {
@Override
public boolean execute(@Nullable VersionedPackage failedPackage,
@FailureReasons int rollbackReason, int mitigationCount) {
- if (rollbackReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH) {
- mHandler.post(() -> rollbackAll(rollbackReason));
- return true;
- }
+ if (Flags.recoverabilityDetection()) {
+ List<RollbackInfo> availableRollbacks = getAvailableRollbacks();
+ if (rollbackReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH) {
+ mHandler.post(() -> rollbackAllLowImpact(availableRollbacks, rollbackReason));
+ return true;
+ }
- RollbackInfo rollback = getAvailableRollback(failedPackage);
- if (rollback != null) {
- mHandler.post(() -> rollbackPackage(rollback, failedPackage, rollbackReason));
+ List<RollbackInfo> lowImpactRollbacks = getRollbacksAvailableForImpactLevel(
+ availableRollbacks, PackageManager.ROLLBACK_USER_IMPACT_LOW);
+ RollbackInfo rollback = getRollbackForPackage(failedPackage, lowImpactRollbacks);
+ if (rollback != null) {
+ mHandler.post(() -> rollbackPackage(rollback, failedPackage, rollbackReason));
+ } else if (!lowImpactRollbacks.isEmpty()) {
+ // Apply all available low impact rollbacks.
+ mHandler.post(() -> rollbackAllLowImpact(availableRollbacks, rollbackReason));
+ }
} else {
- mHandler.post(() -> rollbackAll(rollbackReason));
+ if (rollbackReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH) {
+ mHandler.post(() -> rollbackAll(rollbackReason));
+ return true;
+ }
+
+ RollbackInfo rollback = getAvailableRollback(failedPackage);
+ if (rollback != null) {
+ mHandler.post(() -> rollbackPackage(rollback, failedPackage, rollbackReason));
+ } else {
+ mHandler.post(() -> rollbackAll(rollbackReason));
+ }
}
// Assume rollbacks executed successfully
@@ -150,6 +192,31 @@ final class RollbackPackageHealthObserver implements PackageHealthObserver {
}
@Override
+ public int onBootLoop(int mitigationCount) {
+ int impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
+ if (Flags.recoverabilityDetection()) {
+ List<RollbackInfo> availableRollbacks = getAvailableRollbacks();
+ if (!availableRollbacks.isEmpty()) {
+ impact = getUserImpactBasedOnRollbackImpactLevel(availableRollbacks);
+ }
+ }
+ return impact;
+ }
+
+ @Override
+ public boolean executeBootLoopMitigation(int mitigationCount) {
+ if (Flags.recoverabilityDetection()) {
+ List<RollbackInfo> availableRollbacks = getAvailableRollbacks();
+
+ triggerLeastImpactLevelRollback(availableRollbacks,
+ PackageWatchdog.FAILURE_REASON_BOOT_LOOP);
+ return true;
+ }
+ return false;
+ }
+
+
+ @Override
public String getName() {
return NAME;
}
@@ -161,13 +228,16 @@ final class RollbackPackageHealthObserver implements PackageHealthObserver {
@Override
public boolean mayObservePackage(String packageName) {
- if (mContext.getSystemService(RollbackManager.class)
- .getAvailableRollbacks().isEmpty()) {
+ if (getAvailableRollbacks().isEmpty()) {
return false;
}
return isPersistentSystemApp(packageName);
}
+ private List<RollbackInfo> getAvailableRollbacks() {
+ return mContext.getSystemService(RollbackManager.class).getAvailableRollbacks();
+ }
+
private boolean isPersistentSystemApp(@NonNull String packageName) {
PackageManager pm = mContext.getPackageManager();
try {
@@ -272,6 +342,40 @@ final class RollbackPackageHealthObserver implements PackageHealthObserver {
return null;
}
+ @AnyThread
+ private RollbackInfo getRollbackForPackage(@Nullable VersionedPackage failedPackage,
+ List<RollbackInfo> availableRollbacks) {
+ if (failedPackage == null) {
+ return null;
+ }
+
+ for (RollbackInfo rollback : availableRollbacks) {
+ for (PackageRollbackInfo packageRollback : rollback.getPackages()) {
+ if (packageRollback.getVersionRolledBackFrom().equals(failedPackage)) {
+ return rollback;
+ }
+ // TODO(b/147666157): Extract version number of apk-in-apex so that we don't have
+ // to rely on complicated reasoning as below
+
+ // Due to b/147666157, for apk in apex, we do not know the version we are rolling
+ // back from. But if a package X is embedded in apex A exclusively (not embedded in
+ // any other apex), which is not guaranteed, then it is sufficient to check only
+ // package names here, as the version of failedPackage and the PackageRollbackInfo
+ // can't be different. If failedPackage has a higher version, then it must have
+ // been updated somehow. There are two ways: it was updated by an update of apex A
+ // or updated directly as apk. In both cases, this rollback would have gotten
+ // expired when onPackageReplaced() was called. Since the rollback exists, it has
+ // same version as failedPackage.
+ if (packageRollback.isApkInApex()
+ && packageRollback.getVersionRolledBackFrom().getPackageName()
+ .equals(failedPackage.getPackageName())) {
+ return rollback;
+ }
+ }
+ }
+ return null;
+ }
+
/**
* Returns {@code true} if staged session associated with {@code rollbackId} was marked
* as handled, {@code false} if already handled.
@@ -396,12 +500,6 @@ final class RollbackPackageHealthObserver implements PackageHealthObserver {
@FailureReasons int rollbackReason) {
assertInWorkerThread();
- if (isAutomaticRollbackDenied(SystemConfig.getInstance(), failedPackage)) {
- Slog.d(TAG, "Automatic rollback not allowed for package "
- + failedPackage.getPackageName());
- return;
- }
-
final RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class);
int reasonToLog = WatchdogRollbackLogger.mapFailureReasonToMetric(rollbackReason);
final String failedPackageToLog;
@@ -465,17 +563,6 @@ final class RollbackPackageHealthObserver implements PackageHealthObserver {
}
/**
- * Returns true if this package is not eligible for automatic rollback.
- */
- @VisibleForTesting
- @AnyThread
- public static boolean isAutomaticRollbackDenied(SystemConfig systemConfig,
- VersionedPackage versionedPackage) {
- return systemConfig.getAutomaticRollbackDenylistedPackages()
- .contains(versionedPackage.getPackageName());
- }
-
- /**
* Two-phase rollback:
* 1. roll back rebootless apexes first
* 2. roll back all remaining rollbacks if native crash doesn't stop after (1) is done
@@ -495,14 +582,62 @@ final class RollbackPackageHealthObserver implements PackageHealthObserver {
boolean found = false;
for (RollbackInfo rollback : rollbacks) {
if (isRebootlessApex(rollback)) {
- VersionedPackage sample = rollback.getPackages().get(0).getVersionRolledBackFrom();
- rollbackPackage(rollback, sample, PackageWatchdog.FAILURE_REASON_NATIVE_CRASH);
+ VersionedPackage firstRollback =
+ rollback.getPackages().get(0).getVersionRolledBackFrom();
+ rollbackPackage(rollback, firstRollback,
+ PackageWatchdog.FAILURE_REASON_NATIVE_CRASH);
found = true;
}
}
return found;
}
+ /**
+ * Rollback the package that has minimum rollback impact level.
+ * @param availableRollbacks all available rollbacks
+ * @param rollbackReason reason to rollback
+ */
+ private void triggerLeastImpactLevelRollback(List<RollbackInfo> availableRollbacks,
+ @FailureReasons int rollbackReason) {
+ int minRollbackImpactLevel = getMinRollbackImpactLevel(availableRollbacks);
+
+ if (minRollbackImpactLevel == PackageManager.ROLLBACK_USER_IMPACT_LOW) {
+ // Apply all available low impact rollbacks.
+ mHandler.post(() -> rollbackAllLowImpact(availableRollbacks, rollbackReason));
+ } else if (minRollbackImpactLevel == PackageManager.ROLLBACK_USER_IMPACT_HIGH) {
+ // Rollback one package at a time. If that doesn't resolve the issue, rollback
+ // next with same impact level.
+ mHandler.post(() -> rollbackHighImpact(availableRollbacks, rollbackReason));
+ }
+ }
+
+ /**
+ * sort the available high impact rollbacks by first package name to have a deterministic order.
+ * Apply the first available rollback.
+ * @param availableRollbacks all available rollbacks
+ * @param rollbackReason reason to rollback
+ */
+ @WorkerThread
+ private void rollbackHighImpact(List<RollbackInfo> availableRollbacks,
+ @FailureReasons int rollbackReason) {
+ assertInWorkerThread();
+ List<RollbackInfo> highImpactRollbacks =
+ getRollbacksAvailableForImpactLevel(
+ availableRollbacks, PackageManager.ROLLBACK_USER_IMPACT_HIGH);
+
+ // sort rollbacks based on package name of the first package. This is to have a
+ // deterministic order of rollbacks.
+ List<RollbackInfo> sortedHighImpactRollbacks = highImpactRollbacks.stream().sorted(
+ Comparator.comparing(a -> a.getPackages().get(0).getPackageName())).toList();
+ VersionedPackage firstRollback =
+ sortedHighImpactRollbacks
+ .get(0)
+ .getPackages()
+ .get(0)
+ .getVersionRolledBackFrom();
+ rollbackPackage(sortedHighImpactRollbacks.get(0), firstRollback, rollbackReason);
+ }
+
@WorkerThread
private void rollbackAll(@FailureReasons int rollbackReason) {
assertInWorkerThread();
@@ -522,8 +657,77 @@ final class RollbackPackageHealthObserver implements PackageHealthObserver {
}
for (RollbackInfo rollback : rollbacks) {
- VersionedPackage sample = rollback.getPackages().get(0).getVersionRolledBackFrom();
- rollbackPackage(rollback, sample, rollbackReason);
+ VersionedPackage firstRollback =
+ rollback.getPackages().get(0).getVersionRolledBackFrom();
+ rollbackPackage(rollback, firstRollback, rollbackReason);
}
}
+
+ /**
+ * Rollback all available low impact rollbacks
+ * @param availableRollbacks all available rollbacks
+ * @param rollbackReason reason to rollbacks
+ */
+ @WorkerThread
+ private void rollbackAllLowImpact(
+ List<RollbackInfo> availableRollbacks, @FailureReasons int rollbackReason) {
+ assertInWorkerThread();
+
+ List<RollbackInfo> lowImpactRollbacks = getRollbacksAvailableForImpactLevel(
+ availableRollbacks,
+ PackageManager.ROLLBACK_USER_IMPACT_LOW);
+ if (useTwoPhaseRollback(lowImpactRollbacks)) {
+ return;
+ }
+
+ Slog.i(TAG, "Rolling back all available low impact rollbacks");
+ // Add all rollback ids to mPendingStagedRollbackIds, so that we do not reboot before all
+ // pending staged rollbacks are handled.
+ for (RollbackInfo rollback : lowImpactRollbacks) {
+ if (rollback.isStaged()) {
+ mPendingStagedRollbackIds.add(rollback.getRollbackId());
+ }
+ }
+
+ for (RollbackInfo rollback : lowImpactRollbacks) {
+ VersionedPackage firstRollback =
+ rollback.getPackages().get(0).getVersionRolledBackFrom();
+ rollbackPackage(rollback, firstRollback, rollbackReason);
+ }
+ }
+
+ private List<RollbackInfo> getRollbacksAvailableForImpactLevel(
+ List<RollbackInfo> availableRollbacks, int impactLevel) {
+ return availableRollbacks.stream()
+ .filter(rollbackInfo -> rollbackInfo.getRollbackImpactLevel() == impactLevel)
+ .toList();
+ }
+
+ private int getMinRollbackImpactLevel(List<RollbackInfo> availableRollbacks) {
+ return availableRollbacks.stream()
+ .mapToInt(RollbackInfo::getRollbackImpactLevel)
+ .min()
+ .orElse(-1);
+ }
+
+ private int getUserImpactBasedOnRollbackImpactLevel(List<RollbackInfo> availableRollbacks) {
+ int impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
+ int minImpact = getMinRollbackImpactLevel(availableRollbacks);
+ switch (minImpact) {
+ case PackageManager.ROLLBACK_USER_IMPACT_LOW:
+ impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_70;
+ break;
+ case PackageManager.ROLLBACK_USER_IMPACT_HIGH:
+ impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_90;
+ break;
+ default:
+ impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
+ }
+ return impact;
+ }
+
+ @VisibleForTesting
+ Handler getHandler() {
+ return mHandler;
+ }
}
diff --git a/packages/CrashRecovery/services/java/com/android/server/rollback/WatchdogRollbackLogger.java b/packages/CrashRecovery/services/java/com/android/server/rollback/WatchdogRollbackLogger.java
index 898c5439a293..519c0edfc532 100644
--- a/packages/CrashRecovery/services/java/com/android/server/rollback/WatchdogRollbackLogger.java
+++ b/packages/CrashRecovery/services/java/com/android/server/rollback/WatchdogRollbackLogger.java
@@ -18,6 +18,7 @@ package com.android.server.rollback;
import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_CRASH;
import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_NOT_RESPONDING;
+import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_BOOT_LOOPING;
import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_EXPLICIT_HEALTH_CHECK;
import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH;
import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH_DURING_BOOT;
@@ -258,6 +259,8 @@ public final class WatchdogRollbackLogger {
return WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_CRASH;
case PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING:
return WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_NOT_RESPONDING;
+ case PackageWatchdog.FAILURE_REASON_BOOT_LOOP:
+ return WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_BOOT_LOOPING;
default:
return WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN;
}
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/client/CredentialManagerClient.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/client/CredentialManagerClient.kt
index 3fbff37e0416..6902b6fcc69d 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/client/CredentialManagerClient.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/client/CredentialManagerClient.kt
@@ -19,6 +19,7 @@ package com.android.credentialmanager.client
import android.content.Intent
import android.credentials.selection.BaseDialogResult
import android.credentials.selection.UserSelectionDialogResult
+import com.android.credentialmanager.model.EntryInfo
import com.android.credentialmanager.model.Request
import kotlinx.coroutines.flow.StateFlow
@@ -30,10 +31,7 @@ interface CredentialManagerClient {
fun updateRequest(intent: Intent)
/** Sends an error encountered during the UI. */
- fun sendError(
- @BaseDialogResult.ResultCode resultCode: Int,
- errorMessage: String? = null,
- )
+ fun sendError(@BaseDialogResult.ResultCode resultCode: Int)
/**
* Sends a response to the system service. The response
@@ -54,4 +52,20 @@ interface CredentialManagerClient {
* @throws [IllegalStateException] if [requests] is not [Request.Get].
*/
fun sendResult(result: UserSelectionDialogResult)
+
+ /**
+ * Sends a response to the system service with a selected [EntryInfo].
+ *
+ * @return if the current [Request.Get] flow can be ended peacefully.
+ * if not, App has to keep reacting to the further update from [requests] until [Request.Cancel]
+ * or [Request.Close] is received.
+ *
+ * @throws [IllegalStateException] if [requests] is not [Request.Get].
+ */
+ fun sendEntrySelectionResult(
+ entryInfo: EntryInfo,
+ resultCode: Int? = null,
+ resultData: Intent? = null,
+ isAutoSelected: Boolean = false,
+ ): Boolean
} \ No newline at end of file
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/client/impl/CredentialManagerClientImpl.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/client/impl/CredentialManagerClientImpl.kt
index ec1f052839e4..ab70394057f3 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/client/impl/CredentialManagerClientImpl.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/client/impl/CredentialManagerClientImpl.kt
@@ -16,16 +16,24 @@
package com.android.credentialmanager.client.impl
+import android.app.Activity
import android.content.Context
import android.content.Intent
import android.credentials.selection.BaseDialogResult
+import android.credentials.selection.BaseDialogResult.RESULT_CODE_DIALOG_USER_CANCELED
+import android.credentials.selection.Constants
+import android.credentials.selection.ProviderPendingIntentResponse
import android.credentials.selection.UserSelectionDialogResult
import android.os.Bundle
+import android.os.IBinder
+import android.os.ResultReceiver
import android.util.Log
import com.android.credentialmanager.TAG
import com.android.credentialmanager.model.Request
import com.android.credentialmanager.parse
import com.android.credentialmanager.client.CredentialManagerClient
+import com.android.credentialmanager.model.EntryInfo
+
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -40,9 +48,13 @@ class CredentialManagerClientImpl @Inject constructor(
override fun updateRequest(intent: Intent) {
- val request = intent.parse(
- context = context,
- )
+ val request: Request
+ try {
+ request = intent.parse(context)
+ } catch (e: Exception) {
+ sendError(BaseDialogResult.RESULT_CODE_DATA_PARSING_FAILURE)
+ return
+ }
Log.d(TAG, "Request parsed: $request, client instance: $this")
if (request is Request.Cancel || request is Request.Close) {
if (request.token != null && request.token != _requests.value?.token) {
@@ -53,8 +65,9 @@ class CredentialManagerClientImpl @Inject constructor(
_requests.value = request
}
- override fun sendError(resultCode: Int, errorMessage: String?) {
- TODO("b/300422310 - [Wear] Implement UI for cancellation request with message")
+ override fun sendError(resultCode: Int) {
+ Log.w(TAG, "Error occurred, resultCode: $resultCode, current request: ${ requests.value }")
+ requests.value?.sendCancellationCode(resultCode)
}
override fun sendResult(result: UserSelectionDialogResult) {
@@ -69,4 +82,58 @@ class CredentialManagerClientImpl @Inject constructor(
)
}
}
+
+ override fun sendEntrySelectionResult(
+ entryInfo: EntryInfo,
+ resultCode: Int?,
+ resultData: Intent?,
+ isAutoSelected: Boolean,
+ ): Boolean {
+ Log.d(TAG, "sendEntrySelectionResult, resultCode: $resultCode, resultData: $resultData," +
+ " entryInfo: $entryInfo")
+ val currentRequest = requests.value
+ check(currentRequest is Request.Get) { "current request is not get." }
+ if (resultCode == Activity.RESULT_CANCELED) {
+ if (isAutoSelected) {
+ currentRequest.sendCancellationCode(RESULT_CODE_DIALOG_USER_CANCELED)
+ }
+ return isAutoSelected
+ }
+ val userSelectionDialogResult = UserSelectionDialogResult(
+ currentRequest.token,
+ entryInfo.providerId,
+ entryInfo.entryKey,
+ entryInfo.entrySubkey,
+ if (resultCode != null) ProviderPendingIntentResponse(
+ resultCode,
+ resultData
+ ) else null
+ )
+ sendResult(userSelectionDialogResult)
+ return entryInfo.shouldTerminateUiUponSuccessfulProviderResult
+ }
+
+ private fun Request.sendCancellationCode(cancelCode: Int) {
+ sendCancellationCode(
+ cancelCode = cancelCode,
+ requestToken = token,
+ resultReceiver = resultReceiver,
+ finalResponseReceiver = finalResponseReceiver
+ )
+ }
+
+ private fun sendCancellationCode(
+ cancelCode: Int,
+ requestToken: IBinder?,
+ resultReceiver: ResultReceiver?,
+ finalResponseReceiver: ResultReceiver?
+ ) {
+ if (requestToken != null && resultReceiver != null) {
+ val resultData = Bundle().apply {
+ putParcelable(Constants.EXTRA_FINAL_RESPONSE_RECEIVER, finalResponseReceiver)
+ }
+ BaseDialogResult.addToBundle(BaseDialogResult(requestToken), resultData)
+ resultReceiver.send(cancelCode, resultData)
+ }
+ }
}
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/IntentKtx.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/IntentKtx.kt
index 9242141cfd63..786c441bb2e3 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/IntentKtx.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/IntentKtx.kt
@@ -54,3 +54,9 @@ val Intent.resultReceiver: ResultReceiver?
Constants.EXTRA_RESULT_RECEIVER,
ResultReceiver::class.java
)
+
+val Intent.finalResponseReceiver: ResultReceiver?
+ get() = this.getParcelableExtra(
+ Constants.EXTRA_FINAL_RESPONSE_RECEIVER,
+ ResultReceiver::class.java
+ )
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestGetMapper.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestGetMapper.kt
index f1f1f7ca842e..1683cc43eef1 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestGetMapper.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestGetMapper.kt
@@ -20,6 +20,7 @@ import android.content.Context
import android.content.Intent
import com.android.credentialmanager.ktx.getCredentialProviderDataList
import com.android.credentialmanager.ktx.requestInfo
+import com.android.credentialmanager.ktx.finalResponseReceiver
import com.android.credentialmanager.ktx.resultReceiver
import com.android.credentialmanager.ktx.toProviderList
import com.android.credentialmanager.model.Request
@@ -28,6 +29,7 @@ fun Intent.toGet(context: Context): Request.Get {
return Request.Get(
token = requestInfo?.token,
resultReceiver = resultReceiver,
+ finalResponseReceiver = finalResponseReceiver,
providerInfos = getCredentialProviderDataList.toProviderList(context)
)
}
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/model/Request.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/Request.kt
index 763646233324..fd99275ebc8e 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/model/Request.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/Request.kt
@@ -25,6 +25,8 @@ import com.android.credentialmanager.model.get.ProviderInfo
*/
sealed class Request private constructor(
open val token: IBinder?,
+ open val resultReceiver: ResultReceiver? = null,
+ open val finalResponseReceiver: ResultReceiver? = null,
) {
/**
@@ -48,9 +50,10 @@ sealed class Request private constructor(
*/
data class Get(
override val token: IBinder?,
- val resultReceiver: ResultReceiver?,
+ override val resultReceiver: ResultReceiver?,
+ override val finalResponseReceiver: ResultReceiver?,
val providerInfos: List<ProviderInfo>,
- ) : Request(token)
+ ) : Request(token, resultReceiver, finalResponseReceiver)
/**
* Request to start the create credentials flow.
*/
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index 30973879de10..879d64c761ec 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -50,7 +50,6 @@ import com.android.credentialmanager.createflow.isFlowAutoSelectable
class CredentialManagerRepo(
private val context: Context,
intent: Intent,
- userConfigRepo: UserConfigRepo,
isNewActivity: Boolean,
) {
val requestInfo: RequestInfo?
@@ -111,11 +110,7 @@ class CredentialManagerRepo(
ResultReceiver::class.java
)
- isReqForAllOptions = intent.getBooleanExtra(
- Constants.EXTRA_REQ_FOR_ALL_OPTIONS,
- /*defaultValue=*/ false
- ) || (requestInfo?.isShowAllOptionsRequested ?: false) // TODO(b/323552850) - Remove
- // usage on Constants.EXTRA_REQ_FOR_ALL_OPTIONS once it is deprecated.
+ isReqForAllOptions = requestInfo?.isShowAllOptionsRequested ?: false
val cancellationRequest = getCancelUiRequest(intent)
val cancelUiRequestState = cancellationRequest?.let {
@@ -124,7 +119,6 @@ class CredentialManagerRepo(
initialUiState = when (requestInfo?.type) {
RequestInfo.TYPE_CREATE -> {
- val isPasskeyFirstUse = userConfigRepo.getIsPasskeyFirstUse()
val providerEnableListUiState = getCreateProviderEnableListInitialUiState()
val providerDisableListUiState = getCreateProviderDisableListInitialUiState()
val requestDisplayInfoUiState =
@@ -137,8 +131,6 @@ class CredentialManagerRepo(
defaultProviderIdsSetByUser =
requestDisplayInfoUiState.userSetDefaultProviderIds,
requestDisplayInfo = requestDisplayInfoUiState,
- isOnPasskeyIntroStateAlready = false,
- isPasskeyFirstUse = isPasskeyFirstUse,
)!!
val isFlowAutoSelectable = isFlowAutoSelectable(createCredentialUiState)
UiState(
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
index 4771237d449f..ec0da0986e45 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
@@ -61,9 +61,7 @@ class CredentialSelectorActivity : ComponentActivity() {
if (isCancellationRequest && !shouldShowCancellationUi) {
return
}
- val userConfigRepo = UserConfigRepo(this)
- val credManRepo = CredentialManagerRepo(
- this, intent, userConfigRepo, isNewActivity = true)
+ val credManRepo = CredentialManagerRepo(this, intent, isNewActivity = true)
val backPressedCallback = object : OnBackPressedCallback(
true // default to enabled
@@ -78,10 +76,7 @@ class CredentialSelectorActivity : ComponentActivity() {
setContent {
PlatformTheme {
- CredentialManagerBottomSheet(
- credManRepo,
- userConfigRepo
- )
+ CredentialManagerBottomSheet(credManRepo)
}
}
} catch (e: Exception) {
@@ -103,9 +98,7 @@ class CredentialSelectorActivity : ComponentActivity() {
return
}
} else {
- val userConfigRepo = UserConfigRepo(this)
- val credManRepo = CredentialManagerRepo(
- this, intent, userConfigRepo, isNewActivity = false)
+ val credManRepo = CredentialManagerRepo(this, intent, isNewActivity = false)
viewModel.onNewCredentialManagerRepo(credManRepo)
}
} catch (e: Exception) {
@@ -147,10 +140,9 @@ class CredentialSelectorActivity : ComponentActivity() {
@Composable
private fun CredentialManagerBottomSheet(
credManRepo: CredentialManagerRepo,
- userConfigRepo: UserConfigRepo,
) {
val viewModel: CredentialSelectorViewModel = viewModel {
- CredentialSelectorViewModel(credManRepo, userConfigRepo)
+ CredentialSelectorViewModel(credManRepo)
}
val launcher = rememberLauncherForActivityResult(
StartBalIntentSenderForResultContract()
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
index f4da1e6c4770..1f2fa200e43d 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
@@ -34,7 +34,6 @@ import com.android.credentialmanager.common.DialogState
import com.android.credentialmanager.common.ProviderActivityResult
import com.android.credentialmanager.common.ProviderActivityState
import com.android.credentialmanager.createflow.ActiveEntry
-import com.android.credentialmanager.createflow.isFlowAutoSelectable
import com.android.credentialmanager.createflow.CreateCredentialUiState
import com.android.credentialmanager.createflow.CreateScreenState
import com.android.credentialmanager.getflow.GetCredentialUiState
@@ -63,7 +62,6 @@ data class CancelUiRequestState(
class CredentialSelectorViewModel(
private var credManRepo: CredentialManagerRepo,
- private val userConfigRepo: UserConfigRepo,
) : ViewModel() {
var uiState by mutableStateOf(credManRepo.initState())
private set
@@ -266,42 +264,6 @@ class CredentialSelectorViewModel(
/**************************************************************************/
/***** Create Flow Callbacks *****/
/**************************************************************************/
- fun createFlowOnConfirmIntro() {
- userConfigRepo.setIsPasskeyFirstUse(false)
- val prevUiState = uiState.createCredentialUiState
- if (prevUiState == null) {
- Log.d(Constants.LOG_TAG, "Encountered unexpected null create ui state")
- onInternalError()
- return
- }
- val newScreenState = CreateFlowUtils.toCreateScreenState(
- createOptionSize = prevUiState.sortedCreateOptionsPairs.size,
- isOnPasskeyIntroStateAlready = true,
- requestDisplayInfo = prevUiState.requestDisplayInfo,
- remoteEntry = prevUiState.remoteEntry,
- isPasskeyFirstUse = true,
- )
- if (newScreenState == null) {
- Log.d(Constants.LOG_TAG, "Unexpected: couldn't resolve new screen state")
- onInternalError()
- return
- }
- val newCreateCredentialUiState = prevUiState.copy(
- currentScreenState = newScreenState,
- )
- val isFlowAutoSelectable = isFlowAutoSelectable(newCreateCredentialUiState)
- uiState = uiState.copy(
- createCredentialUiState = newCreateCredentialUiState,
- isAutoSelectFlow = isFlowAutoSelectable,
- providerActivityState =
- if (isFlowAutoSelectable) ProviderActivityState.READY_TO_LAUNCH
- else ProviderActivityState.NOT_APPLICABLE,
- selectedEntry =
- if (isFlowAutoSelectable) newCreateCredentialUiState.activeEntry?.activeEntryInfo
- else null,
- )
- }
-
fun createFlowOnMoreOptionsSelectedOnCreationSelection() {
uiState = uiState.copy(
createCredentialUiState = uiState.createCredentialUiState?.copy(
@@ -318,14 +280,6 @@ class CredentialSelectorViewModel(
)
}
- fun createFlowOnBackPasskeyIntroButtonSelected() {
- uiState = uiState.copy(
- createCredentialUiState = uiState.createCredentialUiState?.copy(
- currentScreenState = CreateScreenState.PASSKEY_INTRO,
- )
- )
- }
-
fun createFlowOnEntrySelectedFromMoreOptionScreen(activeEntry: ActiveEntry) {
uiState = uiState.copy(
createCredentialUiState = uiState.createCredentialUiState?.copy(
@@ -348,14 +302,6 @@ class CredentialSelectorViewModel(
uiState = uiState.copy(dialogState = DialogState.CANCELED_FOR_SETTINGS)
}
- fun createFlowOnLearnMore() {
- uiState = uiState.copy(
- createCredentialUiState = uiState.createCredentialUiState?.copy(
- currentScreenState = CreateScreenState.MORE_ABOUT_PASSKEYS_INTRO,
- )
- )
- }
-
fun createFlowOnUseOnceSelected() {
uiState = uiState.copy(
createCredentialUiState = uiState.createCredentialUiState?.copy(
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index 997c45e84180..5830b9fc1028 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -342,8 +342,6 @@ class CreateFlowUtils {
defaultProviderIdPreferredByApp: String?,
defaultProviderIdsSetByUser: Set<String>,
requestDisplayInfo: RequestDisplayInfo,
- isOnPasskeyIntroStateAlready: Boolean,
- isPasskeyFirstUse: Boolean,
): CreateCredentialUiState? {
var remoteEntry: RemoteInfo? = null
var remoteEntryProvider: EnabledProviderInfo? = null
@@ -392,11 +390,8 @@ class CreateFlowUtils {
val defaultProvider = defaultProviderPreferredByApp ?: defaultProviderSetByUser
val initialScreenState = toCreateScreenState(
createOptionSize = createOptionsPairs.size,
- isOnPasskeyIntroStateAlready = isOnPasskeyIntroStateAlready,
- requestDisplayInfo = requestDisplayInfo,
remoteEntry = remoteEntry,
- isPasskeyFirstUse = isPasskeyFirstUse
- ) ?: return null
+ )
val sortedCreateOptionsPairs = createOptionsPairs.sortedWith(
compareByDescending { it.first.lastUsedTime }
)
@@ -419,15 +414,9 @@ class CreateFlowUtils {
fun toCreateScreenState(
createOptionSize: Int,
- isOnPasskeyIntroStateAlready: Boolean,
- requestDisplayInfo: RequestDisplayInfo,
remoteEntry: RemoteInfo?,
- isPasskeyFirstUse: Boolean,
- ): CreateScreenState? {
- return if (isPasskeyFirstUse && requestDisplayInfo.type == CredentialType.PASSKEY &&
- !isOnPasskeyIntroStateAlready) {
- CreateScreenState.PASSKEY_INTRO
- } else if (createOptionSize == 0 && remoteEntry != null) {
+ ): CreateScreenState {
+ return if (createOptionSize == 0 && remoteEntry != null) {
CreateScreenState.EXTERNAL_ONLY_SELECTION
} else {
CreateScreenState.CREATION_OPTION_SELECTION
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/UserConfigRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/UserConfigRepo.kt
deleted file mode 100644
index bfcca4970a71..000000000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/UserConfigRepo.kt
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.credentialmanager
-
-import android.content.Context
-import android.content.SharedPreferences
-
-class UserConfigRepo(context: Context) {
- val sharedPreferences: SharedPreferences = context.getSharedPreferences(
- context.packageName, Context.MODE_PRIVATE)
-
- fun setIsPasskeyFirstUse(
- isFirstUse: Boolean
- ) {
- sharedPreferences.edit().apply {
- putBoolean(IS_PASSKEY_FIRST_USE, isFirstUse)
- apply()
- }
- }
-
- fun getIsPasskeyFirstUse(): Boolean {
- return sharedPreferences.getBoolean(IS_PASSKEY_FIRST_USE, true)
- }
-
- companion object {
- // This first use value only applies to passkeys, not related with if generally
- // credential manager is first use or not
- const val IS_PASSKEY_FIRST_USE = "is_passkey_first_use"
- }
-}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
index 121f207122a0..5599ccd41de9 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
@@ -607,10 +607,14 @@ class CredentialAutofillService : AutofillService() {
autofillId: AutofillId,
responseClientState: Bundle
): MutableList<CredentialOption> {
- if (viewNode.credentialManagerRequest != null &&
- viewNode.credentialManagerCallback != null) {
+ if (viewNode.credentialManagerRequest != null) {
val options = viewNode.credentialManagerRequest?.getCredentialOptions()
if (options != null) {
+ for (option in options) {
+ option.candidateQueryData.putParcelable(
+ CredentialProviderService.EXTRA_AUTOFILL_ID, autofillId
+ )
+ }
return options
}
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
index f261d1fa062d..d24adb567bc4 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -20,8 +20,6 @@ import android.text.TextUtils
import androidx.activity.compose.ManagedActivityResultLauncher
import androidx.activity.result.ActivityResult
import androidx.activity.result.IntentSenderRequest
-import androidx.compose.foundation.isSystemInDarkTheme
-import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
@@ -36,12 +34,9 @@ import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.outlined.QrCodeScanner
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.asImageBitmap
-import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
@@ -63,10 +58,8 @@ import com.android.credentialmanager.common.ui.Entry
import com.android.credentialmanager.common.ui.HeadlineIcon
import com.android.credentialmanager.common.ui.LargeLabelTextOnSurfaceVariant
import com.android.credentialmanager.common.ui.ModalBottomSheet
-import com.android.credentialmanager.common.ui.MoreAboutPasskeySectionHeader
import com.android.credentialmanager.common.ui.MoreOptionTopAppBar
import com.android.credentialmanager.common.ui.SheetContainerCard
-import com.android.credentialmanager.common.ui.PasskeyBenefitRow
import com.android.credentialmanager.common.ui.HeadlineText
import com.android.credentialmanager.logging.CreateCredentialEvent
import com.android.credentialmanager.model.creation.CreateOptionInfo
@@ -87,11 +80,6 @@ fun CreateCredentialScreen(
when (viewModel.uiState.providerActivityState) {
ProviderActivityState.NOT_APPLICABLE -> {
when (createCredentialUiState.currentScreenState) {
- CreateScreenState.PASSKEY_INTRO -> PasskeyIntroCard(
- onConfirm = viewModel::createFlowOnConfirmIntro,
- onLearnMore = viewModel::createFlowOnLearnMore,
- onLog = { viewModel.logUiEvent(it) },
- )
CreateScreenState.CREATION_OPTION_SELECTION -> CreationSelectionCard(
requestDisplayInfo = createCredentialUiState.requestDisplayInfo,
enabledProviderList = createCredentialUiState.enabledProviders,
@@ -144,11 +132,6 @@ fun CreateCredentialScreen(
onConfirm = viewModel::createFlowOnConfirmEntrySelected,
onLog = { viewModel.logUiEvent(it) },
)
- CreateScreenState.MORE_ABOUT_PASSKEYS_INTRO -> MoreAboutPasskeysIntroCard(
- onBackPasskeyIntroButtonSelected =
- viewModel::createFlowOnBackPasskeyIntroButtonSelected,
- onLog = { viewModel.logUiEvent(it) },
- )
}
}
ProviderActivityState.READY_TO_LAUNCH -> {
@@ -188,78 +171,6 @@ fun CreateCredentialScreen(
}
@Composable
-fun PasskeyIntroCard(
- onConfirm: () -> Unit,
- onLearnMore: () -> Unit,
- onLog: @Composable (UiEventEnum) -> Unit,
-) {
- SheetContainerCard {
- item {
- val onboardingImageResource = remember {
- mutableStateOf(R.drawable.ic_passkeys_onboarding)
- }
- if (isSystemInDarkTheme()) {
- onboardingImageResource.value = R.drawable.ic_passkeys_onboarding_dark
- } else {
- onboardingImageResource.value = R.drawable.ic_passkeys_onboarding
- }
- Row(
- modifier = Modifier.wrapContentHeight().fillMaxWidth(),
- horizontalArrangement = Arrangement.Center,
- ) {
- Image(
- painter = painterResource(onboardingImageResource.value),
- contentDescription = null,
- modifier = Modifier.size(316.dp, 168.dp)
- )
- }
- }
- item { Divider(thickness = 16.dp, color = Color.Transparent) }
- item { HeadlineText(text = stringResource(R.string.passkey_creation_intro_title)) }
- item { Divider(thickness = 16.dp, color = Color.Transparent) }
- item {
- PasskeyBenefitRow(
- leadingIconPainter = painterResource(R.drawable.ic_passkeys_onboarding_password),
- text = stringResource(R.string.passkey_creation_intro_body_password),
- )
- }
- item { Divider(thickness = 16.dp, color = Color.Transparent) }
- item {
- PasskeyBenefitRow(
- leadingIconPainter = painterResource(R.drawable.ic_passkeys_onboarding_fingerprint),
- text = stringResource(R.string.passkey_creation_intro_body_fingerprint),
- )
- }
- item { Divider(thickness = 16.dp, color = Color.Transparent) }
- item {
- PasskeyBenefitRow(
- leadingIconPainter = painterResource(R.drawable.ic_passkeys_onboarding_device),
- text = stringResource(R.string.passkey_creation_intro_body_device),
- )
- }
- item { Divider(thickness = 24.dp, color = Color.Transparent) }
-
- item {
- CtaButtonRow(
- leftButton = {
- ActionButton(
- stringResource(R.string.string_learn_more),
- onClick = onLearnMore
- )
- },
- rightButton = {
- ConfirmButton(
- stringResource(R.string.string_continue),
- onClick = onConfirm
- )
- },
- )
- }
- }
- onLog(CreateCredentialEvent.CREDMAN_CREATE_CRED_PASSKEY_INTRO)
-}
-
-@Composable
fun MoreOptionsSelectionCard(
requestDisplayInfo: RequestDisplayInfo,
enabledProviderList: List<EnabledProviderInfo>,
@@ -522,59 +433,6 @@ fun ExternalOnlySelectionCard(
}
@Composable
-fun MoreAboutPasskeysIntroCard(
- onBackPasskeyIntroButtonSelected: () -> Unit,
- onLog: @Composable (UiEventEnum) -> Unit,
-) {
- SheetContainerCard(
- topAppBar = {
- MoreOptionTopAppBar(
- text = stringResource(R.string.more_about_passkeys_title),
- onNavigationIconClicked = onBackPasskeyIntroButtonSelected,
- bottomPadding = 0.dp,
- )
- },
- ) {
- item {
- MoreAboutPasskeySectionHeader(
- text = stringResource(R.string.passwordless_technology_title)
- )
- Row(modifier = Modifier.fillMaxWidth().wrapContentHeight()) {
- BodyMediumText(text = stringResource(R.string.passwordless_technology_detail))
- }
- }
- item {
- Divider(thickness = 8.dp, color = Color.Transparent)
- MoreAboutPasskeySectionHeader(
- text = stringResource(R.string.public_key_cryptography_title)
- )
- Row(modifier = Modifier.fillMaxWidth().wrapContentHeight()) {
- BodyMediumText(text = stringResource(R.string.public_key_cryptography_detail))
- }
- }
- item {
- Divider(thickness = 8.dp, color = Color.Transparent)
- MoreAboutPasskeySectionHeader(
- text = stringResource(R.string.improved_account_security_title)
- )
- Row(modifier = Modifier.fillMaxWidth().wrapContentHeight()) {
- BodyMediumText(text = stringResource(R.string.improved_account_security_detail))
- }
- }
- item {
- Divider(thickness = 8.dp, color = Color.Transparent)
- MoreAboutPasskeySectionHeader(
- text = stringResource(R.string.seamless_transition_title)
- )
- Row(modifier = Modifier.fillMaxWidth().wrapContentHeight()) {
- BodyMediumText(text = stringResource(R.string.seamless_transition_detail))
- }
- }
- }
- onLog(CreateCredentialEvent.CREDMAN_CREATE_CRED_MORE_ABOUT_PASSKEYS_INTRO)
-}
-
-@Composable
fun PrimaryCreateOptionRow(
requestDisplayInfo: RequestDisplayInfo,
entryInfo: EntryInfo,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
index 8b0ba87fa9be..617a981fc4ba 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
@@ -37,10 +37,6 @@ internal fun isFlowAutoSelectable(
uiState: CreateCredentialUiState
): Boolean {
return uiState.requestDisplayInfo.isAutoSelectRequest &&
- // Even if the flow is auto selectable, still allow passkey intro screen to show once if
- // applicable.
- uiState.currentScreenState != CreateScreenState.PASSKEY_INTRO &&
- uiState.currentScreenState != CreateScreenState.MORE_ABOUT_PASSKEYS_INTRO &&
uiState.sortedCreateOptionsPairs.size == 1 &&
uiState.activeEntry?.activeEntryInfo?.let {
it is CreateOptionInfo && it.allowAutoSelect
@@ -98,8 +94,6 @@ data class ActiveEntry (
/** The name of the current screen. */
enum class CreateScreenState {
- PASSKEY_INTRO,
- MORE_ABOUT_PASSKEYS_INTRO,
CREATION_OPTION_SELECTION,
MORE_OPTIONS_SELECTION,
DEFAULT_PROVIDER_CONFIRMATION,
diff --git a/packages/CredentialManager/wear/res/values/strings.xml b/packages/CredentialManager/wear/res/values/strings.xml
index 4e9174e53864..9480e64aa134 100644
--- a/packages/CredentialManager/wear/res/values/strings.xml
+++ b/packages/CredentialManager/wear/res/values/strings.xml
@@ -27,11 +27,11 @@
<!-- Title of a screen prompting if the user would like to sign in with provider
[CHAR LIMIT=80] -->
<string name="use_password_title">Use password?</string>
- <!-- Content description for the dismiss button of a screen. [CHAR LIMIT=NONE] -->
+ <!-- Text on this dismiss button of a screen. [CHAR LIMIT=NONE] -->
<string name="dialog_dismiss_button">Dismiss</string>
- <!-- Content description for the continue button of a screen. [CHAR LIMIT=NONE] -->
+ <!-- Text on the continue button of a screen. [CHAR LIMIT=NONE] -->
<string name="dialog_continue_button">Continue</string>
- <!-- Content description for the sign in options button of a screen. [CHAR LIMIT=NONE] -->
+ <!-- Text on the sign in options button of a screen. [CHAR LIMIT=NONE] -->
<string name="dialog_sign_in_options_button">Sign-in Options</string>
<!-- Title for multiple credentials folded screen. [CHAR LIMIT=NONE] -->
<string name="sign_in_options_title">Sign-in Options</string>
@@ -41,4 +41,11 @@
<string name="choose_passkey_title">Choose passkey</string>
<!-- Title for multiple credentials screen with only passwords. [CHAR LIMIT=NONE] -->
<string name="choose_password_title">Choose password</string>
+ <!-- Text on the sign in on phone button [CHAR LIMIT=NONE] -->
+ <string name="sign_in_on_phone_button">Sign in on phone</string>
+ <!-- Text on the locked provider button when unlocked[CHAR LIMIT=NONE] -->
+ <string name="locked_credential_entry_label_subtext_no_sign_in">No sign-in info</string>
+ <!-- Text on the locked provider button when locked[CHAR LIMIT=NONE] -->
+ <string name="locked_credential_entry_label_subtext_tap_to_unlock">Tap to unlock</string>
+
</resources> \ No newline at end of file
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt
index 0df40d7adba5..283dc7d6fe08 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt
@@ -25,7 +25,6 @@ import androidx.wear.compose.material.MaterialTheme
import com.android.credentialmanager.ui.WearApp
import com.google.android.horologist.annotations.ExperimentalHorologistApi
import dagger.hilt.android.AndroidEntryPoint
-import kotlin.system.exitProcess
@AndroidEntryPoint(ComponentActivity::class)
class CredentialSelectorActivity : Hilt_CredentialSelectorActivity() {
@@ -40,7 +39,7 @@ class CredentialSelectorActivity : Hilt_CredentialSelectorActivity() {
MaterialTheme {
WearApp(
viewModel = viewModel,
- onCloseApp = { exitProcess(0) },
+ onCloseApp = { finish() },
)
}
}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorApp.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorApp.kt
index 6bd166e855ff..8c5c085d2880 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorApp.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorApp.kt
@@ -19,5 +19,6 @@ package com.android.credentialmanager
import android.app.Application
import dagger.hilt.android.HiltAndroidApp
+/** [Application] of credential selector. */
@HiltAndroidApp(Application::class)
class CredentialSelectorApp : Hilt_CredentialSelectorApp() \ No newline at end of file
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
index 2fc98e27bea1..463c4d1b063e 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
@@ -17,13 +17,22 @@
package com.android.credentialmanager
import android.content.Intent
+import android.credentials.selection.BaseDialogResult
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
+import com.android.credentialmanager.CredentialSelectorUiState.Get
import com.android.credentialmanager.model.Request
import com.android.credentialmanager.client.CredentialManagerClient
+import com.android.credentialmanager.model.EntryInfo
import com.android.credentialmanager.model.get.ActionEntryInfo
+import com.android.credentialmanager.model.get.AuthenticationEntryInfo
import com.android.credentialmanager.model.get.CredentialEntryInfo
import com.android.credentialmanager.ui.mappers.toGet
+import android.util.Log
+import com.android.credentialmanager.CredentialSelectorUiState.Cancel
+import com.android.credentialmanager.CredentialSelectorUiState.Close
+import com.android.credentialmanager.CredentialSelectorUiState.Create
+import com.android.credentialmanager.CredentialSelectorUiState.Idle
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
@@ -35,27 +44,70 @@ import javax.inject.Inject
@HiltViewModel
class CredentialSelectorViewModel @Inject constructor(
private val credentialManagerClient: CredentialManagerClient,
-) : ViewModel() {
- private val isPrimaryScreen = MutableStateFlow(false)
- val uiState: StateFlow<CredentialSelectorUiState> = credentialManagerClient.requests
- .combine(isPrimaryScreen) { request, isPrimary ->
+) : FlowEngine, ViewModel() {
+ private val isPrimaryScreen = MutableStateFlow(true)
+ private val shouldClose = MutableStateFlow(false)
+ val uiState: StateFlow<CredentialSelectorUiState> =
+ combine(
+ credentialManagerClient.requests,
+ isPrimaryScreen,
+ shouldClose
+ ) { request, isPrimary, shouldClose ->
+ if (shouldClose) {
+ Log.d(TAG, "Request finished, closing ")
+ return@combine Close
+ }
+
when (request) {
- null -> CredentialSelectorUiState.Idle
- is Request.Cancel -> CredentialSelectorUiState.Cancel(request.appName)
- is Request.Close -> CredentialSelectorUiState.Close
- is Request.Create -> CredentialSelectorUiState.Create
+ null -> Idle
+ is Request.Cancel -> Cancel(request.appName)
+ is Request.Close -> Close
+ is Request.Create -> Create
is Request.Get -> request.toGet(isPrimary)
}
}
.stateIn(
viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
- initialValue = CredentialSelectorUiState.Idle,
+ initialValue = Idle,
)
fun updateRequest(intent: Intent) {
credentialManagerClient.updateRequest(intent = intent)
}
+
+ override fun back() {
+ Log.d(TAG, "OnBackPressed")
+ when (uiState.value) {
+ is Get.MultipleEntry -> isPrimaryScreen.value = true
+ is Create, Close, is Cancel, Idle -> shouldClose.value = true
+ is Get.SingleEntry, is Get.SingleEntryPerAccount -> cancel()
+ }
+ }
+
+ override fun cancel() {
+ credentialManagerClient.sendError(BaseDialogResult.RESULT_CODE_DIALOG_USER_CANCELED)
+ shouldClose.value = true
+ }
+
+ override fun openSecondaryScreen() {
+ isPrimaryScreen.value = false
+ }
+
+ override fun sendSelectionResult(
+ entryInfo: EntryInfo,
+ resultCode: Int?,
+ resultData: Intent?,
+ isAutoSelected: Boolean,
+ ) {
+ val result = credentialManagerClient.sendEntrySelectionResult(
+ entryInfo = entryInfo,
+ resultCode = resultCode,
+ resultData = resultData,
+ isAutoSelected = isAutoSelected
+ )
+ shouldClose.value = result
+ }
}
sealed class CredentialSelectorUiState {
@@ -66,6 +118,7 @@ sealed class CredentialSelectorUiState {
data class MultipleEntry(
val accounts: List<PerUserNameEntries>,
val actionEntryList: List<ActionEntryInfo>,
+ val authenticationEntryList: List<AuthenticationEntryInfo>,
) : Get() {
data class PerUserNameEntries(
val userName: String,
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/FlowEngine.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/FlowEngine.kt
new file mode 100644
index 000000000000..e4216446772b
--- /dev/null
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/FlowEngine.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.credentialmanager
+
+import android.content.Intent
+import com.android.credentialmanager.model.EntryInfo
+
+/** Engine of the credential selecting flow. */
+interface FlowEngine {
+ /** Back from previous stage. */
+ fun back()
+ /** Cancels the selection flow. */
+ fun cancel()
+ /** Opens secondary screen. */
+ fun openSecondaryScreen()
+ /**
+ * Sends [entryInfo] as long as result after launching [EntryInfo.pendingIntent] with
+ * [EntryInfo.fillInIntent].
+ *
+ * @param entryInfo: selected entry.
+ * @param resultCode: result code received after launch.
+ * @param resultData: data received after launch
+ * @param isAutoSelected: whether the entry is auto selected or by user.
+ */
+ fun sendSelectionResult(
+ entryInfo: EntryInfo,
+ resultCode: Int? = null,
+ resultData: Intent? = null,
+ isAutoSelected: Boolean = false,
+ )
+} \ No newline at end of file
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt
index f7158e89a5cd..f9a5887158eb 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt
@@ -18,8 +18,11 @@
package com.android.credentialmanager.ui
+import android.util.Log
+import androidx.activity.compose.BackHandler
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavController
import androidx.wear.compose.foundation.rememberSwipeToDismissBoxState
@@ -29,6 +32,8 @@ import com.android.credentialmanager.CredentialSelectorUiState
import com.android.credentialmanager.CredentialSelectorUiState.Get.SingleEntry
import com.android.credentialmanager.CredentialSelectorUiState.Get.MultipleEntry
import com.android.credentialmanager.CredentialSelectorViewModel
+import com.android.credentialmanager.FlowEngine
+import com.android.credentialmanager.TAG
import com.android.credentialmanager.ui.screens.LoadingScreen
import com.android.credentialmanager.ui.screens.single.passkey.SinglePasskeyScreen
import com.android.credentialmanager.ui.screens.single.password.SinglePasswordScreen
@@ -44,6 +49,7 @@ import com.android.credentialmanager.ui.screens.multiple.MultiCredentialsFoldScr
@Composable
fun WearApp(
viewModel: CredentialSelectorViewModel,
+ flowEngine: FlowEngine = viewModel,
onCloseApp: () -> Unit,
) {
val navController = rememberSwipeDismissableNavController()
@@ -52,7 +58,6 @@ fun WearApp(
rememberSwipeDismissableNavHostState(swipeToDismissBoxState = swipeToDismissBoxState)
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
-
WearNavScaffold(
startDestination = Screen.Loading.route,
navController = navController,
@@ -61,11 +66,11 @@ fun WearApp(
composable(Screen.Loading.route) {
LoadingScreen()
}
-
scrollable(Screen.SinglePasswordScreen.route) {
SinglePasswordScreen(
- credentialSelectorUiState = viewModel.uiState.value as SingleEntry,
+ entry = (remember { uiState } as SingleEntry).entry,
columnState = it.columnState,
+ flowEngine = flowEngine,
)
}
@@ -88,10 +93,13 @@ fun WearApp(
credentialSelectorUiState = viewModel.uiState.value as MultipleEntry,
screenIcon = null,
columnState = it.columnState,
- )
+ )
}
}
-
+ BackHandler(true) {
+ viewModel.back()
+ }
+ Log.d(TAG, "uiState change, state: $uiState")
when (val state = uiState) {
CredentialSelectorUiState.Idle -> {
if (navController.currentDestination?.route != Screen.Loading.route) {
@@ -112,7 +120,6 @@ fun WearApp(
}
is CredentialSelectorUiState.Cancel -> {
- // TODO: b/300422310 - Implement cancel with message flow
onCloseApp()
}
@@ -142,7 +149,7 @@ private fun handleGetNavigation(
}
}
- is CredentialSelectorUiState.Get.MultipleEntry -> {
+ is MultipleEntry -> {
navController.navigateToMultipleCredentialsFoldScreen()
}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/CredentialsScreenChip.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/CredentialsScreenChip.kt
index 7cd6bb3a6cef..8e5a8666621f 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/CredentialsScreenChip.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/CredentialsScreenChip.kt
@@ -15,32 +15,40 @@
*/
package com.android.credentialmanager.ui.components
+import androidx.compose.foundation.layout.Row
+import androidx.compose.material3.Icon
import android.graphics.drawable.Drawable
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.wear.compose.material.Chip
+import androidx.core.graphics.drawable.toBitmap
import androidx.wear.compose.material.ChipColors
+import androidx.compose.ui.graphics.asImageBitmap
+import androidx.compose.ui.graphics.Color
import androidx.wear.compose.material.ChipDefaults
import androidx.wear.compose.material.Text
import com.android.credentialmanager.R
+import com.android.credentialmanager.model.get.AuthenticationEntryInfo
import com.android.credentialmanager.ui.components.CredentialsScreenChip.TOPPADDING
+/* Used as credential suggestion or user action chip. */
@Composable
fun CredentialsScreenChip(
label: String,
onClick: () -> Unit,
secondaryLabel: String? = null,
icon: Drawable? = null,
+ isAuthenticationEntryLocked: Boolean = false,
modifier: Modifier = Modifier,
colors: ChipColors = ChipDefaults.secondaryChipColors(),
) {
@@ -56,18 +64,37 @@ fun CredentialsScreenChip(
val secondaryLabelParam: (@Composable RowScope.() -> Unit)? =
secondaryLabel?.let {
{
- Text(
- text = secondaryLabel,
- overflow = TextOverflow.Ellipsis,
- maxLines = 1,
- )
+ Row {
+ Text(
+ text = secondaryLabel,
+ overflow = TextOverflow.Ellipsis,
+ maxLines = 1,
+ )
+
+ if (isAuthenticationEntryLocked)
+ // TODO(b/324465527) change this to lock icon and correct size once figma mocks are
+ // updated
+ Icon(
+ bitmap = checkNotNull(icon?.toBitmap()?.asImageBitmap()),
+ // Decorative purpose only.
+ contentDescription = null,
+ modifier = Modifier.size(20.dp),
+ tint = Color.Unspecified
+ )
+ }
}
}
val iconParam: (@Composable BoxScope.() -> Unit)? =
- icon?.let {
+ icon?.toBitmap()?.asImageBitmap()?.let {
{
- ChipDefaults.IconSize
+ Icon(
+ bitmap = it,
+ // Decorative purpose only.
+ contentDescription = null,
+ modifier = Modifier.size(32.dp),
+ tint = Color.Unspecified
+ )
}
}
@@ -139,6 +166,37 @@ fun DismissChip(onClick: () -> Unit) {
)
}
+@Composable
+fun SignInOnPhoneChip(onClick: () -> Unit) {
+ CredentialsScreenChip(
+ label = stringResource(R.string.sign_in_on_phone_button),
+ onClick = onClick,
+ modifier = Modifier
+ .padding(top = TOPPADDING),
+ )
+}
+
+@Composable
+fun LockedProviderChip(
+ authenticationEntryInfo: AuthenticationEntryInfo,
+ onClick: () -> Unit
+) {
+ val secondaryLabel = stringResource(
+ if (authenticationEntryInfo.isUnlockedAndEmpty)
+ R.string.locked_credential_entry_label_subtext_no_sign_in
+ else R.string.locked_credential_entry_label_subtext_tap_to_unlock
+ )
+
+ CredentialsScreenChip(
+ label = authenticationEntryInfo.title,
+ icon = authenticationEntryInfo.icon,
+ secondaryLabel = secondaryLabel,
+ isAuthenticationEntryLocked = !authenticationEntryInfo.isUnlockedAndEmpty,
+ onClick = onClick,
+ modifier = Modifier.padding(top = TOPPADDING),
+ )
+}
+
@Preview
@Composable
fun DismissChipPreview() {
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/SignInHeader.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/SignInHeader.kt
index 1ddf4af1d923..423662c30d6e 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/SignInHeader.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/SignInHeader.kt
@@ -27,10 +27,12 @@ import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.unit.dp
import androidx.core.graphics.drawable.toBitmap
import androidx.wear.compose.material.Text
+import androidx.compose.ui.graphics.Color
import androidx.compose.material3.Icon
import androidx.wear.compose.material.MaterialTheme as WearMaterialTheme
import androidx.compose.ui.text.style.TextAlign
+/* Used as header across Credential Selector screens. */
@Composable
fun SignInHeader(
icon: Drawable?,
@@ -46,7 +48,9 @@ fun SignInHeader(
bitmap = icon.toBitmap().asImageBitmap(),
modifier = Modifier.size(32.dp),
// Decorative purpose only.
- contentDescription = null
+ contentDescription = null,
+ tint = Color.Unspecified,
+
)
}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt
index 5898a40d2edd..03b0931f3eb4 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt
@@ -47,6 +47,7 @@ fun Request.Get.toGet(isPrimary: Boolean): CredentialSelectorUiState.Get {
)
},
actionEntryList = providerInfos.flatMap { it.actionEntryList },
+ authenticationEntryList = providerInfos.flatMap { it.authenticationEntryList }
)
}
}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldScreen.kt
index a0ea4ee10fbf..5515c8696b87 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldScreen.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/multiple/MultiCredentialsFoldScreen.kt
@@ -39,6 +39,7 @@ import com.android.credentialmanager.ui.components.DismissChip
import com.android.credentialmanager.ui.components.CredentialsScreenChip
import com.android.credentialmanager.ui.components.SignInHeader
import com.android.credentialmanager.ui.components.SignInOptionsChip
+import com.android.credentialmanager.ui.components.LockedProviderChip
import com.google.android.horologist.annotations.ExperimentalHorologistApi
import com.google.android.horologist.compose.layout.ScalingLazyColumn
import com.google.android.horologist.compose.layout.ScalingLazyColumnState
@@ -142,7 +143,16 @@ fun MultiCredentialsFoldScreen(
)
}
}
- item { SignInOptionsChip(onSignInOptionsClicked) }
+
+ state.authenticationEntryList.forEach { authenticationEntryInfo ->
+ item {
+ LockedProviderChip(authenticationEntryInfo) {
+ // TODO(b/322797032) invoke LockedProviderScreen here using flow engine
+ }
+ }
+ }
+
+ item { SignInOptionsChip(onSignInOptionsClicked)}
item { DismissChip(onCancelClicked) }
}
}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt
index 4c7f583fcb09..1f1a296dca9b 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt
@@ -18,22 +18,19 @@
package com.android.credentialmanager.ui.screens.single.password
+import android.util.Log
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.SideEffect
-import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
-import androidx.hilt.navigation.compose.hiltViewModel
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import androidx.navigation.NavHostController
-import androidx.navigation.compose.rememberNavController
-import com.android.credentialmanager.CredentialSelectorUiState
+import com.android.credentialmanager.FlowEngine
import com.android.credentialmanager.R
+import com.android.credentialmanager.TAG
import com.android.credentialmanager.activity.StartBalIntentSenderForResultContract
+import com.android.credentialmanager.ktx.getIntentSenderRequest
import com.android.credentialmanager.ui.components.PasswordRow
import com.android.credentialmanager.ui.components.ContinueChip
import com.android.credentialmanager.ui.components.DismissChip
@@ -41,71 +38,30 @@ import com.android.credentialmanager.ui.components.SignInHeader
import com.android.credentialmanager.ui.components.SignInOptionsChip
import com.android.credentialmanager.ui.screens.single.SingleAccountScreen
import com.android.credentialmanager.model.get.CredentialEntryInfo
-import com.android.credentialmanager.ui.screens.single.UiState
import com.google.android.horologist.annotations.ExperimentalHorologistApi
import com.google.android.horologist.compose.layout.ScalingLazyColumnState
/**
* Screen that shows sign in with provider credential.
*
- * @param credentialSelectorUiState The app bar view model.
+ * @param entry The password entry.
* @param columnState ScalingLazyColumn configuration to be be applied to SingleAccountScreen
* @param modifier styling for composable
- * @param viewModel ViewModel that updates ui state for this screen
- * @param navController handles navigation events from this screen
+ * @param flowEngine [FlowEngine] that updates ui state for this screen
*/
@OptIn(ExperimentalHorologistApi::class)
@Composable
fun SinglePasswordScreen(
- credentialSelectorUiState: CredentialSelectorUiState.Get.SingleEntry,
- columnState: ScalingLazyColumnState,
- modifier: Modifier = Modifier,
- viewModel: SinglePasswordScreenViewModel = hiltViewModel(),
- navController: NavHostController = rememberNavController(),
-) {
- viewModel.initialize(credentialSelectorUiState.entry)
-
- val uiState by viewModel.uiState.collectAsStateWithLifecycle()
-
- when (val state = uiState) {
- UiState.CredentialScreen -> {
- SinglePasswordScreen(
- credentialSelectorUiState.entry,
- columnState,
- modifier,
- viewModel
- )
- }
-
- is UiState.CredentialSelected -> {
- val launcher = rememberLauncherForActivityResult(
- StartBalIntentSenderForResultContract()
- ) {
- viewModel.onPasswordInfoRetrieved(it.resultCode, null)
- }
-
- SideEffect {
- state.intentSenderRequest?.let {
- launcher.launch(it)
- }
- }
- }
-
- UiState.Cancel -> {
- // TODO(b/322797032) add valid navigation path here for going back
- navController.popBackStack()
- }
- }
-}
-
-@OptIn(ExperimentalHorologistApi::class)
-@Composable
-private fun SinglePasswordScreen(
entry: CredentialEntryInfo,
columnState: ScalingLazyColumnState,
modifier: Modifier = Modifier,
- viewModel: SinglePasswordScreenViewModel,
+ flowEngine: FlowEngine,
) {
+ val launcher = rememberLauncherForActivityResult(
+ StartBalIntentSenderForResultContract()
+ ) {
+ flowEngine.sendSelectionResult(entry, it.resultCode, it.data)
+ }
SingleAccountScreen(
headerContent = {
SignInHeader(
@@ -124,9 +80,13 @@ private fun SinglePasswordScreen(
) {
item {
Column {
- ContinueChip(viewModel::onContinueClick)
- SignInOptionsChip(viewModel::onSignInOptionsClick)
- DismissChip(viewModel::onDismissClick)
+ ContinueChip {
+ entry.getIntentSenderRequest()?.let {
+ launcher.launch(it)
+ } ?: Log.w(TAG, "Cannot parse IntentSenderRequest")
+ }
+ SignInOptionsChip{ flowEngine.openSecondaryScreen() }
+ DismissChip { flowEngine.cancel() }
}
}
}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreenViewModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreenViewModel.kt
deleted file mode 100644
index 8debecbac599..000000000000
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreenViewModel.kt
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * 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.0N
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.credentialmanager.ui.screens.single.password
-
-import android.content.Intent
-import android.credentials.selection.UserSelectionDialogResult
-import android.credentials.selection.ProviderPendingIntentResponse
-import androidx.annotation.MainThread
-import androidx.lifecycle.ViewModel
-import com.android.credentialmanager.ktx.getIntentSenderRequest
-import com.android.credentialmanager.model.Request
-import com.android.credentialmanager.client.CredentialManagerClient
-import com.android.credentialmanager.model.get.CredentialEntryInfo
-import com.android.credentialmanager.ui.screens.single.UiState
-import dagger.hilt.android.lifecycle.HiltViewModel
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import javax.inject.Inject
-
-@HiltViewModel
-class SinglePasswordScreenViewModel @Inject constructor(
- private val credentialManagerClient: CredentialManagerClient,
-) : ViewModel() {
-
- private lateinit var requestGet: Request.Get
- private lateinit var entryInfo: CredentialEntryInfo
-
- private val _uiState =
- MutableStateFlow<UiState>(UiState.CredentialScreen)
- val uiState: StateFlow<UiState> = _uiState
-
- @MainThread
- fun initialize(entryInfo: CredentialEntryInfo) {
- this.entryInfo = entryInfo
- }
-
- fun onDismissClick() {
- _uiState.value = UiState.Cancel
- }
-
- fun onContinueClick() {
- _uiState.value = UiState.CredentialSelected(
- intentSenderRequest = entryInfo.getIntentSenderRequest()
- )
- }
-
- fun onSignInOptionsClick() {
- }
-
- fun onPasswordInfoRetrieved(
- resultCode: Int? = null,
- resultData: Intent? = null,
- ) {
- val userSelectionDialogResult = UserSelectionDialogResult(
- requestGet.token,
- entryInfo.providerId,
- entryInfo.entryKey,
- entryInfo.entrySubkey,
- if (resultCode != null) ProviderPendingIntentResponse(resultCode, resultData) else null
- )
- credentialManagerClient.sendResult(userSelectionDialogResult)
- }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java
index cf2f85ed5356..634e067a12d4 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java
@@ -20,6 +20,7 @@ import static android.content.res.AssetFileDescriptor.UNKNOWN_LENGTH;
import static com.android.packageinstaller.PackageInstallerActivity.EXTRA_STAGED_SESSION_ID;
+import android.Manifest;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
@@ -27,10 +28,10 @@ import android.app.DialogFragment;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
+import android.content.pm.Flags;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.res.AssetFileDescriptor;
-import android.Manifest;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
@@ -200,7 +201,7 @@ public class InstallStaging extends Activity {
params.setPermissionState(Manifest.permission.USE_FULL_SCREEN_INTENT,
PackageInstaller.SessionParams.PERMISSION_STATE_DENIED);
- if (pfd != null) {
+ if (pfd != null && Flags.readInstallInfo()) {
try {
final PackageInstaller.InstallInfo result = installer.readInstallInfo(pfd,
debugPathName, 0);
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
index 7240fb97611a..904e1843dd44 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
@@ -31,6 +31,7 @@ import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
+import android.content.pm.Flags;
import android.content.pm.InstallSourceInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
@@ -397,7 +398,10 @@ public class PackageInstallerActivity extends Activity {
final int sessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID,
-1 /* defaultValue */);
final SessionInfo info = mInstaller.getSessionInfo(sessionId);
- String resolvedPath = info != null ? info.getResolvedBaseApkPath() : null;
+ String resolvedPath = null;
+ if (info != null && Flags.getResolvedApkPath()) {
+ resolvedPath = info.getResolvedBaseApkPath();
+ }
if (info == null || !info.isSealed() || resolvedPath == null) {
Log.w(TAG, "Session " + mSessionId + " in funky state; ignoring");
finish();
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
index aeabbd53d177..22caabdabbf0 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
@@ -25,6 +25,7 @@ import android.content.ContentResolver
import android.content.Context
import android.content.Intent
import android.content.pm.ApplicationInfo
+import android.content.pm.Flags
import android.content.pm.PackageInfo
import android.content.pm.PackageInstaller
import android.content.pm.PackageInstaller.SessionInfo
@@ -362,7 +363,7 @@ class InstallRepository(private val context: Context) {
params.setPermissionState(
Manifest.permission.USE_FULL_SCREEN_INTENT, SessionParams.PERMISSION_STATE_DENIED
)
- if (pfd != null) {
+ if (pfd != null && Flags.readInstallInfo()) {
try {
val installInfo = packageInstaller.readInstallInfo(pfd, debugPathName, 0)
params.setAppPackageName(installInfo.packageName)
@@ -425,7 +426,8 @@ class InstallRepository(private val context: Context) {
if (PackageInstaller.ACTION_CONFIRM_INSTALL == intent.action) {
val info = packageInstaller.getSessionInfo(sessionId)
- val resolvedPath = info?.resolvedBaseApkPath
+ val resolvedPath =
+ if (Flags.getResolvedApkPath()) info?.resolvedBaseApkPath else null
if (info == null || !info.isSealed || resolvedPath == null) {
Log.w(LOG_TAG, "Session $sessionId in funky state; ignoring")
return InstallAborted(ABORT_REASON_INTERNAL_ERROR)
diff --git a/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java b/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java
index 5b39f4ee1541..18e8fc38ddb0 100644
--- a/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java
+++ b/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java
@@ -219,7 +219,6 @@ public class RestrictedLockUtils {
}
}
-
/**
* Shows restricted setting dialog.
*
diff --git a/packages/SettingsLib/Spa/OWNERS b/packages/SettingsLib/Spa/OWNERS
index 464328e816f2..67386d170a0d 100644
--- a/packages/SettingsLib/Spa/OWNERS
+++ b/packages/SettingsLib/Spa/OWNERS
@@ -1,4 +1,4 @@
-set noparent
+include platform/frameworks/base:/packages/SettingsLib/OWNERS
chaohuiw@google.com
hanxu@google.com
diff --git a/packages/SettingsLib/Spa/build.gradle.kts b/packages/SettingsLib/Spa/build.gradle.kts
index ec519ca61021..463e9be391b6 100644
--- a/packages/SettingsLib/Spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/build.gradle.kts
@@ -29,7 +29,7 @@ val androidTop: String = File(rootDir, "../../../../..").canonicalPath
allprojects {
extra["androidTop"] = androidTop
- extra["jetpackComposeVersion"] = "1.7.0-alpha01"
+ extra["jetpackComposeVersion"] = "1.7.0-alpha02"
}
subprojects {
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
index 460a6f7261ae..e1853675d6d4 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
@@ -28,7 +28,7 @@ import com.android.settingslib.spa.gallery.dialog.DialogMainPageProvider
import com.android.settingslib.spa.gallery.dialog.NavDialogProvider
import com.android.settingslib.spa.gallery.editor.EditorMainPageProvider
import com.android.settingslib.spa.gallery.editor.SettingsExposedDropdownMenuBoxPageProvider
-import com.android.settingslib.spa.gallery.editor.SettingsExposedDropdownMenuCheckBoxProvider
+import com.android.settingslib.spa.gallery.editor.SettingsDropdownCheckBoxProvider
import com.android.settingslib.spa.gallery.home.HomePageProvider
import com.android.settingslib.spa.gallery.itemList.ItemListPageProvider
import com.android.settingslib.spa.gallery.itemList.ItemOperatePageProvider
@@ -100,7 +100,7 @@ class GallerySpaEnvironment(context: Context) : SpaEnvironment(context) {
EditorMainPageProvider,
SettingsOutlinedTextFieldPageProvider,
SettingsExposedDropdownMenuBoxPageProvider,
- SettingsExposedDropdownMenuCheckBoxProvider,
+ SettingsDropdownCheckBoxProvider,
SettingsTextFieldPasswordPageProvider,
SearchScaffoldPageProvider,
SuwScaffoldPageProvider,
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt
index d5cf1a35b4df..79c5ebbce5fa 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt
@@ -88,11 +88,13 @@ object CardPageProvider : SettingsPageProvider {
@Composable
private fun SettingsCardWithoutIcon() {
+ val sampleTitle = stringResource(R.string.sample_title)
+ var title by remember { mutableStateOf(sampleTitle) }
SettingsCard(
CardModel(
- title = stringResource(R.string.sample_title),
+ title = title,
text = stringResource(R.string.sample_text),
- )
+ ) { title = "Clicked" }
)
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/EditorMainPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/EditorMainPageProvider.kt
index 4875ea99d4d6..9f2158a13f25 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/EditorMainPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/EditorMainPageProvider.kt
@@ -37,7 +37,7 @@ object EditorMainPageProvider : SettingsPageProvider {
.build(),
SettingsExposedDropdownMenuBoxPageProvider.buildInjectEntry().setLink(fromPage = owner)
.build(),
- SettingsExposedDropdownMenuCheckBoxProvider.buildInjectEntry().setLink(fromPage = owner)
+ SettingsDropdownCheckBoxProvider.buildInjectEntry().setLink(fromPage = owner)
.build(),
SettingsTextFieldPasswordPageProvider.buildInjectEntry().setLink(fromPage = owner)
.build(),
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/SettingsDropdownCheckBoxProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/SettingsDropdownCheckBoxProvider.kt
new file mode 100644
index 000000000000..33ab75d6189d
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/SettingsDropdownCheckBoxProvider.kt
@@ -0,0 +1,134 @@
+/*
+ * 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.spa.gallery.editor
+
+import android.os.Bundle
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.tooling.preview.Preview
+import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.widget.editor.SettingsDropdownCheckBox
+import com.android.settingslib.spa.widget.editor.SettingsDropdownCheckOption
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.scaffold.RegularScaffold
+
+private const val TITLE = "Sample SettingsDropdownCheckBox"
+
+object SettingsDropdownCheckBoxProvider : SettingsPageProvider {
+ override val name = "SettingsDropdownCheckBox"
+
+ override fun getTitle(arguments: Bundle?): String {
+ return TITLE
+ }
+
+ @Composable
+ override fun Page(arguments: Bundle?) {
+ RegularScaffold(title = TITLE) {
+ SettingsDropdownCheckBox(
+ label = "SettingsDropdownCheckBox",
+ options = remember {
+ listOf(
+ SettingsDropdownCheckOption("Item 1"),
+ SettingsDropdownCheckOption("Item 2"),
+ SettingsDropdownCheckOption("Item 3"),
+ )
+ },
+ )
+ SettingsDropdownCheckBox(
+ label = "Empty list",
+ options = emptyList(),
+ )
+ SettingsDropdownCheckBox(
+ label = "Disabled",
+ options = remember {
+ listOf(
+ SettingsDropdownCheckOption("Item 1", selected = mutableStateOf(true)),
+ SettingsDropdownCheckOption("Item 2"),
+ SettingsDropdownCheckOption("Item 3"),
+ )
+ },
+ enabled = false,
+ )
+ SettingsDropdownCheckBox(
+ label = "With disabled item",
+ options = remember {
+ listOf(
+ SettingsDropdownCheckOption("Item 1"),
+ SettingsDropdownCheckOption("Item 2"),
+ SettingsDropdownCheckOption(
+ text = "Disabled item 1",
+ changeable = false,
+ selected = mutableStateOf(true),
+ ),
+ SettingsDropdownCheckOption("Disabled item 2", changeable = false),
+ )
+ },
+ )
+ SettingsDropdownCheckBox(
+ label = "With select all",
+ options = remember {
+ listOf(
+ SettingsDropdownCheckOption("All", isSelectAll = true),
+ SettingsDropdownCheckOption("Item 1"),
+ SettingsDropdownCheckOption("Item 2"),
+ SettingsDropdownCheckOption("Item 3"),
+ )
+ },
+ )
+ SettingsDropdownCheckBox(
+ label = "With disabled item and select all",
+ options =
+ remember {
+ listOf(
+ SettingsDropdownCheckOption("All", isSelectAll = true, changeable = false),
+ SettingsDropdownCheckOption("Item 1"),
+ SettingsDropdownCheckOption("Item 2"),
+ SettingsDropdownCheckOption(
+ text = "Disabled item 1",
+ changeable = false,
+ selected = mutableStateOf(true),
+ ),
+ SettingsDropdownCheckOption("Disabled item 2", changeable = false),
+ )
+ },
+ )
+ }
+ }
+
+ fun buildInjectEntry(): SettingsEntryBuilder {
+ return SettingsEntryBuilder.createInject(owner = createSettingsPage()).setUiLayoutFn {
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ })
+ }
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+private fun SettingsDropdownCheckBoxPagePreview() {
+ SettingsTheme {
+ SettingsDropdownCheckBoxProvider.Page(null)
+ }
+}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/SettingsExposedDropdownMenuCheckBoxProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/SettingsExposedDropdownMenuCheckBoxProvider.kt
deleted file mode 100644
index d28964676bdd..000000000000
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/SettingsExposedDropdownMenuCheckBoxProvider.kt
+++ /dev/null
@@ -1,75 +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.
- */
-
-package com.android.settingslib.spa.gallery.editor
-
-import android.os.Bundle
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.mutableStateListOf
-import androidx.compose.runtime.remember
-import androidx.compose.ui.tooling.preview.Preview
-import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
-import com.android.settingslib.spa.framework.common.SettingsPageProvider
-import com.android.settingslib.spa.framework.common.createSettingsPage
-import com.android.settingslib.spa.framework.compose.navigator
-import com.android.settingslib.spa.framework.theme.SettingsTheme
-import com.android.settingslib.spa.widget.editor.SettingsExposedDropdownMenuCheckBox
-import com.android.settingslib.spa.widget.preference.Preference
-import com.android.settingslib.spa.widget.preference.PreferenceModel
-import com.android.settingslib.spa.widget.scaffold.RegularScaffold
-
-private const val TITLE = "Sample SettingsExposedDropdownMenuCheckBox"
-
-object SettingsExposedDropdownMenuCheckBoxProvider : SettingsPageProvider {
- override val name = "SettingsExposedDropdownMenuCheckBox"
- private const val exposedDropdownMenuCheckBoxLabel = "ExposedDropdownMenuCheckBoxLabel"
- private val options = listOf("item1", "item2", "item3")
- private val selectedOptionsState1 = mutableStateListOf(0, 1)
-
- override fun getTitle(arguments: Bundle?): String {
- return TITLE
- }
-
- @Composable
- override fun Page(arguments: Bundle?) {
- RegularScaffold(title = TITLE) {
- SettingsExposedDropdownMenuCheckBox(
- label = exposedDropdownMenuCheckBoxLabel,
- options = options,
- selectedOptionsState = remember { selectedOptionsState1 },
- enabled = true,
- onSelectedOptionStateChange = {},
- )
- }
- }
-
- fun buildInjectEntry(): SettingsEntryBuilder {
- return SettingsEntryBuilder.createInject(owner = createSettingsPage()).setUiLayoutFn {
- Preference(object : PreferenceModel {
- override val title = TITLE
- override val onClick = navigator(name)
- })
- }
- }
-}
-
-@Preview(showBackground = true)
-@Composable
-private fun SettingsExposedDropdownMenuCheckBoxPagePreview() {
- SettingsTheme {
- SettingsExposedDropdownMenuCheckBoxProvider.Page(null)
- }
-} \ No newline at end of file
diff --git a/packages/SettingsLib/Spa/gradle/libs.versions.toml b/packages/SettingsLib/Spa/gradle/libs.versions.toml
index f6fbc02de3b0..fe378c27523c 100644
--- a/packages/SettingsLib/Spa/gradle/libs.versions.toml
+++ b/packages/SettingsLib/Spa/gradle/libs.versions.toml
@@ -16,7 +16,7 @@
[versions]
agp = "8.2.2"
-compose-compiler = "1.5.8"
+compose-compiler = "1.5.9"
dexmaker-mockito = "2.28.3"
jvm = "17"
kotlin = "1.9.22"
diff --git a/packages/SettingsLib/Spa/spa/build.gradle.kts b/packages/SettingsLib/Spa/spa/build.gradle.kts
index 08a87973468b..2259bd74d56e 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/spa/build.gradle.kts
@@ -57,13 +57,13 @@ dependencies {
api("androidx.slice:slice-builders:1.1.0-alpha02")
api("androidx.slice:slice-core:1.1.0-alpha02")
api("androidx.slice:slice-view:1.1.0-alpha02")
- api("androidx.compose.material3:material3:1.2.0-rc01")
+ api("androidx.compose.material3:material3:1.2.0")
api("androidx.compose.material:material-icons-extended:$jetpackComposeVersion")
api("androidx.compose.runtime:runtime-livedata:$jetpackComposeVersion")
api("androidx.compose.ui:ui-tooling-preview:$jetpackComposeVersion")
api("androidx.lifecycle:lifecycle-livedata-ktx")
api("androidx.lifecycle:lifecycle-runtime-compose")
- api("androidx.navigation:navigation-compose:2.8.0-alpha01")
+ api("androidx.navigation:navigation-compose:2.8.0-alpha02")
api("com.github.PhilJay:MPAndroidChart:v3.1.0-alpha")
api("com.google.android.material:material:1.7.0-alpha03")
debugApi("androidx.compose.ui:ui-tooling:$jetpackComposeVersion")
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/CardModel.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/CardModel.kt
index 960ebccf6c25..8100fd58f5a0 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/CardModel.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/CardModel.kt
@@ -45,4 +45,6 @@ data class CardModel(
/** If specified, this color will be used to tint the icon and the buttons. */
val containerColor: Color = Color.Unspecified,
+
+ val onClick: (() -> Unit)? = null,
)
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt
index 700fa487ed73..621825a82c1e 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt
@@ -17,6 +17,7 @@
package com.android.settingslib.spa.widget.card
import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
@@ -102,10 +103,11 @@ internal fun SettingsCardImpl(model: CardModel) {
AnimatedVisibility(visible = model.isVisible()) {
SettingsCardContent(containerColor = model.containerColor) {
Column(
- modifier = Modifier.padding(
- horizontal = SettingsDimension.dialogItemPaddingHorizontal,
- vertical = SettingsDimension.itemPaddingAround,
- ),
+ modifier = (model.onClick?.let { Modifier.clickable(onClick = it) } ?: Modifier)
+ .padding(
+ horizontal = SettingsDimension.dialogItemPaddingHorizontal,
+ vertical = SettingsDimension.itemPaddingAround,
+ ),
verticalArrangement = Arrangement.spacedBy(SettingsDimension.itemPaddingAround)
) {
CardHeader(model.imageVector, model.tintColor, model.onDismiss)
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsDropdownCheckBox.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsDropdownCheckBox.kt
new file mode 100644
index 000000000000..57963e6eaa40
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsDropdownCheckBox.kt
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.editor
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.material3.Checkbox
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.ExposedDropdownMenuBox
+import androidx.compose.material3.ExposedDropdownMenuDefaults
+import androidx.compose.material3.OutlinedTextField
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.onSizeChanged
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.framework.theme.SettingsOpacity.alphaForEnabled
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.widget.editor.SettingsDropdownCheckOption.Companion.changeable
+
+data class SettingsDropdownCheckOption(
+ /** The displayed text of this option. */
+ val text: String,
+
+ /** If true, check / uncheck this item will check / uncheck all enabled options. */
+ val isSelectAll: Boolean = false,
+
+ /** If not changeable, cannot check or uncheck this option. */
+ val changeable: Boolean = true,
+
+ /** The selected state of this option. */
+ val selected: MutableState<Boolean> = mutableStateOf(false),
+
+ /** Get called when the option is clicked, no matter if it's changeable. */
+ val onClick: () -> Unit = {},
+) {
+ companion object {
+ val List<SettingsDropdownCheckOption>.changeable: Boolean
+ get() = filter { !it.isSelectAll }.any { it.changeable }
+ }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun SettingsDropdownCheckBox(
+ label: String,
+ options: List<SettingsDropdownCheckOption>,
+ emptyText: String = "",
+ enabled: Boolean = true,
+ errorMessage: String? = null,
+ onSelectedStateChange: () -> Unit = {},
+) {
+ var dropDownWidth by remember { mutableIntStateOf(0) }
+ var expanded by remember { mutableStateOf(false) }
+ val changeable = enabled && options.changeable
+ ExposedDropdownMenuBox(
+ expanded = expanded,
+ onExpandedChange = { expanded = changeable && it },
+ modifier = Modifier
+ .width(350.dp)
+ .padding(SettingsDimension.textFieldPadding)
+ .onSizeChanged { dropDownWidth = it.width },
+ ) {
+ OutlinedTextField(
+ // The `menuAnchor` modifier must be passed to the text field for correctness.
+ modifier = Modifier
+ .menuAnchor()
+ .fillMaxWidth(),
+ value = getDisplayText(options) ?: emptyText,
+ onValueChange = {},
+ label = { Text(text = label) },
+ trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded) },
+ readOnly = true,
+ enabled = changeable,
+ isError = errorMessage != null,
+ supportingText = errorMessage?.let { { Text(text = it) } },
+ )
+ ExposedDropdownMenu(
+ expanded = expanded,
+ modifier = Modifier.width(with(LocalDensity.current) { dropDownWidth.toDp() }),
+ onDismissRequest = { expanded = false },
+ ) {
+ for (option in options) {
+ CheckboxItem(option) {
+ option.onClick()
+ if (option.changeable) {
+ checkboxItemOnClick(options, option)
+ onSelectedStateChange()
+ }
+ }
+ }
+ }
+ }
+}
+
+private fun getDisplayText(options: List<SettingsDropdownCheckOption>): String? {
+ val selectedOptions = options.filter { it.selected.value }
+ if (selectedOptions.isEmpty()) return null
+ return selectedOptions.filter { it.isSelectAll }.ifEmpty { selectedOptions }
+ .joinToString { it.text }
+}
+
+private fun checkboxItemOnClick(
+ options: List<SettingsDropdownCheckOption>,
+ clickedOption: SettingsDropdownCheckOption,
+) {
+ if (!clickedOption.changeable) return
+ val newChecked = !clickedOption.selected.value
+ if (clickedOption.isSelectAll) {
+ for (option in options.filter { it.changeable }) option.selected.value = newChecked
+ } else {
+ clickedOption.selected.value = newChecked
+ }
+ val (selectAllOptions, regularOptions) = options.partition { it.isSelectAll }
+ val isAllRegularOptionsChecked = regularOptions.all { it.selected.value }
+ selectAllOptions.forEach { it.selected.value = isAllRegularOptionsChecked }
+}
+
+@Composable
+private fun CheckboxItem(
+ option: SettingsDropdownCheckOption,
+ onClick: (SettingsDropdownCheckOption) -> Unit,
+) {
+ TextButton(
+ onClick = { onClick(option) },
+ modifier = Modifier.fillMaxWidth(),
+ ) {
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.spacedBy(SettingsDimension.itemPaddingAround),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Checkbox(
+ checked = option.selected.value,
+ onCheckedChange = null,
+ enabled = option.changeable,
+ )
+ Text(text = option.text, modifier = Modifier.alphaForEnabled(option.changeable))
+ }
+ }
+}
+
+@Preview
+@Composable
+private fun ActionButtonsPreview() {
+ val item1 = SettingsDropdownCheckOption("item1")
+ val item2 = SettingsDropdownCheckOption("item2")
+ val item3 = SettingsDropdownCheckOption("item3")
+ val options = listOf(item1, item2, item3)
+ SettingsTheme {
+ SettingsDropdownCheckBox(
+ label = "label",
+ options = options,
+ )
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBox.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBox.kt
deleted file mode 100644
index e704505117cf..000000000000
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBox.kt
+++ /dev/null
@@ -1,164 +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.
- */
-
-package com.android.settingslib.spa.widget.editor
-
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.width
-import androidx.compose.material3.Checkbox
-import androidx.compose.material3.ExperimentalMaterial3Api
-import androidx.compose.material3.ExposedDropdownMenuBox
-import androidx.compose.material3.ExposedDropdownMenuDefaults
-import androidx.compose.material3.OutlinedTextField
-import androidx.compose.material3.Text
-import androidx.compose.material3.TextButton
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableIntStateOf
-import androidx.compose.runtime.mutableStateListOf
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.runtime.snapshots.SnapshotStateList
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.layout.onSizeChanged
-import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.tooling.preview.Preview
-import androidx.compose.ui.unit.dp
-import com.android.settingslib.spa.framework.theme.SettingsDimension
-import com.android.settingslib.spa.framework.theme.SettingsTheme
-
-@OptIn(ExperimentalMaterial3Api::class)
-@Composable
-fun SettingsExposedDropdownMenuCheckBox(
- label: String,
- options: List<String>,
- selectedOptionsState: SnapshotStateList<Int>,
- emptyVal: String = "",
- enabled: Boolean,
- errorMessage: String? = null,
- onSelectedOptionStateChange: () -> Unit,
-) {
- var dropDownWidth by remember { mutableIntStateOf(0) }
- var expanded by remember { mutableStateOf(false) }
- val allIndex = options.indexOf("*")
- ExposedDropdownMenuBox(
- expanded = expanded,
- onExpandedChange = { expanded = it },
- modifier = Modifier
- .width(350.dp)
- .padding(SettingsDimension.textFieldPadding)
- .onSizeChanged { dropDownWidth = it.width },
- ) {
- OutlinedTextField(
- // The `menuAnchor` modifier must be passed to the text field for correctness.
- modifier = Modifier
- .menuAnchor()
- .fillMaxWidth(),
- value = if (selectedOptionsState.size == 0) emptyVal
- else if (selectedOptionsState.contains(allIndex)) "*"
- else selectedOptionsState.joinToString { options[it] },
- onValueChange = {},
- label = { Text(text = label) },
- trailingIcon = {
- ExposedDropdownMenuDefaults.TrailingIcon(
- expanded = expanded
- )
- },
- readOnly = true,
- enabled = enabled,
- isError = errorMessage != null,
- supportingText = {
- if (errorMessage != null) {
- Text(text = errorMessage)
- }
- }
- )
- if (options.isNotEmpty()) {
- ExposedDropdownMenu(
- expanded = expanded,
- modifier = Modifier.width(with(LocalDensity.current) { dropDownWidth.toDp() }),
- onDismissRequest = { expanded = false },
- ) {
- options.forEachIndexed { index, option ->
- CheckboxItem(
- selectedOptionsState,
- index,
- allIndex,
- onSelectedOptionStateChange,
- option,
- )
- }
- }
- }
- }
-}
-
-@Composable
-private fun CheckboxItem(
- selectedOptionsState: SnapshotStateList<Int>,
- index: Int,
- allIndex: Int,
- onSelectedOptionStateChange: () -> Unit,
- option: String
-) {
- TextButton(
- modifier = Modifier.fillMaxWidth(),
- onClick = {
- if (selectedOptionsState.contains(index)) {
- if (index == allIndex) {
- selectedOptionsState.clear()
- } else {
- selectedOptionsState.remove(index)
- selectedOptionsState.remove(allIndex)
- }
- } else {
- selectedOptionsState.add(index)
- }
- onSelectedOptionStateChange()
- }) {
- Row(
- modifier = Modifier.fillMaxWidth(),
- horizontalArrangement = Arrangement.spacedBy(SettingsDimension.itemPaddingAround),
- verticalAlignment = Alignment.CenterVertically
- ) {
- Checkbox(
- checked = selectedOptionsState.contains(index),
- onCheckedChange = null,
- )
- Text(text = option)
- }
- }
-}
-
-@Preview
-@Composable
-private fun ActionButtonsPreview() {
- val options = listOf("item1", "item2", "item3")
- val selectedOptionsState = remember { mutableStateListOf(0, 1) }
- SettingsTheme {
- SettingsExposedDropdownMenuCheckBox(
- label = "label",
- options = options,
- selectedOptionsState = selectedOptionsState,
- enabled = true,
- onSelectedOptionStateChange = {})
- }
-}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsOutlinedTextField.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsOutlinedTextField.kt
index 2ce3c66796db..bdc6a6890cbd 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsOutlinedTextField.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsOutlinedTextField.kt
@@ -19,6 +19,7 @@ package com.android.settingslib.spa.widget.editor
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.OutlinedTextField
+import androidx.compose.material3.OutlinedTextFieldDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
@@ -26,6 +27,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.tooling.preview.Preview
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.framework.theme.SettingsTheme
@@ -37,7 +39,8 @@ fun SettingsOutlinedTextField(
errorMessage: String? = null,
singleLine: Boolean = true,
enabled: Boolean = true,
- onTextChange: (String) -> Unit,
+ shape: Shape = OutlinedTextFieldDefaults.shape,
+ onTextChange: (String) -> Unit
) {
OutlinedTextField(
modifier = Modifier
@@ -55,7 +58,8 @@ fun SettingsOutlinedTextField(
if (errorMessage != null) {
Text(text = errorMessage)
}
- }
+ },
+ shape = shape
)
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ListPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ListPreference.kt
index a0149da8f4b8..1a04bb8351db 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ListPreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ListPreference.kt
@@ -37,11 +37,13 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.semantics.Role
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.widget.dialog.SettingsDialog
+import com.android.settingslib.spa.widget.ui.SettingsBody
import com.android.settingslib.spa.widget.ui.SettingsDialogItem
data class ListPreferenceOption(
val id: Int,
val text: String,
+ val summary: String = String()
)
/**
@@ -129,6 +131,14 @@ private fun Radio(
) {
RadioButton(selected = selected, onClick = null, enabled = enabled)
Spacer(modifier = Modifier.width(SettingsDimension.itemPaddingEnd))
- SettingsDialogItem(text = option.text, enabled = enabled)
+ Column {
+ SettingsDialogItem(text = option.text, enabled = enabled)
+ if (option.summary != String()) {
+ SettingsBody(
+ body = option.summary,
+ maxLines = 1
+ )
+ }
+ }
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetPreference.kt
index e36572fdff65..3216e37b5db4 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetPreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetPreference.kt
@@ -24,7 +24,6 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
@@ -46,14 +45,14 @@ internal fun TwoTargetPreference(
verticalAlignment = Alignment.CenterVertically,
) {
Box(modifier = Modifier.weight(1f)) {
- Preference(remember {
+ Preference(
object : PreferenceModel {
override val title = title
override val summary = summary
override val icon = icon
override val onClick = onClick
}
- })
+ )
}
PreferenceDivider()
widget()
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SettingsCardTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SettingsCardTest.kt
index b5b2525bffdd..ffc7e86665dd 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SettingsCardTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SettingsCardTest.kt
@@ -141,6 +141,23 @@ class SettingsCardTest {
composeTestRule.onNodeWithText(TEXT).isNotDisplayed()
}
+ @Test
+ fun settingsCard_clickable() {
+ var clicked by mutableStateOf(false)
+ composeTestRule.setContent {
+ SettingsCard(
+ CardModel(
+ title = TITLE,
+ text = "",
+ ) { clicked = true }
+ )
+ }
+
+ composeTestRule.onNodeWithText(TITLE).performClick()
+
+ assertThat(clicked).isTrue()
+ }
+
private companion object {
const val TITLE = "Title"
const val TEXT = "Text"
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/editor/SettingsDropdownCheckBoxTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/editor/SettingsDropdownCheckBoxTest.kt
new file mode 100644
index 000000000000..72b7b98c2a94
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/editor/SettingsDropdownCheckBoxTest.kt
@@ -0,0 +1,145 @@
+/*
+ * 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.spa.widget.editor
+
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.hasAnyAncestor
+import androidx.compose.ui.test.hasText
+import androidx.compose.ui.test.isPopup
+import androidx.compose.ui.test.junit4.ComposeContentTestRule
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.testutils.hasRole
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SettingsDropdownCheckBoxTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ @Test
+ fun dropdownCheckBox_displayed() {
+ val item1 = SettingsDropdownCheckOption("item1")
+ val item2 = SettingsDropdownCheckOption("item2")
+ val item3 = SettingsDropdownCheckOption("item3")
+ composeTestRule.setContent {
+ SettingsDropdownCheckBox(
+ label = LABEL,
+ options = listOf(item1, item2, item3),
+ )
+ }
+
+ composeTestRule.onNodeWithText(LABEL).assertIsDisplayed()
+ }
+
+ @Test
+ fun dropdownCheckBox_expanded() {
+ val item1 = SettingsDropdownCheckOption("item1")
+ val item2 = SettingsDropdownCheckOption("item2")
+ val item3 = SettingsDropdownCheckOption("item3")
+ composeTestRule.setContent {
+ SettingsDropdownCheckBox(
+ label = LABEL,
+ options = listOf(item1, item2, item3),
+ )
+ }
+ composeTestRule.onOption(item3).assertDoesNotExist()
+
+ composeTestRule.onNodeWithText(LABEL).performClick()
+
+ composeTestRule.onOption(item3).assertIsDisplayed()
+ }
+
+ @Test
+ fun dropdownCheckBox_valueAdded() {
+ val item1 = SettingsDropdownCheckOption("item1")
+ val item2 = SettingsDropdownCheckOption("item2")
+ val item3 = SettingsDropdownCheckOption("item3")
+ composeTestRule.setContent {
+ SettingsDropdownCheckBox(
+ label = LABEL,
+ options = listOf(item1, item2, item3),
+ )
+ }
+ composeTestRule.onDropdownBox(item3.text).assertDoesNotExist()
+
+ composeTestRule.onNodeWithText(LABEL).performClick()
+ composeTestRule.onOption(item3).performClick()
+
+ composeTestRule.onDropdownBox(item3.text).assertIsDisplayed()
+ assertThat(item3.selected.value).isTrue()
+ }
+
+ @Test
+ fun dropdownCheckBox_valueDeleted() {
+ val item1 = SettingsDropdownCheckOption("item1")
+ val item2 = SettingsDropdownCheckOption("item2", selected = mutableStateOf(true))
+ val item3 = SettingsDropdownCheckOption("item3")
+ composeTestRule.setContent {
+ SettingsDropdownCheckBox(
+ label = LABEL,
+ options = listOf(item1, item2, item3),
+ )
+ }
+ composeTestRule.onDropdownBox(item2.text).assertIsDisplayed()
+
+ composeTestRule.onNodeWithText(LABEL).performClick()
+ composeTestRule.onOption(item2).performClick()
+
+ composeTestRule.onDropdownBox(item2.text).assertDoesNotExist()
+ assertThat(item2.selected.value).isFalse()
+ }
+
+ @Test
+ fun dropdownCheckBox_withSelectAll() {
+ val selectAll = SettingsDropdownCheckOption("All", isSelectAll = true)
+ val item1 = SettingsDropdownCheckOption("item1")
+ val item2 = SettingsDropdownCheckOption("item2")
+ composeTestRule.setContent {
+ SettingsDropdownCheckBox(
+ label = LABEL,
+ options = listOf(selectAll, item1, item2),
+ )
+ }
+
+ composeTestRule.onNodeWithText(LABEL).performClick()
+ composeTestRule.onOption(selectAll).performClick()
+
+ composeTestRule.onDropdownBox(selectAll.text).assertIsDisplayed()
+ composeTestRule.onDropdownBox(item1.text).assertDoesNotExist()
+ composeTestRule.onDropdownBox(item2.text).assertDoesNotExist()
+ assertThat(item1.selected.value).isTrue()
+ assertThat(item2.selected.value).isTrue()
+ }
+
+ private companion object {
+ const val LABEL = "Label"
+ }
+}
+
+private fun ComposeContentTestRule.onDropdownBox(text: String) =
+ onNode(hasRole(Role.DropdownList) and hasText(text))
+
+private fun ComposeContentTestRule.onOption(option: SettingsDropdownCheckOption) =
+ onNode(hasAnyAncestor(isPopup()) and hasText(option.text))
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBoxTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBoxTest.kt
deleted file mode 100644
index 2b78ed7d6fa2..000000000000
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBoxTest.kt
+++ /dev/null
@@ -1,115 +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.
- */
-
-package com.android.settingslib.spa.widget.editor
-
-import androidx.compose.runtime.mutableStateListOf
-import androidx.compose.runtime.remember
-import androidx.compose.ui.test.SemanticsNodeInteraction
-import androidx.compose.ui.test.assertIsDisplayed
-import androidx.compose.ui.test.hasText
-import androidx.compose.ui.test.isFocused
-import androidx.compose.ui.test.junit4.ComposeContentTestRule
-import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.test.onNodeWithText
-import androidx.compose.ui.test.performClick
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(AndroidJUnit4::class)
-class SettingsExposedDropdownMenuCheckBoxTest {
- @get:Rule
- val composeTestRule = createComposeRule()
- private val item1 = "item1"
- private val item2 = "item2"
- private val item3 = "item3"
- private val options = listOf(item1, item2, item3)
- private val selectedOptionsState1 = mutableStateListOf(0, 1)
- private val exposedDropdownMenuCheckBoxLabel = "ExposedDropdownMenuCheckBoxLabel"
-
- @Test
- fun exposedDropdownMenuCheckBox_displayed() {
- composeTestRule.setContent {
- SettingsExposedDropdownMenuCheckBox(
- label = exposedDropdownMenuCheckBoxLabel,
- options = options,
- selectedOptionsState = remember { selectedOptionsState1 },
- enabled = true,
- ) {}
- }
- composeTestRule.onNodeWithText(
- exposedDropdownMenuCheckBoxLabel, substring = true
- ).assertIsDisplayed()
- }
-
- @Test
- fun exposedDropdownMenuCheckBox_expanded() {
- composeTestRule.setContent {
- SettingsExposedDropdownMenuCheckBox(
- label = exposedDropdownMenuCheckBoxLabel,
- options = options,
- selectedOptionsState = remember { selectedOptionsState1 },
- enabled = true,
- ) {}
- }
- composeTestRule.onNodeWithText(item3, substring = true).assertDoesNotExist()
- composeTestRule.onNodeWithText(exposedDropdownMenuCheckBoxLabel, substring = true)
- .performClick()
- composeTestRule.onNodeWithText(item3, substring = true).assertIsDisplayed()
- }
-
- @Test
- fun exposedDropdownMenuCheckBox_valueAdded() {
- composeTestRule.setContent {
- SettingsExposedDropdownMenuCheckBox(
- label = exposedDropdownMenuCheckBoxLabel,
- options = options,
- selectedOptionsState = remember { selectedOptionsState1 },
- enabled = true,
- ) {}
- }
- composeTestRule.onNodeWithText(item3, substring = true).assertDoesNotExist()
- composeTestRule.onNodeWithText(exposedDropdownMenuCheckBoxLabel, substring = true)
- .performClick()
- composeTestRule.onNodeWithText(item3, substring = true).performClick()
- composeTestRule.onFocusedText(item3).assertIsDisplayed()
- }
-
- @Test
- fun exposedDropdownMenuCheckBox_valueDeleted() {
- composeTestRule.setContent {
- SettingsExposedDropdownMenuCheckBox(
- label = exposedDropdownMenuCheckBoxLabel,
- options = options,
- selectedOptionsState = remember { selectedOptionsState1 },
- enabled = true,
- ) {}
- }
- composeTestRule.onNodeWithText(item2, substring = true).assertIsDisplayed()
- composeTestRule.onNodeWithText(exposedDropdownMenuCheckBoxLabel, substring = true)
- .performClick()
- composeTestRule.onNotFocusedText(item2).performClick()
- composeTestRule.onFocusedText(item2).assertDoesNotExist()
- }
-}
-
-fun ComposeContentTestRule.onFocusedText(text: String): SemanticsNodeInteraction =
- onNode(isFocused() and hasText(text, substring = true))
-
-fun ComposeContentTestRule.onNotFocusedText(text: String): SemanticsNodeInteraction =
- onNode(!isFocused() and hasText(text, substring = true)) \ No newline at end of file
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/ListPreferenceTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/ListPreferenceTest.kt
index 796ac4851cb5..417ce6ed830b 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/ListPreferenceTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/ListPreferenceTest.kt
@@ -123,6 +123,26 @@ class ListPreferenceTest {
}
@Test
+ fun click_optionsNotEmptyAndItemHasSummary_itemShowSummary() {
+ composeTestRule.setContent {
+ ListPreference(remember {
+ object : ListPreferenceModel {
+ override val title = TITLE
+ override val options =
+ listOf(ListPreferenceOption(id = 1, text = "A", summary = "A_Summary"))
+ override val selectedId = mutableIntStateOf(1)
+ override val onIdSelected: (Int) -> Unit = {}
+ }
+ })
+ }
+
+ composeTestRule.onNodeWithText(TITLE).performClick()
+
+ composeTestRule.onDialogText(TITLE).assertIsDisplayed()
+ composeTestRule.onNodeWithText("A_Summary").assertIsDisplayed()
+ }
+
+ @Test
fun select() {
val selectedId = mutableIntStateOf(1)
composeTestRule.setContent {
diff --git a/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/SemanticsMatcher.kt b/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/SemanticsMatcher.kt
new file mode 100644
index 000000000000..856bed603354
--- /dev/null
+++ b/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/SemanticsMatcher.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.testutils
+
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.semantics.SemanticsProperties
+import androidx.compose.ui.semantics.getOrNull
+import androidx.compose.ui.test.SemanticsMatcher
+
+fun hasRole(role: Role) = SemanticsMatcher("${SemanticsProperties.Role.name} has $role") {
+ it.config.getOrNull(SemanticsProperties.Role) == role
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
index 9432d5995151..6b1893c73b3f 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
@@ -31,7 +31,6 @@ import kotlinx.coroutines.flow.flowOn
data class EnhancedConfirmation(
val key: String,
- val uid: Int,
val packageName: String,
)
data class Restrictions(
@@ -91,7 +90,7 @@ internal class RestrictionsProviderImpl(
restrictions.enhancedConfirmation?.let { ec ->
RestrictedLockUtilsInternal
.checkIfRequiresEnhancedConfirmation(context, ec.key,
- ec.uid, ec.packageName)
+ ec.packageName)
?.let { intent -> return BlockedByEcmImpl(context = context, intent = intent) }
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
index 74b556ea106e..27e00c05c33f 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
@@ -159,7 +159,6 @@ internal fun <T : AppRecord> TogglePermissionAppListModel<T>.TogglePermissionApp
keys = switchRestrictionKeys,
enhancedConfirmation = enhancedConfirmationKey?.let { EnhancedConfirmation(
key = it,
- uid = checkNotNull(applicationInfo).uid,
packageName = packageName) })
RestrictedSwitchPreference(switchModel, restrictions, restrictionsProviderFactory)
InfoPageAdditionalContent(record, isAllowed)
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
index 4b474379c54b..2e8b76a03722 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
@@ -150,15 +150,13 @@ internal class TogglePermissionInternalAppListModel<T : AppRecord>(
@Composable
fun getSummary(record: T): () -> String {
- val restrictions = remember(record.app.userId,
- record.app.uid, record.app.packageName) {
+ val restrictions = remember(record.app.userId, record.app.packageName) {
Restrictions(
userId = record.app.userId,
keys = listModel.switchRestrictionKeys,
enhancedConfirmation = listModel.enhancedConfirmationKey?.let {
EnhancedConfirmation(
key = it,
- uid = record.app.uid,
packageName = record.app.packageName)
})
}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceTest.kt
index 00ba9b4d9836..b88d1c5760a6 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceTest.kt
@@ -32,6 +32,7 @@ import com.android.settingslib.spaprivileged.model.enterprise.BaseUserRestricted
import com.android.settingslib.spaprivileged.model.enterprise.NoRestricted
import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
import com.android.settingslib.spaprivileged.tests.testutils.FakeBlockedByAdmin
+import com.android.settingslib.spaprivileged.tests.testutils.FakeBlockedByEcm
import com.android.settingslib.spaprivileged.tests.testutils.FakeRestrictionsProvider
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
@@ -44,6 +45,7 @@ class RestrictedSwitchPreferenceTest {
val composeTestRule = createComposeRule()
private val fakeBlockedByAdmin = FakeBlockedByAdmin()
+ private val fakeBlockedByEcm = FakeBlockedByEcm()
private val fakeRestrictionsProvider = FakeRestrictionsProvider()
@@ -141,6 +143,29 @@ class RestrictedSwitchPreferenceTest {
assertThat(fakeBlockedByAdmin.sendShowAdminSupportDetailsIntentIsCalled).isTrue()
}
+ @Test
+ fun whenBlockedByEcm_disabled() {
+ val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+ fakeRestrictionsProvider.restrictedMode = fakeBlockedByEcm
+
+ setContent(restrictions)
+
+ composeTestRule.onNodeWithText(TITLE).assertIsDisplayed().assertIsEnabled()
+ composeTestRule.onNodeWithText(FakeBlockedByEcm.SUMMARY).assertIsDisplayed()
+ composeTestRule.onNode(isOn()).assertIsDisplayed()
+ }
+
+ @Test
+ fun whenBlockedByEcm_click() {
+ val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+ fakeRestrictionsProvider.restrictedMode = fakeBlockedByEcm
+
+ setContent(restrictions)
+ composeTestRule.onRoot().performClick()
+
+ assertThat(fakeBlockedByEcm.showRestrictedSettingsDetailsIsCalled).isTrue()
+ }
+
private fun setContent(restrictions: Restrictions) {
composeTestRule.setContent {
RestrictedSwitchPreference(switchPreferenceModel, restrictions) { _, _ ->
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItemTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItemTest.kt
index 2ccf323de2a3..556adc750763 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItemTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItemTest.kt
@@ -29,6 +29,7 @@ import com.android.settingslib.spaprivileged.model.enterprise.BaseUserRestricted
import com.android.settingslib.spaprivileged.model.enterprise.NoRestricted
import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
import com.android.settingslib.spaprivileged.tests.testutils.FakeBlockedByAdmin
+import com.android.settingslib.spaprivileged.tests.testutils.FakeBlockedByEcm
import com.android.settingslib.spaprivileged.tests.testutils.FakeRestrictionsProvider
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
@@ -41,6 +42,7 @@ class RestrictedMenuItemTest {
val composeTestRule = createComposeRule()
private val fakeBlockedByAdmin = FakeBlockedByAdmin()
+ private val fakeBlockedByEcm = FakeBlockedByEcm()
private val fakeRestrictionsProvider = FakeRestrictionsProvider()
@@ -129,6 +131,28 @@ class RestrictedMenuItemTest {
assertThat(menuItemOnClickIsCalled).isFalse()
}
+ @Test
+ fun whenBlockedByEcm_disabled() {
+ val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+ fakeRestrictionsProvider.restrictedMode = fakeBlockedByEcm
+
+ setContent(restrictions)
+
+ composeTestRule.onNodeWithText(TEXT).assertIsDisplayed().assertIsEnabled()
+ }
+
+ @Test
+ fun whenBlockedByEcm_onClick_showEcmDetails() {
+ val restrictions = Restrictions(userId = USER_ID, keys = listOf(RESTRICTION_KEY))
+ fakeRestrictionsProvider.restrictedMode = fakeBlockedByEcm
+
+ setContent(restrictions)
+ composeTestRule.onRoot().performClick()
+
+ assertThat(fakeBlockedByEcm.showRestrictedSettingsDetailsIsCalled).isTrue()
+ assertThat(menuItemOnClickIsCalled).isFalse()
+ }
+
private fun setContent(restrictions: Restrictions) {
val fakeMoreOptionsScope = object : MoreOptionsScope() {
override fun dismiss() {}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/RestrictedTestUtils.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/RestrictedTestUtils.kt
index 93fa17d1eeca..f8ca2a084f14 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/RestrictedTestUtils.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/RestrictedTestUtils.kt
@@ -19,6 +19,7 @@ package com.android.settingslib.spaprivileged.tests.testutils
import androidx.compose.runtime.Composable
import com.android.settingslib.spa.framework.compose.stateOf
import com.android.settingslib.spaprivileged.model.enterprise.BlockedByAdmin
+import com.android.settingslib.spaprivileged.model.enterprise.BlockedByEcm
import com.android.settingslib.spaprivileged.model.enterprise.RestrictedMode
import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProvider
@@ -36,6 +37,18 @@ class FakeBlockedByAdmin : BlockedByAdmin {
}
}
+class FakeBlockedByEcm : BlockedByEcm {
+ var showRestrictedSettingsDetailsIsCalled = false
+
+ override fun showRestrictedSettingsDetails() {
+ showRestrictedSettingsDetailsIsCalled = true
+ }
+
+ companion object {
+ const val SUMMARY = "Disabled"
+ }
+}
+
class FakeRestrictionsProvider : RestrictionsProvider {
var restrictedMode: RestrictedMode? = null
diff --git a/packages/SettingsLib/aconfig/settingslib.aconfig b/packages/SettingsLib/aconfig/settingslib.aconfig
index d622eb859cd1..6a1ee3a3c623 100644
--- a/packages/SettingsLib/aconfig/settingslib.aconfig
+++ b/packages/SettingsLib/aconfig/settingslib.aconfig
@@ -23,3 +23,24 @@ flag {
description: "Displays the auto on toggle in the bluetooth QS tile dialog"
bug: "316985153"
}
+
+flag {
+ name: "legacy_le_audio_sharing"
+ namespace: "pixel_cross_device_control"
+ description: "Gates the legacy le audio sharing UI."
+ bug: "322295262"
+}
+
+flag {
+ name: "enable_le_audio_sharing"
+ namespace: "pixel_cross_device_control"
+ description: "Gates whether to enable LE audio sharing"
+ bug: "305620450"
+}
+
+flag {
+ name: "enable_le_audio_qr_code_private_broadcast_sharing"
+ namespace: "pixel_cross_device_control"
+ description: "Gates whether to enable LE audio private broadcast sharing via QR code"
+ bug: "308368124"
+} \ No newline at end of file
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 1092a16216f9..9588e502f28e 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1129,7 +1129,7 @@
<!-- [CHAR_LIMIT=80] Label for battery level chart when charge been limited -->
<string name="power_charging_limited"><xliff:g id="level">%1$s</xliff:g> - Charging optimized</string>
<!-- [CHAR_LIMIT=80] Label for battery charging future pause -->
- <string name="power_charging_future_paused"><xliff:g id="level">%1$s</xliff:g> - Charging optimized</string>
+ <string name="power_charging_future_paused"><xliff:g id="level">%1$s</xliff:g> - Charging</string>
<!-- Battery Info screen. Value for a status item. Used for diagnostic info screens, precise translation isn't needed -->
<string name="battery_info_status_unknown">Unknown</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedDropDownPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedDropDownPreference.java
new file mode 100644
index 000000000000..6578eb7d50a6
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedDropDownPreference.java
@@ -0,0 +1,70 @@
+/*
+ * 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.settingslib;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+import androidx.preference.DropDownPreference;
+import androidx.preference.PreferenceViewHolder;
+
+public class RestrictedDropDownPreference extends DropDownPreference {
+ RestrictedPreferenceHelper mHelper;
+
+ public RestrictedDropDownPreference(@NonNull Context context) {
+ super(context);
+ mHelper = new RestrictedPreferenceHelper(context, this, null);
+ }
+
+ /**
+ * Checks if the given setting is subject to Enhanced Confirmation Mode restrictions for this
+ * package. Marks the preference as disabled if so.
+ * @param settingIdentifier The key identifying the setting
+ * @param packageName the package to check the settingIdentifier for
+ */
+ public void checkEcmRestrictionAndSetDisabled(@NonNull String settingIdentifier,
+ @NonNull String packageName) {
+ mHelper.checkEcmRestrictionAndSetDisabled(settingIdentifier, packageName);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull PreferenceViewHolder holder) {
+ super.onBindViewHolder(holder);
+ mHelper.onBindViewHolder(holder);
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ if (enabled && isDisabledByEcm()) {
+ mHelper.setDisabledByEcm(null);
+ return;
+ }
+
+ super.setEnabled(enabled);
+ }
+
+ @Override
+ public void performClick() {
+ if (!mHelper.performClick()) {
+ super.performClick();
+ }
+ }
+
+ public boolean isDisabledByEcm() {
+ return mHelper.isDisabledByEcm();
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
index d9024575f247..f36da19afd30 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
@@ -23,11 +23,11 @@ import static android.app.role.RoleManager.ROLE_FINANCED_DEVICE_KIOSK;
import static com.android.settingslib.Utils.getColorAttrDefaultColor;
-import android.Manifest;
import android.annotation.UserIdInt;
import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.admin.DevicePolicyManager;
+import android.app.ecm.EnhancedConfirmationManager;
import android.app.role.RoleManager;
import android.content.ComponentName;
import android.content.Context;
@@ -42,12 +42,10 @@ import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.UserManager.EnforcingUser;
-import android.provider.Settings;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.style.ForegroundColorSpan;
import android.text.style.ImageSpan;
-import android.util.ArraySet;
import android.util.Log;
import android.view.MenuItem;
import android.widget.TextView;
@@ -60,7 +58,6 @@ import androidx.annotation.VisibleForTesting;
import com.android.internal.widget.LockPatternUtils;
import java.util.List;
-import java.util.Set;
/**
* Utility class to host methods usable in adding a restricted padlock icon and showing admin
@@ -70,24 +67,11 @@ public class RestrictedLockUtilsInternal extends RestrictedLockUtils {
private static final String LOG_TAG = "RestrictedLockUtils";
private static final boolean DEBUG = Log.isLoggable(LOG_TAG, Log.DEBUG);
- private static final Set<String> ECM_KEYS = new ArraySet<>();
// TODO(b/281701062): reference role name from role manager once its exposed.
private static final String ROLE_DEVICE_LOCK_CONTROLLER =
"android.app.role.SYSTEM_FINANCED_DEVICE_CONTROLLER";
- static {
- if (android.security.Flags.extendEcmToAllSettings()) {
- ECM_KEYS.add(AppOpsManager.OPSTR_SYSTEM_ALERT_WINDOW);
- ECM_KEYS.add(AppOpsManager.OPSTR_GET_USAGE_STATS);
- ECM_KEYS.add(AppOpsManager.OPSTR_LOADER_USAGE_STATS);
- ECM_KEYS.add(Manifest.permission.BIND_DEVICE_ADMIN);
- }
-
- ECM_KEYS.add(AppOpsManager.OPSTR_ACCESS_NOTIFICATIONS);
- ECM_KEYS.add(AppOpsManager.OPSTR_BIND_ACCESSIBILITY_SERVICE);
- }
-
/**
* @return drawables for displaying with settings that are locked by a device admin.
*/
@@ -112,32 +96,63 @@ public class RestrictedLockUtilsInternal extends RestrictedLockUtils {
*/
@Nullable
public static Intent checkIfRequiresEnhancedConfirmation(@NonNull Context context,
- @NonNull String restriction,
- int uid,
- @Nullable String packageName) {
- // TODO(b/297372999): Replace with call to mainline module once ready
+ @NonNull String settingIdentifier, @NonNull String packageName) {
- if (!ECM_KEYS.contains(restriction)) {
+ if (!android.permission.flags.Flags.enhancedConfirmationModeApisEnabled()
+ || !android.security.Flags.extendEcmToAllSettings()) {
return null;
}
- final AppOpsManager appOps = (AppOpsManager) context
- .getSystemService(Context.APP_OPS_SERVICE);
- final int mode = appOps.noteOpNoThrow(AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS,
- uid, packageName, null, null);
- final boolean ecmEnabled = context.getResources().getBoolean(
- com.android.internal.R.bool.config_enhancedConfirmationModeEnabled);
- if (ecmEnabled && mode != AppOpsManager.MODE_ALLOWED) {
- final Intent intent = new Intent(Settings.ACTION_SHOW_RESTRICTED_SETTING_DIALOG);
- intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName);
- intent.putExtra(Intent.EXTRA_UID, uid);
- return intent;
+ EnhancedConfirmationManager ecManager = (EnhancedConfirmationManager) context
+ .getSystemService(Context.ECM_ENHANCED_CONFIRMATION_SERVICE);
+ try {
+ if (ecManager.isRestricted(packageName, settingIdentifier)) {
+ return ecManager.createRestrictedSettingDialogIntent(
+ packageName, settingIdentifier);
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(LOG_TAG, "package not found: " + packageName, e);
}
return null;
}
/**
+ * <p>This is {@code true} when the setting is a protected setting (i.e., a sensitive resource),
+ * and the app is restricted (i.e., considered dangerous), and the user has not yet cleared the
+ * app's restriction status (i.e., by clicking "Allow restricted settings" for this app). *
+ */
+ public static boolean isEnhancedConfirmationRestricted(@NonNull Context context,
+ @NonNull String settingIdentifier, @NonNull String packageName) {
+ if (android.permission.flags.Flags.enhancedConfirmationModeApisEnabled()
+ && android.security.Flags.extendEcmToAllSettings()) {
+ try {
+ return context.getSystemService(EnhancedConfirmationManager.class)
+ .isRestricted(packageName, settingIdentifier);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(LOG_TAG, "Exception when retrieving package:" + packageName, e);
+ return false;
+ }
+ } else {
+ try {
+ if (!settingIdentifier.equals(AppOpsManager.OPSTR_BIND_ACCESSIBILITY_SERVICE)) {
+ return false;
+ }
+ int uid = context.getPackageManager().getPackageUid(packageName, 0);
+ final int mode = context.getSystemService(AppOpsManager.class)
+ .noteOpNoThrow(AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS,
+ uid, packageName);
+ final boolean ecmEnabled = context.getResources().getBoolean(
+ com.android.internal.R.bool.config_enhancedConfirmationModeEnabled);
+ return ecmEnabled && mode != AppOpsManager.MODE_ALLOWED;
+ } catch (Exception e) {
+ // Fallback in case if app ops is not available in testing.
+ return false;
+ }
+ }
+ }
+
+ /**
* Checks if a restriction is enforced on a user and returns the enforced admin and
* admin userId.
*
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java
index 50e3bd08026c..495410b0a8ae 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java
@@ -23,6 +23,7 @@ import android.os.Process;
import android.os.UserHandle;
import android.util.AttributeSet;
+import androidx.annotation.NonNull;
import androidx.core.content.res.TypedArrayUtils;
import androidx.preference.PreferenceManager;
import androidx.preference.PreferenceViewHolder;
@@ -99,12 +100,12 @@ public class RestrictedPreference extends TwoTargetPreference {
/**
* Checks if the given setting is subject to Enhanced Confirmation Mode restrictions for this
* package. Marks the preference as disabled if so.
- * @param restriction The key identifying the setting
- * @param packageName the package to check the restriction for
- * @param uid the uid of the package
+ * @param settingIdentifier The key identifying the setting
+ * @param packageName the package to check the settingIdentifier for
*/
- public void checkEcmRestrictionAndSetDisabled(String restriction, String packageName, int uid) {
- mHelper.checkEcmRestrictionAndSetDisabled(restriction, packageName, uid);
+ public void checkEcmRestrictionAndSetDisabled(@NonNull String settingIdentifier,
+ @NonNull String packageName) {
+ mHelper.checkEcmRestrictionAndSetDisabled(settingIdentifier, packageName);
}
@Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
index a479269f40fb..734b92c7ac6e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
@@ -31,6 +31,8 @@ import android.util.AttributeSet;
import android.util.TypedValue;
import android.widget.TextView;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
@@ -43,9 +45,17 @@ import com.android.settingslib.utils.BuildCompatUtils;
* by device admins via user restrictions.
*/
public class RestrictedPreferenceHelper {
+ private static final String TAG = "RestrictedPreferenceHelper";
+
private final Context mContext;
private final Preference mPreference;
String packageName;
+
+ /**
+ * @deprecated TODO(b/308921175): This will be deleted with the
+ * {@link android.security.Flags#extendEcmToAllSettings} feature flag. Do not use for any new
+ * code.
+ */
int uid;
private boolean mDisabledByAdmin;
@@ -148,14 +158,15 @@ public class RestrictedPreferenceHelper {
return true;
}
if (mDisabledByEcm) {
- if (android.security.Flags.extendEcmToAllSettings()) {
+ if (android.permission.flags.Flags.enhancedConfirmationModeApisEnabled()
+ && android.security.Flags.extendEcmToAllSettings()) {
mContext.startActivity(mDisabledByEcmIntent);
return true;
+ } else {
+ RestrictedLockUtilsInternal.sendShowRestrictedSettingDialogIntent(mContext,
+ packageName, uid);
+ return true;
}
-
- RestrictedLockUtilsInternal.sendShowRestrictedSettingDialogIntent(mContext, packageName,
- uid);
- return true;
}
return false;
}
@@ -184,14 +195,14 @@ public class RestrictedPreferenceHelper {
/**
* Checks if the given setting is subject to Enhanced Confirmation Mode restrictions for this
* package. Marks the preference as disabled if so.
- * @param restriction The key identifying the setting
- * @param packageName the package to check the restriction for
- * @param uid the uid of the package
+ * @param settingIdentifier The key identifying the setting
+ * @param packageName the package to check the settingIdentifier for
*/
- public void checkEcmRestrictionAndSetDisabled(String restriction, String packageName, int uid) {
- updatePackageDetails(packageName, uid);
+ public void checkEcmRestrictionAndSetDisabled(@NonNull String settingIdentifier,
+ @NonNull String packageName) {
+ updatePackageDetails(packageName, android.os.Process.INVALID_UID);
Intent intent = RestrictedLockUtilsInternal.checkIfRequiresEnhancedConfirmation(
- mContext, restriction, uid, packageName);
+ mContext, settingIdentifier, packageName);
setDisabledByEcm(intent);
}
@@ -240,7 +251,7 @@ public class RestrictedPreferenceHelper {
* be disabled.
* @return true if the disabled state was changed.
*/
- public boolean setDisabledByEcm(Intent disabledIntent) {
+ public boolean setDisabledByEcm(@Nullable Intent disabledIntent) {
boolean disabled = disabledIntent != null;
boolean changed = false;
if (mDisabledByEcm != disabled) {
@@ -275,6 +286,10 @@ public class RestrictedPreferenceHelper {
if (mPreference instanceof PrimarySwitchPreference) {
((PrimarySwitchPreference) mPreference).setSwitchEnabled(isEnabled);
}
+
+ if (!isEnabled && mDisabledByEcm) {
+ mPreference.setSummary(R.string.disabled_by_app_ops_text);
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
index 70ece0fa8076..0c54c1903742 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
@@ -200,12 +200,12 @@ public class RestrictedSwitchPreference extends SwitchPreferenceCompat {
/**
* Checks if the given setting is subject to Enhanced Confirmation Mode restrictions for this
* package. Marks the preference as disabled if so.
- * @param restriction The key identifying the setting
- * @param packageName the package to check the restriction for
- * @param uid the uid of the package
+ * @param settingIdentifier The key identifying the setting
+ * @param packageName the package to check the settingIdentifier for
*/
- public void checkEcmRestrictionAndSetDisabled(String restriction, String packageName, int uid) {
- mHelper.checkEcmRestrictionAndSetDisabled(restriction, packageName, uid);
+ public void checkEcmRestrictionAndSetDisabled(@NonNull String settingIdentifier,
+ @NonNull String packageName) {
+ mHelper.checkEcmRestrictionAndSetDisabled(settingIdentifier, packageName);
}
@Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index 249fa7f0df77..e489bc552b25 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -1702,7 +1702,8 @@ public class ApplicationsState {
}
public boolean isPrivateProfile() {
- return UserManager.USER_TYPE_PROFILE_PRIVATE.equals(mProfileType);
+ return android.os.Flags.allowPrivateProfile()
+ && UserManager.USER_TYPE_PROFILE_PRIVATE.equals(mProfileType);
}
/**
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothBroadcastUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothBroadcastUtils.java
index 1ff2befd19ed..5e3bd9a66e88 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothBroadcastUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothBroadcastUtils.java
@@ -43,5 +43,5 @@ public final class BluetoothBroadcastUtils {
/**
* Bluetooth scheme.
*/
- public static final String SCHEME_BT_BROADCAST_METADATA = "BT:";
+ public static final String SCHEME_BT_BROADCAST_METADATA = "BLUETOOTH:UUID:184F;";
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt
index 9bb11f8da645..da1fd55c4ccb 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt
@@ -31,38 +31,34 @@ import com.android.settingslib.bluetooth.BluetoothBroadcastUtils.SCHEME_BT_BROAD
object BluetoothLeBroadcastMetadataExt {
private const val TAG = "BtLeBroadcastMetadataExt"
- // BluetoothLeBroadcastMetadata
- private const val KEY_BT_QR_VER = "R"
- private const val KEY_BT_ADDRESS_TYPE = "T"
- private const val KEY_BT_DEVICE = "D"
- private const val KEY_BT_ADVERTISING_SID = "AS"
- private const val KEY_BT_BROADCAST_ID = "B"
+ // Data Elements for directing Broadcast Assistants
private const val KEY_BT_BROADCAST_NAME = "BN"
- private const val KEY_BT_PUBLIC_BROADCAST_DATA = "PM"
- private const val KEY_BT_SYNC_INTERVAL = "SI"
- private const val KEY_BT_BROADCAST_CODE = "C"
- private const val KEY_BT_SUBGROUPS = "SG"
- private const val KEY_BT_VENDOR_SPECIFIC = "V"
- private const val KEY_BT_ANDROID_VERSION = "VN"
-
- // Subgroup data
+ private const val KEY_BT_ADVERTISER_ADDRESS_TYPE = "AT"
+ private const val KEY_BT_ADVERTISER_ADDRESS = "AD"
+ private const val KEY_BT_BROADCAST_ID = "BI"
+ private const val KEY_BT_BROADCAST_CODE = "BC"
+ private const val KEY_BT_STREAM_METADATA = "MD"
+ private const val KEY_BT_STANDARD_QUALITY = "SQ"
+ private const val KEY_BT_HIGH_QUALITY = "HQ"
+
+ // Extended Bluetooth URI Data Elements
+ private const val KEY_BT_ADVERTISING_SID = "AS"
+ private const val KEY_BT_PA_INTERVAL = "PI"
+ private const val KEY_BT_NUM_SUBGROUPS = "NS"
+
+ // Subgroup data elements
private const val KEY_BTSG_BIS_SYNC = "BS"
- private const val KEY_BTSG_BIS_MASK = "BM"
- private const val KEY_BTSG_AUDIO_CONTENT = "AC"
+ private const val KEY_BTSG_NUM_BISES = "NB"
+ private const val KEY_BTSG_METADATA = "SM"
- // Vendor specific data
- private const val KEY_BTVSD_COMPANY_ID = "VI"
- private const val KEY_BTVSD_VENDOR_DATA = "VD"
+ // Vendor specific data, not being used
+ private const val KEY_BTVSD_VENDOR_DATA = "VS"
private const val DELIMITER_KEY_VALUE = ":"
- private const val DELIMITER_BT_LEVEL_1 = ";"
- private const val DELIMITER_BT_LEVEL_2 = ","
+ private const val DELIMITER_ELEMENT = ";"
private const val SUFFIX_QR_CODE = ";;"
- private const val ANDROID_VER = "U"
- private const val QR_CODE_VER = 0x010000
-
// BT constants
private const val BIS_SYNC_MAX_CHANNEL = 32
private const val BIS_SYNC_NO_PREFERENCE = 0xFFFFFFFFu
@@ -71,33 +67,55 @@ object BluetoothLeBroadcastMetadataExt {
/**
* Converts [BluetoothLeBroadcastMetadata] to QR code string.
*
- * QR code string will prefix with "BT:".
+ * QR code string will prefix with "BLUETOOTH:UUID:184F".
*/
fun BluetoothLeBroadcastMetadata.toQrCodeString(): String {
val entries = mutableListOf<Pair<String, String>>()
- entries.add(Pair(KEY_BT_QR_VER, QR_CODE_VER.toString()))
- entries.add(Pair(KEY_BT_ADDRESS_TYPE, this.sourceAddressType.toString()))
- entries.add(Pair(KEY_BT_DEVICE, this.sourceDevice.address.replace(":", "-")))
- entries.add(Pair(KEY_BT_ADVERTISING_SID, this.sourceAdvertisingSid.toString()))
- entries.add(Pair(KEY_BT_BROADCAST_ID, this.broadcastId.toString()))
- if (this.broadcastName != null) {
- entries.add(Pair(KEY_BT_BROADCAST_NAME, Base64.encodeToString(
- this.broadcastName?.toByteArray(Charsets.UTF_8), Base64.NO_WRAP)))
- }
- if (this.publicBroadcastMetadata != null) {
- entries.add(Pair(KEY_BT_PUBLIC_BROADCAST_DATA, Base64.encodeToString(
- this.publicBroadcastMetadata?.rawMetadata, Base64.NO_WRAP)))
- }
- entries.add(Pair(KEY_BT_SYNC_INTERVAL, this.paSyncInterval.toString()))
+ // Generate data elements for directing Broadcast Assistants
+ require(this.broadcastName != null) { "Broadcast name is mandatory for QR code" }
+ entries.add(Pair(KEY_BT_BROADCAST_NAME, Base64.encodeToString(
+ this.broadcastName?.toByteArray(Charsets.UTF_8), Base64.NO_WRAP)))
+ entries.add(Pair(KEY_BT_ADVERTISER_ADDRESS_TYPE, this.sourceAddressType.toString()))
+ entries.add(Pair(KEY_BT_ADVERTISER_ADDRESS, this.sourceDevice.address.replace(":", "")))
+ entries.add(Pair(KEY_BT_BROADCAST_ID, String.format("%X", this.broadcastId.toLong())))
if (this.broadcastCode != null) {
entries.add(Pair(KEY_BT_BROADCAST_CODE,
Base64.encodeToString(this.broadcastCode, Base64.NO_WRAP)))
}
+ if (this.publicBroadcastMetadata != null &&
+ this.publicBroadcastMetadata?.rawMetadata?.size != 0) {
+ entries.add(Pair(KEY_BT_STREAM_METADATA, Base64.encodeToString(
+ this.publicBroadcastMetadata?.rawMetadata, Base64.NO_WRAP)))
+ }
+ if ((this.audioConfigQuality and
+ BluetoothLeBroadcastMetadata.AUDIO_CONFIG_QUALITY_STANDARD) != 0) {
+ entries.add(Pair(KEY_BT_STANDARD_QUALITY, "1"))
+ }
+ if ((this.audioConfigQuality and
+ BluetoothLeBroadcastMetadata.AUDIO_CONFIG_QUALITY_HIGH) != 0) {
+ entries.add(Pair(KEY_BT_HIGH_QUALITY, "1"))
+ }
+
+ // Generate extended Bluetooth URI data elements
+ entries.add(Pair(KEY_BT_ADVERTISING_SID,
+ String.format("%X", this.sourceAdvertisingSid.toLong())))
+ entries.add(Pair(KEY_BT_PA_INTERVAL, String.format("%X", this.paSyncInterval.toLong())))
+ entries.add(Pair(KEY_BT_NUM_SUBGROUPS, String.format("%X", this.subgroups.size.toLong())))
+
this.subgroups.forEach {
- subgroup -> entries.add(Pair(KEY_BT_SUBGROUPS, subgroup.toQrCodeString())) }
- entries.add(Pair(KEY_BT_ANDROID_VERSION, ANDROID_VER))
+ val (bisSync, bisCount) = getBisSyncFromChannels(it.channels)
+ entries.add(Pair(KEY_BTSG_BIS_SYNC, String.format("%X", bisSync.toLong())))
+ if (bisCount > 0u) {
+ entries.add(Pair(KEY_BTSG_NUM_BISES, String.format("%X", bisCount.toLong())))
+ }
+ if (it.contentMetadata.rawMetadata.size != 0) {
+ entries.add(Pair(KEY_BTSG_METADATA,
+ Base64.encodeToString(it.contentMetadata.rawMetadata, Base64.NO_WRAP)))
+ }
+ }
+
val qrCodeString = SCHEME_BT_BROADCAST_METADATA +
- entries.toQrCodeString(DELIMITER_BT_LEVEL_1) + SUFFIX_QR_CODE
+ entries.toQrCodeString(DELIMITER_ELEMENT) + SUFFIX_QR_CODE
Log.d(TAG, "Generated QR string : $qrCodeString")
return qrCodeString
}
@@ -105,7 +123,7 @@ object BluetoothLeBroadcastMetadataExt {
/**
* Converts QR code string to [BluetoothLeBroadcastMetadata].
*
- * QR code string should prefix with "BT:BluetoothLeBroadcastMetadata:".
+ * QR code string should prefix with "BLUETOOTH:UUID:184F".
*/
fun convertToBroadcastMetadata(qrCodeString: String): BluetoothLeBroadcastMetadata? {
if (!qrCodeString.startsWith(SCHEME_BT_BROADCAST_METADATA)) {
@@ -126,15 +144,6 @@ object BluetoothLeBroadcastMetadataExt {
}
}
- private fun BluetoothLeBroadcastSubgroup.toQrCodeString(): String {
- val entries = mutableListOf<Pair<String, String>>()
- entries.add(Pair(KEY_BTSG_BIS_SYNC, getBisSyncFromChannels(this.channels).toString()))
- entries.add(Pair(KEY_BTSG_BIS_MASK, getBisMaskFromChannels(this.channels).toString()))
- entries.add(Pair(KEY_BTSG_AUDIO_CONTENT,
- Base64.encodeToString(this.contentMetadata.rawMetadata, Base64.NO_WRAP)))
- return entries.toQrCodeString(DELIMITER_BT_LEVEL_2)
- }
-
private fun List<Pair<String, String>>.toQrCodeString(delimiter: String): String {
val entryStrings = this.map{ it.first + DELIMITER_KEY_VALUE + it.second }
return entryStrings.joinToString(separator = delimiter)
@@ -143,23 +152,29 @@ object BluetoothLeBroadcastMetadataExt {
@TargetApi(Build.VERSION_CODES.TIRAMISU)
private fun parseQrCodeToMetadata(input: String): BluetoothLeBroadcastMetadata {
// Split into a list of list
- val level1Fields = input.split(DELIMITER_BT_LEVEL_1)
+ val elementFields = input.split(DELIMITER_ELEMENT)
.map{it.split(DELIMITER_KEY_VALUE, limit = 2)}
- var qrCodeVersion = -1
+
var sourceAddrType = BluetoothDevice.ADDRESS_TYPE_UNKNOWN
var sourceAddrString: String? = null
var sourceAdvertiserSid = -1
var broadcastId = -1
var broadcastName: String? = null
- var publicBroadcastMetadata: BluetoothLeAudioContentMetadata? = null
+ var streamMetadata: BluetoothLeAudioContentMetadata? = null
var paSyncInterval = -1
var broadcastCode: ByteArray? = null
- // List of VendorID -> Data Pairs
- var vendorDataList = mutableListOf<Pair<Int, ByteArray?>>()
- var androidVersion: String? = null
+ var audioConfigQualityStandard = -1
+ var audioConfigQualityHigh = -1
+ var numSubgroups = -1
+
+ // List of subgroup data
+ var subgroupBisSyncList = mutableListOf<UInt>()
+ var subgroupNumOfBisesList = mutableListOf<UInt>()
+ var subgroupMetadataList = mutableListOf<ByteArray?>()
+
val builder = BluetoothLeBroadcastMetadata.Builder()
- for (field: List<String> in level1Fields) {
+ for (field: List<String> in elementFields) {
if (field.isEmpty()) {
continue
}
@@ -167,190 +182,200 @@ object BluetoothLeBroadcastMetadataExt {
// Ignore 3rd value and after
val value = if (field.size > 1) field[1] else ""
when (key) {
- KEY_BT_QR_VER -> {
- require(qrCodeVersion == -1) { "Duplicate qrCodeVersion: $input" }
- qrCodeVersion = value.toInt()
+ // Parse data elements for directing Broadcast Assistants
+ KEY_BT_BROADCAST_NAME -> {
+ require(broadcastName == null) { "Duplicate broadcastName: $input" }
+ broadcastName = String(Base64.decode(value, Base64.NO_WRAP))
}
- KEY_BT_ADDRESS_TYPE -> {
+ KEY_BT_ADVERTISER_ADDRESS_TYPE -> {
require(sourceAddrType == BluetoothDevice.ADDRESS_TYPE_UNKNOWN) {
"Duplicate sourceAddrType: $input"
}
sourceAddrType = value.toInt()
}
- KEY_BT_DEVICE -> {
+ KEY_BT_ADVERTISER_ADDRESS -> {
require(sourceAddrString == null) { "Duplicate sourceAddr: $input" }
- sourceAddrString = value.replace("-", ":")
- }
- KEY_BT_ADVERTISING_SID -> {
- require(sourceAdvertiserSid == -1) { "Duplicate sourceAdvertiserSid: $input" }
- sourceAdvertiserSid = value.toInt()
+ sourceAddrString = value.chunked(2).joinToString(":")
}
KEY_BT_BROADCAST_ID -> {
require(broadcastId == -1) { "Duplicate broadcastId: $input" }
- broadcastId = value.toInt()
+ broadcastId = value.toInt(16)
}
- KEY_BT_BROADCAST_NAME -> {
- require(broadcastName == null) { "Duplicate broadcastName: $input" }
- broadcastName = String(Base64.decode(value, Base64.NO_WRAP))
+ KEY_BT_BROADCAST_CODE -> {
+ require(broadcastCode == null) { "Duplicate broadcastCode: $input" }
+
+ broadcastCode = Base64.decode(value.dropLastWhile { it.equals(0.toByte()) }
+ .toByteArray(), Base64.NO_WRAP)
}
- KEY_BT_PUBLIC_BROADCAST_DATA -> {
- require(publicBroadcastMetadata == null) {
- "Duplicate publicBroadcastMetadata $input"
+ KEY_BT_STREAM_METADATA -> {
+ require(streamMetadata == null) {
+ "Duplicate streamMetadata $input"
}
- publicBroadcastMetadata = BluetoothLeAudioContentMetadata
+ streamMetadata = BluetoothLeAudioContentMetadata
.fromRawBytes(Base64.decode(value, Base64.NO_WRAP))
}
- KEY_BT_SYNC_INTERVAL -> {
+ KEY_BT_STANDARD_QUALITY -> {
+ require(audioConfigQualityStandard == -1) {
+ "Duplicate audioConfigQualityStandard: $input"
+ }
+ audioConfigQualityStandard = value.toInt()
+ }
+ KEY_BT_HIGH_QUALITY -> {
+ require(audioConfigQualityHigh == -1) {
+ "Duplicate audioConfigQualityHigh: $input"
+ }
+ audioConfigQualityHigh = value.toInt()
+ }
+
+ // Parse extended Bluetooth URI data elements
+ KEY_BT_ADVERTISING_SID -> {
+ require(sourceAdvertiserSid == -1) { "Duplicate sourceAdvertiserSid: $input" }
+ sourceAdvertiserSid = value.toInt(16)
+ }
+ KEY_BT_PA_INTERVAL -> {
require(paSyncInterval == -1) { "Duplicate paSyncInterval: $input" }
- paSyncInterval = value.toInt()
+ paSyncInterval = value.toInt(16)
}
- KEY_BT_BROADCAST_CODE -> {
- require(broadcastCode == null) { "Duplicate broadcastCode: $input" }
- broadcastCode = Base64.decode(value, Base64.NO_WRAP)
+ KEY_BT_NUM_SUBGROUPS -> {
+ require(numSubgroups == -1) { "Duplicate numSubgroups: $input" }
+ numSubgroups = value.toInt(16)
}
- KEY_BT_ANDROID_VERSION -> {
- require(androidVersion == null) { "Duplicate androidVersion: $input" }
- androidVersion = value
- Log.i(TAG, "QR code Android version: $androidVersion")
+
+ // Repeatable subgroup elements
+ KEY_BTSG_BIS_SYNC -> {
+ subgroupBisSyncList.add(value.toUInt(16))
}
- // Repeatable
- KEY_BT_SUBGROUPS -> {
- builder.addSubgroup(parseSubgroupData(value))
+ KEY_BTSG_NUM_BISES -> {
+ subgroupNumOfBisesList.add(value.toUInt(16))
}
- // Repeatable
- KEY_BT_VENDOR_SPECIFIC -> {
- vendorDataList.add(parseVendorData(value))
+ KEY_BTSG_METADATA -> {
+ subgroupMetadataList.add(Base64.decode(value, Base64.NO_WRAP))
}
}
}
- Log.d(TAG, "parseQrCodeToMetadata: sourceAddrType=$sourceAddrType, " +
+ Log.d(TAG, "parseQrCodeToMetadata: main data elements sourceAddrType=$sourceAddrType, " +
"sourceAddr=$sourceAddrString, sourceAdvertiserSid=$sourceAdvertiserSid, " +
"broadcastId=$broadcastId, broadcastName=$broadcastName, " +
- "publicBroadcastMetadata=${publicBroadcastMetadata != null}, " +
+ "streamMetadata=${streamMetadata != null}, " +
"paSyncInterval=$paSyncInterval, " +
- "broadcastCode=${broadcastCode?.toString(Charsets.UTF_8)}")
- Log.d(TAG, "Not used in current code, but part of the specification: " +
- "qrCodeVersion=$qrCodeVersion, androidVersion=$androidVersion, " +
- "vendorDataListSize=${vendorDataList.size}")
+ "broadcastCode=${broadcastCode?.toString(Charsets.UTF_8)}, " +
+ "audioConfigQualityStandard=$audioConfigQualityStandard, " +
+ "audioConfigQualityHigh=$audioConfigQualityHigh")
+
val adapter = BluetoothAdapter.getDefaultAdapter()
+ // Check parsed elements data
+ require(broadcastName != null) {
+ "broadcastName($broadcastName) must present in QR code string"
+ }
+ var addr = sourceAddrString
+ var addrType = sourceAddrType
+ if (sourceAddrString != null) {
+ require(sourceAddrType != BluetoothDevice.ADDRESS_TYPE_UNKNOWN) {
+ "sourceAddrType($sourceAddrType) must present if address present"
+ }
+ } else {
+ // Use placeholder device if not present
+ addr = "FF:FF:FF:FF:FF:FF"
+ addrType = BluetoothDevice.ADDRESS_TYPE_RANDOM
+ }
+ val device = adapter.getRemoteLeDevice(requireNotNull(addr), addrType)
+
// add source device and set broadcast code
- val device = adapter.getRemoteLeDevice(requireNotNull(sourceAddrString), sourceAddrType)
+ var audioConfigQuality = BluetoothLeBroadcastMetadata.AUDIO_CONFIG_QUALITY_NONE or
+ (if (audioConfigQualityStandard != -1) audioConfigQualityStandard else 0) or
+ (if (audioConfigQualityHigh != -1) audioConfigQualityHigh else 0)
+
+ // process subgroup data
+ // metadata should include at least 1 subgroup for metadata, add a placeholder group if not present
+ numSubgroups = if (numSubgroups > 0) numSubgroups else 1
+ for (i in 0 until numSubgroups) {
+ val bisSync = subgroupBisSyncList.getOrNull(i)
+ val bisNum = subgroupNumOfBisesList.getOrNull(i)
+ val metadata = subgroupMetadataList.getOrNull(i)
+
+ val channels = convertToChannels(bisSync, bisNum)
+ val audioCodecConfigMetadata = BluetoothLeAudioCodecConfigMetadata.Builder()
+ .setAudioLocation(0).build()
+ val subgroup = BluetoothLeBroadcastSubgroup.Builder().apply {
+ setCodecId(SUBGROUP_LC3_CODEC_ID)
+ setCodecSpecificConfig(audioCodecConfigMetadata)
+ setContentMetadata(
+ BluetoothLeAudioContentMetadata.fromRawBytes(metadata ?: ByteArray(0)))
+ channels.forEach(::addChannel)
+ }.build()
+
+ Log.d(TAG, "parseQrCodeToMetadata: subgroup $i elements bisSync=$bisSync, " +
+ "bisNum=$bisNum, metadata=${metadata != null}")
+
+ builder.addSubgroup(subgroup)
+ }
+
builder.apply {
setSourceDevice(device, sourceAddrType)
setSourceAdvertisingSid(sourceAdvertiserSid)
setBroadcastId(broadcastId)
setBroadcastName(broadcastName)
- setPublicBroadcast(publicBroadcastMetadata != null)
- setPublicBroadcastMetadata(publicBroadcastMetadata)
+ // QR code should set PBP(public broadcast profile) for auracast
+ setPublicBroadcast(true)
+ setPublicBroadcastMetadata(streamMetadata)
setPaSyncInterval(paSyncInterval)
setEncrypted(broadcastCode != null)
setBroadcastCode(broadcastCode)
// Presentation delay is unknown and not useful when adding source
// Broadcast sink needs to sync to the Broadcast source to get presentation delay
setPresentationDelayMicros(0)
+ setAudioConfigQuality(audioConfigQuality)
}
return builder.build()
}
- private fun parseSubgroupData(input: String): BluetoothLeBroadcastSubgroup {
- Log.d(TAG, "parseSubgroupData: $input")
- val fields = input.split(DELIMITER_BT_LEVEL_2)
- var bisSync: UInt? = null
- var bisMask: UInt? = null
- var metadata: ByteArray? = null
-
- fields.forEach { field ->
- val(key, value) = field.split(DELIMITER_KEY_VALUE)
- when (key) {
- KEY_BTSG_BIS_SYNC -> {
- require(bisSync == null) { "Duplicate bisSync: $input" }
- bisSync = value.toUInt()
- }
- KEY_BTSG_BIS_MASK -> {
- require(bisMask == null) { "Duplicate bisMask: $input" }
- bisMask = value.toUInt()
- }
- KEY_BTSG_AUDIO_CONTENT -> {
- require(metadata == null) { "Duplicate metadata: $input" }
- metadata = Base64.decode(value, Base64.NO_WRAP)
- }
- }
- }
- val channels = convertToChannels(requireNotNull(bisSync), requireNotNull(bisMask))
- val audioCodecConfigMetadata = BluetoothLeAudioCodecConfigMetadata.Builder()
- .setAudioLocation(0).build()
- return BluetoothLeBroadcastSubgroup.Builder().apply {
- setCodecId(SUBGROUP_LC3_CODEC_ID)
- setCodecSpecificConfig(audioCodecConfigMetadata)
- setContentMetadata(
- BluetoothLeAudioContentMetadata.fromRawBytes(metadata ?: ByteArray(0)))
- channels.forEach(::addChannel)
- }.build()
- }
-
- private fun parseVendorData(input: String): Pair<Int, ByteArray?> {
- var companyId = -1
- var data: ByteArray? = null
- val fields = input.split(DELIMITER_BT_LEVEL_2)
- fields.forEach { field ->
- val(key, value) = field.split(DELIMITER_KEY_VALUE)
- when (key) {
- KEY_BTVSD_COMPANY_ID -> {
- require(companyId == -1) { "Duplicate companyId: $input" }
- companyId = value.toInt()
- }
- KEY_BTVSD_VENDOR_DATA -> {
- require(data == null) { "Duplicate data: $input" }
- data = Base64.decode(value, Base64.NO_WRAP)
- }
- }
- }
- return Pair(companyId, data)
- }
-
- private fun getBisSyncFromChannels(channels: List<BluetoothLeBroadcastChannel>): UInt {
+ private fun getBisSyncFromChannels(
+ channels: List<BluetoothLeBroadcastChannel>
+ ): Pair<UInt, UInt> {
var bisSync = 0u
- // channel index starts from 1
- channels.forEach { channel ->
- if (channel.isSelected && channel.channelIndex > 0) {
- bisSync = bisSync or (1u shl (channel.channelIndex - 1))
- }
- }
- // No channel is selected means no preference on Android platform
- return if (bisSync == 0u) BIS_SYNC_NO_PREFERENCE else bisSync
- }
-
- private fun getBisMaskFromChannels(channels: List<BluetoothLeBroadcastChannel>): UInt {
- var bisMask = 0u
+ var bisCount = 0u
// channel index starts from 1
channels.forEach { channel ->
if (channel.channelIndex > 0) {
- bisMask = bisMask or (1u shl (channel.channelIndex - 1))
+ bisCount++
+ if (channel.isSelected) {
+ bisSync = bisSync or (1u shl (channel.channelIndex - 1))
+ }
}
}
- return bisMask
+ // No channel is selected means no preference on Android platform
+ return if (bisSync == 0u) Pair(BIS_SYNC_NO_PREFERENCE, bisCount)
+ else Pair(bisSync, bisCount)
}
- private fun convertToChannels(bisSync: UInt, bisMask: UInt):
- List<BluetoothLeBroadcastChannel> {
- Log.d(TAG, "convertToChannels: bisSync=$bisSync, bisMask=$bisMask")
- var selectionMask = bisSync
- if (bisSync != BIS_SYNC_NO_PREFERENCE) {
- require(bisMask == (bisMask or bisSync)) {
- "bisSync($bisSync) must select a subset of bisMask($bisMask) if it has preferences"
- }
- } else {
- // No channel preference means no channel is selected
- selectionMask = 0u
- }
+ private fun convertToChannels(
+ bisSync: UInt?,
+ bisNum: UInt?
+ ): List<BluetoothLeBroadcastChannel> {
+ Log.d(TAG, "convertToChannels: bisSync=$bisSync, bisNum=$bisNum")
+ // if no BIS_SYNC or BIS_NUM available or BIS_SYNC is no preference
+ // return empty channel map with one placeholder channel
+ var selectedChannels = if (bisSync != null && bisNum != null) bisSync else 0u
val channels = mutableListOf<BluetoothLeBroadcastChannel>()
val audioCodecConfigMetadata = BluetoothLeAudioCodecConfigMetadata.Builder()
.setAudioLocation(0).build()
+
+ if (bisSync == BIS_SYNC_NO_PREFERENCE || selectedChannels == 0u) {
+ // No channel preference means no channel is selected
+ // Generate one placeholder channel for metadata
+ val channel = BluetoothLeBroadcastChannel.Builder().apply {
+ setSelected(false)
+ setChannelIndex(1)
+ setCodecMetadata(audioCodecConfigMetadata)
+ }
+ return listOf(channel.build())
+ }
+
for (i in 0 until BIS_SYNC_MAX_CHANNEL) {
val channelMask = 1u shl i
- if ((bisMask and channelMask) != 0u) {
+ if ((selectedChannels and channelMask) != 0u) {
val channel = BluetoothLeBroadcastChannel.Builder().apply {
- setSelected((selectionMask and channelMask) != 0u)
+ setSelected(true)
setChannelIndex(i + 1)
setCodecMetadata(audioCodecConfigMetadata)
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index bcdb64d17636..61c3ce7f6988 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -300,37 +300,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
mLocalNapRoleConnected = false;
}
- if (!HearingAidStatsLogUtils.isUserCategorized(mContext)) {
- if (HearingAidStatsLogUtils.isJustBonded(getAddress())) {
- // Saves bonded timestamp as the source for judging whether to display
- // the survey
- if (getProfiles().stream().anyMatch(
- p -> (p instanceof HearingAidProfile
- || p instanceof HapClientProfile))) {
- HearingAidStatsLogUtils.addCurrentTimeToHistory(mContext,
- HearingAidStatsLogUtils.HistoryType.TYPE_HEARING_AIDS_PAIRED);
- } else if (getProfiles().stream().anyMatch(
- p -> (p instanceof A2dpSinkProfile || p instanceof HeadsetProfile))) {
- HearingAidStatsLogUtils.addCurrentTimeToHistory(mContext,
- HearingAidStatsLogUtils.HistoryType.TYPE_HEARING_DEVICES_PAIRED);
- }
- HearingAidStatsLogUtils.removeFromJustBonded(getAddress());
- }
-
- // Saves connected timestamp as the source for judging whether to display
- // the survey
- if (newProfileState == BluetoothProfile.STATE_CONNECTED) {
- if (profile instanceof HearingAidProfile
- || profile instanceof HapClientProfile) {
- HearingAidStatsLogUtils.addCurrentTimeToHistory(mContext,
- HearingAidStatsLogUtils.HistoryType.TYPE_HEARING_AIDS_CONNECTED);
- } else if (profile instanceof A2dpSinkProfile
- || profile instanceof HeadsetProfile) {
- HearingAidStatsLogUtils.addCurrentTimeToHistory(mContext,
- HearingAidStatsLogUtils.HistoryType.TYPE_HEARING_DEVICES_CONNECTED);
- }
- }
- }
+ HearingAidStatsLogUtils.updateHistoryIfNeeded(mContext, this, profile, newProfileState);
}
fetchActiveDevices();
@@ -987,11 +957,9 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
connect();
}
- if (!HearingAidStatsLogUtils.isUserCategorized(mContext)) {
- // Saves this device as just bonded and checks if it's an hearing device after
- // profiles are connected. This is for judging whether to display the survey.
- HearingAidStatsLogUtils.addToJustBonded(getAddress());
- }
+ // Saves this device as just bonded and checks if it's an hearing device after
+ // profiles are connected. This is for judging whether to display the survey.
+ HearingAidStatsLogUtils.addToJustBonded(getAddress());
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
index 9fd174d4586c..ca47efdc5df3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
@@ -337,51 +337,39 @@ 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();
- }
+
+ 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();
- }
+
+ 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;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidStatsLogUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidStatsLogUtils.java
index 97b94da60274..8e3df8bcc2dd 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidStatsLogUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidStatsLogUtils.java
@@ -16,10 +16,9 @@
package com.android.settingslib.bluetooth;
+import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.content.SharedPreferences;
-import android.icu.text.SimpleDateFormat;
-import android.icu.util.TimeZone;
import android.util.Log;
import androidx.annotation.IntDef;
@@ -30,12 +29,14 @@ import com.android.internal.util.FrameworkStatsLog;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.ZoneId;
+import java.time.temporal.ChronoUnit;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
-import java.util.Locale;
import java.util.Set;
-import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/** Utils class to report hearing aid metrics to statsd */
@@ -54,13 +55,13 @@ public final class HearingAidStatsLogUtils {
private static final String BT_HEARING_USER_CATEGORY = "bt_hearing_user_category";
private static final String HISTORY_RECORD_DELIMITER = ",";
- private static final String CATEGORY_HEARING_AIDS = "A11yHearingAidsUser";
- private static final String CATEGORY_NEW_HEARING_AIDS = "A11yNewHearingAidsUser";
- private static final String CATEGORY_HEARING_DEVICES = "A11yHearingDevicesUser";
- private static final String CATEGORY_NEW_HEARING_DEVICES = "A11yNewHearingDevicesUser";
+ static final String CATEGORY_HEARING_AIDS = "A11yHearingAidsUser";
+ static final String CATEGORY_NEW_HEARING_AIDS = "A11yNewHearingAidsUser";
+ static final String CATEGORY_HEARING_DEVICES = "A11yHearingDevicesUser";
+ static final String CATEGORY_NEW_HEARING_DEVICES = "A11yNewHearingDevicesUser";
- private static final long PAIRED_HISTORY_EXPIRED_TIME = TimeUnit.DAYS.toMillis(30);
- private static final long CONNECTED_HISTORY_EXPIRED_TIME = TimeUnit.DAYS.toMillis(7);
+ static final int PAIRED_HISTORY_EXPIRED_DAY = 30;
+ static final int CONNECTED_HISTORY_EXPIRED_DAY = 7;
private static final int VALID_PAIRED_EVENT_COUNT = 1;
private static final int VALID_CONNECTED_EVENT_COUNT = 7;
@@ -126,16 +127,43 @@ public final class HearingAidStatsLogUtils {
}
/**
- * Indicates if user is categorized as one of {@link #CATEGORY_HEARING_AIDS},
- * {@link #CATEGORY_NEW_HEARING_AIDS}, {@link #CATEGORY_HEARING_DEVICES}, and
- * {@link #CATEGORY_NEW_HEARING_DEVICES}.
+ * Updates corresponding history if we found the device is a hearing device after profile state
+ * changed.
*
* @param context the request context
- * @return true if user is already categorized as one of interested group
+ * @param cachedDevice the remote device
+ * @param profile the profile that has a state changed
+ * @param profileState the new profile state
*/
- public static boolean isUserCategorized(Context context) {
- String userCategory = getSharedPreferences(context).getString(BT_HEARING_USER_CATEGORY, "");
- return !userCategory.isEmpty();
+ public static void updateHistoryIfNeeded(Context context, CachedBluetoothDevice cachedDevice,
+ LocalBluetoothProfile profile, int profileState) {
+
+ if (isJustBonded(cachedDevice.getAddress())) {
+ // Saves bonded timestamp as the source for judging whether to display
+ // the survey
+ if (cachedDevice.getProfiles().stream().anyMatch(
+ p -> (p instanceof HearingAidProfile || p instanceof HapClientProfile))) {
+ HearingAidStatsLogUtils.addCurrentTimeToHistory(context,
+ HearingAidStatsLogUtils.HistoryType.TYPE_HEARING_AIDS_PAIRED);
+ } else if (cachedDevice.getProfiles().stream().anyMatch(
+ p -> (p instanceof A2dpSinkProfile || p instanceof HeadsetProfile))) {
+ HearingAidStatsLogUtils.addCurrentTimeToHistory(context,
+ HearingAidStatsLogUtils.HistoryType.TYPE_HEARING_DEVICES_PAIRED);
+ }
+ removeFromJustBonded(cachedDevice.getAddress());
+ }
+
+ // Saves connected timestamp as the source for judging whether to display
+ // the survey
+ if (profileState == BluetoothProfile.STATE_CONNECTED) {
+ if (profile instanceof HearingAidProfile || profile instanceof HapClientProfile) {
+ HearingAidStatsLogUtils.addCurrentTimeToHistory(context,
+ HearingAidStatsLogUtils.HistoryType.TYPE_HEARING_AIDS_CONNECTED);
+ } else if (profile instanceof A2dpSinkProfile || profile instanceof HeadsetProfile) {
+ HearingAidStatsLogUtils.addCurrentTimeToHistory(context,
+ HearingAidStatsLogUtils.HistoryType.TYPE_HEARING_DEVICES_CONNECTED);
+ }
+ }
}
/**
@@ -186,14 +214,6 @@ public final class HearingAidStatsLogUtils {
userCategory = CATEGORY_HEARING_DEVICES;
}
}
-
- if (!userCategory.isEmpty()) {
- // History become useless once user is categorized. Clear all history.
- SharedPreferences.Editor editor = getSharedPreferences(context).edit();
- editor.putString(BT_HEARING_USER_CATEGORY, userCategory).apply();
- clearHistory(context);
- sJustBondedDeviceAddressSet.clear();
- }
return userCategory;
}
@@ -211,7 +231,7 @@ public final class HearingAidStatsLogUtils {
* Removes the device address from the just bonded list.
* @param address the device address
*/
- public static void removeFromJustBonded(String address) {
+ private static void removeFromJustBonded(String address) {
sJustBondedDeviceAddressSet.remove(address);
}
@@ -220,24 +240,11 @@ public final class HearingAidStatsLogUtils {
* @param address the device address
* @return true if the device address is in the just bonded list
*/
- public static boolean isJustBonded(String address) {
+ private static boolean isJustBonded(String address) {
return sJustBondedDeviceAddressSet.contains(address);
}
/**
- * Clears all BT hearing devices related history stored in shared preference.
- * @param context the request context
- */
- private static synchronized void clearHistory(Context context) {
- SharedPreferences.Editor editor = getSharedPreferences(context).edit();
- editor.remove(BT_HEARING_AIDS_PAIRED_HISTORY)
- .remove(BT_HEARING_AIDS_CONNECTED_HISTORY)
- .remove(BT_HEARING_DEVICES_PAIRED_HISTORY)
- .remove(BT_HEARING_DEVICES_CONNECTED_HISTORY)
- .apply();
- }
-
- /**
* Adds current timestamp into BT hearing devices related history.
* @param context the request context
* @param type the type of history to store the data. See {@link HistoryType}.
@@ -256,7 +263,7 @@ public final class HearingAidStatsLogUtils {
}
return;
}
- if (history.peekLast() != null && isSameDay(history.peekLast(), timestamp)) {
+ if (history.peekLast() != null && isSameDay(timestamp, history.peekLast())) {
if (DEBUG) {
Log.w(TAG, "Skip this record, it's same day record");
}
@@ -275,25 +282,25 @@ public final class HearingAidStatsLogUtils {
|| BT_HEARING_DEVICES_PAIRED_HISTORY.equals(spName)) {
LinkedList<Long> history = convertToHistoryList(
getSharedPreferences(context).getString(spName, ""));
- removeRecordsBeforeTime(history, PAIRED_HISTORY_EXPIRED_TIME);
+ removeRecordsBeforeDay(history, PAIRED_HISTORY_EXPIRED_DAY);
return history;
} else if (BT_HEARING_AIDS_CONNECTED_HISTORY.equals(spName)
|| BT_HEARING_DEVICES_CONNECTED_HISTORY.equals(spName)) {
LinkedList<Long> history = convertToHistoryList(
getSharedPreferences(context).getString(spName, ""));
- removeRecordsBeforeTime(history, CONNECTED_HISTORY_EXPIRED_TIME);
+ removeRecordsBeforeDay(history, CONNECTED_HISTORY_EXPIRED_DAY);
return history;
}
return null;
}
- private static void removeRecordsBeforeTime(LinkedList<Long> history, long time) {
- if (history == null) {
+ private static void removeRecordsBeforeDay(LinkedList<Long> history, int day) {
+ if (history == null || history.isEmpty()) {
return;
}
- Long currentTime = System.currentTimeMillis();
+ long currentTime = System.currentTimeMillis();
while (history.peekFirst() != null
- && currentTime - history.peekFirst() > time) {
+ && dayDifference(currentTime, history.peekFirst()) >= day) {
history.poll();
}
}
@@ -324,11 +331,13 @@ public final class HearingAidStatsLogUtils {
* @return {@code true} if two timestamps are on the same day
*/
private static boolean isSameDay(long t1, long t2) {
- final SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd", Locale.getDefault());
- sdf.setTimeZone(TimeZone.getDefault());
- String dateString1 = sdf.format(t1);
- String dateString2 = sdf.format(t2);
- return dateString1.equals(dateString2);
+ return dayDifference(t1, t2) == 0;
+ }
+ private static long dayDifference(long t1, long t2) {
+ ZoneId zoneId = ZoneId.systemDefault();
+ LocalDate date1 = Instant.ofEpochMilli(t1).atZone(zoneId).toLocalDate();
+ LocalDate date2 = Instant.ofEpochMilli(t2).atZone(zoneId).toLocalDate();
+ return Math.abs(ChronoUnit.DAYS.between(date1, date2));
}
private static SharedPreferences getSharedPreferences(Context context) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index 581c7dea002c..50e2f9cb13f7 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -92,13 +92,13 @@ public abstract class InfoMediaManager extends MediaManager {
}
}
- protected String mPackageName;
+ @NonNull protected final String mPackageName;
private MediaDevice mCurrentConnectedDevice;
private final LocalBluetoothManager mBluetoothManager;
private final Map<String, RouteListingPreference.Item> mPreferenceItemMap =
new ConcurrentHashMap<>();
- public InfoMediaManager(
+ /* package */ InfoMediaManager(
Context context,
@NonNull String packageName,
Notification notification,
@@ -112,7 +112,7 @@ public abstract class InfoMediaManager extends MediaManager {
/** Creates an instance of InfoMediaManager. */
public static InfoMediaManager createInstance(
Context context,
- String packageName,
+ @Nullable String packageName,
Notification notification,
LocalBluetoothManager localBluetoothManager) {
@@ -148,8 +148,7 @@ public abstract class InfoMediaManager extends MediaManager {
}
private void updateRouteListingPreference() {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
- && !TextUtils.isEmpty(mPackageName)) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
RouteListingPreference routeListingPreference =
getRouteListingPreference();
Api34Impl.onRouteListingPreferenceUpdated(routeListingPreference,
@@ -162,11 +161,6 @@ public abstract class InfoMediaManager extends MediaManager {
protected abstract void startScanOnRouter();
- /**
- * Transfer MediaDevice for media without package name.
- */
- protected abstract boolean connectDeviceWithoutPackageName(@NonNull MediaDevice device);
-
protected abstract void transferToRoute(@NonNull MediaRoute2Info route);
protected abstract void selectRoute(
@@ -200,6 +194,12 @@ public abstract class InfoMediaManager extends MediaManager {
@NonNull
protected abstract List<RoutingSessionInfo> getRemoteSessions();
+ /**
+ * Returns a non-empty list containing the routing sessions associated to the target media app.
+ *
+ * <p> The first item of the list is always the {@link RoutingSessionInfo#isSystemSession()
+ * system session}, followed other remote sessions linked to the target media app.
+ */
@NonNull
protected abstract List<RoutingSessionInfo> getRoutingSessionsForPackage();
@@ -207,9 +207,6 @@ public abstract class InfoMediaManager extends MediaManager {
protected abstract RoutingSessionInfo getRoutingSessionById(@NonNull String sessionId);
@NonNull
- protected abstract List<MediaRoute2Info> getAllRoutes();
-
- @NonNull
protected abstract List<MediaRoute2Info> getAvailableRoutesFromRouter();
@NonNull
@@ -218,11 +215,7 @@ public abstract class InfoMediaManager extends MediaManager {
protected final void rebuildDeviceList() {
mMediaDevices.clear();
mCurrentConnectedDevice = null;
- if (TextUtils.isEmpty(mPackageName)) {
- buildAllRoutes();
- } else {
- buildAvailableRoutes();
- }
+ buildAvailableRoutes();
}
protected final void notifyCurrentConnectedDeviceChanged() {
@@ -250,12 +243,8 @@ public abstract class InfoMediaManager extends MediaManager {
return;
}
- if (TextUtils.isEmpty(mPackageName)) {
- connectDeviceWithoutPackageName(device);
- } else {
- device.setConnectedRecord();
- transferToRoute(device.mRouteInfo);
- }
+ device.setConnectedRecord();
+ transferToRoute(device.mRouteInfo);
}
/**
@@ -265,13 +254,8 @@ public abstract class InfoMediaManager extends MediaManager {
* @return If add device successful return {@code true}, otherwise return {@code false}
*/
boolean addDeviceToPlayMedia(MediaDevice device) {
- if (TextUtils.isEmpty(mPackageName)) {
- Log.w(TAG, "addDeviceToPlayMedia() package name is null or empty!");
- return false;
- }
-
- final RoutingSessionInfo info = getRoutingSessionInfo();
- if (info == null || !info.getSelectableRoutes().contains(device.mRouteInfo.getId())) {
+ final RoutingSessionInfo info = getActiveRoutingSession();
+ if (!info.getSelectableRoutes().contains(device.mRouteInfo.getId())) {
Log.w(TAG, "addDeviceToPlayMedia() Ignoring selecting a non-selectable device : "
+ device.getName());
return false;
@@ -281,13 +265,11 @@ public abstract class InfoMediaManager extends MediaManager {
return true;
}
- private RoutingSessionInfo getRoutingSessionInfo() {
- final List<RoutingSessionInfo> sessionInfos = getRoutingSessionsForPackage();
-
- if (sessionInfos.isEmpty()) {
- return null;
- }
- return sessionInfos.get(sessionInfos.size() - 1);
+ @NonNull
+ private RoutingSessionInfo getActiveRoutingSession() {
+ // List is never empty.
+ final List<RoutingSessionInfo> sessions = getRoutingSessionsForPackage();
+ return sessions.get(sessions.size() - 1);
}
boolean isRoutingSessionAvailableForVolumeControl() {
@@ -306,7 +288,6 @@ public abstract class InfoMediaManager extends MediaManager {
boolean preferRouteListingOrdering() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
- && !TextUtils.isEmpty(mPackageName)
&& Api34Impl.preferRouteListingOrdering(getRouteListingPreference());
}
@@ -326,13 +307,8 @@ public abstract class InfoMediaManager extends MediaManager {
* @return If device stop successful return {@code true}, otherwise return {@code false}
*/
boolean removeDeviceFromPlayMedia(MediaDevice device) {
- if (TextUtils.isEmpty(mPackageName)) {
- Log.w(TAG, "removeDeviceFromMedia() package name is null or empty!");
- return false;
- }
-
- final RoutingSessionInfo info = getRoutingSessionInfo();
- if (info == null || !info.getSelectedRoutes().contains(device.mRouteInfo.getId())) {
+ final RoutingSessionInfo info = getActiveRoutingSession();
+ if (!info.getSelectedRoutes().contains(device.mRouteInfo.getId())) {
Log.w(TAG, "removeDeviceFromMedia() Ignoring deselecting a non-deselectable device : "
+ device.getName());
return false;
@@ -346,18 +322,7 @@ public abstract class InfoMediaManager extends MediaManager {
* Release session to stop playing media on MediaDevice.
*/
boolean releaseSession() {
- if (TextUtils.isEmpty(mPackageName)) {
- Log.w(TAG, "releaseSession() package name is null or empty!");
- return false;
- }
-
- final RoutingSessionInfo sessionInfo = getRoutingSessionInfo();
- if (sessionInfo == null) {
- Log.w(TAG, "releaseSession() Ignoring release session : " + mPackageName);
- return false;
- }
-
- releaseSession(sessionInfo);
+ releaseSession(getActiveRoutingSession());
return true;
}
@@ -367,17 +332,7 @@ public abstract class InfoMediaManager extends MediaManager {
*/
@NonNull
List<MediaDevice> getSelectableMediaDevices() {
- if (TextUtils.isEmpty(mPackageName)) {
- Log.w(TAG, "getSelectableMediaDevices() package name is null or empty!");
- return Collections.emptyList();
- }
-
- final RoutingSessionInfo info = getRoutingSessionInfo();
- if (info == null) {
- Log.w(TAG, "getSelectableMediaDevices() cannot find selectable MediaDevice from : "
- + mPackageName);
- return Collections.emptyList();
- }
+ final RoutingSessionInfo info = getActiveRoutingSession();
final List<MediaDevice> deviceList = new ArrayList<>();
for (MediaRoute2Info route : getSelectableRoutes(info)) {
@@ -394,17 +349,7 @@ public abstract class InfoMediaManager extends MediaManager {
*/
@NonNull
List<MediaDevice> getDeselectableMediaDevices() {
- if (TextUtils.isEmpty(mPackageName)) {
- Log.d(TAG, "getDeselectableMediaDevices() package name is null or empty!");
- return Collections.emptyList();
- }
-
- final RoutingSessionInfo info = getRoutingSessionInfo();
- if (info == null) {
- Log.d(TAG, "getDeselectableMediaDevices() cannot find deselectable MediaDevice from : "
- + mPackageName);
- return Collections.emptyList();
- }
+ final RoutingSessionInfo info = getActiveRoutingSession();
final List<MediaDevice> deviceList = new ArrayList<>();
for (MediaRoute2Info route : getDeselectableRoutes(info)) {
@@ -422,13 +367,7 @@ public abstract class InfoMediaManager extends MediaManager {
*/
@NonNull
List<MediaDevice> getSelectedMediaDevices() {
- RoutingSessionInfo info = getRoutingSessionInfo();
-
- if (info == null) {
- Log.w(TAG, "getSelectedMediaDevices() cannot find selectable MediaDevice from : "
- + mPackageName);
- return Collections.emptyList();
- }
+ RoutingSessionInfo info = getActiveRoutingSession();
final List<MediaDevice> deviceList = new ArrayList<>();
for (MediaRoute2Info route : getSelectedRoutes(info)) {
@@ -462,20 +401,8 @@ public abstract class InfoMediaManager extends MediaManager {
* @param volume the value of volume
*/
void adjustSessionVolume(int volume) {
- if (TextUtils.isEmpty(mPackageName)) {
- Log.w(TAG, "adjustSessionVolume() package name is null or empty!");
- return;
- }
-
- final RoutingSessionInfo info = getRoutingSessionInfo();
- if (info == null) {
- Log.w(TAG, "adjustSessionVolume() can't found corresponding RoutingSession with : "
- + mPackageName);
- return;
- }
-
Log.d(TAG, "adjustSessionVolume() adjust volume: " + volume + ", with : " + mPackageName);
- setSessionVolume(info, volume);
+ setSessionVolume(getActiveRoutingSession(), volume);
}
/**
@@ -484,19 +411,7 @@ public abstract class InfoMediaManager extends MediaManager {
* @return maximum volume of the session, and return -1 if not found.
*/
public int getSessionVolumeMax() {
- if (TextUtils.isEmpty(mPackageName)) {
- Log.w(TAG, "getSessionVolumeMax() package name is null or empty!");
- return -1;
- }
-
- final RoutingSessionInfo info = getRoutingSessionInfo();
- if (info == null) {
- Log.w(TAG, "getSessionVolumeMax() can't find corresponding RoutingSession with : "
- + mPackageName);
- return -1;
- }
-
- return info.getVolumeMax();
+ return getActiveRoutingSession().getVolumeMax();
}
/**
@@ -505,34 +420,11 @@ public abstract class InfoMediaManager extends MediaManager {
* @return current volume of the session, and return -1 if not found.
*/
public int getSessionVolume() {
- if (TextUtils.isEmpty(mPackageName)) {
- Log.w(TAG, "getSessionVolume() package name is null or empty!");
- return -1;
- }
-
- final RoutingSessionInfo info = getRoutingSessionInfo();
- if (info == null) {
- Log.w(TAG, "getSessionVolume() can't find corresponding RoutingSession with : "
- + mPackageName);
- return -1;
- }
-
- return info.getVolume();
+ return getActiveRoutingSession().getVolume();
}
CharSequence getSessionName() {
- if (TextUtils.isEmpty(mPackageName)) {
- Log.w(TAG, "Unable to get session name. The package name is null or empty!");
- return null;
- }
-
- final RoutingSessionInfo info = getRoutingSessionInfo();
- if (info == null) {
- Log.w(TAG, "Unable to get session name for package: " + mPackageName);
- return null;
- }
-
- return info.getName();
+ return getActiveRoutingSession().getName();
}
@TargetApi(Build.VERSION_CODES.R)
@@ -548,20 +440,6 @@ public abstract class InfoMediaManager extends MediaManager {
// MediaRoute2Info.getType was made public on API 34, but exists since API 30.
@SuppressWarnings("NewApi")
- private void buildAllRoutes() {
- for (MediaRoute2Info route : getAllRoutes()) {
- if (DEBUG) {
- Log.d(TAG, "buildAllRoutes() route : " + route.getName() + ", volume : "
- + route.getVolume() + ", type : " + route.getType());
- }
- if (route.isSystemRoute()) {
- addMediaDevice(route);
- }
- }
- }
-
- // MediaRoute2Info.getType was made public on API 34, but exists since API 30.
- @SuppressWarnings("NewApi")
private synchronized void buildAvailableRoutes() {
for (MediaRoute2Info route : getAvailableRoutes()) {
if (DEBUG) {
@@ -572,42 +450,39 @@ public abstract class InfoMediaManager extends MediaManager {
}
}
private synchronized List<MediaRoute2Info> getAvailableRoutes() {
- List<MediaRoute2Info> infos = new ArrayList<>();
- RoutingSessionInfo routingSessionInfo = getRoutingSessionInfo();
- List<MediaRoute2Info> selectedRouteInfos = new ArrayList<>();
- if (routingSessionInfo != null) {
- selectedRouteInfos = getSelectedRoutes(routingSessionInfo);
- infos.addAll(selectedRouteInfos);
- infos.addAll(getSelectableRoutes(routingSessionInfo));
- }
- final List<MediaRoute2Info> transferableRoutes =
- getTransferableRoutes(mPackageName);
+ List<MediaRoute2Info> availableRoutes = new ArrayList<>();
+ RoutingSessionInfo activeSession = getActiveRoutingSession();
+
+ List<MediaRoute2Info> selectedRoutes = getSelectedRoutes(activeSession);
+ availableRoutes.addAll(selectedRoutes);
+ availableRoutes.addAll(getSelectableRoutes(activeSession));
+
+ final List<MediaRoute2Info> transferableRoutes = getTransferableRoutes(mPackageName);
for (MediaRoute2Info transferableRoute : transferableRoutes) {
boolean alreadyAdded = false;
- for (MediaRoute2Info mediaRoute2Info : infos) {
+ for (MediaRoute2Info mediaRoute2Info : availableRoutes) {
if (TextUtils.equals(transferableRoute.getId(), mediaRoute2Info.getId())) {
alreadyAdded = true;
break;
}
}
if (!alreadyAdded) {
- infos.add(transferableRoute);
+ availableRoutes.add(transferableRoute);
}
}
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
- && !TextUtils.isEmpty(mPackageName)) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
RouteListingPreference routeListingPreference = getRouteListingPreference();
if (routeListingPreference != null) {
final List<RouteListingPreference.Item> preferenceRouteListing =
Api34Impl.composePreferenceRouteListing(
routeListingPreference);
- infos = Api34Impl.arrangeRouteListByPreference(selectedRouteInfos,
+ availableRoutes = Api34Impl.arrangeRouteListByPreference(selectedRoutes,
getAvailableRoutesFromRouter(),
preferenceRouteListing);
}
- return Api34Impl.filterDuplicatedIds(infos);
+ return Api34Impl.filterDuplicatedIds(availableRoutes);
} else {
- return infos;
+ return availableRoutes;
}
}
@@ -679,8 +554,8 @@ public abstract class InfoMediaManager extends MediaManager {
break;
}
- if (mediaDevice != null && !TextUtils.isEmpty(mPackageName)
- && getRoutingSessionInfo().getSelectedRoutes().contains(route.getId())) {
+ if (mediaDevice != null
+ && getActiveRoutingSession().getSelectedRoutes().contains(route.getId())) {
mediaDevice.setState(STATE_SELECTED);
if (mCurrentConnectedDevice == null) {
mCurrentConnectedDevice = mediaDevice;
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java
index 97bbf12fd055..453e807947cf 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java
@@ -51,9 +51,9 @@ public class ManagerInfoMediaManager extends InfoMediaManager {
private final Executor mExecutor = Executors.newSingleThreadExecutor();
- public ManagerInfoMediaManager(
+ /* package */ ManagerInfoMediaManager(
Context context,
- String packageName,
+ @NonNull String packageName,
Notification notification,
LocalBluetoothManager localBluetoothManager) {
super(context, packageName, notification, localBluetoothManager);
@@ -86,18 +86,6 @@ public class ManagerInfoMediaManager extends InfoMediaManager {
}
@Override
- protected boolean connectDeviceWithoutPackageName(@NonNull MediaDevice device) {
- final RoutingSessionInfo info = mRouterManager.getSystemRoutingSession(null);
- if (info != null) {
- // TODO: b/279555229 - provide real user handle and package name of a caller.
- mRouterManager.transfer(
- info, device.mRouteInfo, android.os.Process.myUserHandle(), mPackageName);
- return true;
- }
- return false;
- }
-
- @Override
protected void selectRoute(@NonNull MediaRoute2Info route, @NonNull RoutingSessionInfo info) {
mRouterManager.selectRoute(info, route);
}
@@ -174,12 +162,6 @@ public class ManagerInfoMediaManager extends InfoMediaManager {
@Override
@NonNull
- protected List<MediaRoute2Info> getAllRoutes() {
- return mRouterManager.getAllRoutes();
- }
-
- @Override
- @NonNull
protected List<MediaRoute2Info> getAvailableRoutesFromRouter() {
return mRouterManager.getAvailableRoutes(mPackageName);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java
index 9d578bcc3b16..ea4de392e139 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java
@@ -41,7 +41,7 @@ import java.util.List;
NoOpInfoMediaManager(
Context context,
- String packageName,
+ @NonNull String packageName,
Notification notification,
LocalBluetoothManager localBluetoothManager) {
super(context, packageName, notification, localBluetoothManager);
@@ -58,11 +58,6 @@ import java.util.List;
}
@Override
- protected boolean connectDeviceWithoutPackageName(@NonNull MediaDevice device) {
- return false;
- }
-
- @Override
protected void transferToRoute(@NonNull MediaRoute2Info route) {
// Do nothing.
}
@@ -136,12 +131,6 @@ import java.util.List;
@NonNull
@Override
- protected List<MediaRoute2Info> getAllRoutes() {
- return Collections.emptyList();
- }
-
- @NonNull
- @Override
protected List<MediaRoute2Info> getAvailableRoutesFromRouter() {
return Collections.emptyList();
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/OWNERS b/packages/SettingsLib/src/com/android/settingslib/media/OWNERS
index 3cae39f4ea40..7467ee1c1a7c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/OWNERS
+++ b/packages/SettingsLib/src/com/android/settingslib/media/OWNERS
@@ -1,4 +1,7 @@
# Default reviewers for this and subdirectories.
+ethibodeau@google.com
+michaelmikhil@google.com
+apotapov@google.com
shaoweishen@google.com
#Android Media - For minor changes and renames only.
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
index aef09ac236f3..0f08605a50d0 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
@@ -65,9 +65,9 @@ public final class RouterInfoMediaManager extends InfoMediaManager {
};
// TODO (b/321969740): Plumb target UserHandle between UMO and RouterInfoMediaManager.
- public RouterInfoMediaManager(
+ /* package */ RouterInfoMediaManager(
Context context,
- String packageName,
+ @NonNull String packageName,
Notification notification,
LocalBluetoothManager localBluetoothManager)
throws PackageNotAvailableException {
@@ -114,17 +114,6 @@ public final class RouterInfoMediaManager extends InfoMediaManager {
}
@Override
- protected boolean connectDeviceWithoutPackageName(@NonNull MediaDevice device) {
- if (device.mRouteInfo == null) {
- return false;
- }
-
- RoutingController controller = mRouter.getSystemController();
- mRouter.transfer(controller, device.mRouteInfo);
- return true;
- }
-
- @Override
protected void transferToRoute(@NonNull MediaRoute2Info route) {
mRouter.transferTo(route);
}
@@ -241,12 +230,6 @@ public final class RouterInfoMediaManager extends InfoMediaManager {
@NonNull
@Override
- protected List<MediaRoute2Info> getAllRoutes() {
- return mRouter.getAllRoutes();
- }
-
- @NonNull
- @Override
protected List<MediaRoute2Info> getAvailableRoutesFromRouter() {
return mRouter.getRoutes();
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/OWNERS b/packages/SettingsLib/src/com/android/settingslib/volume/OWNERS
new file mode 100644
index 000000000000..75c7642fbed1
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/OWNERS
@@ -0,0 +1,5 @@
+apotapov@google.com
+ethibodeau@google.com
+michaelmikhil@google.com
+
+juliacr@google.com #{LAST_RESORT_SUGGESTION} \ No newline at end of file
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
index f729c04fb849..6851997ac323 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
@@ -21,10 +21,12 @@ import android.media.AudioManager
import android.media.AudioManager.OnCommunicationDeviceChangedListener
import androidx.concurrent.futures.DirectExecutor
import com.android.internal.util.ConcurrentUtils
-import com.android.settingslib.volume.shared.AudioManagerIntentsReceiver
+import com.android.settingslib.volume.shared.AudioManagerEventsReceiver
+import com.android.settingslib.volume.shared.model.AudioManagerEvent
import com.android.settingslib.volume.shared.model.AudioStream
import com.android.settingslib.volume.shared.model.AudioStreamModel
import com.android.settingslib.volume.shared.model.RingerMode
+import com.android.settingslib.volume.shared.model.StreamAudioManagerEvent
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
@@ -33,6 +35,7 @@ import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
@@ -72,7 +75,7 @@ interface AudioRepository {
}
class AudioRepositoryImpl(
- private val audioManagerIntentsReceiver: AudioManagerIntentsReceiver,
+ private val audioManagerEventsReceiver: AudioManagerEventsReceiver,
private val audioManager: AudioManager,
private val backgroundCoroutineContext: CoroutineContext,
private val coroutineScope: CoroutineScope,
@@ -89,8 +92,8 @@ class AudioRepositoryImpl(
.stateIn(coroutineScope, SharingStarted.WhileSubscribed(), audioManager.mode)
override val ringerMode: StateFlow<RingerMode> =
- audioManagerIntentsReceiver.intents
- .filter { AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION == it.action }
+ audioManagerEventsReceiver.events
+ .filterIsInstance(AudioManagerEvent.InternalRingerModeChanged::class)
.map { RingerMode(audioManager.ringerModeInternal) }
.flowOn(backgroundCoroutineContext)
.stateIn(
@@ -120,7 +123,14 @@ class AudioRepositoryImpl(
)
override suspend fun getAudioStream(audioStream: AudioStream): Flow<AudioStreamModel> {
- return audioManagerIntentsReceiver.intents
+ return audioManagerEventsReceiver.events
+ .filter {
+ if (it is StreamAudioManagerEvent) {
+ it.audioStream == audioStream
+ } else {
+ true
+ }
+ }
.map { getCurrentAudioStream(audioStream) }
.flowOn(backgroundCoroutineContext)
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/LocalMediaRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/LocalMediaRepository.kt
index aa9ae76c66c4..298dd71e555e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/LocalMediaRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/LocalMediaRepository.kt
@@ -15,13 +15,13 @@
*/
package com.android.settingslib.volume.data.repository
-import android.media.AudioManager
import android.media.MediaRouter2Manager
import android.media.RoutingSessionInfo
import com.android.settingslib.media.LocalMediaManager
import com.android.settingslib.media.MediaDevice
import com.android.settingslib.volume.data.model.RoutingSession
-import com.android.settingslib.volume.shared.AudioManagerIntentsReceiver
+import com.android.settingslib.volume.shared.AudioManagerEventsReceiver
+import com.android.settingslib.volume.shared.model.AudioManagerEvent
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
@@ -29,7 +29,7 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.callbackFlow
-import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapNotNull
@@ -54,7 +54,7 @@ interface LocalMediaRepository {
}
class LocalMediaRepositoryImpl(
- audioManagerIntentsReceiver: AudioManagerIntentsReceiver,
+ audioManagerEventsReceiver: AudioManagerEventsReceiver,
private val localMediaManager: LocalMediaManager,
private val mediaRouter2Manager: MediaRouter2Manager,
coroutineScope: CoroutineScope,
@@ -62,9 +62,9 @@ class LocalMediaRepositoryImpl(
) : LocalMediaRepository {
private val devicesChanges =
- audioManagerIntentsReceiver.intents.filter {
- AudioManager.STREAM_DEVICES_CHANGED_ACTION == it.action
- }
+ audioManagerEventsReceiver.events.filterIsInstance(
+ AudioManagerEvent.StreamDevicesChanged::class
+ )
private val mediaDevicesUpdates: Flow<DevicesUpdate> =
callbackFlow {
val callback =
@@ -109,6 +109,7 @@ class LocalMediaRepositoryImpl(
override val currentConnectedDevice: StateFlow<MediaDevice?> =
merge(devicesChanges, mediaDevicesUpdates)
.map { localMediaManager.currentConnectedDevice }
+ .onStart { emit(localMediaManager.currentConnectedDevice) }
.stateIn(
coroutineScope,
SharingStarted.WhileSubscribed(),
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/MediaControllerRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/MediaControllerRepository.kt
index 6925c71fc68f..7c231d1fad4e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/MediaControllerRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/MediaControllerRepository.kt
@@ -16,21 +16,19 @@
package com.android.settingslib.volume.data.repository
-import android.content.Intent
-import android.media.AudioManager
import android.media.session.MediaController
import android.media.session.MediaSessionManager
-import android.media.session.PlaybackState
import com.android.settingslib.bluetooth.LocalBluetoothManager
import com.android.settingslib.bluetooth.headsetAudioModeChanges
import com.android.settingslib.media.session.activeMediaChanges
-import com.android.settingslib.volume.shared.AudioManagerIntentsReceiver
+import com.android.settingslib.volume.shared.AudioManagerEventsReceiver
+import com.android.settingslib.volume.shared.model.AudioManagerEvent
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.onStart
@@ -44,7 +42,7 @@ interface MediaControllerRepository {
}
class MediaControllerRepositoryImpl(
- audioManagerIntentsReceiver: AudioManagerIntentsReceiver,
+ audioManagerEventsReceiver: AudioManagerEventsReceiver,
private val mediaSessionManager: MediaSessionManager,
localBluetoothManager: LocalBluetoothManager?,
coroutineScope: CoroutineScope,
@@ -52,9 +50,9 @@ class MediaControllerRepositoryImpl(
) : MediaControllerRepository {
private val devicesChanges =
- audioManagerIntentsReceiver.intents.filter {
- AudioManager.STREAM_DEVICES_CHANGED_ACTION == it.action
- }
+ audioManagerEventsReceiver.events.filterIsInstance(
+ AudioManagerEvent.StreamDevicesChanged::class
+ )
override val activeLocalMediaController: StateFlow<MediaController?> =
combine(
@@ -63,7 +61,7 @@ class MediaControllerRepositoryImpl(
},
localBluetoothManager?.headsetAudioModeChanges?.onStart { emit(Unit) }
?: flowOf(null),
- devicesChanges.onStart { emit(Intent()) },
+ devicesChanges.onStart { emit(AudioManagerEvent.StreamDevicesChanged) },
) { controllers, _, _ ->
controllers?.let(::findLocalMediaController)
}
@@ -98,9 +96,4 @@ class MediaControllerRepositoryImpl(
}
return localController
}
-
- private companion object {
- val inactivePlaybackStates =
- setOf(PlaybackState.STATE_STOPPED, PlaybackState.STATE_NONE, PlaybackState.STATE_ERROR)
- }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/shared/AudioManagerIntentsReceiver.kt b/packages/SettingsLib/src/com/android/settingslib/volume/shared/AudioManagerEventsReceiver.kt
index 9fa4c86cdea1..13ed9a802318 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/shared/AudioManagerIntentsReceiver.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/shared/AudioManagerEventsReceiver.kt
@@ -21,6 +21,9 @@ import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.media.AudioManager
+import android.util.Log
+import com.android.settingslib.volume.shared.model.AudioManagerEvent
+import com.android.settingslib.volume.shared.model.AudioStream
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.SharedFlow
@@ -28,19 +31,20 @@ import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.launch
-/** Exposes [AudioManager] intents as a observable shared flow. */
-interface AudioManagerIntentsReceiver {
+/** Exposes [AudioManager] events as a observable shared flow. */
+interface AudioManagerEventsReceiver {
- val intents: SharedFlow<Intent>
+ val events: SharedFlow<AudioManagerEvent>
}
-class AudioManagerIntentsReceiverImpl(
+class AudioManagerEventsReceiverImpl(
private val context: Context,
coroutineScope: CoroutineScope,
-) : AudioManagerIntentsReceiver {
+) : AudioManagerEventsReceiver {
private val allActions: Collection<String>
get() =
@@ -52,7 +56,7 @@ class AudioManagerIntentsReceiverImpl(
AudioManager.STREAM_DEVICES_CHANGED_ACTION,
)
- override val intents: SharedFlow<Intent> =
+ override val events: SharedFlow<AudioManagerEvent> =
callbackFlow {
val receiver =
object : BroadcastReceiver() {
@@ -73,5 +77,34 @@ class AudioManagerIntentsReceiverImpl(
}
.filterNotNull()
.filter { intent -> allActions.contains(intent.action) }
+ .mapNotNull { it.toAudioManagerEvent() }
.shareIn(coroutineScope, SharingStarted.WhileSubscribed())
+
+ private fun Intent.toAudioManagerEvent(): AudioManagerEvent? {
+ when (action) {
+ AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION ->
+ return AudioManagerEvent.InternalRingerModeChanged
+ AudioManager.STREAM_DEVICES_CHANGED_ACTION ->
+ return AudioManagerEvent.StreamDevicesChanged
+ AudioManager.MASTER_MUTE_CHANGED_ACTION ->
+ return AudioManagerEvent.StreamMasterMuteChanged
+ }
+
+ val audioStreamType: Int =
+ getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, AudioManager.ERROR)
+ if (audioStreamType == AudioManager.ERROR) {
+ Log.e(
+ "AudioManagerIntentsReceiver",
+ "Intent doesn't have AudioManager.EXTRA_VOLUME_STREAM_TYPE extra",
+ )
+ return null
+ }
+ val audioStream = AudioStream(audioStreamType)
+ return when (action) {
+ AudioManager.STREAM_MUTE_CHANGED_ACTION ->
+ AudioManagerEvent.StreamMuteChanged(audioStream)
+ AudioManager.VOLUME_CHANGED_ACTION -> AudioManagerEvent.StreamVolumeChanged(audioStream)
+ else -> null
+ }
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/AudioManagerEvent.kt b/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/AudioManagerEvent.kt
new file mode 100644
index 000000000000..e19896bc5e87
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/AudioManagerEvent.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.volume.shared.model
+
+/** Model events happening with the [android.media.AudioManager]. */
+sealed interface AudioManagerEvent {
+
+ data class StreamMuteChanged(override val audioStream: AudioStream) : StreamAudioManagerEvent
+
+ data class StreamVolumeChanged(override val audioStream: AudioStream) : StreamAudioManagerEvent
+
+ data object StreamMasterMuteChanged : AudioManagerEvent
+
+ data object InternalRingerModeChanged : AudioManagerEvent
+
+ data object StreamDevicesChanged : AudioManagerEvent
+}
+
+/** [AudioManagerEvent] that happens for a specific [AudioStream]. */
+sealed interface StreamAudioManagerEvent : AudioManagerEvent {
+
+ val audioStream: AudioStream
+}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java
index 213a66e546ab..1ad7d4930ecc 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java
@@ -22,21 +22,30 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import android.content.pm.ApplicationInfo;
+import android.os.Flags;
import android.os.UserManager;
+import android.platform.test.flag.junit.SetFlagsRule;
+
+import androidx.test.core.app.ApplicationProvider;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@RunWith(JUnit4.class)
public class ApplicationsStateTest {
+ private static final int APP_ENTRY_ID = 1;
private ApplicationsState.AppEntry mEntry;
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Before
public void setUp() {
- mEntry = mock(ApplicationsState.AppEntry.class);
- mEntry.info = mock(ApplicationInfo.class);
+ mEntry = new ApplicationsState.AppEntry(
+ ApplicationProvider.getApplicationContext(),
+ mock(ApplicationInfo.class),
+ APP_ENTRY_ID);
}
@Test
@@ -310,6 +319,8 @@ public class ApplicationsStateTest {
@Test
public void testPrivateProfileFilterDisplaysCorrectApps() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+
mEntry.showInPersonalTab = true;
mEntry.mProfileType = UserManager.USER_TYPE_FULL_SYSTEM;
assertThat(ApplicationsState.FILTER_PERSONAL.filterApp(mEntry)).isTrue();
@@ -320,4 +331,14 @@ public class ApplicationsStateTest {
assertThat(ApplicationsState.FILTER_PERSONAL.filterApp(mEntry)).isFalse();
assertThat(ApplicationsState.FILTER_PRIVATE_PROFILE.filterApp(mEntry)).isTrue();
}
+
+ @Test
+ public void testPrivateProfileFilterDisplaysCorrectAppsWhenFlagDisabled() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+
+ mEntry.showInPersonalTab = false;
+ mEntry.mProfileType = UserManager.USER_TYPE_PROFILE_PRIVATE;
+ assertThat(ApplicationsState.FILTER_PERSONAL.filterApp(mEntry)).isFalse();
+ assertThat(ApplicationsState.FILTER_PRIVATE_PROFILE.filterApp(mEntry)).isFalse();
+ }
}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/OWNERS b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/OWNERS
new file mode 100644
index 000000000000..b7ade19dbb1e
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/OWNERS
@@ -0,0 +1 @@
+include /packages/SettingsLib/src/com/android/settingslib/volume/OWNERS \ No newline at end of file
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
index 48b04db5b50b..9ddf876be68e 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
@@ -20,7 +20,8 @@ import android.media.AudioDeviceInfo
import android.media.AudioManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.settingslib.volume.shared.FakeAudioManagerIntentsReceiver
+import com.android.settingslib.volume.shared.FakeAudioManagerEventsReceiver
+import com.android.settingslib.volume.shared.model.AudioManagerEvent
import com.android.settingslib.volume.shared.model.AudioStream
import com.android.settingslib.volume.shared.model.AudioStreamModel
import com.android.settingslib.volume.shared.model.RingerMode
@@ -46,7 +47,6 @@ import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
-@Suppress("UnspecifiedRegisterReceiverFlag")
@RunWith(AndroidJUnit4::class)
class AudioRepositoryTest {
@@ -59,7 +59,7 @@ class AudioRepositoryTest {
@Mock private lateinit var audioManager: AudioManager
@Mock private lateinit var communicationDevice: AudioDeviceInfo
- private val intentsReceiver = FakeAudioManagerIntentsReceiver()
+ private val eventsReceiver = FakeAudioManagerEventsReceiver()
private val volumeByStream: MutableMap<Int, Int> = mutableMapOf()
private val isAffectedByRingerModeByStream: MutableMap<Int, Boolean> = mutableMapOf()
private val isMuteByStream: MutableMap<Int, Boolean> = mutableMapOf()
@@ -77,12 +77,14 @@ class AudioRepositoryTest {
`when`(audioManager.getStreamMaxVolume(anyInt())).thenReturn(MAX_VOLUME)
`when`(audioManager.ringerModeInternal).thenReturn(AudioManager.RINGER_MODE_NORMAL)
`when`(audioManager.setStreamVolume(anyInt(), anyInt(), anyInt())).then {
- volumeByStream[it.arguments[0] as Int] = it.arguments[1] as Int
- triggerIntent(AudioManager.ACTION_VOLUME_CHANGED)
+ val streamType = it.arguments[1] as Int
+ volumeByStream[it.arguments[0] as Int] = streamType
+ triggerEvent(AudioManagerEvent.StreamVolumeChanged(AudioStream(streamType)))
}
`when`(audioManager.adjustStreamVolume(anyInt(), anyInt(), anyInt())).then {
- isMuteByStream[it.arguments[0] as Int] = it.arguments[2] == AudioManager.ADJUST_MUTE
- triggerIntent(AudioManager.STREAM_MUTE_CHANGED_ACTION)
+ val streamType = it.arguments[0] as Int
+ isMuteByStream[streamType] = it.arguments[2] == AudioManager.ADJUST_MUTE
+ triggerEvent(AudioManagerEvent.StreamMuteChanged(AudioStream(streamType)))
}
`when`(audioManager.getStreamVolume(anyInt())).thenAnswer {
volumeByStream.getOrDefault(it.arguments[0] as Int, 0)
@@ -96,7 +98,7 @@ class AudioRepositoryTest {
underTest =
AudioRepositoryImpl(
- intentsReceiver,
+ eventsReceiver,
audioManager,
testScope.testScheduler,
testScope.backgroundScope,
@@ -125,7 +127,7 @@ class AudioRepositoryTest {
runCurrent()
`when`(audioManager.ringerModeInternal).thenReturn(AudioManager.RINGER_MODE_SILENT)
- triggerIntent(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION)
+ triggerEvent(AudioManagerEvent.InternalRingerModeChanged)
runCurrent()
assertThat(modes)
@@ -267,8 +269,8 @@ class AudioRepositoryTest {
modeListenerCaptor.value.onModeChanged(mode)
}
- private fun triggerIntent(action: String) {
- testScope.launch { intentsReceiver.triggerIntent(action) }
+ private fun triggerEvent(event: AudioManagerEvent) {
+ testScope.launch { eventsReceiver.triggerEvent(event) }
}
private companion object {
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/LocalMediaRepositoryImplTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/LocalMediaRepositoryImplTest.kt
index dc9ea10a1074..2d12dae36ff1 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/LocalMediaRepositoryImplTest.kt
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/LocalMediaRepositoryImplTest.kt
@@ -23,7 +23,7 @@ import androidx.test.filters.SmallTest
import com.android.settingslib.media.LocalMediaManager
import com.android.settingslib.media.MediaDevice
import com.android.settingslib.volume.data.model.RoutingSession
-import com.android.settingslib.volume.shared.FakeAudioManagerIntentsReceiver
+import com.android.settingslib.volume.shared.FakeAudioManagerEventsReceiver
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn
@@ -58,7 +58,7 @@ class LocalMediaRepositoryImplTest {
@Captor
private lateinit var deviceCallbackCaptor: ArgumentCaptor<LocalMediaManager.DeviceCallback>
- private val intentsReceiver = FakeAudioManagerIntentsReceiver()
+ private val eventsReceiver = FakeAudioManagerEventsReceiver()
private val testScope = TestScope()
private lateinit var underTest: LocalMediaRepository
@@ -69,7 +69,7 @@ class LocalMediaRepositoryImplTest {
underTest =
LocalMediaRepositoryImpl(
- intentsReceiver,
+ eventsReceiver,
localMediaManager,
mediaRouter2Manager,
testScope.backgroundScope,
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/MediaControllerRepositoryImplTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/MediaControllerRepositoryImplTest.kt
index 7bd43d2cf8ab..f3d17141334e 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/MediaControllerRepositoryImplTest.kt
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/MediaControllerRepositoryImplTest.kt
@@ -16,7 +16,6 @@
package com.android.settingslib.volume.data.repository
-import android.media.AudioManager
import android.media.session.MediaController
import android.media.session.MediaController.PlaybackInfo
import android.media.session.MediaSessionManager
@@ -26,7 +25,8 @@ import androidx.test.filters.SmallTest
import com.android.settingslib.bluetooth.BluetoothCallback
import com.android.settingslib.bluetooth.BluetoothEventManager
import com.android.settingslib.bluetooth.LocalBluetoothManager
-import com.android.settingslib.volume.shared.FakeAudioManagerIntentsReceiver
+import com.android.settingslib.volume.shared.FakeAudioManagerEventsReceiver
+import com.android.settingslib.volume.shared.model.AudioManagerEvent
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn
@@ -66,7 +66,7 @@ class MediaControllerRepositoryImplTest {
@Mock private lateinit var localPlaybackInfo: PlaybackInfo
private val testScope = TestScope()
- private val intentsReceiver = FakeAudioManagerIntentsReceiver()
+ private val eventsReceiver = FakeAudioManagerEventsReceiver()
private lateinit var underTest: MediaControllerRepository
@@ -94,7 +94,7 @@ class MediaControllerRepositoryImplTest {
underTest =
MediaControllerRepositoryImpl(
- intentsReceiver,
+ eventsReceiver,
mediaSessionManager,
localBluetoothManager,
testScope.backgroundScope,
@@ -121,7 +121,7 @@ class MediaControllerRepositoryImplTest {
.launchIn(backgroundScope)
runCurrent()
- intentsReceiver.triggerIntent(AudioManager.STREAM_DEVICES_CHANGED_ACTION)
+ eventsReceiver.triggerEvent(AudioManagerEvent.StreamDevicesChanged)
triggerOnAudioModeChanged()
runCurrent()
@@ -146,7 +146,7 @@ class MediaControllerRepositoryImplTest {
.launchIn(backgroundScope)
runCurrent()
- intentsReceiver.triggerIntent(AudioManager.STREAM_DEVICES_CHANGED_ACTION)
+ eventsReceiver.triggerEvent(AudioManagerEvent.StreamDevicesChanged)
triggerOnAudioModeChanged()
runCurrent()
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/shared/AudioManagerEventsReceiverTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/shared/AudioManagerEventsReceiverTest.kt
new file mode 100644
index 000000000000..35ee8287d52f
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/shared/AudioManagerEventsReceiverTest.kt
@@ -0,0 +1,139 @@
+/*
+ * 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.shared
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.media.AudioManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.volume.shared.model.AudioManagerEvent
+import com.android.settingslib.volume.shared.model.AudioStream
+import com.google.common.truth.Expect
+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.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@Suppress("UnspecifiedRegisterReceiverFlag")
+@RunWith(AndroidJUnit4::class)
+class AudioManagerEventsReceiverTest {
+
+ @JvmField @Rule val expect = Expect.create()
+ private val testScope = TestScope()
+
+ @Mock private lateinit var context: Context
+ @Captor private lateinit var receiverCaptor: ArgumentCaptor<BroadcastReceiver>
+
+ private lateinit var underTest: AudioManagerEventsReceiver
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+
+ underTest = AudioManagerEventsReceiverImpl(context, testScope.backgroundScope)
+ }
+
+ @Test
+ fun validIntent_translatedToEvent() {
+ testScope.runTest {
+ val events = mutableListOf<AudioManagerEvent>()
+ underTest.events.onEach { events.add(it) }.launchIn(backgroundScope)
+ runCurrent()
+
+ triggerIntent(
+ Intent(AudioManager.STREAM_MUTE_CHANGED_ACTION).apply {
+ putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, AudioManager.STREAM_SYSTEM)
+ }
+ )
+ triggerIntent(
+ Intent(AudioManager.VOLUME_CHANGED_ACTION).apply {
+ putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, AudioManager.STREAM_SYSTEM)
+ }
+ )
+ triggerIntent(Intent(AudioManager.MASTER_MUTE_CHANGED_ACTION))
+ triggerIntent(Intent(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION))
+ triggerIntent(Intent(AudioManager.STREAM_DEVICES_CHANGED_ACTION))
+ runCurrent()
+
+ expect
+ .that(events)
+ .containsExactly(
+ AudioManagerEvent.StreamMuteChanged(
+ AudioStream(AudioManager.STREAM_SYSTEM),
+ ),
+ AudioManagerEvent.StreamVolumeChanged(
+ AudioStream(AudioManager.STREAM_SYSTEM),
+ ),
+ AudioManagerEvent.StreamMasterMuteChanged,
+ AudioManagerEvent.InternalRingerModeChanged,
+ AudioManagerEvent.StreamDevicesChanged,
+ )
+ }
+ }
+
+ @Test
+ fun streamAudioManagerEvent_withoutAudioStream_areSkipped() {
+ testScope.runTest {
+ val events = mutableListOf<AudioManagerEvent>()
+ underTest.events.onEach { events.add(it) }.launchIn(backgroundScope)
+ runCurrent()
+
+ triggerIntent(Intent(AudioManager.STREAM_MUTE_CHANGED_ACTION))
+ triggerIntent(Intent(AudioManager.VOLUME_CHANGED_ACTION))
+ runCurrent()
+
+ expect.that(events).isEmpty()
+ }
+ }
+
+ @Test
+ fun invalidIntents_areSkipped() {
+ testScope.runTest {
+ val events = mutableListOf<AudioManagerEvent>()
+ underTest.events.onEach { events.add(it) }.launchIn(backgroundScope)
+ runCurrent()
+
+ triggerIntent(null)
+ triggerIntent(Intent())
+ triggerIntent(Intent("invalid_action"))
+ runCurrent()
+
+ expect.that(events).isEmpty()
+ }
+ }
+
+ private fun triggerIntent(intent: Intent?) {
+ verify(context).registerReceiver(receiverCaptor.capture(), any())
+ receiverCaptor.value.onReceive(context, intent)
+ }
+}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/shared/FakeAudioManagerIntentsReceiver.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/shared/FakeAudioManagerEventsReceiver.kt
index 530690a5faa9..b742df7afc46 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/shared/FakeAudioManagerIntentsReceiver.kt
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/shared/FakeAudioManagerEventsReceiver.kt
@@ -16,21 +16,17 @@
package com.android.settingslib.volume.shared
-import android.content.Intent
+import com.android.settingslib.volume.shared.model.AudioManagerEvent
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.asSharedFlow
-class FakeAudioManagerIntentsReceiver : AudioManagerIntentsReceiver {
+class FakeAudioManagerEventsReceiver : AudioManagerEventsReceiver {
- private val mutableIntents = MutableSharedFlow<Intent>()
- override val intents: SharedFlow<Intent> = mutableIntents.asSharedFlow()
+ private val mutableIntents = MutableSharedFlow<AudioManagerEvent>()
+ override val events: SharedFlow<AudioManagerEvent> = mutableIntents.asSharedFlow()
- suspend fun triggerIntent(intent: Intent) {
- mutableIntents.emit(intent)
- }
-
- suspend fun triggerIntent(action: String) {
- triggerIntent(Intent(action))
+ suspend fun triggerEvent(event: AudioManagerEvent) {
+ mutableIntents.emit(event)
}
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/RestrictedPreferenceHelperTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/RestrictedPreferenceHelperTest.java
index 701f00865da9..7ad54e187ae5 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/RestrictedPreferenceHelperTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/RestrictedPreferenceHelperTest.java
@@ -17,6 +17,7 @@
package com.android.settingslib;
import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
import static org.mockito.Mockito.doReturn;
@@ -28,6 +29,7 @@ import static org.mockito.Mockito.when;
import android.app.admin.DevicePolicyManager;
import android.app.admin.DevicePolicyResourcesManager;
import android.content.Context;
+import android.content.Intent;
import android.view.View;
import android.widget.TextView;
@@ -87,6 +89,19 @@ public class RestrictedPreferenceHelperTest {
}
@Test
+ public void bindPreference_disabledByEcm_shouldDisplayDisabledSummary() {
+ final TextView summaryView = mock(TextView.class, RETURNS_DEEP_STUBS);
+ when(mViewHolder.itemView.findViewById(android.R.id.summary))
+ .thenReturn(summaryView);
+
+ mHelper.setDisabledByEcm(mock(Intent.class));
+ mHelper.onBindViewHolder(mViewHolder);
+
+ verify(mPreference).setSummary(R.string.disabled_by_app_ops_text);
+ verify(summaryView, never()).setVisibility(View.GONE);
+ }
+
+ @Test
public void bindPreference_notDisabled_shouldNotHideSummary() {
final TextView summaryView = mock(TextView.class, RETURNS_DEEP_STUBS);
when(mViewHolder.itemView.findViewById(android.R.id.summary))
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidStatsLogUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidStatsLogUtilsTest.java
index 8a75bdc7bc35..bd5a022e44e5 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidStatsLogUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidStatsLogUtilsTest.java
@@ -16,6 +16,9 @@
package com.android.settingslib.bluetooth;
+import static com.android.settingslib.bluetooth.HearingAidStatsLogUtils.CONNECTED_HISTORY_EXPIRED_DAY;
+import static com.android.settingslib.bluetooth.HearingAidStatsLogUtils.PAIRED_HISTORY_EXPIRED_DAY;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.when;
@@ -34,6 +37,9 @@ import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.robolectric.RobolectricTestRunner;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.ZoneId;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.concurrent.TimeUnit;
@@ -84,8 +90,8 @@ public class HearingAidStatsLogUtilsTest {
@Test
public void addCurrentTimeToHistory_addNewData() {
- final long currentTime = System.currentTimeMillis();
- final long lastData = currentTime - TimeUnit.DAYS.toMillis(2);
+ final long todayStartOfDay = convertToStartOfDayTime(System.currentTimeMillis());
+ final long lastData = todayStartOfDay - TimeUnit.DAYS.toMillis(6);
HearingAidStatsLogUtils.addToHistory(mContext, TEST_HISTORY_TYPE, lastData);
HearingAidStatsLogUtils.addCurrentTimeToHistory(mContext, TEST_HISTORY_TYPE);
@@ -96,22 +102,21 @@ public class HearingAidStatsLogUtilsTest {
}
@Test
public void addCurrentTimeToHistory_skipSameDateData() {
- final long currentTime = System.currentTimeMillis();
- final long lastData = currentTime - 1;
- HearingAidStatsLogUtils.addToHistory(mContext, TEST_HISTORY_TYPE, lastData);
+ final long todayStartOfDay = convertToStartOfDayTime(System.currentTimeMillis());
+ HearingAidStatsLogUtils.addToHistory(mContext, TEST_HISTORY_TYPE, todayStartOfDay);
HearingAidStatsLogUtils.addCurrentTimeToHistory(mContext, TEST_HISTORY_TYPE);
LinkedList<Long> history = HearingAidStatsLogUtils.getHistory(mContext, TEST_HISTORY_TYPE);
assertThat(history).isNotNull();
assertThat(history.size()).isEqualTo(1);
- assertThat(history.getFirst()).isEqualTo(lastData);
+ assertThat(history.getFirst()).isEqualTo(todayStartOfDay);
}
@Test
public void addCurrentTimeToHistory_cleanUpExpiredData() {
- final long currentTime = System.currentTimeMillis();
- final long expiredData = currentTime - TimeUnit.DAYS.toMillis(10);
+ final long todayStartOfDay = convertToStartOfDayTime(System.currentTimeMillis());
+ final long expiredData = todayStartOfDay - TimeUnit.DAYS.toMillis(6) - 1;
HearingAidStatsLogUtils.addToHistory(mContext, TEST_HISTORY_TYPE, expiredData);
HearingAidStatsLogUtils.addCurrentTimeToHistory(mContext, TEST_HISTORY_TYPE);
@@ -121,4 +126,71 @@ public class HearingAidStatsLogUtilsTest {
assertThat(history.size()).isEqualTo(1);
assertThat(history.getFirst()).isNotEqualTo(expiredData);
}
+
+ @Test
+ public void getUserCategory_hearingAidsUser() {
+ prepareHearingAidsUserHistory();
+
+ assertThat(HearingAidStatsLogUtils.getUserCategory(mContext)).isEqualTo(
+ HearingAidStatsLogUtils.CATEGORY_HEARING_AIDS);
+ }
+
+ @Test
+ public void getUserCategory_newHearingAidsUser() {
+ prepareHearingAidsUserHistory();
+ prepareNewUserHistory();
+
+ assertThat(HearingAidStatsLogUtils.getUserCategory(mContext)).isEqualTo(
+ HearingAidStatsLogUtils.CATEGORY_NEW_HEARING_AIDS);
+ }
+
+ @Test
+ public void getUserCategory_hearingDevicesUser() {
+ prepareHearingDevicesUserHistory();
+
+ assertThat(HearingAidStatsLogUtils.getUserCategory(mContext)).isEqualTo(
+ HearingAidStatsLogUtils.CATEGORY_HEARING_DEVICES);
+ }
+
+ @Test
+ public void getUserCategory_newHearingDevicesUser() {
+ prepareHearingDevicesUserHistory();
+ prepareNewUserHistory();
+
+ assertThat(HearingAidStatsLogUtils.getUserCategory(mContext)).isEqualTo(
+ HearingAidStatsLogUtils.CATEGORY_NEW_HEARING_DEVICES);
+ }
+
+ private long convertToStartOfDayTime(long timestamp) {
+ ZoneId zoneId = ZoneId.systemDefault();
+ LocalDate date = Instant.ofEpochMilli(timestamp).atZone(zoneId).toLocalDate();
+ return date.atStartOfDay(zoneId).toInstant().toEpochMilli();
+ }
+
+ private void prepareHearingAidsUserHistory() {
+ final long todayStartOfDay = convertToStartOfDayTime(System.currentTimeMillis());
+ for (int i = CONNECTED_HISTORY_EXPIRED_DAY - 1; i >= 0; i--) {
+ final long data = todayStartOfDay - TimeUnit.DAYS.toMillis(i);
+ HearingAidStatsLogUtils.addToHistory(mContext,
+ HearingAidStatsLogUtils.HistoryType.TYPE_HEARING_AIDS_CONNECTED, data);
+ }
+ }
+
+ private void prepareHearingDevicesUserHistory() {
+ final long todayStartOfDay = convertToStartOfDayTime(System.currentTimeMillis());
+ for (int i = CONNECTED_HISTORY_EXPIRED_DAY - 1; i >= 0; i--) {
+ final long data = todayStartOfDay - TimeUnit.DAYS.toMillis(i);
+ HearingAidStatsLogUtils.addToHistory(mContext,
+ HearingAidStatsLogUtils.HistoryType.TYPE_HEARING_DEVICES_CONNECTED, data);
+ }
+ }
+
+ private void prepareNewUserHistory() {
+ final long todayStartOfDay = convertToStartOfDayTime(System.currentTimeMillis());
+ final long data = todayStartOfDay - TimeUnit.DAYS.toMillis(PAIRED_HISTORY_EXPIRED_DAY - 1);
+ HearingAidStatsLogUtils.addToHistory(mContext,
+ HearingAidStatsLogUtils.HistoryType.TYPE_HEARING_AIDS_PAIRED, data);
+ HearingAidStatsLogUtils.addToHistory(mContext,
+ HearingAidStatsLogUtils.HistoryType.TYPE_HEARING_DEVICES_PAIRED, data);
+ }
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
index f0330c46315c..c159d5ee37f0 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
@@ -30,6 +30,7 @@ import static com.android.settingslib.media.LocalMediaManager.MediaDeviceState.S
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -124,7 +125,11 @@ public class InfoMediaManagerTest {
@Test
public void stopScan_startFirst_callsUnregister() {
+ RoutingSessionInfo sessionInfo = mock(RoutingSessionInfo.class);
mInfoMediaManager.mRouterManager = mRouterManager;
+ // Since test is running in Robolectric, return a fake session to avoid NPE.
+ when(mRouterManager.getRoutingSessions(anyString())).thenReturn(List.of(sessionInfo));
+
mInfoMediaManager.startScan();
mInfoMediaManager.stopScan();
@@ -212,28 +217,6 @@ public class InfoMediaManagerTest {
}
@Test
- public void onRouteAdded_buildAllRoutes_shouldAddMediaDevice() {
- final MediaRoute2Info info = mock(MediaRoute2Info.class);
- when(info.getId()).thenReturn(TEST_ID);
- when(info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
- when(info.isSystemRoute()).thenReturn(true);
-
- final List<MediaRoute2Info> routes = new ArrayList<>();
- routes.add(info);
- mShadowRouter2Manager.setAllRoutes(routes);
-
- final MediaDevice mediaDevice = mInfoMediaManager.findMediaDevice(TEST_ID);
- assertThat(mediaDevice).isNull();
-
- mInfoMediaManager.mPackageName = "";
- mInfoMediaManager.mMediaRouterCallback.onRoutesUpdated();
-
- final MediaDevice infoDevice = mInfoMediaManager.mMediaDevices.get(0);
- assertThat(infoDevice.getId()).isEqualTo(TEST_ID);
- assertThat(mInfoMediaManager.mMediaDevices).hasSize(routes.size());
- }
-
- @Test
public void onPreferredFeaturesChanged_samePackageName_shouldAddMediaDevice() {
final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
final RoutingSessionInfo sessionInfo = mock(RoutingSessionInfo.class);
@@ -436,29 +419,6 @@ public class InfoMediaManagerTest {
}
@Test
- public void onRoutesChanged_buildAllRoutes_shouldAddMediaDevice() {
- final MediaRoute2Info info = mock(MediaRoute2Info.class);
- when(info.getId()).thenReturn(TEST_ID);
- when(info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
- when(info.isSystemRoute()).thenReturn(true);
- when(info.getDeduplicationIds()).thenReturn(Set.of());
-
- final List<MediaRoute2Info> routes = new ArrayList<>();
- routes.add(info);
- mShadowRouter2Manager.setAllRoutes(routes);
-
- final MediaDevice mediaDevice = mInfoMediaManager.findMediaDevice(TEST_ID);
- assertThat(mediaDevice).isNull();
-
- mInfoMediaManager.mPackageName = "";
- mInfoMediaManager.mMediaRouterCallback.onRoutesUpdated();
-
- final MediaDevice infoDevice = mInfoMediaManager.mMediaDevices.get(0);
- assertThat(infoDevice.getId()).isEqualTo(TEST_ID);
- assertThat(mInfoMediaManager.mMediaDevices).hasSize(routes.size());
- }
-
- @Test
public void hasPreferenceRouteListing_oldSdkVersion_returnsFalse() {
assertThat(mInfoMediaManager.preferRouteListingOrdering()).isFalse();
}
@@ -545,18 +505,6 @@ public class InfoMediaManagerTest {
}
@Test
- public void connectDeviceWithoutPackageName_noSession_returnFalse() {
- final MediaRoute2Info info = mock(MediaRoute2Info.class);
- final MediaDevice device = new InfoMediaDevice(mContext, info);
-
- final List<RoutingSessionInfo> infos = new ArrayList<>();
-
- mShadowRouter2Manager.setRemoteSessions(infos);
-
- assertThat(mInfoMediaManager.connectDeviceWithoutPackageName(device)).isFalse();
- }
-
- @Test
public void onRoutesRemoved_getAvailableRoutes_shouldAddMediaDevice() {
final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
final RoutingSessionInfo sessionInfo = mock(RoutingSessionInfo.class);
@@ -587,36 +535,6 @@ public class InfoMediaManagerTest {
}
@Test
- public void onRoutesRemoved_buildAllRoutes_shouldAddMediaDevice() {
- final MediaRoute2Info info = mock(MediaRoute2Info.class);
- when(info.getId()).thenReturn(TEST_ID);
- when(info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
- when(info.isSystemRoute()).thenReturn(true);
-
- final List<MediaRoute2Info> routes = new ArrayList<>();
- routes.add(info);
- when(mRouterManager.getAllRoutes()).thenReturn(routes);
-
- final MediaDevice mediaDevice = mInfoMediaManager.findMediaDevice(TEST_ID);
- assertThat(mediaDevice).isNull();
-
- mInfoMediaManager.mPackageName = "";
- mInfoMediaManager.mMediaRouterCallback.onRoutesUpdated();
-
- final MediaDevice infoDevice = mInfoMediaManager.mMediaDevices.get(0);
- assertThat(infoDevice.getId()).isEqualTo(TEST_ID);
- assertThat(mInfoMediaManager.mMediaDevices).hasSize(routes.size());
- }
-
- @Test
- public void addDeviceToPlayMedia_packageNameIsNull_returnFalse() {
- mInfoMediaManager.mPackageName = null;
- final MediaDevice device = mock(MediaDevice.class);
-
- assertThat(mInfoMediaManager.addDeviceToPlayMedia(device)).isFalse();
- }
-
- @Test
public void addDeviceToPlayMedia_containSelectableRoutes_returnTrue() {
final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
final RoutingSessionInfo info = mock(RoutingSessionInfo.class);
@@ -660,14 +578,6 @@ public class InfoMediaManagerTest {
}
@Test
- public void removeDeviceFromMedia_packageNameIsNull_returnFalse() {
- mInfoMediaManager.mPackageName = null;
- final MediaDevice device = mock(MediaDevice.class);
-
- assertThat(mInfoMediaManager.removeDeviceFromPlayMedia(device)).isFalse();
- }
-
- @Test
public void removeDeviceFromMedia_containSelectedRoutes_returnTrue() {
final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
final RoutingSessionInfo info = mock(RoutingSessionInfo.class);
@@ -711,13 +621,6 @@ public class InfoMediaManagerTest {
}
@Test
- public void getSelectableMediaDevice_packageNameIsNull_returnFalse() {
- mInfoMediaManager.mPackageName = null;
-
- assertThat(mInfoMediaManager.getSelectableMediaDevices()).isEmpty();
- }
-
- @Test
public void getSelectableMediaDevice_notContainPackageName_returnEmpty() {
final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
final RoutingSessionInfo info = mock(RoutingSessionInfo.class);
@@ -730,13 +633,6 @@ public class InfoMediaManagerTest {
}
@Test
- public void getDeselectableMediaDevice_packageNameIsNull_returnFalse() {
- mInfoMediaManager.mPackageName = null;
-
- assertThat(mInfoMediaManager.getDeselectableMediaDevices()).isEmpty();
- }
-
- @Test
public void getDeselectableMediaDevice_checkList() {
final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
final RoutingSessionInfo info = mock(RoutingSessionInfo.class);
@@ -761,20 +657,6 @@ public class InfoMediaManagerTest {
}
@Test
- public void adjustSessionVolume_packageNameIsNull_noCrash() {
- mInfoMediaManager.mPackageName = null;
-
- mInfoMediaManager.adjustSessionVolume(10);
- }
-
- @Test
- public void getSessionVolumeMax_packageNameIsNull_returnNotFound() {
- mInfoMediaManager.mPackageName = null;
-
- assertThat(mInfoMediaManager.getSessionVolumeMax()).isEqualTo(-1);
- }
-
- @Test
public void getSessionVolumeMax_containPackageName_returnMaxVolume() {
final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
final RoutingSessionInfo info = mock(RoutingSessionInfo.class);
@@ -789,24 +671,6 @@ public class InfoMediaManagerTest {
}
@Test
- public void getSessionVolumeMax_routeSessionInfoIsNull_returnNotFound() {
- final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
- final RoutingSessionInfo info = null;
- routingSessionInfos.add(info);
-
- mShadowRouter2Manager.setRoutingSessions(routingSessionInfos);
-
- assertThat(mInfoMediaManager.getSessionVolumeMax()).isEqualTo(-1);
- }
-
- @Test
- public void getSessionVolume_packageNameIsNull_returnNotFound() {
- mInfoMediaManager.mPackageName = null;
-
- assertThat(mInfoMediaManager.getSessionVolume()).isEqualTo(-1);
- }
-
- @Test
public void getSessionVolume_containPackageName_returnMaxVolume() {
final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
final RoutingSessionInfo info = mock(RoutingSessionInfo.class);
@@ -821,17 +685,6 @@ public class InfoMediaManagerTest {
}
@Test
- public void getSessionVolume_routeSessionInfoIsNull_returnNotFound() {
- final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
- final RoutingSessionInfo info = null;
- routingSessionInfos.add(info);
-
- mShadowRouter2Manager.setRoutingSessions(routingSessionInfos);
-
- assertThat(mInfoMediaManager.getSessionVolume()).isEqualTo(-1);
- }
-
- @Test
public void getRemoteSessions_returnsRemoteSessions() {
final List<RoutingSessionInfo> infos = new ArrayList<>();
infos.add(mock(RoutingSessionInfo.class));
@@ -841,13 +694,6 @@ public class InfoMediaManagerTest {
}
@Test
- public void releaseSession_packageNameIsNull_returnFalse() {
- mInfoMediaManager.mPackageName = null;
-
- assertThat(mInfoMediaManager.releaseSession()).isFalse();
- }
-
- @Test
public void releaseSession_removeSuccessfully_returnTrue() {
final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
final RoutingSessionInfo info = mock(RoutingSessionInfo.class);
@@ -860,24 +706,6 @@ public class InfoMediaManagerTest {
}
@Test
- public void getSessionName_packageNameIsNull_returnNull() {
- mInfoMediaManager.mPackageName = null;
-
- assertThat(mInfoMediaManager.getSessionName()).isNull();
- }
-
- @Test
- public void getSessionName_routeSessionInfoIsNull_returnNull() {
- final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
- final RoutingSessionInfo info = null;
- routingSessionInfos.add(info);
-
- mShadowRouter2Manager.setRoutingSessions(routingSessionInfos);
-
- assertThat(mInfoMediaManager.getSessionName()).isNull();
- }
-
- @Test
public void getSessionName_containPackageName_returnName() {
final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
final RoutingSessionInfo info = mock(RoutingSessionInfo.class);
@@ -942,32 +770,6 @@ public class InfoMediaManagerTest {
}
@Test
- public void onTransferred_buildAllRoutes_shouldAddMediaDevice() {
- final MediaRoute2Info info = mock(MediaRoute2Info.class);
- final RoutingSessionInfo sessionInfo = mock(RoutingSessionInfo.class);
- mInfoMediaManager.registerCallback(mCallback);
-
- when(info.getId()).thenReturn(TEST_ID);
- when(info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
- when(info.isSystemRoute()).thenReturn(true);
-
- final List<MediaRoute2Info> routes = new ArrayList<>();
- routes.add(info);
- mShadowRouter2Manager.setAllRoutes(routes);
-
- final MediaDevice mediaDevice = mInfoMediaManager.findMediaDevice(TEST_ID);
- assertThat(mediaDevice).isNull();
-
- mInfoMediaManager.mPackageName = "";
- mInfoMediaManager.mMediaRouterCallback.onTransferred(sessionInfo, sessionInfo);
-
- final MediaDevice infoDevice = mInfoMediaManager.mMediaDevices.get(0);
- assertThat(infoDevice.getId()).isEqualTo(TEST_ID);
- assertThat(mInfoMediaManager.mMediaDevices).hasSize(routes.size());
- verify(mCallback).onConnectedDeviceChanged(null);
- }
-
- @Test
public void onSessionUpdated_shouldDispatchDeviceListAdded() {
final MediaRoute2Info info = mock(MediaRoute2Info.class);
when(info.getId()).thenReturn(TEST_ID);
@@ -978,7 +780,6 @@ public class InfoMediaManagerTest {
routes.add(info);
mShadowRouter2Manager.setAllRoutes(routes);
- mInfoMediaManager.mPackageName = "";
mInfoMediaManager.registerCallback(mCallback);
mInfoMediaManager.mMediaRouterCallback.onSessionUpdated(mock(RoutingSessionInfo.class));
diff --git a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowRouter2Manager.java b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowRouter2Manager.java
index fde378fc3e5e..3adb20409bd9 100644
--- a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowRouter2Manager.java
+++ b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowRouter2Manager.java
@@ -27,12 +27,13 @@ import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.shadow.api.Shadow;
+import java.util.ArrayList;
import java.util.List;
@Implements(MediaRouter2Manager.class)
public class ShadowRouter2Manager {
- private List<MediaRoute2Info> mAvailableRoutes;
+ private List<MediaRoute2Info> mAvailableRoutes = new ArrayList<>();
private List<MediaRoute2Info> mAllRoutes;
private List<MediaRoute2Info> mDeselectableRoutes;
private List<RoutingSessionInfo> mRemoteSessions;
diff --git a/packages/SettingsLib/tests/unit/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExtTest.kt b/packages/SettingsLib/tests/unit/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExtTest.kt
index 27d7078774d5..1ad20dc02042 100644
--- a/packages/SettingsLib/tests/unit/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExtTest.kt
+++ b/packages/SettingsLib/tests/unit/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExtTest.kt
@@ -32,7 +32,7 @@ import org.junit.runner.RunWith
class BluetoothLeBroadcastMetadataExtTest {
@Test
- fun toQrCodeString() {
+ fun toQrCodeString_encrypted() {
val subgroup = BluetoothLeBroadcastSubgroup.Builder().apply {
setCodecId(0x6)
val audioCodecConfigMetadata = BluetoothLeAudioCodecConfigMetadata.Builder().build()
@@ -70,6 +70,37 @@ class BluetoothLeBroadcastMetadataExtTest {
}
@Test
+ fun toQrCodeString_non_encrypted() {
+ val subgroup = BluetoothLeBroadcastSubgroup.Builder().apply {
+ setCodecId(0x6)
+ val audioCodecConfigMetadata = BluetoothLeAudioCodecConfigMetadata.Builder().build()
+ setContentMetadata(BluetoothLeAudioContentMetadata.Builder()
+ .build())
+ setCodecSpecificConfig(audioCodecConfigMetadata)
+ addChannel(BluetoothLeBroadcastChannel.Builder().apply {
+ setSelected(true)
+ setChannelIndex(1)
+ setCodecMetadata(audioCodecConfigMetadata)
+ }.build())
+ }.build()
+
+ val metadata = BluetoothLeBroadcastMetadata.Builder().apply {
+ setSourceDevice(DevicePublic, BluetoothDevice.ADDRESS_TYPE_PUBLIC)
+ setSourceAdvertisingSid(1)
+ setBroadcastId(0xDE51E9)
+ setBroadcastName("Hockey")
+ setAudioConfigQuality(BluetoothLeBroadcastMetadata.AUDIO_CONFIG_QUALITY_STANDARD)
+ setPaSyncInterval(0xFFFF)
+ setEncrypted(false)
+ addSubgroup(subgroup)
+ }.build()
+
+ val qrCodeString = metadata.toQrCodeString()
+
+ assertThat(qrCodeString).isEqualTo(QR_CODE_STRING_NON_ENCRYPTED)
+ }
+
+ @Test
fun toQrCodeString_NoChannelSelected() {
val subgroup = BluetoothLeBroadcastSubgroup.Builder().apply {
setCodecId(0x6)
@@ -102,6 +133,7 @@ class BluetoothLeBroadcastMetadataExtTest {
addSubgroup(subgroup)
}.build()
+ // if no channel is selected, no preference(0xFFFFFFFFu) will be set in BIS
val qrCodeString = metadata.toQrCodeString()
val parsedMetadata =
@@ -111,13 +143,11 @@ class BluetoothLeBroadcastMetadataExtTest {
assertThat(parsedMetadata.subgroups).isNotNull()
assertThat(parsedMetadata.subgroups.size).isEqualTo(1)
assertThat(parsedMetadata.subgroups[0].channels).isNotNull()
- assertThat(parsedMetadata.subgroups[0].channels.size).isEqualTo(2)
+ assertThat(parsedMetadata.subgroups[0].channels.size).isEqualTo(1)
assertThat(parsedMetadata.subgroups[0].hasChannelPreference()).isFalse()
- // Input order does not matter due to parsing through bisMask
+ // placeholder channel with not selected
assertThat(parsedMetadata.subgroups[0].channels[0].channelIndex).isEqualTo(1)
assertThat(parsedMetadata.subgroups[0].channels[0].isSelected).isFalse()
- assertThat(parsedMetadata.subgroups[0].channels[1].channelIndex).isEqualTo(2)
- assertThat(parsedMetadata.subgroups[0].channels[1].isSelected).isFalse()
}
@Test
@@ -162,13 +192,11 @@ class BluetoothLeBroadcastMetadataExtTest {
assertThat(parsedMetadata.subgroups).isNotNull()
assertThat(parsedMetadata.subgroups.size).isEqualTo(1)
assertThat(parsedMetadata.subgroups[0].channels).isNotNull()
- // Only selected channel can be recovered
- assertThat(parsedMetadata.subgroups[0].channels.size).isEqualTo(2)
+ // Only selected channel can be recovered, non-selected ones will be ignored
+ assertThat(parsedMetadata.subgroups[0].channels.size).isEqualTo(1)
assertThat(parsedMetadata.subgroups[0].hasChannelPreference()).isTrue()
- assertThat(parsedMetadata.subgroups[0].channels[0].channelIndex).isEqualTo(1)
- assertThat(parsedMetadata.subgroups[0].channels[0].isSelected).isFalse()
- assertThat(parsedMetadata.subgroups[0].channels[1].channelIndex).isEqualTo(2)
- assertThat(parsedMetadata.subgroups[0].channels[1].isSelected).isTrue()
+ assertThat(parsedMetadata.subgroups[0].channels[0].channelIndex).isEqualTo(2)
+ assertThat(parsedMetadata.subgroups[0].channels[0].isSelected).isTrue()
}
@Test
@@ -180,16 +208,34 @@ class BluetoothLeBroadcastMetadataExtTest {
assertThat(qrCodeString).isEqualTo(QR_CODE_STRING)
}
+ @Test
+ fun decodeAndEncodeAgain_sameString_non_encrypted() {
+ val metadata =
+ BluetoothLeBroadcastMetadataExt
+ .convertToBroadcastMetadata(QR_CODE_STRING_NON_ENCRYPTED)!!
+
+ val qrCodeString = metadata.toQrCodeString()
+
+ assertThat(qrCodeString).isEqualTo(QR_CODE_STRING_NON_ENCRYPTED)
+ }
+
private companion object {
const val TEST_DEVICE_ADDRESS = "00:A1:A1:A1:A1:A1"
+ const val TEST_DEVICE_ADDRESS_PUBLIC = "AA:BB:CC:00:11:22"
val Device: BluetoothDevice =
BluetoothAdapter.getDefaultAdapter().getRemoteLeDevice(TEST_DEVICE_ADDRESS,
BluetoothDevice.ADDRESS_TYPE_RANDOM)
+ val DevicePublic: BluetoothDevice =
+ BluetoothAdapter.getDefaultAdapter().getRemoteLeDevice(TEST_DEVICE_ADDRESS_PUBLIC,
+ BluetoothDevice.ADDRESS_TYPE_PUBLIC)
+
const val QR_CODE_STRING =
- "BT:R:65536;T:1;D:00-A1-A1-A1-A1-A1;AS:1;B:123456;BN:VGVzdA==;" +
- "PM:BgNwVGVzdA==;SI:160;C:VGVzdENvZGU=;SG:BS:3,BM:3,AC:BQNUZXN0BARlbmc=;" +
- "VN:U;;"
+ "BLUETOOTH:UUID:184F;BN:VGVzdA==;AT:1;AD:00A1A1A1A1A1;BI:1E240;BC:VGVzdENvZGU=;" +
+ "MD:BgNwVGVzdA==;AS:1;PI:A0;NS:1;BS:3;NB:2;SM:BQNUZXN0BARlbmc=;;"
+ const val QR_CODE_STRING_NON_ENCRYPTED =
+ "BLUETOOTH:UUID:184F;BN:SG9ja2V5;AT:0;AD:AABBCC001122;BI:DE51E9;SQ:1;AS:1;PI:FFFF;" +
+ "NS:1;BS:1;NB:1;;"
}
} \ No newline at end of file
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/TEST_MAPPING b/packages/SystemUI/accessibility/accessibilitymenu/TEST_MAPPING
index 29a25ad04cbe..4a10108b3e04 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/TEST_MAPPING
+++ b/packages/SystemUI/accessibility/accessibilitymenu/TEST_MAPPING
@@ -1,14 +1,5 @@
{
- "presubmit": [
- {
- "name": "AccessibilityMenuServiceTests",
- "options": [
- {
- "exclude-annotation": "android.support.test.filters.FlakyTest"
- }
- ]
- }
- ],
+ // TODO: b/324945360 - Re-enable on presubmit after fixing failures
"postsubmit": [
{
"name": "AccessibilityMenuServiceTests",
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 7f229fb65d4c..71f9ba2788a1 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -15,6 +15,16 @@ flag {
}
flag {
+ name: "udfps_view_performance"
+ namespace: "systemui"
+ description: "Decrease screen off blocking calls by waiting until the device is finished going to sleep before adding the udfps view."
+ bug: "225183106"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "notification_async_group_header_inflation"
namespace: "systemui"
description: "Inflates the notification group summary header views from the background thread."
@@ -317,7 +327,7 @@ flag {
description: "Refactors shade header and keyguard status bar to read status bar dimens from a"
" central place, instead of reading resources directly. This is to take into account display"
" cutouts and other special cases. "
- bug: "317199366"
+ bug: "317016114"
metadata {
purpose: PURPOSE_BUGFIX
}
@@ -451,6 +461,16 @@ flag {
}
flag {
+ name: "slice_manager_binder_call_background"
+ namespace: "systemui"
+ description: "Move the ISliceManager#getPinnedSpecs binder call to the background thread."
+ bug: "322745650"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "register_new_wallet_card_in_background"
namespace: "systemui"
description: "Decide whether the call to registerNewWalletCards method should be issued on background thread."
@@ -469,3 +489,43 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "register_zen_mode_content_observer_background"
+ namespace: "systemui"
+ description: "Decide whether to register zen mode content observers in the background thread."
+ bug: "324515627"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "clipboard_noninteractive_on_lockscreen"
+ namespace: "systemui"
+ description: "Prevents the interactive clipboard UI from appearing when device is locked"
+ bug: "317048495"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "trim_resources_with_background_trim_at_lock"
+ namespace: "systemui"
+ description: "Trim fonts and other caches when the device locks to lower memory consumption."
+ bug: "322143614"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "dedicated_notif_inflation_thread"
+ namespace: "systemui"
+ description: "Create a separate background thread for inflating notifications"
+ bug: "308967184"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt
index e4dc9beb51f8..5d5f12e8e567 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt
@@ -123,7 +123,7 @@ constructor(
val views = LinkedList<View>().apply { add(view) }
while (views.isNotEmpty()) {
- val v = views.removeFirst()
+ val v = views.removeAt(0)
if (v.background != null) {
return v.background
}
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/anc/AncModule.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/anc/AncModule.kt
new file mode 100644
index 000000000000..a4fb05d3b5b9
--- /dev/null
+++ b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/anc/AncModule.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.anc
+
+import dagger.Module
+
+@Module interface AncModule
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
index aa567364d130..76931a2b4d41 100644
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
@@ -22,12 +22,15 @@ import android.view.View
import android.view.WindowInsets
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.lifecycle.LifecycleOwner
+import com.android.compose.theme.LocalAndroidColorScheme
import com.android.compose.theme.PlatformTheme
import com.android.compose.ui.platform.DensityAwareComposeView
import com.android.internal.policy.ScreenDecorationsUtils
@@ -89,12 +92,18 @@ object ComposeFacade : BaseComposeFacade {
) {
activity.setContent {
PlatformTheme {
- CommunalHub(
- viewModel = viewModel,
- onOpenWidgetPicker = onOpenWidgetPicker,
- widgetConfigurator = widgetConfigurator,
- onEditDone = onEditDone,
- )
+ Box(
+ modifier =
+ Modifier.fillMaxSize()
+ .background(LocalAndroidColorScheme.current.outlineVariant),
+ ) {
+ CommunalHub(
+ viewModel = viewModel,
+ onOpenWidgetPicker = onOpenWidgetPicker,
+ widgetConfigurator = widgetConfigurator,
+ onEditDone = onEditDone,
+ )
+ }
}
}
}
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 bc85513ae296..be5aa8a4c3b9 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
@@ -1,6 +1,7 @@
package com.android.systemui.communal.ui.compose
import androidx.compose.animation.core.tween
+import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
@@ -12,6 +13,7 @@ import androidx.compose.ui.unit.dp
import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.FixedSizeEdgeDetector
+import com.android.compose.animation.scene.LowestZIndexScenePicker
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.SceneScope
@@ -21,6 +23,7 @@ import com.android.compose.animation.scene.SwipeDirection
import com.android.compose.animation.scene.observableTransitionState
import com.android.compose.animation.scene.transitions
import com.android.compose.animation.scene.updateSceneTransitionLayoutState
+import com.android.compose.theme.LocalAndroidColorScheme
import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
import com.android.systemui.communal.ui.compose.extensions.allowGestures
@@ -31,16 +34,25 @@ import kotlinx.coroutines.flow.transform
object Communal {
object Elements {
+ val Scrim = ElementKey("Scrim", scenePicker = LowestZIndexScenePicker)
val Content = ElementKey("CommunalContent")
}
}
val sceneTransitions = transitions {
- from(TransitionSceneKey.Blank, to = TransitionSceneKey.Communal) {
- spec = tween(durationMillis = 500)
-
+ to(TransitionSceneKey.Communal) {
+ spec = tween(durationMillis = 1000)
+ translate(Communal.Elements.Content, Edge.Right)
+ timestampRange(startMillis = 167, endMillis = 334) {
+ fade(Communal.Elements.Scrim)
+ fade(Communal.Elements.Content)
+ }
+ }
+ to(TransitionSceneKey.Blank) {
+ spec = tween(durationMillis = 1000)
translate(Communal.Elements.Content, Edge.Right)
- fade(Communal.Elements.Content)
+ timestampRange(endMillis = 167) { fade(Communal.Elements.Content) }
+ timestampRange(startMillis = 167, endMillis = 334) { fade(Communal.Elements.Scrim) }
}
}
@@ -111,6 +123,12 @@ private fun SceneScope.CommunalScene(
viewModel: BaseCommunalViewModel,
modifier: Modifier = Modifier,
) {
+ Box(
+ modifier =
+ Modifier.element(Communal.Elements.Scrim)
+ .fillMaxSize()
+ .background(LocalAndroidColorScheme.current.outlineVariant),
+ )
Box(modifier.element(Communal.Elements.Content)) { CommunalHub(viewModel = viewModel) }
}
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 090750e046a7..cddd4fa1c33d 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
@@ -141,7 +141,6 @@ fun CommunalHub(
modifier =
modifier
.fillMaxSize()
- .background(LocalAndroidColorScheme.current.outlineVariant)
.pointerInput(gridState, contentOffset, contentListState) {
// If not in edit mode, don't allow selecting items.
if (!viewModel.isEditMode) return@pointerInput
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
index 7b21d091d451..dd043dbebaa6 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
@@ -18,11 +18,14 @@ package com.android.systemui.keyguard.ui.composable
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import com.android.compose.animation.scene.SceneScope
+import com.android.compose.animation.scene.animateSceneFloatAsState
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
+import com.android.systemui.qs.ui.composable.QuickSettings
import com.android.systemui.scene.shared.model.Direction
import com.android.systemui.scene.shared.model.Edge
import com.android.systemui.scene.shared.model.SceneKey
@@ -87,10 +90,15 @@ constructor(
}
@Composable
-private fun LockscreenScene(
+private fun SceneScope.LockscreenScene(
lockscreenContent: Lazy<LockscreenContent>,
modifier: Modifier = Modifier,
) {
+ animateSceneFloatAsState(
+ value = QuickSettings.SharedValues.SquishinessValues.LockscreenSceneStarting,
+ key = QuickSettings.SharedValues.TilesSquishiness,
+ )
+
lockscreenContent
.get()
.Content(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/BurnInState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/BurnInState.kt
index c4184905f28d..7a73c58ba193 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/BurnInState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/BurnInState.kt
@@ -73,7 +73,7 @@ private fun rememberBurnInParameters(
BurnInParameters(
clockControllerProvider = { clock },
topInset = topInset,
- statusViewTop = topmostTop,
+ minViewY = topmostTop,
)
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt
index 735c43356a40..61b2d4e26097 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt
@@ -21,8 +21,8 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.viewinterop.AndroidView
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.SceneScope
-import com.android.systemui.media.controls.ui.MediaCarouselController
-import com.android.systemui.media.controls.ui.MediaHost
+import com.android.systemui.media.controls.ui.controller.MediaCarouselController
+import com.android.systemui.media.controls.ui.view.MediaHost
import com.android.systemui.util.animation.MeasurementInput
private object MediaCarousel {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
index de8f2ec6e941..5d0b9ba2c736 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
@@ -16,7 +16,6 @@
package com.android.systemui.qs.ui.composable
-import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
@@ -32,14 +31,12 @@ import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.MovableElementScenePicker
import com.android.compose.animation.scene.SceneScope
import com.android.compose.animation.scene.TransitionState
+import com.android.compose.animation.scene.ValueKey
import com.android.compose.modifiers.thenIf
-import com.android.compose.theme.colorAttr
import com.android.systemui.qs.ui.adapter.QSSceneAdapter
import com.android.systemui.qs.ui.adapter.QSSceneAdapter.State.Companion.Collapsing
import com.android.systemui.qs.ui.adapter.QSSceneAdapter.State.Expanding
-import com.android.systemui.res.R
-import com.android.systemui.scene.ui.composable.Gone
-import com.android.systemui.scene.ui.composable.Lockscreen
+import com.android.systemui.qs.ui.adapter.QSSceneAdapter.State.Unsquishing
import com.android.systemui.scene.ui.composable.QuickSettings as QuickSettingsSceneKey
import com.android.systemui.scene.ui.composable.Shade
@@ -51,15 +48,24 @@ object QuickSettings {
)
object Elements {
- // TODO RENAME
val Content =
ElementKey("QuickSettingsContent", scenePicker = MovableElementScenePicker(SCENES))
- val CollapsedGrid = ElementKey("QuickSettingsCollapsedGrid")
val FooterActions = ElementKey("QuickSettingsFooterActions")
}
+
+ object SharedValues {
+ val TilesSquishiness = ValueKey("QuickSettingsTileSquishiness")
+ object SquishinessValues {
+ val Default = 1f
+ val LockscreenSceneStarting = 0f
+ val GoneSceneStarting = 0.3f
+ }
+ }
}
-private fun SceneScope.stateForQuickSettingsContent(): QSSceneAdapter.State {
+private fun SceneScope.stateForQuickSettingsContent(
+ squishiness: Float = QuickSettings.SharedValues.SquishinessValues.Default
+): QSSceneAdapter.State {
return when (val transitionState = layoutState.transitionState) {
is TransitionState.Idle -> {
when (transitionState.currentScene) {
@@ -73,10 +79,10 @@ private fun SceneScope.stateForQuickSettingsContent(): QSSceneAdapter.State {
when {
fromScene == Shade && toScene == QuickSettingsSceneKey -> Expanding(progress)
fromScene == QuickSettingsSceneKey && toScene == Shade -> Collapsing(progress)
- toScene == Shade -> QSSceneAdapter.State.QQS
- toScene == QuickSettingsSceneKey -> QSSceneAdapter.State.QS
- toScene == Gone -> QSSceneAdapter.State.CLOSED
- toScene == Lockscreen -> QSSceneAdapter.State.CLOSED
+ fromScene == Shade || toScene == Shade -> Unsquishing(squishiness)
+ fromScene == QuickSettingsSceneKey || toScene == QuickSettingsSceneKey -> {
+ QSSceneAdapter.State.QS
+ }
else ->
error(
"Bad transition for QuickSettings: fromScene=$fromScene," +
@@ -90,14 +96,24 @@ private fun SceneScope.stateForQuickSettingsContent(): QSSceneAdapter.State {
/**
* This composable will show QuickSettingsContent in the correct state (as determined by its
* [SceneScope]).
+ *
+ * If adding to scenes not in:
+ * * QuickSettingsScene
+ * * ShadeScene
+ *
+ * amend:
+ * * [stateForQuickSettingsContent],
+ * * [QuickSettings.SCENES],
+ * * this doc.
*/
@Composable
fun SceneScope.QuickSettings(
qsSceneAdapter: QSSceneAdapter,
heightProvider: () -> Int,
modifier: Modifier = Modifier,
+ squishiness: Float = QuickSettings.SharedValues.SquishinessValues.Default,
) {
- val contentState = stateForQuickSettingsContent()
+ val contentState = stateForQuickSettingsContent(squishiness)
MovableElement(
key = QuickSettings.Elements.Content,
@@ -136,7 +152,7 @@ private fun QuickSettingsContent(
modifier.fillMaxWidth().thenIf(isCustomizing) { Modifier.fillMaxHeight() }
) {
AndroidView(
- modifier = Modifier.fillMaxWidth().background(colorAttr(R.attr.underSurface)),
+ modifier = Modifier.fillMaxWidth(),
factory = { _ ->
qsSceneAdapter.setState(state)
view
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
index d36345a310d3..66cef86fb773 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
@@ -54,6 +54,7 @@ import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.unit.dp
import com.android.compose.animation.scene.SceneScope
import com.android.compose.animation.scene.TransitionState
+import com.android.compose.animation.scene.animateSceneFloatAsState
import com.android.compose.windowsizeclass.LocalWindowSizeClass
import com.android.systemui.battery.BatteryMeterViewController
import com.android.systemui.compose.modifiers.sysuiResTag
@@ -128,6 +129,7 @@ private fun SceneScope.QuickSettingsScene(
remember(lifecycleOwner, viewModel) {
viewModel.getFooterActionsViewModel(lifecycleOwner)
}
+ animateSceneFloatAsState(value = 1f, key = QuickSettings.SharedValues.TilesSquishiness)
// ############## SCROLLING ################
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
index f90f29d8b9dd..9ca751e81eed 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
@@ -19,9 +19,12 @@ package com.android.systemui.scene.ui.composable
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import com.android.compose.animation.scene.SceneScope
+import com.android.compose.animation.scene.animateSceneFloatAsState
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.ui.composable.QuickSettings
import com.android.systemui.scene.shared.model.Direction
import com.android.systemui.scene.shared.model.Edge
import com.android.systemui.scene.shared.model.SceneKey
@@ -63,6 +66,10 @@ constructor(
override fun SceneScope.Content(
modifier: Modifier,
) {
+ animateSceneFloatAsState(
+ value = QuickSettings.SharedValues.SquishinessValues.GoneSceneStarting,
+ key = QuickSettings.SharedValues.TilesSquishiness,
+ )
Spacer(modifier.fillMaxSize())
}
}
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 5006beb01fb4..9779d7170d0d 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
@@ -78,6 +78,7 @@ fun SceneContainer(
val state: MutableSceneTransitionLayoutState = remember {
MutableSceneTransitionLayoutState(
initialScene = currentSceneKey.asComposeAware(),
+ canChangeScene = { toScene -> viewModel.canChangeScene(toScene.asComposeUnaware()) },
transitions = SceneContainerTransitions,
)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt
index 6f115d88dbe2..30c82f46f01d 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt
@@ -13,7 +13,9 @@ fun TransitionBuilder.goneToShadeTransition(
) {
spec = tween(durationMillis = DefaultDuration.times(durationScale).inWholeMilliseconds.toInt())
- fractionRange(start = .58f) { fade(ShadeHeader.Elements.CollapsedContent) }
+ fractionRange(start = .58f) { fade(ShadeHeader.Elements.CollapsedContentStart) }
+ fractionRange(start = .58f) { fade(ShadeHeader.Elements.CollapsedContentEnd) }
+ fractionRange(start = .58f) { fade(ShadeHeader.Elements.PrivacyChip) }
translate(QuickSettings.Elements.Content, y = -ShadeHeader.Dimensions.CollapsedHeight * .66f)
translate(Notifications.Elements.NotificationScrim, Edge.Top, false)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt
index e71f99669df2..48ab68a6f097 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt
@@ -1,11 +1,11 @@
package com.android.systemui.scene.ui.composable.transitions
import androidx.compose.animation.core.tween
-import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.TransitionBuilder
import com.android.systemui.notifications.ui.composable.Notifications
import com.android.systemui.qs.ui.composable.QuickSettings
import com.android.systemui.shade.ui.composable.Shade
+import com.android.systemui.shade.ui.composable.ShadeHeader
import kotlin.time.Duration.Companion.milliseconds
fun TransitionBuilder.lockscreenToShadeTransition(
@@ -13,15 +13,12 @@ fun TransitionBuilder.lockscreenToShadeTransition(
) {
spec = tween(durationMillis = DefaultDuration.times(durationScale).inWholeMilliseconds.toInt())
- fractionRange(end = 0.5f) {
- fade(Shade.Elements.BackgroundScrim)
- translate(
- QuickSettings.Elements.CollapsedGrid,
- Edge.Top,
- startsOutsideLayoutBounds = false,
- )
+ fractionRange(end = 0.5f) { fade(Shade.Elements.BackgroundScrim) }
+ translate(QuickSettings.Elements.Content, y = -ShadeHeader.Dimensions.CollapsedHeight * .66f)
+ fractionRange(start = 0.5f) {
+ fade(QuickSettings.Elements.Content)
+ fade(Notifications.Elements.NotificationScrim)
}
- fractionRange(start = 0.5f) { fade(Notifications.Elements.NotificationScrim) }
}
private val DefaultDuration = 500.milliseconds
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt
index d5c2a03b3f9f..3c15da12b647 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt
@@ -13,10 +13,15 @@ fun TransitionBuilder.shadeToQuickSettingsTransition() {
translate(Notifications.Elements.NotificationScrim, Edge.Bottom)
timestampRange(endMillis = 83) { fade(QuickSettings.Elements.FooterActions) }
- translate(ShadeHeader.Elements.CollapsedContent, y = ShadeHeader.Dimensions.CollapsedHeight)
+ translate(
+ ShadeHeader.Elements.CollapsedContentStart,
+ y = ShadeHeader.Dimensions.CollapsedHeight
+ )
+ translate(ShadeHeader.Elements.CollapsedContentEnd, y = ShadeHeader.Dimensions.CollapsedHeight)
translate(ShadeHeader.Elements.ExpandedContent, y = (-ShadeHeader.Dimensions.ExpandedHeight))
- fractionRange(end = .14f) { fade(ShadeHeader.Elements.CollapsedContent) }
+ fractionRange(end = .14f) { fade(ShadeHeader.Elements.CollapsedContentStart) }
+ fractionRange(end = .14f) { fade(ShadeHeader.Elements.CollapsedContentEnd) }
fractionRange(start = .58f) { fade(ShadeHeader.Elements.ExpandedContent) }
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
index b11edf7b47b7..00b494b7f994 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
@@ -20,6 +20,7 @@ package com.android.systemui.shade.ui.composable
import android.view.ContextThemeWrapper
import android.view.ViewGroup
import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
@@ -48,6 +49,7 @@ import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.LowestZIndexScenePicker
import com.android.compose.animation.scene.SceneScope
import com.android.compose.animation.scene.ValueKey
import com.android.compose.animation.scene.animateSceneFloatAsState
@@ -57,9 +59,11 @@ import com.android.systemui.battery.BatteryMeterView
import com.android.systemui.battery.BatteryMeterViewController
import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation
import com.android.systemui.common.ui.compose.windowinsets.LocalDisplayCutout
+import com.android.systemui.privacy.OngoingPrivacyChip
import com.android.systemui.res.R
import com.android.systemui.scene.ui.composable.QuickSettings
import com.android.systemui.scene.ui.composable.Shade as ShadeKey
+import com.android.systemui.shade.ui.composable.ShadeHeader.Dimensions.CollapsedHeight
import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
import com.android.systemui.statusbar.phone.StatusBarIconController
import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager
@@ -72,7 +76,9 @@ import com.android.systemui.statusbar.policy.Clock
object ShadeHeader {
object Elements {
val ExpandedContent = ElementKey("ShadeHeaderExpandedContent")
- val CollapsedContent = ElementKey("ShadeHeaderCollapsedContent")
+ val CollapsedContentStart = ElementKey("ShadeHeaderCollapsedContentStart")
+ val CollapsedContentEnd = ElementKey("ShadeHeaderCollapsedContentEnd")
+ val PrivacyChip = ElementKey("PrivacyChip", scenePicker = LowestZIndexScenePicker)
}
object Keys {
@@ -106,15 +112,16 @@ fun SceneScope.CollapsedShadeHeader(
cutoutLocation != CutoutLocation.CENTER || formatProgress.value > 0.5f
}
}
+ val isPrivacyChipVisible by viewModel.isPrivacyChipVisible.collectAsState()
// This layout assumes it is globally positioned at (0, 0) and is the
// same size as the screen.
Layout(
- modifier = modifier.element(ShadeHeader.Elements.CollapsedContent),
+ modifier = modifier,
contents =
listOf(
{
- Row {
+ Row(modifier = Modifier.element(ShadeHeader.Elements.CollapsedContentStart)) {
AndroidView(
factory = { context ->
Clock(
@@ -132,32 +139,45 @@ fun SceneScope.CollapsedShadeHeader(
}
},
{
- Row(horizontalArrangement = Arrangement.End) {
- SystemIconContainer {
- when (LocalWindowSizeClass.current.widthSizeClass) {
- WindowWidthSizeClass.Medium,
- WindowWidthSizeClass.Expanded ->
- ShadeCarrierGroup(
- viewModel = viewModel,
- modifier = Modifier.align(Alignment.CenterVertically),
- )
- }
- StatusIcons(
+ if (isPrivacyChipVisible) {
+ Box(modifier = Modifier.height(CollapsedHeight).fillMaxWidth()) {
+ PrivacyChip(
viewModel = viewModel,
- createTintedIconManager = createTintedIconManager,
- statusBarIconController = statusBarIconController,
- useExpandedFormat = useExpandedFormat,
- modifier =
- Modifier.align(Alignment.CenterVertically)
- .padding(end = 6.dp)
- .weight(1f, fill = false)
- )
- BatteryIcon(
- createBatteryMeterViewController = createBatteryMeterViewController,
- useExpandedFormat = useExpandedFormat,
- modifier = Modifier.align(Alignment.CenterVertically),
+ modifier = Modifier.align(Alignment.CenterEnd),
)
}
+ } else {
+ Row(
+ horizontalArrangement = Arrangement.End,
+ modifier = Modifier.element(ShadeHeader.Elements.CollapsedContentEnd)
+ ) {
+ SystemIconContainer {
+ when (LocalWindowSizeClass.current.widthSizeClass) {
+ WindowWidthSizeClass.Medium,
+ WindowWidthSizeClass.Expanded ->
+ ShadeCarrierGroup(
+ viewModel = viewModel,
+ modifier = Modifier.align(Alignment.CenterVertically),
+ )
+ }
+ StatusIcons(
+ viewModel = viewModel,
+ createTintedIconManager = createTintedIconManager,
+ statusBarIconController = statusBarIconController,
+ useExpandedFormat = useExpandedFormat,
+ modifier =
+ Modifier.align(Alignment.CenterVertically)
+ .padding(end = 6.dp)
+ .weight(1f, fill = false)
+ )
+ BatteryIcon(
+ createBatteryMeterViewController =
+ createBatteryMeterViewController,
+ useExpandedFormat = useExpandedFormat,
+ modifier = Modifier.align(Alignment.CenterVertically),
+ )
+ }
+ }
}
},
),
@@ -223,67 +243,77 @@ fun SceneScope.ExpandedShadeHeader(
.unsafeCompositionState(initialValue = 1f)
val useExpandedFormat by
remember(formatProgress) { derivedStateOf { formatProgress.value > 0.5f } }
+ val isPrivacyChipVisible by viewModel.isPrivacyChipVisible.collectAsState()
- Column(
- verticalArrangement = Arrangement.Bottom,
- modifier =
- modifier
- .element(ShadeHeader.Elements.ExpandedContent)
- .fillMaxWidth()
- .defaultMinSize(minHeight = ShadeHeader.Dimensions.ExpandedHeight)
- ) {
- Row {
- AndroidView(
- factory = { context ->
- Clock(ContextThemeWrapper(context, R.style.TextAppearance_QS_Status), null)
- },
- modifier =
- Modifier.align(Alignment.CenterVertically)
- // use graphicsLayer instead of Modifier.scale to anchor transform to
- // the (start, top) corner
- .graphicsLayer(
- scaleX = 2.57f,
- scaleY = 2.57f,
- transformOrigin =
- TransformOrigin(
- when (LocalLayoutDirection.current) {
- LayoutDirection.Ltr -> 0f
- LayoutDirection.Rtl -> 1f
- },
- 0.5f
- )
- ),
- )
- Spacer(modifier = Modifier.weight(1f))
- ShadeCarrierGroup(
- viewModel = viewModel,
- modifier = Modifier.align(Alignment.CenterVertically),
- )
- }
- Spacer(modifier = Modifier.width(5.dp))
- Row {
- VariableDayDate(
- viewModel = viewModel,
- modifier = Modifier.widthIn(max = 90.dp).align(Alignment.CenterVertically),
- )
- Spacer(modifier = Modifier.weight(1f))
- SystemIconContainer {
- StatusIcons(
+ Box(modifier = modifier) {
+ if (isPrivacyChipVisible) {
+ Box(modifier = Modifier.height(CollapsedHeight).fillMaxWidth()) {
+ PrivacyChip(
viewModel = viewModel,
- createTintedIconManager = createTintedIconManager,
- statusBarIconController = statusBarIconController,
- useExpandedFormat = useExpandedFormat,
+ modifier = Modifier.align(Alignment.CenterEnd),
+ )
+ }
+ }
+ Column(
+ verticalArrangement = Arrangement.Bottom,
+ modifier =
+ Modifier.element(ShadeHeader.Elements.ExpandedContent)
+ .fillMaxWidth()
+ .defaultMinSize(minHeight = ShadeHeader.Dimensions.ExpandedHeight)
+ ) {
+ Row {
+ AndroidView(
+ factory = { context ->
+ Clock(ContextThemeWrapper(context, R.style.TextAppearance_QS_Status), null)
+ },
modifier =
Modifier.align(Alignment.CenterVertically)
- .padding(end = 6.dp)
- .weight(1f, fill = false),
+ // use graphicsLayer instead of Modifier.scale to anchor transform to
+ // the (start, top) corner
+ .graphicsLayer(
+ scaleX = 2.57f,
+ scaleY = 2.57f,
+ transformOrigin =
+ TransformOrigin(
+ when (LocalLayoutDirection.current) {
+ LayoutDirection.Ltr -> 0f
+ LayoutDirection.Rtl -> 1f
+ },
+ 0.5f
+ )
+ ),
)
- BatteryIcon(
- useExpandedFormat = useExpandedFormat,
- createBatteryMeterViewController = createBatteryMeterViewController,
+ Spacer(modifier = Modifier.weight(1f))
+ ShadeCarrierGroup(
+ viewModel = viewModel,
modifier = Modifier.align(Alignment.CenterVertically),
)
}
+ Spacer(modifier = Modifier.width(5.dp))
+ Row {
+ VariableDayDate(
+ viewModel = viewModel,
+ modifier = Modifier.widthIn(max = 90.dp).align(Alignment.CenterVertically),
+ )
+ Spacer(modifier = Modifier.weight(1f))
+ SystemIconContainer {
+ StatusIcons(
+ viewModel = viewModel,
+ createTintedIconManager = createTintedIconManager,
+ statusBarIconController = statusBarIconController,
+ useExpandedFormat = useExpandedFormat,
+ modifier =
+ Modifier.align(Alignment.CenterVertically)
+ .padding(end = 6.dp)
+ .weight(1f, fill = false),
+ )
+ BatteryIcon(
+ useExpandedFormat = useExpandedFormat,
+ createBatteryMeterViewController = createBatteryMeterViewController,
+ modifier = Modifier.align(Alignment.CenterVertically),
+ )
+ }
+ }
}
}
}
@@ -359,7 +389,14 @@ private fun SceneScope.StatusIcons(
) {
val carrierIconSlots =
listOf(stringResource(id = com.android.internal.R.string.status_bar_mobile))
+ val cameraSlot = stringResource(id = com.android.internal.R.string.status_bar_camera)
+ val micSlot = stringResource(id = com.android.internal.R.string.status_bar_microphone)
+ val locationSlot = stringResource(id = com.android.internal.R.string.status_bar_location)
+
val isSingleCarrier by viewModel.isSingleCarrier.collectAsState()
+ val isPrivacyChipEnabled by viewModel.isPrivacyChipEnabled.collectAsState()
+ val isMicCameraIndicationEnabled by viewModel.isMicCameraIndicationEnabled.collectAsState()
+ val isLocationIndicationEnabled by viewModel.isLocationIndicationEnabled.collectAsState()
AndroidView(
factory = { context ->
@@ -382,6 +419,25 @@ private fun SceneScope.StatusIcons(
} else {
iconContainer.addIgnoredSlots(carrierIconSlots)
}
+
+ if (isPrivacyChipEnabled) {
+ if (isMicCameraIndicationEnabled) {
+ iconContainer.addIgnoredSlot(cameraSlot)
+ iconContainer.addIgnoredSlot(micSlot)
+ } else {
+ iconContainer.removeIgnoredSlot(cameraSlot)
+ iconContainer.removeIgnoredSlot(micSlot)
+ }
+ if (isLocationIndicationEnabled) {
+ iconContainer.addIgnoredSlot(locationSlot)
+ } else {
+ iconContainer.removeIgnoredSlot(locationSlot)
+ }
+ } else {
+ iconContainer.removeIgnoredSlot(cameraSlot)
+ iconContainer.removeIgnoredSlot(micSlot)
+ iconContainer.removeIgnoredSlot(locationSlot)
+ }
},
modifier = modifier,
)
@@ -394,7 +450,28 @@ private fun SystemIconContainer(
) {
// TODO(b/298524053): add hover state for this container
Row(
- modifier = modifier.height(ShadeHeader.Dimensions.CollapsedHeight),
+ modifier = modifier.height(CollapsedHeight),
content = content,
)
}
+
+@Composable
+private fun SceneScope.PrivacyChip(
+ viewModel: ShadeHeaderViewModel,
+ modifier: Modifier = Modifier,
+) {
+ val privacyList by viewModel.privacyItems.collectAsState()
+
+ AndroidView(
+ factory = { context ->
+ val view =
+ OngoingPrivacyChip(context, null).also { privacyChip ->
+ privacyChip.privacyList = privacyList
+ privacyChip.setOnClickListener { viewModel.onPrivacyChipClicked(privacyChip) }
+ }
+ view
+ },
+ update = { it.privacyList = privacyList },
+ modifier = modifier.element(ShadeHeader.Elements.PrivacyChip),
+ )
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index 25df3e49b618..ff6e89548a75 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -28,6 +28,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
@@ -40,14 +41,15 @@ import androidx.compose.ui.unit.dp
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.LowestZIndexScenePicker
import com.android.compose.animation.scene.SceneScope
+import com.android.compose.animation.scene.animateSceneFloatAsState
import com.android.systemui.battery.BatteryMeterViewController
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.media.controls.ui.MediaCarouselController
-import com.android.systemui.media.controls.ui.MediaHierarchyManager
-import com.android.systemui.media.controls.ui.MediaHost
-import com.android.systemui.media.controls.ui.MediaHostState
import com.android.systemui.media.controls.ui.composable.MediaCarousel
+import com.android.systemui.media.controls.ui.controller.MediaCarouselController
+import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
+import com.android.systemui.media.controls.ui.view.MediaHost
+import com.android.systemui.media.controls.ui.view.MediaHostState
import com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL
import com.android.systemui.notifications.ui.composable.NotificationScrollingStack
import com.android.systemui.qs.ui.composable.QuickSettings
@@ -73,7 +75,6 @@ import kotlinx.coroutines.flow.stateIn
object Shade {
object Elements {
- val QuickSettings = ElementKey("ShadeQuickSettings")
val MediaCarousel = ElementKey("ShadeMediaCarousel")
val BackgroundScrim =
ElementKey("ShadeBackgroundScrim", scenePicker = LowestZIndexScenePicker)
@@ -160,6 +161,8 @@ private fun SceneScope.ShadeScene(
val density = LocalDensity.current
val layoutWidth = remember { mutableStateOf(0) }
val maxNotifScrimTop = remember { mutableStateOf(0f) }
+ val tileSquishiness by
+ animateSceneFloatAsState(value = 1f, key = QuickSettings.SharedValues.TilesSquishiness)
Box(
modifier =
@@ -190,7 +193,11 @@ private fun SceneScope.ShadeScene(
)
QuickSettings(
viewModel.qsSceneAdapter,
- { viewModel.qsSceneAdapter.qqsHeight },
+ {
+ (viewModel.qsSceneAdapter.qqsHeight * tileSquishiness)
+ .roundToInt()
+ },
+ squishiness = tileSquishiness,
)
if (viewModel.isMediaVisible()) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryExt.kt b/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryExt.kt
index 7d692cc17015..d66bada04297 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryExt.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryExt.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.phone
import android.content.Context
+import androidx.annotation.GravityInt
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
@@ -45,14 +46,17 @@ import com.android.compose.theme.PlatformTheme
* @param context the [Context] in which the dialog will be constructed.
* @param dismissOnDeviceLock whether the dialog should be automatically dismissed when the device
* is locked (true by default).
+ * @param dialogGravity is one of the [android.view.Gravity] and determines dialog position on the
+ * screen.
*/
fun SystemUIDialogFactory.create(
context: Context = this.applicationContext,
theme: Int = SystemUIDialog.DEFAULT_THEME,
dismissOnDeviceLock: Boolean = SystemUIDialog.DEFAULT_DISMISS_ON_DEVICE_LOCK,
+ @GravityInt dialogGravity: Int? = null,
content: @Composable (SystemUIDialog) -> Unit,
): ComponentSystemUIDialog {
- val dialog = create(context, theme, dismissOnDeviceLock)
+ val dialog = create(context, theme, dismissOnDeviceLock, dialogGravity)
// Create the dialog so that it is properly constructed before we set the Compose content.
// Otherwise, the ComposeView won't render properly.
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/AncModule.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/AncModule.kt
new file mode 100644
index 000000000000..ccb5d367c357
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/AncModule.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.volume.panel.component.anc
+
+import com.android.systemui.volume.panel.component.anc.domain.AncAvailabilityCriteria
+import com.android.systemui.volume.panel.component.anc.ui.composable.AncPopup
+import com.android.systemui.volume.panel.component.anc.ui.viewmodel.AncViewModel
+import com.android.systemui.volume.panel.component.button.ui.composable.ButtonComponent
+import com.android.systemui.volume.panel.component.shared.model.VolumePanelComponents
+import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria
+import com.android.systemui.volume.panel.shared.model.VolumePanelUiComponent
+import dagger.Binds
+import dagger.Module
+import dagger.Provides
+import dagger.multibindings.IntoMap
+import dagger.multibindings.StringKey
+
+/** Dagger module, that provides Active Noise Cancellation Volume Panel UI functionality. */
+@Module
+interface AncModule {
+
+ @Binds
+ @IntoMap
+ @StringKey(VolumePanelComponents.ANC)
+ fun bindComponentAvailabilityCriteria(
+ criteria: AncAvailabilityCriteria
+ ): ComponentAvailabilityCriteria
+
+ companion object {
+
+ @Provides
+ @IntoMap
+ @StringKey(VolumePanelComponents.ANC)
+ fun provideVolumePanelUiComponent(
+ viewModel: AncViewModel,
+ popup: AncPopup,
+ ): VolumePanelUiComponent = ButtonComponent(viewModel.button, popup::show)
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt
new file mode 100644
index 000000000000..8ac84ff819eb
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.anc.ui.composable
+
+import android.content.Context
+import android.view.View
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.SideEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.viewinterop.AndroidView
+import androidx.slice.Slice
+import androidx.slice.widget.SliceView
+import com.android.systemui.animation.Expandable
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.volume.panel.component.anc.ui.viewmodel.AncViewModel
+import com.android.systemui.volume.panel.component.popup.ui.composable.VolumePanelPopup
+import javax.inject.Inject
+
+/** ANC popup up displaying ANC control [Slice]. */
+class AncPopup
+@Inject
+constructor(
+ private val volumePanelPopup: VolumePanelPopup,
+ private val viewModel: AncViewModel,
+) {
+
+ /** Shows a popup with the [expandable] animation. */
+ fun show(expandable: Expandable) {
+ volumePanelPopup.show(expandable, { Title() }, { Content(it) })
+ }
+
+ @Composable
+ private fun Title() {
+ Text(
+ text = stringResource(R.string.volume_panel_noise_control_title),
+ style = MaterialTheme.typography.titleMedium,
+ textAlign = TextAlign.Center,
+ maxLines = 1,
+ )
+ }
+
+ @Composable
+ private fun Content(dialog: SystemUIDialog) {
+ val slice: Slice? by viewModel.slice.collectAsState()
+
+ if (slice == null) {
+ SideEffect { dialog.dismiss() }
+ return
+ }
+
+ AndroidView<SliceView>(
+ modifier = Modifier.fillMaxWidth(),
+ factory = { context: Context ->
+ SliceView(context).apply {
+ mode = SliceView.MODE_LARGE
+ isScrollable = false
+ importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
+ setShowTitleItems(true)
+ addOnLayoutChangeListener(
+ OnWidthChangedLayoutListener(viewModel::changeSliceWidth)
+ )
+ }
+ },
+ update = { sliceView: SliceView -> sliceView.slice = slice }
+ )
+ }
+
+ private class OnWidthChangedLayoutListener(private val widthChanged: (Int) -> Unit) :
+ View.OnLayoutChangeListener {
+ override fun onLayoutChange(
+ v: View?,
+ left: Int,
+ top: Int,
+ right: Int,
+ bottom: Int,
+ oldLeft: Int,
+ oldTop: Int,
+ oldRight: Int,
+ oldBottom: Int
+ ) {
+ val newWidth = right - left
+ val oldWidth = oldRight - oldLeft
+ if (oldWidth != newWidth) {
+ widthChanged(newWidth)
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/bottombar/ui/BottomBarComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/bottombar/ui/BottomBarComponent.kt
index 0cf43672c716..d40126198c33 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/bottombar/ui/BottomBarComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/bottombar/ui/BottomBarComponent.kt
@@ -47,7 +47,7 @@ constructor(
@Composable
override fun VolumePanelComposeScope.Content(modifier: Modifier) {
Row(
- modifier = modifier.height(48.dp).fillMaxWidth(),
+ modifier = modifier.height(if (isLargeScreen) 54.dp else 48.dp).fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt
new file mode 100644
index 000000000000..5f7bd47f2a1b
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.button.ui.composable
+
+import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.dp
+import com.android.compose.animation.Expandable
+import com.android.systemui.animation.Expandable
+import com.android.systemui.common.ui.compose.Icon
+import com.android.systemui.volume.panel.component.button.ui.viewmodel.ButtonViewModel
+import com.android.systemui.volume.panel.ui.composable.ComposeVolumePanelUiComponent
+import com.android.systemui.volume.panel.ui.composable.VolumePanelComposeScope
+import kotlinx.coroutines.flow.StateFlow
+
+/** [ComposeVolumePanelUiComponent] implementing a clickable button from a bottom row. */
+class ButtonComponent(
+ private val viewModelFlow: StateFlow<ButtonViewModel?>,
+ private val onClick: (Expandable) -> Unit
+) : ComposeVolumePanelUiComponent {
+
+ @Composable
+ override fun VolumePanelComposeScope.Content(modifier: Modifier) {
+ val viewModelByState by viewModelFlow.collectAsState()
+ val viewModel = viewModelByState ?: return
+
+ Column(
+ modifier = modifier,
+ verticalArrangement = Arrangement.spacedBy(12.dp),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ Expandable(
+ modifier = Modifier.height(64.dp).fillMaxWidth(),
+ color = MaterialTheme.colorScheme.primaryContainer,
+ shape = RoundedCornerShape(28.dp),
+ contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
+ borderStroke = BorderStroke(8.dp, MaterialTheme.colorScheme.surface),
+ onClick = onClick,
+ ) {
+ Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
+ Icon(modifier = Modifier.size(24.dp), icon = viewModel.icon)
+ }
+ }
+ Text(
+ text = viewModel.label.toString(),
+ style = MaterialTheme.typography.labelMedium,
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis,
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
index 228111dae8d9..dfee684c417f 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
@@ -22,6 +22,7 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.IconButtonDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedIconToggleButton
@@ -39,6 +40,7 @@ import com.android.systemui.volume.panel.ui.composable.ComposeVolumePanelUiCompo
import com.android.systemui.volume.panel.ui.composable.VolumePanelComposeScope
import kotlinx.coroutines.flow.StateFlow
+/** [ComposeVolumePanelUiComponent] implementing a toggleable button from a bottom row. */
class ToggleButtonComponent(
private val viewModelFlow: StateFlow<ToggleButtonViewModel?>,
private val onCheckedChange: (isChecked: Boolean) -> Unit
@@ -57,6 +59,7 @@ class ToggleButtonComponent(
modifier = Modifier.height(64.dp).fillMaxWidth(),
checked = viewModel.isChecked,
onCheckedChange = onCheckedChange,
+ shape = RoundedCornerShape(28.dp),
colors =
IconButtonDefaults.outlinedIconToggleButtonColors(
containerColor = MaterialTheme.colorScheme.surface,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt
index 8ad6fdf829d7..d49fed5d6e10 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt
@@ -167,6 +167,7 @@ constructor(
) {
Icon(
icon = it.icon,
+ tint = it.iconColor.toColor(),
modifier = Modifier.padding(12.dp).fillMaxSize(),
)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/popup/ui/composable/VolumePanelPopup.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/popup/ui/composable/VolumePanelPopup.kt
new file mode 100644
index 000000000000..89251939f77a
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/popup/ui/composable/VolumePanelPopup.kt
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.popup.ui.composable
+
+import android.view.Gravity
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.material3.IconButtonDefaults
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import com.android.compose.PlatformIconButton
+import com.android.systemui.animation.DialogTransitionAnimator
+import com.android.systemui.animation.Expandable
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.statusbar.phone.SystemUIDialogFactory
+import com.android.systemui.statusbar.phone.create
+import javax.inject.Inject
+
+/** Volume panel bottom popup menu. */
+class VolumePanelPopup
+@Inject
+constructor(
+ private val dialogFactory: SystemUIDialogFactory,
+ private val dialogTransitionAnimator: DialogTransitionAnimator,
+) {
+
+ /**
+ * Shows a popup with the [expandable] animation.
+ *
+ * @param title is shown on the top of the popup
+ * @param content is the popup body
+ */
+ fun show(
+ expandable: Expandable,
+ title: @Composable (SystemUIDialog) -> Unit,
+ content: @Composable (SystemUIDialog) -> Unit,
+ ) {
+ val dialog =
+ dialogFactory.create(
+ theme = R.style.Theme_VolumePanelActivity_Popup,
+ dialogGravity = Gravity.BOTTOM,
+ ) {
+ PopupComposable(it, title, content)
+ }
+ val controller = expandable.dialogTransitionController()
+ if (controller == null) {
+ dialog.show()
+ } else {
+ dialogTransitionAnimator.show(dialog, controller)
+ }
+ }
+
+ @Composable
+ private fun PopupComposable(
+ dialog: SystemUIDialog,
+ title: @Composable (SystemUIDialog) -> Unit,
+ content: @Composable (SystemUIDialog) -> Unit,
+ ) {
+ Box(Modifier.fillMaxWidth()) {
+ Column(
+ modifier = Modifier.fillMaxWidth().padding(vertical = 20.dp),
+ verticalArrangement = Arrangement.spacedBy(20.dp),
+ ) {
+ Box(
+ modifier =
+ Modifier.padding(horizontal = 80.dp).fillMaxWidth().wrapContentHeight(),
+ contentAlignment = Alignment.Center
+ ) {
+ title(dialog)
+ }
+
+ Box(
+ modifier =
+ Modifier.padding(horizontal = 16.dp).fillMaxWidth().wrapContentHeight(),
+ contentAlignment = Alignment.Center
+ ) {
+ content(dialog)
+ }
+ }
+
+ PlatformIconButton(
+ modifier = Modifier.align(Alignment.TopEnd).size(64.dp).padding(20.dp),
+ iconResource = R.drawable.ic_close,
+ contentDescription = null,
+ onClick = { dialog.dismiss() },
+ colors =
+ IconButtonDefaults.iconButtonColors(
+ contentColor = MaterialTheme.colorScheme.outline
+ )
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt
index 86eb84929c02..22851286354c 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt
@@ -52,7 +52,7 @@ fun VolumePanelComposeScope.VerticalVolumePanelContent(
if (layout.footerComponents.isNotEmpty()) {
Row(
modifier = Modifier.fillMaxWidth().wrapContentHeight(),
- horizontalArrangement = Arrangement.spacedBy(20.dp),
+ horizontalArrangement = Arrangement.spacedBy(if (isLargeScreen) 28.dp else 20.dp),
) {
for (component in layout.footerComponents) {
AnimatedVisibility(component.isVisible) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelComposeScope.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelComposeScope.kt
index 10731c7f2df7..8df8d2e81b51 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelComposeScope.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelComposeScope.kt
@@ -27,8 +27,8 @@ class VolumePanelComposeScope(private val state: VolumePanelState) {
val orientation: Int
get() = state.orientation
- /** Is true when Volume Panel is using wide-screen layout and false the otherwise. */
- val isWideScreen: Boolean
+ /** Is true when Volume Panel is using large-screen layout and false the otherwise. */
+ val isLargeScreen: Boolean
get() = state.isWideScreen
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt
index dd6342029885..8a1e6a8b74f4 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt
@@ -19,6 +19,7 @@ package com.android.systemui.volume.panel.ui.composable
import android.content.res.Configuration
import androidx.compose.foundation.clickable
import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
@@ -26,6 +27,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
@@ -43,6 +45,8 @@ import com.android.systemui.volume.panel.ui.layout.ComponentsLayout
import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelState
import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
+private val padding = 24.dp
+
@Composable
fun VolumePanelRoot(
viewModel: VolumePanelViewModel,
@@ -84,7 +88,18 @@ fun VolumePanelRoot(
shape = RoundedCornerShape(topStart = radius, topEnd = radius),
color = MaterialTheme.colorScheme.surfaceContainer,
) {
- Column { components?.let { componentsState -> Components(componentsState) } }
+ components?.let { componentsState ->
+ Components(
+ componentsState,
+ Modifier.padding(
+ start = padding,
+ top = padding,
+ end = padding,
+ bottom = 20.dp,
+ )
+ .navigationBarsPadding()
+ )
+ }
}
}
}
@@ -92,36 +107,35 @@ fun VolumePanelRoot(
}
@Composable
-private fun VolumePanelComposeScope.Components(components: ComponentsLayout) {
- if (orientation == Configuration.ORIENTATION_PORTRAIT) {
- VerticalVolumePanelContent(
- components,
- modifier = Modifier.padding(24.dp),
- )
- } else {
- HorizontalVolumePanelContent(
- components,
- modifier =
- Modifier.padding(start = 24.dp, top = 24.dp, end = 24.dp, bottom = 20.dp)
- .heightIn(max = 236.dp),
- )
+private fun VolumePanelComposeScope.Components(
+ layout: ComponentsLayout,
+ modifier: Modifier = Modifier
+) {
+ var columnModifier = modifier.widthIn(max = 800.dp)
+ if (!isLargeScreen && orientation != Configuration.ORIENTATION_PORTRAIT) {
+ columnModifier = columnModifier.heightIn(max = 332.dp)
+ }
+ Column(modifier = columnModifier, verticalArrangement = Arrangement.spacedBy(padding)) {
+ if (orientation == Configuration.ORIENTATION_PORTRAIT || isLargeScreen) {
+ VerticalVolumePanelContent(layout)
+ } else {
+ HorizontalVolumePanelContent(layout)
+ }
+ BottomBar(layout = layout, modifier = Modifier)
}
+}
- if (components.bottomBarComponent.isVisible) {
- val horizontalPadding =
- dimensionResource(R.dimen.volume_panel_bottom_bar_horizontal_padding)
+@Composable
+private fun VolumePanelComposeScope.BottomBar(
+ layout: ComponentsLayout,
+ modifier: Modifier = Modifier
+) {
+ if (layout.bottomBarComponent.isVisible) {
Box(
- modifier =
- Modifier.fillMaxWidth()
- .navigationBarsPadding()
- .padding(
- start = horizontalPadding,
- end = horizontalPadding,
- bottom = dimensionResource(R.dimen.volume_panel_bottom_bar_bottom_padding),
- ),
+ modifier = modifier.fillMaxWidth(),
contentAlignment = Alignment.Center,
) {
- with(components.bottomBarComponent.component as ComposeVolumePanelUiComponent) {
+ with(layout.bottomBarComponent.component as ComposeVolumePanelUiComponent) {
Content(Modifier)
}
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
index 828e34da378d..2e781e69bb4a 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
@@ -29,13 +29,17 @@ import androidx.compose.ui.geometry.lerp
import androidx.compose.ui.graphics.CompositingStrategy
import androidx.compose.ui.graphics.drawscope.ContentDrawScope
import androidx.compose.ui.graphics.drawscope.scale
-import androidx.compose.ui.layout.IntermediateMeasureScope
+import androidx.compose.ui.layout.ApproachLayoutModifierNode
+import androidx.compose.ui.layout.ApproachMeasureScope
+import androidx.compose.ui.layout.LayoutCoordinates
+import androidx.compose.ui.layout.LookaheadScope
import androidx.compose.ui.layout.Measurable
+import androidx.compose.ui.layout.MeasureResult
import androidx.compose.ui.layout.Placeable
-import androidx.compose.ui.layout.intermediateLayout
import androidx.compose.ui.node.DrawModifierNode
import androidx.compose.ui.node.ModifierNodeElement
import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.semantics.testTag
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.round
@@ -91,23 +95,7 @@ internal fun Modifier.element(
layoutImpl: SceneTransitionLayoutImpl,
scene: Scene,
key: ElementKey,
-): Modifier {
- return this.then(ElementModifier(layoutImpl, scene, key))
- // TODO(b/311132415): Move this into ElementNode once we can create a delegate
- // IntermediateLayoutModifierNode.
- .intermediateLayout { measurable, constraints ->
- // TODO(b/311132415): No need to fetch the element and sceneState from the map anymore
- // once this is merged into ElementNode.
- val element = layoutImpl.elements.getValue(key)
- val sceneState = element.sceneStates.getValue(scene.key)
-
- val placeable = measure(layoutImpl, scene, element, sceneState, measurable, constraints)
- layout(placeable.width, placeable.height) {
- place(layoutImpl, scene, element, sceneState, placeable, placementScope = this)
- }
- }
- .testTag(key.testTag)
-}
+): Modifier = this.then(ElementModifier(layoutImpl, scene, key)).testTag(key.testTag)
/**
* An element associated to [ElementNode]. Note that this element does not support updates as its
@@ -129,7 +117,7 @@ internal class ElementNode(
private var layoutImpl: SceneTransitionLayoutImpl,
private var scene: Scene,
private var key: ElementKey,
-) : Modifier.Node(), DrawModifierNode {
+) : Modifier.Node(), DrawModifierNode, ApproachLayoutModifierNode {
private var _element: Element? = null
private val element: Element
get() = _element!!
@@ -197,6 +185,31 @@ internal class ElementNode(
maybePruneMaps(layoutImpl, prevElement, prevSceneState)
}
+ override fun isMeasurementApproachComplete(lookaheadSize: IntSize): Boolean {
+ // TODO(b/324191441): Investigate whether making this check more complex (checking if this
+ // element is shared or transformed) would lead to better performance.
+ return layoutImpl.state.currentTransition == null
+ }
+
+ override fun Placeable.PlacementScope.isPlacementApproachComplete(
+ lookaheadCoordinates: LayoutCoordinates
+ ): Boolean {
+ // TODO(b/324191441): Investigate whether making this check more complex (checking if this
+ // element is shared or transformed) would lead to better performance.
+ return layoutImpl.state.currentTransition == null
+ }
+
+ @ExperimentalComposeUiApi
+ override fun ApproachMeasureScope.approachMeasure(
+ measurable: Measurable,
+ constraints: Constraints,
+ ): MeasureResult {
+ val placeable = measure(layoutImpl, scene, element, sceneState, measurable, constraints)
+ return layout(placeable.width, placeable.height) {
+ place(layoutImpl, scene, element, sceneState, placeable, placementScope = this)
+ }
+ }
+
override fun ContentDrawScope.draw() {
val drawScale = getDrawScale(layoutImpl, element, scene)
if (drawScale == Scale.Default) {
@@ -368,7 +381,7 @@ private fun elementAlpha(
}
@OptIn(ExperimentalComposeUiApi::class)
-private fun IntermediateMeasureScope.measure(
+private fun ApproachMeasureScope.measure(
layoutImpl: SceneTransitionLayoutImpl,
scene: Scene,
element: Element,
@@ -431,7 +444,7 @@ private fun getDrawScale(
}
@OptIn(ExperimentalComposeUiApi::class)
-private fun IntermediateMeasureScope.place(
+private fun ApproachMeasureScope.place(
layoutImpl: SceneTransitionLayoutImpl,
scene: Scene,
element: Element,
@@ -439,6 +452,8 @@ private fun IntermediateMeasureScope.place(
placeable: Placeable,
placementScope: Placeable.PlacementScope,
) {
+ this as LookaheadScope
+
with(placementScope) {
// Update the offset (relative to the SceneTransitionLayout) this element has in this scene
// when idle.
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 c8fbad4f4eef..76e7c95f274a 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
@@ -348,6 +348,8 @@ internal class SceneGestureHandler(
// Compute the destination scene (and therefore offset) to settle in.
val offset = swipeTransition.dragOffset
val distance = swipeTransition.distance
+ var targetScene: Scene
+ var targetOffset: Float
if (
shouldCommitSwipe(
offset,
@@ -356,12 +358,24 @@ internal class SceneGestureHandler(
wasCommitted = swipeTransition._currentScene == toScene,
)
) {
- // Animate to the next scene
- animateTo(targetScene = toScene, targetOffset = distance)
+ targetScene = toScene
+ targetOffset = distance
} else {
- // Animate to the initial scene
- animateTo(targetScene = fromScene, targetOffset = 0f)
+ targetScene = fromScene
+ targetOffset = 0f
+ }
+
+ if (
+ targetScene != swipeTransition._currentScene &&
+ !layoutState.canChangeScene(targetScene.key)
+ ) {
+ // We wanted to change to a new scene but we are not allowed to, so we animate back
+ // to the current scene.
+ targetScene = swipeTransition._currentScene
+ targetOffset = if (targetScene == fromScene) 0f else distance
}
+
+ animateTo(targetScene = targetScene, targetOffset = targetOffset)
} else {
// We are doing an overscroll animation between scenes. In this case, we can also start
// from the idle position.
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 8c5a4720e7fb..08399ff03f63 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
@@ -232,7 +232,12 @@ internal class SceneTransitionLayoutImpl(
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) } }
+ BackHandler {
+ val targetScene = result.toScene
+ if (state.canChangeScene(targetScene)) {
+ with(state) { coroutineScope.onChangeScene(targetScene) }
+ }
+ }
}
Box {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
index a8da55101548..662f33f3e88b 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
@@ -101,13 +101,30 @@ sealed interface MutableSceneTransitionLayoutState : SceneTransitionLayoutState
): TransitionState.Transition?
}
-/** Return a [MutableSceneTransitionLayoutState] initially idle at [initialScene]. */
+/**
+ * Return a [MutableSceneTransitionLayoutState] initially idle at [initialScene].
+ *
+ * @param initialScene the initial scene to which this state is initialized.
+ * @param transitions the [SceneTransitions] used when this state is transitioning between scenes.
+ * @param canChangeScene whether we can transition to the given scene. This is called when the user
+ * commits a transition to a new scene because of a [UserAction]. If [canChangeScene] returns
+ * `true`, then the gesture will be committed and we will animate to the other scene. Otherwise,
+ * the gesture will be cancelled and we will animate back to the current scene.
+ * @param stateLinks the [StateLink] connecting this [SceneTransitionLayoutState] to other
+ * [SceneTransitionLayoutState]s.
+ */
fun MutableSceneTransitionLayoutState(
initialScene: SceneKey,
transitions: SceneTransitions = SceneTransitions.Empty,
+ canChangeScene: (SceneKey) -> Boolean = { true },
stateLinks: List<StateLink> = emptyList(),
): MutableSceneTransitionLayoutState {
- return MutableSceneTransitionLayoutStateImpl(initialScene, transitions, stateLinks)
+ return MutableSceneTransitionLayoutStateImpl(
+ initialScene,
+ transitions,
+ canChangeScene,
+ stateLinks,
+ )
}
/**
@@ -120,18 +137,32 @@ fun MutableSceneTransitionLayoutState(
* 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 canChangeScene whether we can transition to the given scene. This is called when the user
+ * commits a transition to a new scene because of a [UserAction]. If [canChangeScene] returns
+ * `true`, then [onChangeScene] will be called right afterwards with the same [SceneKey]. If it
+ * returns `false`, the user action will be cancelled and we will animate back to the current
+ * scene.
+ * @param stateLinks the [StateLink] connecting this [SceneTransitionLayoutState] to other
+ * [SceneTransitionLayoutState]s.
*/
@Composable
fun updateSceneTransitionLayoutState(
currentScene: SceneKey,
onChangeScene: (SceneKey) -> Unit,
transitions: SceneTransitions = SceneTransitions.Empty,
+ canChangeScene: (SceneKey) -> Boolean = { true },
stateLinks: List<StateLink> = emptyList(),
): SceneTransitionLayoutState {
return remember {
- HoistedSceneTransitionLayoutScene(currentScene, transitions, onChangeScene, stateLinks)
+ HoistedSceneTransitionLayoutState(
+ currentScene,
+ transitions,
+ onChangeScene,
+ canChangeScene,
+ stateLinks,
+ )
}
- .apply { update(currentScene, onChangeScene, transitions, stateLinks) }
+ .apply { update(currentScene, onChangeScene, canChangeScene, transitions, stateLinks) }
}
@Stable
@@ -208,6 +239,9 @@ internal abstract class BaseSceneTransitionLayoutState(
private val activeTransitionLinks = mutableMapOf<StateLink, LinkedTransition>()
+ /** Whether we can transition to the given [scene]. */
+ internal abstract fun canChangeScene(scene: SceneKey): Boolean
+
/**
* Called when the [current scene][TransitionState.currentScene] should be changed to [scene].
*
@@ -330,25 +364,30 @@ internal abstract class BaseSceneTransitionLayoutState(
* A [SceneTransitionLayout] whose current scene/source of truth is hoisted (its current value comes
* from outside).
*/
-internal class HoistedSceneTransitionLayoutScene(
+internal class HoistedSceneTransitionLayoutState(
initialScene: SceneKey,
override var transitions: SceneTransitions,
private var changeScene: (SceneKey) -> Unit,
+ private var canChangeScene: (SceneKey) -> Boolean,
stateLinks: List<StateLink> = emptyList(),
) : BaseSceneTransitionLayoutState(initialScene, stateLinks) {
private val targetSceneChannel = Channel<SceneKey>(Channel.CONFLATED)
- override fun CoroutineScope.onChangeScene(scene: SceneKey) = changeScene(scene)
+ override fun canChangeScene(scene: SceneKey): Boolean = canChangeScene.invoke(scene)
+
+ override fun CoroutineScope.onChangeScene(scene: SceneKey) = changeScene.invoke(scene)
@Composable
fun update(
currentScene: SceneKey,
onChangeScene: (SceneKey) -> Unit,
+ canChangeScene: (SceneKey) -> Boolean,
transitions: SceneTransitions,
stateLinks: List<StateLink>,
) {
SideEffect {
this.changeScene = onChangeScene
+ this.canChangeScene = canChangeScene
this.transitions = transitions
this.stateLinks = stateLinks
@@ -361,7 +400,7 @@ internal class HoistedSceneTransitionLayoutScene(
// late.
val newKey = targetSceneChannel.tryReceive().getOrNull() ?: newKey
animateToScene(
- layoutState = this@HoistedSceneTransitionLayoutScene,
+ layoutState = this@HoistedSceneTransitionLayoutState,
target = newKey,
transitionKey = null,
)
@@ -374,6 +413,7 @@ internal class HoistedSceneTransitionLayoutScene(
internal class MutableSceneTransitionLayoutStateImpl(
initialScene: SceneKey,
override var transitions: SceneTransitions,
+ private val canChangeScene: (SceneKey) -> Boolean = { true },
stateLinks: List<StateLink> = emptyList(),
) : MutableSceneTransitionLayoutState, BaseSceneTransitionLayoutState(initialScene, stateLinks) {
override fun setTargetScene(
@@ -388,6 +428,8 @@ internal class MutableSceneTransitionLayoutStateImpl(
)
}
+ override fun canChangeScene(scene: SceneKey): Boolean = canChangeScene.invoke(scene)
+
override fun CoroutineScope.onChangeScene(scene: SceneKey) {
setTargetScene(scene, coroutineScope = this)
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
index c9b5b75d6c49..33be1dc83dc8 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
@@ -44,7 +44,6 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertThrows
-import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -248,11 +247,9 @@ class ElementTest {
}
@Test
- @Ignore
- fun elementIsReusedInSameSceneAndBetweenScenes() {
+ fun elementIsReusedBetweenScenes() {
var currentScene by mutableStateOf(TestScenes.SceneA)
var sceneCState by mutableStateOf(0)
- var sceneDState by mutableStateOf(0)
val key = TestElements.Foo
var nullableLayoutImpl: SceneTransitionLayoutImpl? = null
@@ -270,19 +267,6 @@ class ElementTest {
scene(TestScenes.SceneC) {
when (sceneCState) {
0 -> Row(Modifier.element(key)) {}
- 1 -> Column(Modifier.element(key)) {}
- else -> {
- /* Nothing */
- }
- }
- }
- scene(TestScenes.SceneD) {
- // We should be able to extract the modifier before assigning it to different
- // nodes.
- val childModifier = Modifier.element(key)
- when (sceneDState) {
- 0 -> Row(childModifier) {}
- 1 -> Column(childModifier) {}
else -> {
/* Nothing */
}
@@ -315,35 +299,10 @@ class ElementTest {
assertThat(layoutImpl.elements.getValue(key)).isSameInstanceAs(element)
assertThat(element.sceneStates.keys).containsExactly(TestScenes.SceneC)
- // Scene C, state 1: the same element is reused.
+ // Scene C, state 1: the element is removed from the map.
sceneCState = 1
rule.waitForIdle()
- assertThat(layoutImpl.elements.keys).containsExactly(key)
- assertThat(layoutImpl.elements.getValue(key)).isSameInstanceAs(element)
- assertThat(element.sceneStates.keys).containsExactly(TestScenes.SceneC)
-
- // Scene D, state 0: the same element is reused.
- currentScene = TestScenes.SceneD
- sceneDState = 0
- rule.waitForIdle()
-
- assertThat(layoutImpl.elements.keys).containsExactly(key)
- assertThat(layoutImpl.elements.getValue(key)).isSameInstanceAs(element)
- assertThat(element.sceneStates.keys).containsExactly(TestScenes.SceneD)
-
- // Scene D, state 1: the same element is reused.
- sceneDState = 1
- rule.waitForIdle()
-
- assertThat(layoutImpl.elements.keys).containsExactly(key)
- assertThat(layoutImpl.elements.getValue(key)).isSameInstanceAs(element)
- assertThat(element.sceneStates.keys).containsExactly(TestScenes.SceneD)
-
- // Scene D, state 2: the element is removed from the map.
- sceneDState = 2
- rule.waitForIdle()
-
assertThat(element.sceneStates).isEmpty()
assertThat(layoutImpl.elements).isEmpty()
}
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 c91d29880ffb..fe53d5b45420 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
@@ -51,8 +51,13 @@ class SceneGestureHandlerTest {
private class TestGestureScope(
private val testScope: MonotonicClockTestScope,
) {
+ var canChangeScene: (SceneKey) -> Boolean = { true }
private val layoutState =
- MutableSceneTransitionLayoutStateImpl(SceneA, EmptyTestTransitions)
+ MutableSceneTransitionLayoutStateImpl(
+ SceneA,
+ EmptyTestTransitions,
+ canChangeScene = { canChangeScene(it) },
+ )
val mutableUserActionsA = mutableMapOf(Swipe.Up to SceneB, Swipe.Down to SceneC)
val mutableUserActionsB = mutableMapOf(Swipe.Up to SceneC, Swipe.Down to SceneA)
@@ -890,4 +895,41 @@ class SceneGestureHandlerTest {
)
assertThat(transitionState).isNotSameInstanceAs(firstTransition)
}
+
+ @Test
+ fun blockTransition() = runGestureTest {
+ assertIdle(SceneA)
+
+ // Swipe up to scene B.
+ onDragStarted(overSlop = up(0.1f))
+ assertTransition(currentScene = SceneA, fromScene = SceneA, toScene = SceneB)
+
+ // Block the transition when the user release their finger.
+ canChangeScene = { false }
+ onDragStopped(velocity = -velocityThreshold)
+ advanceUntilIdle()
+ assertIdle(SceneA)
+ }
+
+ @Test
+ fun blockInterceptedTransition() = runGestureTest {
+ assertIdle(SceneA)
+
+ // Swipe up to B.
+ onDragStarted(overSlop = up(0.1f))
+ assertTransition(currentScene = SceneA, fromScene = SceneA, toScene = SceneB)
+ onDragStopped(velocity = -velocityThreshold)
+ assertTransition(currentScene = SceneB, fromScene = SceneA, toScene = SceneB)
+
+ // Intercept the transition and swipe down back to scene A.
+ assertThat(sceneGestureHandler.shouldImmediatelyIntercept(startedPosition = null)).isTrue()
+ onDragStartedImmediately()
+
+ // Block the transition when the user release their finger.
+ canChangeScene = { false }
+ onDragStopped(velocity = velocityThreshold)
+
+ advanceUntilIdle()
+ assertIdle(SceneB)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/FaceHelpMessageDeferralTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/FaceHelpMessageDeferralTest.kt
index 059620502a49..b31f6f5b096b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/FaceHelpMessageDeferralTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/FaceHelpMessageDeferralTest.kt
@@ -220,6 +220,7 @@ class FaceHelpMessageDeferralTest : SysuiTestCase() {
threshold,
logger,
dumpManager,
+ "0",
)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
index 45f98be2ca12..1cdc2b69034b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
@@ -21,8 +21,8 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.media.controls.models.player.MediaData
-import com.android.systemui.media.controls.pipeline.MediaDataManager
+import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
+import com.android.systemui.media.controls.shared.model.MediaData
import com.android.systemui.util.mockito.KotlinArgumentCaptor
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
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 5b20ae5131b2..06b3806cb382 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
@@ -25,7 +25,6 @@ import com.android.systemui.coroutines.collectLastValue
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.model.SceneKey
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.flowOf
@@ -57,46 +56,6 @@ class CommunalRepositoryImplTest : SysuiTestCase() {
}
@Test
- fun isCommunalShowing_sceneContainerDisabled_onCommunalScene_true() =
- testScope.runTest {
- underTest.setDesiredScene(CommunalSceneKey.Communal)
-
- val isCommunalHubShowing by collectLastValue(underTest.isCommunalHubShowing)
- assertThat(isCommunalHubShowing).isTrue()
- }
-
- @Test
- fun isCommunalShowing_sceneContainerDisabled_onBlankScene_false() =
- testScope.runTest {
- underTest.setDesiredScene(CommunalSceneKey.Blank)
-
- val isCommunalHubShowing by collectLastValue(underTest.isCommunalHubShowing)
- assertThat(isCommunalHubShowing).isFalse()
- }
-
- @Test
- fun isCommunalShowing_sceneContainerEnabled_onCommunalScene_true() =
- testScope.runTest {
- underTest = createRepositoryImpl(true)
-
- sceneContainerRepository.changeScene(SceneKey.Communal)
-
- val isCommunalHubShowing by collectLastValue(underTest.isCommunalHubShowing)
- assertThat(isCommunalHubShowing).isTrue()
- }
-
- @Test
- fun isCommunalShowing_sceneContainerEnabled_onLockscreenScene_false() =
- testScope.runTest {
- underTest = createRepositoryImpl(true)
-
- sceneContainerRepository.changeScene(SceneKey.Lockscreen)
-
- val isCommunalHubShowing by collectLastValue(underTest.isCommunalHubShowing)
- assertThat(isCommunalHubShowing).isFalse()
- }
-
- @Test
fun transitionState_idleByDefault() =
testScope.runTest {
val transitionState by collectLastValue(underTest.transitionState)
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 824733bcc47b..5a7cbf6e02ca 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
@@ -67,7 +67,7 @@ class CommunalInteractorCommunalDisabledTest : SysuiTestCase() {
@Test
fun isCommunalEnabled_false() =
- testScope.runTest { assertThat(underTest.isCommunalEnabled).isFalse() }
+ testScope.runTest { assertThat(underTest.isCommunalEnabled.value).isFalse() }
@Test
fun isCommunalAvailable_whenStorageUnlock_false() =
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 3ac19e4672c3..4156d833b0de 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
@@ -47,6 +47,10 @@ import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
+import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository
import com.android.systemui.smartspace.data.repository.fakeSmartspaceRepository
import com.android.systemui.testKosmos
@@ -91,6 +95,7 @@ class CommunalInteractorTest : SysuiTestCase() {
private lateinit var keyguardRepository: FakeKeyguardRepository
private lateinit var communalPrefsRepository: FakeCommunalPrefsRepository
private lateinit var editWidgetsActivityStarter: EditWidgetsActivityStarter
+ private lateinit var sceneInteractor: SceneInteractor
private lateinit var underTest: CommunalInteractor
@@ -107,6 +112,7 @@ class CommunalInteractorTest : SysuiTestCase() {
keyguardRepository = kosmos.fakeKeyguardRepository
editWidgetsActivityStarter = kosmos.editWidgetsActivityStarter
communalPrefsRepository = kosmos.fakeCommunalPrefsRepository
+ sceneInteractor = kosmos.sceneInteractor
whenever(mainUser.isMain).thenReturn(true)
whenever(secondaryUser.isMain).thenReturn(false)
@@ -123,7 +129,7 @@ class CommunalInteractorTest : SysuiTestCase() {
testScope.runTest {
userRepository.setSelectedUserInfo(mainUser)
runCurrent()
- assertThat(underTest.isCommunalEnabled).isTrue()
+ assertThat(underTest.isCommunalEnabled.value).isTrue()
}
@Test
@@ -598,17 +604,53 @@ class CommunalInteractorTest : SysuiTestCase() {
}
@Test
- fun isCommunalShowing() =
+ fun isCommunalShowing_whenSceneContainerDisabled() =
testScope.runTest {
- var isCommunalShowing = collectLastValue(underTest.isCommunalShowing)
+ // Verify default is false
+ val isCommunalShowing by collectLastValue(underTest.isCommunalShowing)
runCurrent()
- assertThat(isCommunalShowing()).isEqualTo(false)
+ assertThat(isCommunalShowing).isFalse()
+ // Verify scene changes with the flag doesn't have any impact
+ sceneInteractor.changeScene(SceneKey.Communal, loggingReason = "")
+ runCurrent()
+ assertThat(isCommunalShowing).isFalse()
+
+ // Verify scene changes (without the flag) to communal sets the value to true
+ underTest.onSceneChanged(CommunalSceneKey.Communal)
+ runCurrent()
+ assertThat(isCommunalShowing).isTrue()
+
+ // Verify scene changes (without the flag) to blank sets the value back to false
+ underTest.onSceneChanged(CommunalSceneKey.Blank)
+ runCurrent()
+ assertThat(isCommunalShowing).isFalse()
+ }
+
+ @Test
+ fun isCommunalShowing_whenSceneContainerEnabled() =
+ testScope.runTest {
+ kosmos.fakeSceneContainerFlags.enabled = true
+
+ // Verify default is false
+ val isCommunalShowing by collectLastValue(underTest.isCommunalShowing)
+ runCurrent()
+ assertThat(isCommunalShowing).isFalse()
+
+ // Verify scene changes without the flag doesn't have any impact
underTest.onSceneChanged(CommunalSceneKey.Communal)
+ runCurrent()
+ assertThat(isCommunalShowing).isFalse()
+
+ // Verify scene changes (with the flag) to communal sets the value to true
+ sceneInteractor.changeScene(SceneKey.Communal, loggingReason = "")
+ runCurrent()
+ assertThat(isCommunalShowing).isTrue()
- isCommunalShowing = collectLastValue(underTest.isCommunalShowing)
+ // Verify scene changes (with the flag) to lockscreen sets the value to false
+ sceneInteractor.changeScene(SceneKey.Lockscreen, loggingReason = "")
runCurrent()
- assertThat(isCommunalShowing()).isEqualTo(true)
+ assertThat(isCommunalShowing).isFalse()
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt
index ceb7fac1046f..5211c55ac911 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt
@@ -24,10 +24,9 @@ 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.data.repository.FakeCommunalRepository
import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository
-import com.android.systemui.communal.data.repository.fakeCommunalRepository
import com.android.systemui.communal.data.repository.fakeCommunalTutorialRepository
+import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
@@ -38,13 +37,11 @@ import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.user.data.repository.fakeUserRepository
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class CommunalTutorialInteractorTest : SysuiTestCase() {
@@ -54,7 +51,6 @@ class CommunalTutorialInteractorTest : SysuiTestCase() {
private lateinit var underTest: CommunalTutorialInteractor
private lateinit var keyguardRepository: FakeKeyguardRepository
private lateinit var communalTutorialRepository: FakeCommunalTutorialRepository
- private lateinit var communalRepository: FakeCommunalRepository
private lateinit var communalInteractor: CommunalInteractor
private lateinit var userRepository: FakeUserRepository
@@ -62,7 +58,6 @@ class CommunalTutorialInteractorTest : SysuiTestCase() {
fun setUp() {
keyguardRepository = kosmos.fakeKeyguardRepository
communalTutorialRepository = kosmos.fakeCommunalTutorialRepository
- communalRepository = kosmos.fakeCommunalRepository
communalInteractor = kosmos.communalInteractor
userRepository = kosmos.fakeUserRepository
@@ -90,7 +85,7 @@ class CommunalTutorialInteractorTest : SysuiTestCase() {
setCommunalAvailable(true)
keyguardRepository.setKeyguardShowing(true)
keyguardRepository.setKeyguardOccluded(false)
- communalRepository.setIsCommunalHubShowing(false)
+ communalInteractor.onSceneChanged(CommunalSceneKey.Communal)
communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
assertThat(isTutorialAvailable).isFalse()
}
@@ -112,7 +107,7 @@ class CommunalTutorialInteractorTest : SysuiTestCase() {
setCommunalAvailable(true)
keyguardRepository.setKeyguardShowing(true)
keyguardRepository.setKeyguardOccluded(false)
- communalRepository.setIsCommunalHubShowing(false)
+ communalInteractor.onSceneChanged(CommunalSceneKey.Blank)
communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_NOT_STARTED)
assertThat(isTutorialAvailable).isTrue()
}
@@ -124,7 +119,7 @@ class CommunalTutorialInteractorTest : SysuiTestCase() {
setCommunalAvailable(true)
keyguardRepository.setKeyguardShowing(true)
keyguardRepository.setKeyguardOccluded(false)
- communalRepository.setIsCommunalHubShowing(true)
+ communalInteractor.onSceneChanged(CommunalSceneKey.Communal)
communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_STARTED)
assertThat(isTutorialAvailable).isTrue()
}
@@ -137,7 +132,7 @@ class CommunalTutorialInteractorTest : SysuiTestCase() {
userRepository.setSelectedUserInfo(MAIN_USER_INFO)
communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_NOT_STARTED)
- communalRepository.setIsCommunalHubShowing(true)
+ communalInteractor.onSceneChanged(CommunalSceneKey.Communal)
assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_STARTED)
}
@@ -150,7 +145,7 @@ class CommunalTutorialInteractorTest : SysuiTestCase() {
userRepository.setSelectedUserInfo(MAIN_USER_INFO)
communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_STARTED)
- communalRepository.setIsCommunalHubShowing(true)
+ communalInteractor.onSceneChanged(CommunalSceneKey.Communal)
assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_STARTED)
}
@@ -163,7 +158,7 @@ class CommunalTutorialInteractorTest : SysuiTestCase() {
userRepository.setSelectedUserInfo(MAIN_USER_INFO)
communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
- communalRepository.setIsCommunalHubShowing(true)
+ communalInteractor.onSceneChanged(CommunalSceneKey.Communal)
assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_COMPLETED)
}
@@ -176,7 +171,7 @@ class CommunalTutorialInteractorTest : SysuiTestCase() {
userRepository.setSelectedUserInfo(MAIN_USER_INFO)
communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_NOT_STARTED)
- communalRepository.setIsCommunalHubShowing(false)
+ communalInteractor.onSceneChanged(CommunalSceneKey.Blank)
assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_NOT_STARTED)
}
@@ -187,10 +182,10 @@ class CommunalTutorialInteractorTest : SysuiTestCase() {
val tutorialSettingState by
collectLastValue(communalTutorialRepository.tutorialSettingState)
userRepository.setSelectedUserInfo(MAIN_USER_INFO)
- communalRepository.setIsCommunalHubShowing(true)
+ communalInteractor.onSceneChanged(CommunalSceneKey.Communal)
communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_STARTED)
- communalRepository.setIsCommunalHubShowing(false)
+ communalInteractor.onSceneChanged(CommunalSceneKey.Blank)
assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_COMPLETED)
}
@@ -201,10 +196,10 @@ class CommunalTutorialInteractorTest : SysuiTestCase() {
val tutorialSettingState by
collectLastValue(communalTutorialRepository.tutorialSettingState)
userRepository.setSelectedUserInfo(MAIN_USER_INFO)
- communalRepository.setIsCommunalHubShowing(true)
+ communalInteractor.onSceneChanged(CommunalSceneKey.Communal)
communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
- communalRepository.setIsCommunalHubShowing(false)
+ communalInteractor.onSceneChanged(CommunalSceneKey.Blank)
assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_COMPLETED)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
index ddb8582913e6..352bacc56ca5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
@@ -38,7 +38,7 @@ import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
import com.android.systemui.log.logcatLogBuffer
-import com.android.systemui.media.controls.ui.MediaHost
+import com.android.systemui.media.controls.ui.view.MediaHost
import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository
import com.android.systemui.smartspace.data.repository.fakeSmartspaceRepository
import com.android.systemui.testKosmos
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index b299ca7ee804..cc322d085acd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -43,8 +43,8 @@ import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.kosmos.testScope
import com.android.systemui.log.logcatLogBuffer
-import com.android.systemui.media.controls.ui.MediaHierarchyManager
-import com.android.systemui.media.controls.ui.MediaHost
+import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
+import com.android.systemui.media.controls.ui.view.MediaHost
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository
import com.android.systemui.smartspace.data.repository.fakeSmartspaceRepository
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
index 8a35ef11a364..a6715dfcec24 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
@@ -8,7 +8,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.complication.ComplicationHostViewController
-import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel
+import com.android.systemui.dreams.ui.viewmodel.DreamOverlayViewModel
import com.android.systemui.log.core.FakeLogBuffer
import com.android.systemui.statusbar.BlurUtils
import com.android.systemui.statusbar.policy.ConfigurationController
@@ -47,7 +47,7 @@ class DreamOverlayAnimationsControllerTest : SysuiTestCase() {
@Mock private lateinit var statusBarViewController: DreamOverlayStatusBarViewController
@Mock private lateinit var stateController: DreamOverlayStateController
@Mock private lateinit var configController: ConfigurationController
- @Mock private lateinit var transitionViewModel: DreamingToLockscreenTransitionViewModel
+ @Mock private lateinit var transitionViewModel: DreamOverlayViewModel
private val logBuffer = FakeLogBuffer.Factory.create()
private lateinit var controller: DreamOverlayAnimationsController
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt
index f6c056698967..fb46ed9d54ed 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt
@@ -175,6 +175,21 @@ class LightRevealScrimRepositoryTest : SysuiTestCase() {
animatorTestRule.advanceTimeBy(500L)
assertEquals(1.0f, value)
}
+
+ @Test
+ @TestableLooper.RunWithLooper(setAsMainLooper = true)
+ fun revealAmount_startingRevealTwiceWontRerunAnimator() =
+ runTest(UnconfinedTestDispatcher()) {
+ val value by collectLastValue(underTest.revealAmount)
+ underTest.startRevealAmountAnimator(true)
+ assertEquals(0.0f, value)
+ animatorTestRule.advanceTimeBy(250L)
+ assertEquals(0.5f, value)
+ underTest.startRevealAmountAnimator(true)
+ animatorTestRule.advanceTimeBy(250L)
+ assertEquals(1.0f, value)
+ }
+
@Test
@TestableLooper.RunWithLooper(setAsMainLooper = true)
fun revealAmount_emitsTo0AfterAnimationStartedReversed() =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
index 9368097c0ac9..3484025f8d80 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
@@ -197,7 +197,16 @@ class KeyguardTransitionInteractorTest : SysuiTestCase() {
runCurrent()
}
- assertThat(startedSteps).isEqualTo(listOf(steps[0], steps[3], steps[6]))
+ assertThat(startedSteps)
+ .isEqualTo(
+ listOf(
+ // The initial transition will also get sent when collect started
+ TransitionStep(OFF, LOCKSCREEN, 0f, STARTED),
+ steps[0],
+ steps[3],
+ steps[6]
+ )
+ )
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
index 9b302ae60e21..2b6e6c7c1575 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt
@@ -22,7 +22,6 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.keyguard.data.fakeLightRevealScrimRepository
import com.android.systemui.keyguard.data.repository.FakeLightRevealScrimRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
-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.testScope
@@ -33,16 +32,11 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.test.UnconfinedTestDispatcher
-import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito
-import org.mockito.Mockito.anyBoolean
-import org.mockito.Mockito.never
-import org.mockito.Mockito.reset
-import org.mockito.Mockito.verify
@SmallTest
@OptIn(ExperimentalCoroutinesApi::class)
@@ -103,41 +97,4 @@ class LightRevealScrimInteractorTest : SysuiTestCase() {
job.cancel()
}
-
- @Test
- fun lightRevealEffect_startsAnimationOnlyForDifferentStateTargets() =
- testScope.runTest {
- runCurrent()
- reset(fakeLightRevealScrimRepository)
-
- fakeKeyguardTransitionRepository.sendTransitionStep(
- TransitionStep(
- transitionState = TransitionState.STARTED,
- from = KeyguardState.OFF,
- to = KeyguardState.OFF
- )
- )
- runCurrent()
- verify(fakeLightRevealScrimRepository, never()).startRevealAmountAnimator(anyBoolean())
-
- fakeKeyguardTransitionRepository.sendTransitionStep(
- TransitionStep(
- transitionState = TransitionState.STARTED,
- from = KeyguardState.DOZING,
- to = KeyguardState.LOCKSCREEN
- )
- )
- runCurrent()
- verify(fakeLightRevealScrimRepository).startRevealAmountAnimator(true)
-
- fakeKeyguardTransitionRepository.sendTransitionStep(
- TransitionStep(
- transitionState = TransitionState.STARTED,
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.DOZING
- )
- )
- runCurrent()
- verify(fakeLightRevealScrimRepository).startRevealAmountAnimator(false)
- }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModelTest.kt
index 837a9db6eea7..d33c10e7e663 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModelTest.kt
@@ -20,6 +20,7 @@ package com.android.systemui.keyguard.ui.viewmodel
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags as AConfigFlags
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
@@ -66,6 +67,25 @@ class AodAlphaViewModelTest : SysuiTestCase() {
}
@Test
+ fun alpha_WhenNotGone_clockMigrationFlagIsOff_emitsKeyguardAlpha() =
+ testScope.runTest {
+ mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
+ val alpha by collectLastValue(underTest.alpha)
+
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ testScope = testScope,
+ )
+
+ keyguardRepository.setKeyguardAlpha(0.5f)
+ assertThat(alpha).isEqualTo(0.5f)
+
+ keyguardRepository.setKeyguardAlpha(0.8f)
+ assertThat(alpha).isEqualTo(0.8f)
+ }
+
+ @Test
fun alpha_WhenGoneToAod() =
testScope.runTest {
val alpha by collectLastValue(underTest.alpha)
@@ -112,6 +132,7 @@ class AodAlphaViewModelTest : SysuiTestCase() {
@Test
fun alpha_whenGone_equalsZero() =
testScope.runTest {
+ mSetFlagsRule.enableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
val alpha by collectLastValue(underTest.alpha)
keyguardTransitionRepository.sendTransitionStep(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
index 74fa46519629..b0f59fe68f11 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
@@ -127,7 +127,7 @@ class AodBurnInViewModelTest : SysuiTestCase() {
@Test
fun translationAndScale_whenFullyDozing() =
testScope.runTest {
- burnInParameters = burnInParameters.copy(statusViewTop = 100)
+ burnInParameters = burnInParameters.copy(minViewY = 100)
val translationX by collectLastValue(underTest.translationX(burnInParameters))
val translationY by collectLastValue(underTest.translationY(burnInParameters))
val scale by collectLastValue(underTest.scale(burnInParameters))
@@ -182,11 +182,77 @@ class AodBurnInViewModelTest : SysuiTestCase() {
}
@Test
- fun translationAndScale_whenFullyDozing_staysOutOfTopInset() =
+ fun translationAndScale_whenFullyDozing_MigrationFlagOff_staysOutOfTopInset() =
testScope.runTest {
+ mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
+
+ burnInParameters =
+ burnInParameters.copy(
+ minViewY = 100,
+ topInset = 80,
+ )
+ val translationX by collectLastValue(underTest.translationX(burnInParameters))
+ val translationY by collectLastValue(underTest.translationY(burnInParameters))
+ val scale by collectLastValue(underTest.scale(burnInParameters))
+
+ // Set to dozing (on AOD)
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.GONE,
+ to = KeyguardState.AOD,
+ value = 1f,
+ transitionState = TransitionState.FINISHED
+ ),
+ validateStep = false,
+ )
+
+ // Trigger a change to the burn-in model
+ burnInFlow.value =
+ BurnInModel(
+ translationX = 20,
+ translationY = -30,
+ scale = 0.5f,
+ )
+ assertThat(translationX).isEqualTo(20)
+ // -20 instead of -30, due to inset of 80
+ assertThat(translationY).isEqualTo(-20)
+ assertThat(scale)
+ .isEqualTo(
+ BurnInScaleViewModel(
+ scale = 0.5f,
+ scaleClockOnly = true,
+ )
+ )
+
+ // Set to the beginning of GONE->AOD transition
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.GONE,
+ to = KeyguardState.AOD,
+ value = 0f,
+ transitionState = TransitionState.STARTED
+ ),
+ validateStep = false,
+ )
+ assertThat(translationX).isEqualTo(0)
+ assertThat(translationY).isEqualTo(0)
+ assertThat(scale)
+ .isEqualTo(
+ BurnInScaleViewModel(
+ scale = 1f,
+ scaleClockOnly = true,
+ )
+ )
+ }
+
+ @Test
+ fun translationAndScale_whenFullyDozing_MigrationFlagOn_staysOutOfTopInset() =
+ testScope.runTest {
+ mSetFlagsRule.enableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
+
burnInParameters =
burnInParameters.copy(
- statusViewTop = 100,
+ minViewY = 100,
topInset = 80,
)
val translationX by collectLastValue(underTest.translationX(burnInParameters))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt
index c381749ec6d3..31b67b43adc4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt
@@ -27,6 +27,7 @@ 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.testScope
+import com.android.systemui.shade.data.repository.fakeShadeRepository
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -42,6 +43,7 @@ class AodToLockscreenTransitionViewModelTest : SysuiTestCase() {
val kosmos = testKosmos()
val testScope = kosmos.testScope
val repository = kosmos.fakeKeyguardTransitionRepository
+ val shadeRepository = kosmos.fakeShadeRepository
val fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository
val underTest = kosmos.aodToLockscreenTransitionViewModel
@@ -59,6 +61,38 @@ class AodToLockscreenTransitionViewModelTest : SysuiTestCase() {
}
@Test
+ fun notificationAlpha_whenShadeIsExpanded_equalsOne() =
+ testScope.runTest {
+ val alpha by collectLastValue(underTest.notificationAlpha)
+
+ shadeRepository.setQsExpansion(0.5f)
+ runCurrent()
+
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ assertThat(alpha).isEqualTo(1f)
+ repository.sendTransitionStep(step(0.5f))
+ assertThat(alpha).isEqualTo(1f)
+ repository.sendTransitionStep(step(1f))
+ assertThat(alpha).isEqualTo(1f)
+ }
+
+ @Test
+ fun notificationAlpha_whenShadeIsNotExpanded_usesTransitionValue() =
+ testScope.runTest {
+ val alpha by collectLastValue(underTest.notificationAlpha)
+
+ shadeRepository.setQsExpansion(0f)
+ runCurrent()
+
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ assertThat(alpha).isEqualTo(0f)
+ repository.sendTransitionStep(step(0.5f))
+ assertThat(alpha).isEqualTo(0.5f)
+ repository.sendTransitionStep(step(1f))
+ assertThat(alpha).isEqualTo(1f)
+ }
+
+ @Test
fun lockscreenAlphaStartsFromViewStateAccessorAlpha() =
testScope.runTest {
val viewState = ViewStateAccessor(alpha = { 0.5f })
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModelTest.kt
new file mode 100644
index 000000000000..4defe8a08d3d
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModelTest.kt
@@ -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 com.android.systemui.keyguard.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+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.testScope
+import com.android.systemui.testKosmos
+import com.google.common.collect.Range
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DreamingToGlanceableHubTransitionViewModelTest : SysuiTestCase() {
+ val kosmos = testKosmos()
+ val testScope = kosmos.testScope
+
+ val underTest by lazy { kosmos.dreamingToGlanceableHubTransitionViewModel }
+
+ @Test
+ fun dreamOverlayAlpha() =
+ testScope.runTest {
+ val values by collectValues(underTest.dreamOverlayAlpha)
+ assertThat(values).isEmpty()
+
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
+ listOf(
+ // Should start running here...
+ step(0f, TransitionState.STARTED),
+ step(0f),
+ step(0.1f),
+ step(0.5f),
+ // Up to here...
+ step(1f),
+ ),
+ testScope,
+ )
+
+ assertThat(values).hasSize(4)
+ values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
+ }
+
+ @Test
+ fun dreamOverlayTranslationX() =
+ testScope.runTest {
+ val values by collectValues(underTest.dreamOverlayTranslationX(100))
+ assertThat(values).isEmpty()
+
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
+ listOf(
+ step(0f, TransitionState.STARTED),
+ step(0.3f),
+ step(0.6f),
+ ),
+ testScope,
+ )
+
+ assertThat(values).hasSize(3)
+ values.forEach { assertThat(it).isIn(Range.closed(-100f, 0f)) }
+ }
+
+ private fun step(
+ value: Float,
+ state: TransitionState = TransitionState.RUNNING
+ ): TransitionStep {
+ return TransitionStep(
+ from = KeyguardState.DREAMING,
+ to = KeyguardState.GLANCEABLE_HUB,
+ value = value,
+ transitionState = state,
+ ownerName = "DreamingToGlanceableHubTransitionViewModelTest"
+ )
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModelTest.kt
new file mode 100644
index 000000000000..64125f139a2e
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModelTest.kt
@@ -0,0 +1,114 @@
+/*
+ * 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 androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+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.testScope
+import com.android.systemui.res.R
+import com.android.systemui.testKosmos
+import com.google.common.collect.Range
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class GlanceableHubToLockscreenTransitionViewModelTest : SysuiTestCase() {
+ val kosmos = testKosmos()
+ val testScope = kosmos.testScope
+
+ val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+ val configurationRepository = kosmos.fakeConfigurationRepository
+ val underTest by lazy { kosmos.glanceableHubToLockscreenTransitionViewModel }
+
+ @Test
+ fun lockscreenFadeIn() =
+ testScope.runTest {
+ val values by collectValues(underTest.keyguardAlpha)
+ assertThat(values).containsExactly(0f)
+
+ keyguardTransitionRepository.sendTransitionSteps(
+ listOf(
+ step(0f, TransitionState.STARTED),
+ // Should start running here...
+ step(0.1f),
+ step(0.2f),
+ step(0.3f),
+ step(0.4f),
+ // ...up to here
+ step(0.5f),
+ step(0.6f),
+ step(0.7f),
+ step(0.8f),
+ step(1f),
+ ),
+ testScope,
+ )
+
+ assertThat(values).hasSize(5)
+ values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
+ }
+
+ @Test
+ fun lockscreenTranslationX() =
+ testScope.runTest {
+ configurationRepository.setDimensionPixelSize(
+ R.dimen.hub_to_lockscreen_transition_lockscreen_translation_x,
+ 100
+ )
+ val values by collectValues(underTest.keyguardTranslationX)
+ assertThat(values).isEmpty()
+
+ keyguardTransitionRepository.sendTransitionSteps(
+ listOf(
+ step(0f, TransitionState.STARTED),
+ step(0f),
+ step(0.3f),
+ step(0.5f),
+ step(1f),
+ ),
+ testScope,
+ )
+
+ assertThat(values).hasSize(5)
+ values.forEach { assertThat(it.value).isIn(Range.closed(-100f, 0f)) }
+ }
+
+ private fun step(
+ value: Float,
+ state: TransitionState = TransitionState.RUNNING
+ ): TransitionStep {
+ return TransitionStep(
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.LOCKSCREEN,
+ value = value,
+ transitionState = state,
+ ownerName = this::class.java.simpleName
+ )
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
index e04cbfd88bb3..503fd34ce2c2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
@@ -39,6 +39,7 @@ 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.testScope
+import com.android.systemui.shade.data.repository.fakeShadeRepository
import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationsKeyguardInteractor
import com.android.systemui.statusbar.phone.dozeParameters
import com.android.systemui.statusbar.phone.screenOffAnimationController
@@ -72,6 +73,7 @@ class KeyguardRootViewModelTest : SysuiTestCase() {
private val deviceEntryRepository = kosmos.fakeDeviceEntryRepository
private val notificationsKeyguardInteractor = kosmos.notificationsKeyguardInteractor
private val dozeParameters = kosmos.dozeParameters
+ private val shadeRepository = kosmos.fakeShadeRepository
private val underTest by lazy { kosmos.keyguardRootViewModel }
private val viewState = ViewStateAccessor()
@@ -308,4 +310,22 @@ class KeyguardRootViewModelTest : SysuiTestCase() {
assertThat(alpha).isEqualTo(1.0f)
}
+
+ @Test
+ fun alpha_emitsOnShadeExpansion() =
+ testScope.runTest {
+ val alpha by collectLastValue(underTest.alpha(viewState))
+
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ testScope,
+ )
+
+ shadeRepository.setQsExpansion(0f)
+ assertThat(alpha).isEqualTo(1f)
+
+ shadeRepository.setQsExpansion(0.5f)
+ assertThat(alpha).isEqualTo(0f)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModelTest.kt
new file mode 100644
index 000000000000..241d0b818193
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModelTest.kt
@@ -0,0 +1,115 @@
+/*
+ * 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 androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+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.testScope
+import com.android.systemui.res.R
+import com.android.systemui.testKosmos
+import com.google.common.collect.Range
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class LockscreenToGlanceableHubTransitionViewModelTest : SysuiTestCase() {
+ val kosmos = testKosmos()
+ val testScope = kosmos.testScope
+
+ val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+ val configurationRepository = kosmos.fakeConfigurationRepository
+ val underTest by lazy { kosmos.lockscreenToGlanceableHubTransitionViewModel }
+
+ @Test
+ fun lockscreenFadeOut() =
+ testScope.runTest {
+ val values by collectValues(underTest.keyguardAlpha)
+ assertThat(values).containsExactly(1f)
+
+ keyguardTransitionRepository.sendTransitionSteps(
+ listOf(
+ // Should start running here
+ step(0f, TransitionState.STARTED),
+ step(0.1f),
+ step(0.2f),
+ // ...up to here
+ step(0.3f),
+ step(0.4f),
+ step(0.5f),
+ step(0.6f),
+ step(0.7f),
+ step(0.8f),
+ // ...up to here
+ step(1f),
+ ),
+ testScope,
+ )
+
+ assertThat(values).hasSize(4)
+ values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
+ }
+
+ @Test
+ fun lockscreenTranslationX() =
+ testScope.runTest {
+ configurationRepository.setDimensionPixelSize(
+ R.dimen.lockscreen_to_hub_transition_lockscreen_translation_x,
+ -100
+ )
+ val values by collectValues(underTest.keyguardTranslationX)
+ assertThat(values).isEmpty()
+
+ keyguardTransitionRepository.sendTransitionSteps(
+ listOf(
+ step(0f, TransitionState.STARTED),
+ step(0f),
+ step(0.3f),
+ step(0.5f),
+ step(1f),
+ ),
+ testScope,
+ )
+
+ assertThat(values).hasSize(5)
+ values.forEach { assertThat(it.value).isIn(Range.closed(-100f, 0f)) }
+ }
+
+ private fun step(
+ value: Float,
+ state: TransitionState = TransitionState.RUNNING
+ ): TransitionStep {
+ return TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GLANCEABLE_HUB,
+ value = value,
+ transitionState = state,
+ ownerName = this::class.java.simpleName
+ )
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
index 15cf83c50de3..47e1ee9c1b71 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
@@ -119,15 +119,19 @@ class PrimaryBouncerToGoneTransitionViewModelTest : SysuiTestCase() {
fun lockscreenAlpha_runDimissFromKeyguard() =
testScope.runTest {
val values by collectValues(underTest.lockscreenAlpha)
- runCurrent()
-
sysuiStatusBarStateController.setLeaveOpenOnKeyguardHide(true)
+ runCurrent()
- keyguardTransitionRepository.sendTransitionStep(step(0f, TransitionState.STARTED))
- keyguardTransitionRepository.sendTransitionStep(step(1f))
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.GONE,
+ testScope,
+ )
- assertThat(values.size).isEqualTo(2)
- values.forEach { assertThat(it).isEqualTo(1f) }
+ assertThat(values[0]).isEqualTo(1f)
+ assertThat(values[1]).isEqualTo(1f)
+ // Ensure FINISHED sets alpha to 0
+ assertThat(values[2]).isEqualTo(0f)
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/MinimumTilesResourceRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/MinimumTilesResourceRepositoryTest.kt
new file mode 100644
index 000000000000..62c91633ea81
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/MinimumTilesResourceRepositoryTest.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.data.repository
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.res.R
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class MinimumTilesResourceRepositoryTest : SysuiTestCase() {
+
+ val testableResources = context.orCreateTestableResources
+
+ @Test
+ fun minimumQSTiles_followsConfig() {
+ val minTwo = 2
+ testableResources.addOverride(R.integer.quick_settings_min_num_tiles, minTwo)
+ val underTest = MinimumTilesResourceRepository(context.resources)
+ assertThat(underTest.minNumberOfTiles).isEqualTo(minTwo)
+
+ val minSix = 6
+ testableResources.addOverride(R.integer.quick_settings_min_num_tiles, minSix)
+ val otherUnderTest = MinimumTilesResourceRepository(context.resources)
+ assertThat(otherUnderTest.minNumberOfTiles).isEqualTo(minSix)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
index 3418977c3211..37d472169ae5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
@@ -20,11 +20,11 @@ import android.platform.test.annotations.EnabledOnRavenwood
import android.provider.Settings
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
+import com.android.systemui.res.R
import com.android.systemui.retail.data.repository.FakeRetailModeRepository
import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth.assertThat
@@ -187,6 +187,22 @@ class TileSpecSettingsRepositoryTest : SysuiTestCase() {
assertThat(loadTilesForUser(0)).isEqualTo(DEFAULT_TILES)
}
+ @Test
+ fun prependDefault() =
+ testScope.runTest {
+ val tiles by collectLastValue(underTest.tilesSpecs(0))
+
+ val startingTiles = listOf(TileSpec.create("e"), TileSpec.create("f"))
+
+ underTest.setTiles(0, startingTiles)
+ runCurrent()
+
+ underTest.prependDefault(0)
+
+ assertThat(tiles!!)
+ .containsExactlyElementsIn(DEFAULT_TILES.toTileSpecs() + startingTiles)
+ }
+
private fun TestScope.storeTilesForUser(specs: String, forUser: Int) {
secureSettings.putStringForUser(SETTING, specs, forUser)
runCurrent()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddableTest.kt
index c7e7845f206c..bf34d6ee4435 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddableTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.qs.pipeline.domain.autoaddable
import android.content.pm.UserInfo
+import android.content.pm.UserInfo.FLAG_DISABLED
import android.content.pm.UserInfo.FLAG_FULL
import android.content.pm.UserInfo.FLAG_MANAGED_PROFILE
import android.content.pm.UserInfo.FLAG_PRIMARY
@@ -73,14 +74,14 @@ class WorkTileAutoAddableTest : SysuiTestCase() {
fun changeInProfiles_hasManagedProfile_sendsAddSignal() = runTest {
val signal by collectLastValue(underTest.autoAddSignal(0))
- userTracker.set(listOf(USER_INFO_0, USER_INFO_WORK), selectedUserIndex = 0)
+ userTracker.set(listOf(USER_INFO_0, USER_INFO_WORK_ENABLED), selectedUserIndex = 0)
assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC))
}
@Test
fun changeInProfiles_noManagedProfile_sendsRemoveSignal() = runTest {
- userTracker.set(listOf(USER_INFO_0, USER_INFO_WORK), selectedUserIndex = 0)
+ userTracker.set(listOf(USER_INFO_0, USER_INFO_WORK_ENABLED), selectedUserIndex = 0)
val signal by collectLastValue(underTest.autoAddSignal(0))
@@ -90,8 +91,17 @@ class WorkTileAutoAddableTest : SysuiTestCase() {
}
@Test
+ fun changeInProfile_hasDisabledManagedProfile_noAddSignal() = runTest {
+ val signal by collectLastValue(underTest.autoAddSignal(0))
+
+ userTracker.set(listOf(USER_INFO_0, USER_INFO_WORK_DISABLED), selectedUserIndex = 0)
+
+ assertThat(signal).isNotInstanceOf(AutoAddSignal.Add::class.java)
+ }
+
+ @Test
fun startingWithManagedProfile_sendsAddSignal() = runTest {
- userTracker.set(listOf(USER_INFO_0, USER_INFO_WORK), selectedUserIndex = 0)
+ userTracker.set(listOf(USER_INFO_0, USER_INFO_WORK_ENABLED), selectedUserIndex = 0)
val signal by collectLastValue(underTest.autoAddSignal(0))
@@ -102,14 +112,14 @@ class WorkTileAutoAddableTest : SysuiTestCase() {
fun userChangeToUserWithProfile_noSignalForOriginalUser() = runTest {
val signal by collectLastValue(underTest.autoAddSignal(0))
- userTracker.set(listOf(USER_INFO_1, USER_INFO_WORK), selectedUserIndex = 0)
+ userTracker.set(listOf(USER_INFO_1, USER_INFO_WORK_ENABLED), selectedUserIndex = 0)
assertThat(signal).isNotEqualTo(AutoAddSignal.Add(SPEC))
}
@Test
fun userChangeToUserWithoutProfile_noSignalForOriginalUser() = runTest {
- userTracker.set(listOf(USER_INFO_0, USER_INFO_WORK), selectedUserIndex = 0)
+ userTracker.set(listOf(USER_INFO_0, USER_INFO_WORK_ENABLED), selectedUserIndex = 0)
val signal by collectLastValue(underTest.autoAddSignal(0))
userTracker.set(listOf(USER_INFO_1), selectedUserIndex = 0)
@@ -137,7 +147,7 @@ class WorkTileAutoAddableTest : SysuiTestCase() {
@Test
fun restoreDataWithWorkTile_currentlyManagedProfile_doesntTriggerRemove() = runTest {
- userTracker.set(listOf(USER_INFO_0, USER_INFO_WORK), selectedUserIndex = 0)
+ userTracker.set(listOf(USER_INFO_0, USER_INFO_WORK_ENABLED), selectedUserIndex = 0)
val userId = 0
val signals by collectValues(underTest.autoAddSignal(userId))
runCurrent()
@@ -164,7 +174,7 @@ class WorkTileAutoAddableTest : SysuiTestCase() {
@Test
fun restoreDataWithoutWorkTile_managedProfile_doesntTriggerRemove() = runTest {
- userTracker.set(listOf(USER_INFO_0, USER_INFO_WORK), selectedUserIndex = 0)
+ userTracker.set(listOf(USER_INFO_0, USER_INFO_WORK_ENABLED), selectedUserIndex = 0)
val userId = 0
val signals by collectValues(underTest.autoAddSignal(userId))
runCurrent()
@@ -180,7 +190,9 @@ class WorkTileAutoAddableTest : SysuiTestCase() {
private val SPEC = TileSpec.create(WorkModeTile.TILE_SPEC)
private val USER_INFO_0 = UserInfo(0, "", FLAG_PRIMARY or FLAG_FULL)
private val USER_INFO_1 = UserInfo(1, "", FLAG_FULL)
- private val USER_INFO_WORK = UserInfo(10, "", FLAG_PROFILE or FLAG_MANAGED_PROFILE)
+ private val USER_INFO_WORK_DISABLED =
+ UserInfo(10, "", FLAG_PROFILE or FLAG_MANAGED_PROFILE or FLAG_DISABLED)
+ private val USER_INFO_WORK_ENABLED = UserInfo(10, "", FLAG_PROFILE or FLAG_MANAGED_PROFILE)
private fun createRestoreWithWorkTile(userId: Int): RestoreData {
return RestoreData(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorTest.kt
index 2ea12ef4c88f..8ae917264a37 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorTest.kt
@@ -26,6 +26,7 @@ import com.android.systemui.qs.pipeline.data.repository.FakeAutoAddRepository
import com.android.systemui.qs.pipeline.domain.autoaddable.FakeAutoAddable
import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
import com.android.systemui.qs.pipeline.domain.model.AutoAddable
+import com.android.systemui.qs.pipeline.domain.model.TileModel
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
import com.android.systemui.util.mockito.any
@@ -65,6 +66,7 @@ class AutoAddInteractorTest : SysuiTestCase() {
MockitoAnnotations.initMocks(this)
whenever(currentTilesInteractor.userId).thenReturn(MutableStateFlow(USER))
+ whenever(currentTilesInteractor.currentTiles).thenReturn(MutableStateFlow(emptyList()))
}
@Test
@@ -201,6 +203,45 @@ class AutoAddInteractorTest : SysuiTestCase() {
assertThat(autoAddedTiles).doesNotContain(SPEC)
}
+ @Test
+ fun autoAddable_trackIfNotAdded_currentTile_markedAsAdded() =
+ testScope.runTest {
+ val fakeTile = FakeQSTile(USER).apply { tileSpec = SPEC.spec }
+ val fakeCurrentTileModel = TileModel(SPEC, fakeTile)
+ whenever(currentTilesInteractor.currentTiles)
+ .thenReturn(MutableStateFlow(listOf(fakeCurrentTileModel)))
+
+ val autoAddedTiles by collectLastValue(autoAddRepository.autoAddedTiles(USER))
+ val fakeAutoAddable = FakeAutoAddable(SPEC, AutoAddTracking.IfNotAdded(SPEC))
+
+ underTest = createInteractor(setOf(fakeAutoAddable))
+ runCurrent()
+
+ assertThat(autoAddedTiles).contains(SPEC)
+ }
+
+ @Test
+ fun autoAddable_trackIfNotAdded_tileAddedToCurrentTiles_markedAsAdded() =
+ testScope.runTest {
+ val fakeTile = FakeQSTile(USER).apply { tileSpec = SPEC.spec }
+ val fakeCurrentTileModel = TileModel(SPEC, fakeTile)
+ val currentTilesFlow = MutableStateFlow(emptyList<TileModel>())
+
+ whenever(currentTilesInteractor.currentTiles).thenReturn(currentTilesFlow)
+
+ val autoAddedTiles by collectLastValue(autoAddRepository.autoAddedTiles(USER))
+ val fakeAutoAddable = FakeAutoAddable(SPEC, AutoAddTracking.IfNotAdded(SPEC))
+
+ underTest = createInteractor(setOf(fakeAutoAddable))
+ runCurrent()
+
+ assertThat(autoAddedTiles).doesNotContain(SPEC)
+
+ currentTilesFlow.value = listOf(fakeCurrentTileModel)
+
+ assertThat(autoAddedTiles).contains(SPEC)
+ }
+
private fun createInteractor(autoAddables: Set<AutoAddable>): AutoAddInteractor {
return AutoAddInteractor(
autoAddables,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
index 1e2784a622b1..634c5fa74295 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
@@ -40,6 +40,7 @@ import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepositor
import com.android.systemui.qs.pipeline.data.repository.FakeCustomTileAddedRepository
import com.android.systemui.qs.pipeline.data.repository.FakeInstalledTilesComponentRepository
import com.android.systemui.qs.pipeline.data.repository.FakeTileSpecRepository
+import com.android.systemui.qs.pipeline.data.repository.MinimumTilesFixedRepository
import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository
import com.android.systemui.qs.pipeline.domain.model.TileModel
import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository
@@ -82,6 +83,7 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() {
FakeCustomTileAddedRepository()
private val pipelineFlags = QSPipelineFlagsRepository()
private val tileLifecycleManagerFactory = TLMFactory()
+ private val minimumTilesRepository = MinimumTilesFixedRepository()
@Mock private lateinit var customTileStatePersister: CustomTileStatePersister
@@ -114,6 +116,7 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() {
tileSpecRepository = tileSpecRepository,
installedTilesComponentRepository = installedTilesPackageRepository,
userRepository = userRepository,
+ minimumTilesRepository = minimumTilesRepository,
customTileStatePersister = customTileStatePersister,
tileFactory = tileFactory,
newQSTileFactory = { newQSTileFactory },
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/NoLowNumberOfTilesTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/NoLowNumberOfTilesTest.kt
new file mode 100644
index 000000000000..90c83047e72f
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/NoLowNumberOfTilesTest.kt
@@ -0,0 +1,147 @@
+/*
+ * 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.pipeline.domain.interactor
+
+import android.content.ComponentName
+import android.content.pm.UserInfo
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import com.android.systemui.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.qs.FakeQSFactory
+import com.android.systemui.qs.pipeline.data.model.RestoreData
+import com.android.systemui.qs.pipeline.data.repository.FakeDefaultTilesRepository
+import com.android.systemui.qs.pipeline.data.repository.MinimumTilesFixedRepository
+import com.android.systemui.qs.pipeline.data.repository.fakeDefaultTilesRepository
+import com.android.systemui.qs.pipeline.data.repository.fakeMinimumTilesRepository
+import com.android.systemui.qs.pipeline.data.repository.fakeRestoreRepository
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.qsTileFactory
+import com.android.systemui.settings.fakeUserTracker
+import com.android.systemui.settings.userTracker
+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
+
+/**
+ * This integration test is for testing the solution to b/324575996. In particular, when restoring
+ * from a device that uses different specs for tiles, we may end up with empty (or mostly empty) QS.
+ * In that case, we want to prepend the default tiles instead.
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class NoLowNumberOfTilesTest : SysuiTestCase() {
+
+ private val USER_0_INFO =
+ UserInfo(
+ 0,
+ "zero",
+ "",
+ UserInfo.FLAG_ADMIN or UserInfo.FLAG_FULL,
+ )
+
+ private val defaultTiles =
+ listOf(
+ TileSpec.create("internet"),
+ TileSpec.create("bt"),
+ )
+
+ private val kosmos =
+ Kosmos().apply {
+ fakeMinimumTilesRepository = MinimumTilesFixedRepository(minNumberOfTiles = 2)
+ fakeUserTracker.set(listOf(USER_0_INFO), 0)
+ qsTileFactory = FakeQSFactory(::tileCreator)
+ fakeDefaultTilesRepository = FakeDefaultTilesRepository(defaultTiles)
+ }
+
+ private val currentUser: Int
+ get() = kosmos.userTracker.userId
+
+ private val goodTile = TileSpec.create("correct")
+
+ private val restoredTiles =
+ listOf(
+ TileSpec.create("OEM:internet"),
+ TileSpec.create("OEM:bt"),
+ TileSpec.create("OEM:dnd"),
+ // This is not an installed component so a tile won't be created
+ TileSpec.create(ComponentName.unflattenFromString("oem/.tile")!!),
+ TileSpec.create("OEM:flashlight"),
+ goodTile,
+ )
+
+ @Before
+ fun setUp() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_QS_NEW_PIPELINE)
+
+ with(kosmos) {
+ restoreReconciliationInteractor.start()
+ autoAddInteractor.init(kosmos.currentTilesInteractor)
+ }
+ }
+
+ @Test
+ fun noLessThanTwoTilesAfterOEMRestore_prependedDefault() =
+ with(kosmos) {
+ testScope.runTest {
+ val tiles by collectLastValue(currentTilesInteractor.currentTiles)
+ runCurrent()
+
+ assertThat(tiles!!).isNotEmpty()
+
+ val restoreData = RestoreData(restoredTiles, emptySet(), currentUser)
+ fakeRestoreRepository.onDataRestored(restoreData)
+ runCurrent()
+
+ assertThat(tiles!!.map { it.spec }).isEqualTo(defaultTiles + listOf(goodTile))
+ }
+ }
+
+ @Test
+ fun noEmptyTilesAfterSettingTilesToUnknownNames() =
+ with(kosmos) {
+ testScope.runTest {
+ val tiles by collectLastValue(currentTilesInteractor.currentTiles)
+ runCurrent()
+
+ assertThat(tiles!!).isNotEmpty()
+
+ val badTiles = listOf(TileSpec.create("OEM:unknown_tile"))
+ currentTilesInteractor.setTiles(badTiles)
+ runCurrent()
+
+ assertThat(tiles!!.map { it.spec }).isEqualTo(defaultTiles)
+ }
+ }
+
+ private fun tileCreator(spec: String): QSTile? {
+ return if (spec.contains("OEM")) {
+ null // We don't know how to create OEM spec tiles
+ } else {
+ FakeQSTile(currentUser)
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractorTest.kt
new file mode 100644
index 000000000000..a5c554406848
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractorTest.kt
@@ -0,0 +1,241 @@
+/*
+ * 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.tiles.impl.custom.domain.interactor
+
+import android.content.ComponentName
+import android.content.pm.UserInfo
+import android.graphics.drawable.Icon
+import android.service.quicksettings.Tile
+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.coroutines.collectValues
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.qs.external.componentName
+import com.android.systemui.qs.external.iQSTileService
+import com.android.systemui.qs.external.tileServiceManagerFacade
+import com.android.systemui.qs.external.tileServicesFacade
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.impl.custom.TileSubject.Companion.assertThat
+import com.android.systemui.qs.tiles.impl.custom.customTileDefaultsRepository
+import com.android.systemui.qs.tiles.impl.custom.customTileInteractor
+import com.android.systemui.qs.tiles.impl.custom.customTilePackagesUpdatesRepository
+import com.android.systemui.qs.tiles.impl.custom.customTileRepository
+import com.android.systemui.qs.tiles.impl.custom.customTileServiceInteractor
+import com.android.systemui.qs.tiles.impl.custom.data.entity.CustomTileDefaults
+import com.android.systemui.qs.tiles.impl.custom.tileSpec
+import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.user.data.repository.userRepository
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@OptIn(ExperimentalCoroutinesApi::class)
+class CustomTileDataInteractorTest : SysuiTestCase() {
+
+ private val kosmos =
+ testKosmos().apply {
+ componentName = TEST_COMPONENT
+ tileSpec = TileSpec.create(componentName)
+ }
+ private val underTest =
+ with(kosmos) {
+ CustomTileDataInteractor(
+ tileSpec = tileSpec,
+ defaultsRepository = customTileDefaultsRepository,
+ serviceInteractor = customTileServiceInteractor,
+ customTileInteractor = customTileInteractor,
+ packageUpdatesRepository = customTilePackagesUpdatesRepository,
+ userRepository = userRepository,
+ tileScope = testScope.backgroundScope,
+ )
+ }
+
+ private suspend fun setup() {
+ with(kosmos) {
+ fakeUserRepository.setUserInfos(listOf(TEST_USER_1))
+ fakeUserRepository.setSelectedUserInfo(TEST_USER_1)
+ }
+ }
+
+ @Test
+ fun activeTileIsNotBoundUntilDataCollected() =
+ with(kosmos) {
+ testScope.runTest {
+ setup()
+ customTileRepository.setTileActive(true)
+
+ runCurrent()
+
+ assertThat(iQSTileService.isTileListening).isFalse()
+ assertThat(tileServiceManagerFacade.isBound).isFalse()
+ }
+ }
+
+ @Test
+ fun notActiveTileIsNotBoundUntilDataCollected() =
+ with(kosmos) {
+ testScope.runTest {
+ setup()
+ customTileRepository.setTileActive(false)
+
+ runCurrent()
+
+ assertThat(iQSTileService.isTileListening).isFalse()
+ assertThat(tileServiceManagerFacade.isBound).isFalse()
+ }
+ }
+
+ @Test
+ fun tileIsUnboundWhenDataIsNotListened() =
+ with(kosmos) {
+ testScope.runTest {
+ setup()
+ customTileRepository.setTileActive(false)
+ customTileDefaultsRepository.putDefaults(
+ TEST_USER_1.userHandle,
+ componentName,
+ CustomTileDefaults.Result(TEST_TILE.icon, TEST_TILE.label),
+ )
+ val dataJob =
+ underTest
+ .tileData(TEST_USER_1.userHandle, flowOf(DataUpdateTrigger.InitialRequest))
+ .launchIn(backgroundScope)
+ runCurrent()
+ tileServiceManagerFacade.processPendingBind()
+ assertThat(iQSTileService.isTileListening).isTrue()
+ assertThat(tileServiceManagerFacade.isBound).isTrue()
+
+ dataJob.cancel()
+ runCurrent()
+
+ assertThat(iQSTileService.isTileListening).isFalse()
+ assertThat(tileServiceManagerFacade.isBound).isFalse()
+ }
+ }
+
+ @Test
+ fun tileDataCollection() =
+ with(kosmos) {
+ testScope.runTest {
+ setup()
+ customTileDefaultsRepository.putDefaults(
+ TEST_USER_1.userHandle,
+ componentName,
+ CustomTileDefaults.Result(TEST_TILE.icon, TEST_TILE.label),
+ )
+ val tileData by
+ collectLastValue(
+ underTest.tileData(
+ TEST_USER_1.userHandle,
+ flowOf(DataUpdateTrigger.InitialRequest)
+ )
+ )
+ runCurrent()
+ tileServicesFacade.customTileInterface!!.updateTileState(TEST_TILE, 1)
+
+ runCurrent()
+
+ with(tileData!!) {
+ assertThat(user.identifier).isEqualTo(TEST_USER_1.id)
+ assertThat(componentName).isEqualTo(componentName)
+ assertThat(tile).isEqualTo(TEST_TILE)
+ assertThat(callingAppUid).isEqualTo(1)
+ assertThat(hasPendingBind).isEqualTo(true)
+ assertThat(isToggleable).isEqualTo(false)
+ assertThat(defaultTileIcon).isEqualTo(TEST_TILE.icon)
+ assertThat(defaultTileLabel).isEqualTo(TEST_TILE.label)
+ }
+ }
+ }
+
+ @Test
+ fun tileAvailableWhenDefaultsAreLoaded() =
+ with(kosmos) {
+ testScope.runTest {
+ setup()
+ customTileDefaultsRepository.putDefaults(
+ TEST_USER_1.userHandle,
+ tileSpec.componentName,
+ CustomTileDefaults.Result(TEST_TILE.icon, TEST_TILE.label),
+ )
+
+ val isAvailable by collectValues(underTest.availability(TEST_USER_1.userHandle))
+ runCurrent()
+
+ assertThat(isAvailable).containsExactlyElementsIn(arrayOf(true)).inOrder()
+ }
+ }
+
+ @Test
+ fun tileUnavailableWhenDefaultsAreNotLoaded() =
+ with(kosmos) {
+ testScope.runTest {
+ setup()
+ customTileDefaultsRepository.putDefaults(
+ TEST_USER_1.userHandle,
+ tileSpec.componentName,
+ CustomTileDefaults.Error,
+ )
+
+ val isAvailable by collectValues(underTest.availability(TEST_USER_1.userHandle))
+ runCurrent()
+
+ assertThat(isAvailable).containsExactlyElementsIn(arrayOf(false)).inOrder()
+ }
+ }
+
+ @Test
+ fun tileAvailabilityUndefinedWhenDefaultsAreLoadedForAnotherUser() =
+ with(kosmos) {
+ testScope.runTest {
+ setup()
+ customTileDefaultsRepository.putDefaults(
+ TEST_USER_2.userHandle,
+ tileSpec.componentName,
+ CustomTileDefaults.Error,
+ )
+
+ val isAvailable by collectValues(underTest.availability(TEST_USER_1.userHandle))
+ runCurrent()
+
+ assertThat(isAvailable).containsExactlyElementsIn(arrayOf()).inOrder()
+ }
+ }
+
+ private companion object {
+
+ val TEST_COMPONENT = ComponentName("test.pkg", "test.cls")
+ val TEST_USER_1 = UserInfo(1, "first user", UserInfo.FLAG_MAIN)
+ val TEST_USER_2 = UserInfo(2, "second user", UserInfo.FLAG_MAIN)
+ val TEST_TILE =
+ Tile().apply {
+ label = "test_tile_1"
+ icon = Icon.createWithContentUri("file://test_1")
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt
index 995d6ac66137..9546a32e2a06 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt
@@ -25,7 +25,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectValues
-import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
import com.android.systemui.qs.external.TileServiceKey
import com.android.systemui.qs.pipeline.shared.TileSpec
@@ -35,6 +34,7 @@ import com.android.systemui.qs.tiles.impl.custom.customTileRepository
import com.android.systemui.qs.tiles.impl.custom.customTileStatePersister
import com.android.systemui.qs.tiles.impl.custom.data.entity.CustomTileDefaults
import com.android.systemui.qs.tiles.impl.custom.tileSpec
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.first
@@ -50,16 +50,16 @@ import org.junit.runner.RunWith
@OptIn(ExperimentalCoroutinesApi::class)
class CustomTileInteractorTest : SysuiTestCase() {
- private val kosmos = Kosmos().apply { tileSpec = TileSpec.create(TEST_COMPONENT) }
+ private val kosmos = testKosmos().apply { tileSpec = TileSpec.create(TEST_COMPONENT) }
private val underTest: CustomTileInteractor =
with(kosmos) {
CustomTileInteractor(
- tileSpec,
- customTileDefaultsRepository,
- customTileRepository,
- testScope.backgroundScope,
- testScope.testScheduler,
+ tileSpec = tileSpec,
+ defaultsRepository = customTileDefaultsRepository,
+ customTileRepository = customTileRepository,
+ tileScope = testScope.backgroundScope,
+ backgroundContext = testScope.testScheduler,
)
}
@@ -69,14 +69,14 @@ class CustomTileInteractorTest : SysuiTestCase() {
testScope.runTest {
customTileRepository.setTileActive(true)
customTileStatePersister.persistState(
- TileServiceKey(TEST_COMPONENT, TEST_USER.identifier),
- TEST_TILE,
+ TileServiceKey(TEST_COMPONENT, TEST_USER_1.identifier),
+ TEST_TILE_1,
)
- underTest.initForUser(TEST_USER)
+ underTest.initForUser(TEST_USER_1)
- assertThat(underTest.getTile(TEST_USER)).isEqualTo(TEST_TILE)
- assertThat(underTest.getTiles(TEST_USER).first()).isEqualTo(TEST_TILE)
+ assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(TEST_TILE_1)
+ assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(TEST_TILE_1)
}
}
@@ -86,18 +86,18 @@ class CustomTileInteractorTest : SysuiTestCase() {
testScope.runTest {
customTileRepository.setTileActive(false)
customTileStatePersister.persistState(
- TileServiceKey(TEST_COMPONENT, TEST_USER.identifier),
- TEST_TILE,
+ TileServiceKey(TEST_COMPONENT, TEST_USER_1.identifier),
+ TEST_TILE_1,
)
- val tiles = collectValues(underTest.getTiles(TEST_USER))
- val initJob = launch { underTest.initForUser(TEST_USER) }
+ val tiles = collectValues(underTest.getTiles(TEST_USER_1))
+ val initJob = launch { underTest.initForUser(TEST_USER_1) }
- underTest.updateTile(TEST_TILE)
+ underTest.updateTile(TEST_TILE_1)
runCurrent()
initJob.join()
assertThat(tiles()).hasSize(1)
- assertThat(tiles().last()).isEqualTo(TEST_TILE)
+ assertThat(tiles().last()).isEqualTo(TEST_TILE_1)
}
}
@@ -107,34 +107,34 @@ class CustomTileInteractorTest : SysuiTestCase() {
testScope.runTest {
customTileRepository.setTileActive(false)
customTileStatePersister.persistState(
- TileServiceKey(TEST_COMPONENT, TEST_USER.identifier),
- TEST_TILE,
+ TileServiceKey(TEST_COMPONENT, TEST_USER_1.identifier),
+ TEST_TILE_1,
)
- val tiles = collectValues(underTest.getTiles(TEST_USER))
- val initJob = launch { underTest.initForUser(TEST_USER) }
+ val tiles = collectValues(underTest.getTiles(TEST_USER_1))
+ val initJob = launch { underTest.initForUser(TEST_USER_1) }
- customTileDefaultsRepository.putDefaults(TEST_USER, TEST_COMPONENT, TEST_DEFAULTS)
- customTileDefaultsRepository.requestNewDefaults(TEST_USER, TEST_COMPONENT)
+ customTileDefaultsRepository.putDefaults(TEST_USER_1, TEST_COMPONENT, TEST_DEFAULTS)
+ customTileDefaultsRepository.requestNewDefaults(TEST_USER_1, TEST_COMPONENT)
runCurrent()
initJob.join()
assertThat(tiles()).hasSize(1)
- assertThat(tiles().last()).isEqualTo(TEST_TILE)
+ assertThat(tiles().last()).isEqualTo(TEST_TILE_1)
}
}
@Test(expected = IllegalStateException::class)
fun getTileBeforeInitThrows() =
- with(kosmos) { testScope.runTest { underTest.getTile(TEST_USER) } }
+ with(kosmos) { testScope.runTest { underTest.getTile(TEST_USER_1) } }
@Test
fun initSuspendsForActiveTileNotRestoredAndNotUpdated() =
with(kosmos) {
testScope.runTest {
customTileRepository.setTileActive(true)
- val tiles = collectValues(underTest.getTiles(TEST_USER))
+ val tiles = collectValues(underTest.getTiles(TEST_USER_1))
- val initJob = backgroundScope.launch { underTest.initForUser(TEST_USER) }
+ val initJob = backgroundScope.launch { underTest.initForUser(TEST_USER_1) }
advanceTimeBy(1 * DateUtils.DAY_IN_MILLIS)
// Is still suspended
@@ -149,12 +149,12 @@ class CustomTileInteractorTest : SysuiTestCase() {
testScope.runTest {
customTileRepository.setTileActive(false)
customTileStatePersister.persistState(
- TileServiceKey(TEST_COMPONENT, TEST_USER.identifier),
- TEST_TILE,
+ TileServiceKey(TEST_COMPONENT, TEST_USER_1.identifier),
+ TEST_TILE_1,
)
- val tiles = collectValues(underTest.getTiles(TEST_USER))
+ val tiles = collectValues(underTest.getTiles(TEST_USER_1))
- val initJob = backgroundScope.launch { underTest.initForUser(TEST_USER) }
+ val initJob = backgroundScope.launch { underTest.initForUser(TEST_USER_1) }
advanceTimeBy(1 * DateUtils.DAY_IN_MILLIS)
// Is still suspended
@@ -176,18 +176,89 @@ class CustomTileInteractorTest : SysuiTestCase() {
}
}
+ @Test
+ fun activeFollowsTheRepository() {
+ with(kosmos) {
+ testScope.runTest {
+ customTileRepository.setTileActive(false)
+ assertThat(underTest.isTileActive()).isFalse()
+
+ customTileRepository.setTileActive(true)
+ assertThat(underTest.isTileActive()).isTrue()
+ }
+ }
+ }
+
+ @Test
+ fun initForTheSameUserProcessedOnce() =
+ with(kosmos) {
+ testScope.runTest {
+ customTileRepository.setTileActive(false)
+ customTileStatePersister.persistState(
+ TileServiceKey(TEST_COMPONENT, TEST_USER_1.identifier),
+ TEST_TILE_1,
+ )
+ val tiles = collectValues(underTest.getTiles(TEST_USER_1))
+ val initJob = launch {
+ underTest.initForUser(TEST_USER_1)
+ underTest.initForUser(TEST_USER_1)
+ }
+
+ underTest.updateTile(TEST_TILE_1)
+ runCurrent()
+ initJob.join()
+
+ assertThat(tiles()).hasSize(1)
+ assertThat(tiles().last()).isEqualTo(TEST_TILE_1)
+ }
+ }
+
+ @Test
+ fun initForDifferentUsersProcessedOnce() =
+ with(kosmos) {
+ testScope.runTest {
+ customTileRepository.setTileActive(true)
+ customTileStatePersister.persistState(
+ TileServiceKey(TEST_COMPONENT, TEST_USER_1.identifier),
+ TEST_TILE_1,
+ )
+ customTileStatePersister.persistState(
+ TileServiceKey(TEST_COMPONENT, TEST_USER_2.identifier),
+ TEST_TILE_2,
+ )
+ val tiles1 by collectValues(underTest.getTiles(TEST_USER_1))
+ val tiles2 by collectValues(underTest.getTiles(TEST_USER_2))
+
+ val initJob = launch {
+ underTest.initForUser(TEST_USER_1)
+ underTest.initForUser(TEST_USER_2)
+ }
+ runCurrent()
+ initJob.join()
+
+ assertThat(tiles1).isEmpty()
+ assertThat(tiles2).hasSize(1)
+ assertThat(tiles2.last()).isEqualTo(TEST_TILE_2)
+ }
+ }
+
private companion object {
val TEST_COMPONENT = ComponentName("test.pkg", "test.cls")
- val TEST_USER = UserHandle.of(1)!!
- val TEST_TILE by lazy {
+ val TEST_USER_1 = UserHandle.of(1)!!
+ val TEST_USER_2 = UserHandle.of(2)!!
+ val TEST_TILE_1 by lazy {
Tile().apply {
label = "test_tile_1"
icon = Icon.createWithContentUri("file://test_1")
}
}
- val TEST_DEFAULTS by lazy {
- CustomTileDefaults.Result(TEST_TILE.icon, TEST_TILE.label)
+ val TEST_TILE_2 by lazy {
+ Tile().apply {
+ label = "test_tile_2"
+ icon = Icon.createWithContentUri("file://test_2")
+ }
}
+ val TEST_DEFAULTS by lazy { CustomTileDefaults.Result(TEST_TILE_1.icon, TEST_TILE_1.label) }
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt
new file mode 100644
index 000000000000..a2127a4717ce
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt
@@ -0,0 +1,265 @@
+/*
+ * 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.tiles.impl.custom.domain.interactor
+
+import android.app.IUriGrantsManager
+import android.content.ComponentName
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.Icon
+import android.graphics.drawable.TestStubDrawable
+import android.os.UserHandle
+import android.service.quicksettings.Tile
+import android.widget.Button
+import android.widget.Switch
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject.Companion.assertThat
+import com.android.systemui.qs.tiles.impl.custom.customTileQsTileConfig
+import com.android.systemui.qs.tiles.impl.custom.domain.CustomTileMapper
+import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel
+import com.android.systemui.qs.tiles.impl.custom.tileSpec
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CustomTileMapperTest : SysuiTestCase() {
+
+ private val uriGrantsManager: IUriGrantsManager = mock {}
+ private val kosmos = testKosmos().apply { tileSpec = TileSpec.Companion.create(TEST_COMPONENT) }
+ private val underTest by lazy {
+ CustomTileMapper(
+ context = mock { whenever(createContextAsUser(any(), any())).thenReturn(context) },
+ uriGrantsManager = uriGrantsManager,
+ )
+ }
+
+ @Test
+ fun stateHasPendingBinding() =
+ with(kosmos) {
+ testScope.runTest {
+ val actual =
+ underTest.map(
+ customTileQsTileConfig,
+ createModel(hasPendingBind = true),
+ )
+ val expected =
+ createTileState(
+ activationState = QSTileState.ActivationState.UNAVAILABLE,
+ actions = setOf(QSTileState.UserAction.LONG_CLICK),
+ )
+
+ assertThat(actual).isEqualTo(expected)
+ }
+ }
+
+ @Test
+ fun stateActive() =
+ with(kosmos) {
+ testScope.runTest {
+ val actual =
+ underTest.map(
+ customTileQsTileConfig,
+ createModel(tileState = Tile.STATE_ACTIVE),
+ )
+ val expected =
+ createTileState(
+ activationState = QSTileState.ActivationState.ACTIVE,
+ )
+
+ assertThat(actual).isEqualTo(expected)
+ }
+ }
+
+ @Test
+ fun stateInactive() =
+ with(kosmos) {
+ testScope.runTest {
+ val actual =
+ underTest.map(
+ customTileQsTileConfig,
+ createModel(tileState = Tile.STATE_INACTIVE),
+ )
+ val expected =
+ createTileState(
+ activationState = QSTileState.ActivationState.INACTIVE,
+ )
+
+ assertThat(actual).isEqualTo(expected)
+ }
+ }
+
+ @Test
+ fun stateUnavailable() =
+ with(kosmos) {
+ testScope.runTest {
+ val actual =
+ underTest.map(
+ customTileQsTileConfig,
+ createModel(tileState = Tile.STATE_UNAVAILABLE),
+ )
+ val expected =
+ createTileState(
+ activationState = QSTileState.ActivationState.UNAVAILABLE,
+ actions = setOf(QSTileState.UserAction.LONG_CLICK),
+ )
+
+ assertThat(actual).isEqualTo(expected)
+ }
+ }
+
+ @Test
+ fun tileWithChevron() =
+ with(kosmos) {
+ testScope.runTest {
+ val actual =
+ underTest.map(
+ customTileQsTileConfig,
+ createModel(isToggleable = false),
+ )
+ val expected =
+ createTileState(
+ sideIcon = QSTileState.SideViewIcon.Chevron,
+ a11yClass = Button::class.qualifiedName,
+ )
+
+ assertThat(actual).isEqualTo(expected)
+ }
+ }
+
+ @Test
+ fun defaultIconFallback() =
+ with(kosmos) {
+ testScope.runTest {
+ val actual =
+ underTest.map(
+ customTileQsTileConfig,
+ createModel(tileIcon = createIcon(RuntimeException(), false)),
+ )
+ val expected =
+ createTileState(
+ activationState = QSTileState.ActivationState.INACTIVE,
+ icon = DEFAULT_DRAWABLE,
+ )
+
+ assertThat(actual).isEqualTo(expected)
+ }
+ }
+
+ @Test
+ fun failedToLoadIconTileIsInactive() =
+ with(kosmos) {
+ testScope.runTest {
+ val actual =
+ underTest.map(
+ customTileQsTileConfig,
+ createModel(
+ tileIcon = createIcon(RuntimeException(), false),
+ defaultTileIcon = createIcon(null, true)
+ ),
+ )
+ val expected =
+ createTileState(
+ icon = null,
+ activationState = QSTileState.ActivationState.INACTIVE,
+ )
+
+ assertThat(actual).isEqualTo(expected)
+ }
+ }
+
+ private fun Kosmos.createModel(
+ tileState: Int = Tile.STATE_ACTIVE,
+ tileIcon: Icon = createIcon(DRAWABLE, false),
+ hasPendingBind: Boolean = false,
+ isToggleable: Boolean = true,
+ defaultTileIcon: Icon = createIcon(DEFAULT_DRAWABLE, true),
+ ) =
+ CustomTileDataModel(
+ UserHandle.of(1),
+ tileSpec.componentName,
+ Tile().apply {
+ state = tileState
+ label = "test label"
+ subtitle = "test subtitle"
+ icon = tileIcon
+ contentDescription = "test content description"
+ },
+ callingAppUid = 0,
+ hasPendingBind = hasPendingBind,
+ isToggleable = isToggleable,
+ defaultTileLabel = "test default tile label",
+ defaultTileIcon = defaultTileIcon,
+ )
+
+ private fun createIcon(drawable: Drawable?, isDefault: Boolean): Icon = mock {
+ if (isDefault) {
+ whenever(loadDrawable(any())).thenReturn(drawable)
+ } else {
+ whenever(loadDrawableCheckingUriGrant(any(), any(), any(), any())).thenReturn(drawable)
+ }
+ }
+
+ private fun createIcon(exception: RuntimeException, isDefault: Boolean): Icon = mock {
+ if (isDefault) {
+ whenever(loadDrawable(any())).thenThrow(exception)
+ } else {
+ whenever(loadDrawableCheckingUriGrant(any(), eq(uriGrantsManager), any(), any()))
+ .thenThrow(exception)
+ }
+ }
+
+ private fun createTileState(
+ activationState: QSTileState.ActivationState = QSTileState.ActivationState.ACTIVE,
+ icon: Drawable? = DRAWABLE,
+ sideIcon: QSTileState.SideViewIcon = QSTileState.SideViewIcon.None,
+ actions: Set<QSTileState.UserAction> =
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
+ a11yClass: String? = Switch::class.qualifiedName,
+ ): QSTileState {
+ return QSTileState(
+ { icon?.let { com.android.systemui.common.shared.model.Icon.Loaded(icon, null) } },
+ "test label",
+ activationState,
+ "test subtitle",
+ actions,
+ "test content description",
+ null,
+ sideIcon,
+ QSTileState.EnabledState.ENABLED,
+ a11yClass,
+ )
+ }
+
+ private companion object {
+ val TEST_COMPONENT = ComponentName("test.pkg", "test.cls")
+
+ val DEFAULT_DRAWABLE = TestStubDrawable("default_icon_drawable")
+ val DRAWABLE = TestStubDrawable("icon_drawable")
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractorTest.kt
new file mode 100644
index 000000000000..c709f16c3213
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractorTest.kt
@@ -0,0 +1,283 @@
+/*
+ * 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.tiles.impl.custom.domain.interactor
+
+import android.app.PendingIntent
+import android.content.ComponentName
+import android.content.Intent
+import android.content.pm.ActivityInfo
+import android.content.pm.PackageManager
+import android.content.pm.ResolveInfo
+import android.content.pm.UserInfo
+import android.graphics.drawable.Icon
+import android.provider.Settings
+import android.service.quicksettings.Tile
+import android.service.quicksettings.TileService
+import android.view.IWindowManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testCase
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.qs.external.componentName
+import com.android.systemui.qs.external.iQSTileService
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.actions.intentInputs
+import com.android.systemui.qs.tiles.base.actions.pendingIntentInputs
+import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx.click
+import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx.longClick
+import com.android.systemui.qs.tiles.impl.custom.customTileServiceInteractor
+import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel
+import com.android.systemui.qs.tiles.impl.custom.qsTileLogger
+import com.android.systemui.qs.tiles.impl.custom.tileSpec
+import com.android.systemui.testKosmos
+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.mockito.nullable
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CustomTileUserActionInteractorTest : SysuiTestCase() {
+
+ private val inputHandler = FakeQSTileIntentUserInputHandler()
+ private val packageManagerFacade = FakePackageManagerFacade()
+ private val windowManagerFacade = FakeWindowManagerFacade()
+ private val kosmos =
+ testKosmos().apply {
+ componentName = TEST_COMPONENT
+ tileSpec = TileSpec.create(componentName)
+ testCase = this@CustomTileUserActionInteractorTest
+ }
+
+ private val underTest =
+ with(kosmos) {
+ CustomTileUserActionInteractor(
+ context =
+ mock {
+ whenever(packageManager).thenReturn(packageManagerFacade.packageManager)
+ },
+ tileSpec = tileSpec,
+ qsTileLogger = qsTileLogger,
+ windowManager = windowManagerFacade.windowManager,
+ displayTracker = mock {},
+ qsTileIntentUserInputHandler = inputHandler,
+ backgroundContext = testDispatcher,
+ serviceInteractor = customTileServiceInteractor,
+ )
+ }
+
+ private suspend fun setup() {
+ with(kosmos) {
+ fakeUserRepository.setUserInfos(listOf(TEST_USER_1))
+ fakeUserRepository.setSelectedUserInfo(TEST_USER_1)
+ }
+ }
+
+ @Test
+ fun clickStartsActivityWhenPossible() =
+ with(kosmos) {
+ testScope.runTest {
+ setup()
+ underTest.handleInput(
+ click(customTileModel(activityLaunchForClick = pendingIntent()))
+ )
+
+ assertThat(windowManagerFacade.isTokenGranted).isTrue()
+ assertThat(inputHandler.pendingIntentInputs).hasSize(1)
+ assertThat(iQSTileService.clicks).hasSize(0)
+ }
+ }
+
+ @Test
+ fun clickPassedToTheServiceWhenNoActivity() =
+ with(kosmos) {
+ testScope.runTest {
+ setup()
+ packageManagerFacade.resolutionResult = null
+ underTest.handleInput(click(customTileModel(activityLaunchForClick = null)))
+
+ assertThat(windowManagerFacade.isTokenGranted).isTrue()
+ assertThat(inputHandler.pendingIntentInputs).hasSize(0)
+ assertThat(iQSTileService.clicks).hasSize(1)
+ }
+ }
+
+ @Test
+ fun longClickOpensResolvedIntent() =
+ with(kosmos) {
+ testScope.runTest {
+ setup()
+ packageManagerFacade.resolutionResult =
+ ActivityInfo().apply {
+ packageName = "resolved.pkg"
+ name = "Test"
+ }
+ underTest.handleInput(longClick(customTileModel()))
+
+ assertThat(inputHandler.intentInputs).hasSize(1)
+ with(inputHandler.intentInputs.first()) {
+ assertThat(intent.action).isEqualTo(TileService.ACTION_QS_TILE_PREFERENCES)
+ assertThat(intent.component).isEqualTo(ComponentName("resolved.pkg", "Test"))
+ assertThat(
+ intent.getParcelableExtra(
+ Intent.EXTRA_COMPONENT_NAME,
+ ComponentName::class.java
+ )
+ )
+ .isEqualTo(componentName)
+ assertThat(intent.getIntExtra(TileService.EXTRA_STATE, Int.MAX_VALUE))
+ .isEqualTo(111)
+ }
+ }
+ }
+
+ @Test
+ fun longClickOpensDefaultIntentWhenNoResolved() =
+ with(kosmos) {
+ testScope.runTest {
+ setup()
+ underTest.handleInput(longClick(customTileModel()))
+
+ assertThat(inputHandler.intentInputs).hasSize(1)
+ with(inputHandler.intentInputs.first()) {
+ assertThat(intent.action)
+ .isEqualTo(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
+ assertThat(intent.data.toString()).isEqualTo("package:test.pkg")
+ }
+ }
+ }
+
+ @Test
+ fun revokeTokenDoesntRevokeWhenShowingDialog() =
+ with(kosmos) {
+ testScope.runTest {
+ setup()
+ underTest.handleInput(click(customTileModel()))
+ underTest.setShowingDialog(true)
+
+ underTest.revokeToken(false)
+
+ assertThat(windowManagerFacade.isTokenGranted).isTrue()
+ }
+ }
+
+ @Test
+ fun forceRevokeTokenRevokesWhenShowingDialog() =
+ with(kosmos) {
+ testScope.runTest {
+ setup()
+ underTest.handleInput(click(customTileModel()))
+ underTest.setShowingDialog(true)
+
+ underTest.revokeToken(true)
+
+ assertThat(windowManagerFacade.isTokenGranted).isFalse()
+ }
+ }
+
+ @Test
+ fun revokeTokenRevokesWhenNotShowingDialog() =
+ with(kosmos) {
+ testScope.runTest {
+ setup()
+ underTest.handleInput(click(customTileModel()))
+ underTest.setShowingDialog(false)
+
+ underTest.revokeToken(false)
+
+ assertThat(windowManagerFacade.isTokenGranted).isFalse()
+ }
+ }
+
+ @Test
+ fun startActivityDoesntStartWithNoToken() =
+ with(kosmos) {
+ testScope.runTest {
+ setup()
+ underTest.startActivityAndCollapse(mock())
+
+ // Checking all types of inputs
+ assertThat(inputHandler.handledInputs).isEmpty()
+ }
+ }
+
+ private fun pendingIntent(): PendingIntent = mock { whenever(isActivity).thenReturn(true) }
+
+ private fun Kosmos.customTileModel(
+ componentName: ComponentName = tileSpec.componentName,
+ activityLaunchForClick: PendingIntent? = null,
+ tileState: Int = 111,
+ ) =
+ CustomTileDataModel(
+ TEST_USER_1.userHandle,
+ componentName,
+ Tile().also {
+ it.activityLaunchForClick = activityLaunchForClick
+ it.state = tileState
+ },
+ callingAppUid = 0,
+ hasPendingBind = false,
+ isToggleable = false,
+ defaultTileLabel = "default_label",
+ defaultTileIcon = Icon.createWithContentUri("default_icon"),
+ )
+
+ private class FakePackageManagerFacade(val packageManager: PackageManager = mock()) {
+
+ var resolutionResult: ActivityInfo? = null
+
+ init {
+ whenever(packageManager.resolveActivityAsUser(any(), any<Int>(), any())).then {
+ ResolveInfo().apply { activityInfo = resolutionResult }
+ }
+ }
+ }
+
+ private class FakeWindowManagerFacade(val windowManager: IWindowManager = mock()) {
+
+ var isTokenGranted: Boolean = false
+ private set
+
+ init {
+ with(windowManager) {
+ whenever(removeWindowToken(any(), any())).then {
+ isTokenGranted = false
+ Unit
+ }
+ whenever(addWindowToken(any(), any(), any(), nullable())).then {
+ isTokenGranted = true
+ Unit
+ }
+ }
+ }
+ }
+
+ private companion object {
+
+ val TEST_COMPONENT = ComponentName("test.pkg", "test.cls")
+ val TEST_USER_1 = UserInfo(1, "first user", UserInfo.FLAG_MAIN)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
index f8573cc280c4..3c0ab240cbba 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt
@@ -259,6 +259,37 @@ class QSSceneAdapterImplTest : SysuiTestCase() {
}
@Test
+ fun state_unsquishing() =
+ testScope.runTest {
+ val qsImpl by collectLastValue(underTest.qsImpl)
+ val squishiness = 0.342f
+
+ underTest.inflate(context)
+ runCurrent()
+ clearInvocations(qsImpl!!)
+
+ underTest.setState(QSSceneAdapter.State.Unsquishing(squishiness))
+ with(qsImpl!!) {
+ verify(this).setQsVisible(true)
+ verify(this)
+ .setQsExpansion(
+ /* expansion= */ 0f,
+ /* panelExpansionFraction= */ 1f,
+ /* proposedTranslation= */ 0f,
+ /* squishinessFraction= */ squishiness,
+ )
+ verify(this).setListening(true)
+ verify(this).setExpanded(true)
+ verify(this)
+ .setTransitionToFullShadeProgress(
+ /* isTransitioningToFullShade= */ false,
+ /* qsTransitionFraction= */ 1f,
+ /* qsSquishinessFraction = */ squishiness,
+ )
+ }
+ }
+
+ @Test
fun customizing_QS() =
testScope.runTest {
val customizing by collectLastValue(underTest.isCustomizing)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterTest.kt
index d1bc686385a1..e281383e6250 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterTest.kt
@@ -29,6 +29,12 @@ import org.junit.runner.RunWith
@EnabledOnRavenwood
@RunWith(AndroidJUnit4::class)
class QSSceneAdapterTest : SysuiTestCase() {
+
+ @Test
+ fun expanding_squishiness1() {
+ assertThat(QSSceneAdapter.State.Expanding(0.3f).squishiness).isEqualTo(1f)
+ }
+
@Test
fun expandingSpecialValues() {
assertThat(QSSceneAdapter.State.QQS).isEqualTo(QSSceneAdapter.State.Expanding(0f))
@@ -41,4 +47,11 @@ class QSSceneAdapterTest : SysuiTestCase() {
assertThat(Collapsing(collapsingProgress))
.isEqualTo(QSSceneAdapter.State.Expanding(1 - collapsingProgress))
}
+
+ @Test
+ fun unsquishing_expansionSameAsQQS() {
+ val squishiness = 0.6f
+ assertThat(QSSceneAdapter.State.Unsquishing(squishiness).expansion)
+ .isEqualTo(QSSceneAdapter.State.QQS.expansion)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
index d47da3e47d2f..82862e021b4c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
@@ -26,11 +26,11 @@ import com.android.systemui.kosmos.testScope
import com.android.systemui.qs.FooterActionsController
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter
-import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Direction
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.UserAction
import com.android.systemui.scene.shared.model.UserActionResult
+import com.android.systemui.shade.domain.interactor.privacyChipInteractor
import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
@@ -58,7 +58,6 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private val sceneInteractor by lazy { kosmos.sceneInteractor }
private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
private val qsFlexiglassAdapter = FakeQSSceneAdapter({ mock() })
@@ -95,9 +94,9 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() {
ShadeHeaderViewModel(
applicationScope = testScope.backgroundScope,
context = context,
- sceneInteractor = sceneInteractor,
mobileIconsInteractor = mobileIconsInteractor,
mobileIconsViewModel = mobileIconsViewModel,
+ privacyChipInteractor = kosmos.privacyChipInteractor,
broadcastDispatcher = fakeBroadcastDispatcher,
)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index 9f89d346fd51..7fef00bc92e6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -50,7 +50,7 @@ import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel
import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
import com.android.systemui.kosmos.testScope
-import com.android.systemui.media.controls.pipeline.MediaDataManager
+import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
import com.android.systemui.model.SysUiState
import com.android.systemui.model.sceneContainerPlugin
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
@@ -65,8 +65,10 @@ import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.fakeSceneDataSource
import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
import com.android.systemui.settings.FakeDisplayTracker
+import com.android.systemui.shade.domain.interactor.privacyChipInteractor
import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
import com.android.systemui.shade.ui.viewmodel.ShadeSceneViewModel
+import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
@@ -141,6 +143,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
SceneContainerViewModel(
sceneInteractor = sceneInteractor,
falsingInteractor = kosmos.falsingInteractor,
+ powerInteractor = kosmos.powerInteractor,
)
.apply { setTransitionState(transitionState) }
}
@@ -229,9 +232,9 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
ShadeHeaderViewModel(
applicationScope = testScope.backgroundScope,
context = context,
- sceneInteractor = sceneInteractor,
mobileIconsInteractor = mobileIconsInteractor,
mobileIconsViewModel = mobileIconsViewModel,
+ privacyChipInteractor = kosmos.privacyChipInteractor,
broadcastDispatcher = fakeBroadcastDispatcher,
)
@@ -267,6 +270,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
windowController = mock(),
deviceProvisioningInteractor = kosmos.deviceProvisioningInteractor,
centralSurfaces = mock(),
+ headsUpInteractor = kosmos.headsUpNotificationInteractor,
)
startable.start()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
index 4b9ebdc295c6..dd3eb6845789 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
@@ -22,7 +22,6 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
import com.android.systemui.kosmos.testScope
-import com.android.systemui.power.data.repository.fakePowerRepository
import com.android.systemui.scene.data.repository.sceneContainerRepository
import com.android.systemui.scene.sceneContainerConfig
import com.android.systemui.scene.sceneKeys
@@ -276,12 +275,4 @@ class SceneInteractorTest : SysuiTestCase() {
underTest.setVisible(true, "reason")
assertThat(isVisible).isTrue()
}
-
- @Test
- fun userInput() =
- testScope.runTest {
- assertThat(kosmos.fakePowerRepository.userTouchRegistered).isFalse()
- underTest.onUserInput()
- assertThat(kosmos.fakePowerRepository.userTouchRegistered).isTrue()
- }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index ffea84b70d07..f49b4777cf14 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -49,6 +49,8 @@ import com.android.systemui.scene.shared.model.ObservableTransitionState
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.fakeSceneDataSource
import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository
+import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
import com.android.systemui.statusbar.phone.CentralSurfaces
import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
import com.android.systemui.statusbar.policy.data.repository.fakeDeviceProvisioningRepository
@@ -120,6 +122,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
windowController = windowController,
deviceProvisioningInteractor = kosmos.deviceProvisioningInteractor,
centralSurfaces = centralSurfaces,
+ headsUpInteractor = kosmos.headsUpNotificationInteractor,
)
}
@@ -168,6 +171,12 @@ class SceneContainerStartableTest : SysuiTestCase() {
fakeSceneDataSource.unpause(expectedScene = SceneKey.Gone)
transitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Gone)
assertThat(isVisible).isFalse()
+
+ kosmos.headsUpNotificationRepository.hasPinnedHeadsUp.value = true
+ assertThat(isVisible).isTrue()
+
+ kosmos.headsUpNotificationRepository.hasPinnedHeadsUp.value = false
+ assertThat(isVisible).isFalse()
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
index 6c78317f61e5..ffbdafe338e7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
@@ -22,16 +22,23 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.domain.interactor.falsingInteractor
+import com.android.systemui.classifier.fakeFalsingManager
import com.android.systemui.coroutines.collectLastValue
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.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.sceneContainerConfig
import com.android.systemui.scene.sceneKeys
import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.fakeSceneDataSource
import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@@ -45,6 +52,8 @@ class SceneContainerViewModelTest : SysuiTestCase() {
private val testScope by lazy { kosmos.testScope }
private val interactor by lazy { kosmos.sceneInteractor }
private val fakeSceneDataSource = kosmos.fakeSceneDataSource
+ private val sceneContainerConfig = kosmos.sceneContainerConfig
+ private val falsingManager = kosmos.fakeFalsingManager
private lateinit var underTest: SceneContainerViewModel
@@ -55,6 +64,7 @@ class SceneContainerViewModelTest : SysuiTestCase() {
SceneContainerViewModel(
sceneInteractor = interactor,
falsingInteractor = kosmos.falsingInteractor,
+ powerInteractor = kosmos.powerInteractor,
)
}
@@ -86,4 +96,107 @@ class SceneContainerViewModelTest : SysuiTestCase() {
assertThat(currentScene).isEqualTo(SceneKey.Shade)
}
+
+ @Test
+ fun canChangeScene_whenAllowed_switchingFromGone_returnsTrue() =
+ testScope.runTest {
+ val currentScene by collectLastValue(underTest.currentScene)
+ fakeSceneDataSource.changeScene(toScene = SceneKey.Gone)
+ runCurrent()
+ assertThat(currentScene).isEqualTo(SceneKey.Gone)
+
+ sceneContainerConfig.sceneKeys
+ .filter { it != currentScene }
+ .forEach { toScene ->
+ assertWithMessage("Scene $toScene incorrectly protected when allowed")
+ .that(underTest.canChangeScene(toScene = toScene))
+ .isTrue()
+ }
+ }
+
+ @Test
+ fun canChangeScene_whenAllowed_switchingFromLockscreen_returnsTrue() =
+ testScope.runTest {
+ val currentScene by collectLastValue(underTest.currentScene)
+ fakeSceneDataSource.changeScene(toScene = SceneKey.Lockscreen)
+ runCurrent()
+ assertThat(currentScene).isEqualTo(SceneKey.Lockscreen)
+
+ sceneContainerConfig.sceneKeys
+ .filter { it != currentScene }
+ .forEach { toScene ->
+ assertWithMessage("Scene $toScene incorrectly protected when allowed")
+ .that(underTest.canChangeScene(toScene = toScene))
+ .isTrue()
+ }
+ }
+
+ @Test
+ fun canChangeScene_whenNotAllowed_fromLockscreen_toFalsingProtectedScenes_returnsFalse() =
+ testScope.runTest {
+ falsingManager.setIsFalseTouch(true)
+ val currentScene by collectLastValue(underTest.currentScene)
+ fakeSceneDataSource.changeScene(toScene = SceneKey.Lockscreen)
+ runCurrent()
+ assertThat(currentScene).isEqualTo(SceneKey.Lockscreen)
+
+ sceneContainerConfig.sceneKeys
+ .filter { it != currentScene }
+ .filter {
+ // Moving to the Communal scene is not currently falsing protected.
+ it != SceneKey.Communal
+ }
+ .forEach { toScene ->
+ assertWithMessage("Protected scene $toScene not properly protected")
+ .that(underTest.canChangeScene(toScene = toScene))
+ .isFalse()
+ }
+ }
+
+ @Test
+ fun canChangeScene_whenNotAllowed_fromLockscreen_toFalsingUnprotectedScenes_returnsTrue() =
+ testScope.runTest {
+ falsingManager.setIsFalseTouch(true)
+ val currentScene by collectLastValue(underTest.currentScene)
+ fakeSceneDataSource.changeScene(toScene = SceneKey.Lockscreen)
+ runCurrent()
+ assertThat(currentScene).isEqualTo(SceneKey.Lockscreen)
+
+ sceneContainerConfig.sceneKeys
+ .filter {
+ // Moving to the Communal scene is not currently falsing protected.
+ it == SceneKey.Communal
+ }
+ .forEach { toScene ->
+ assertWithMessage("Unprotected scene $toScene is incorrectly protected")
+ .that(underTest.canChangeScene(toScene = toScene))
+ .isTrue()
+ }
+ }
+
+ @Test
+ fun canChangeScene_whenNotAllowed_fromGone_toAnyOtherScene_returnsTrue() =
+ testScope.runTest {
+ falsingManager.setIsFalseTouch(true)
+ val currentScene by collectLastValue(underTest.currentScene)
+ fakeSceneDataSource.changeScene(toScene = SceneKey.Gone)
+ runCurrent()
+ assertThat(currentScene).isEqualTo(SceneKey.Gone)
+
+ sceneContainerConfig.sceneKeys
+ .filter { it != currentScene }
+ .forEach { toScene ->
+ assertWithMessage("Protected scene $toScene not properly protected")
+ .that(underTest.canChangeScene(toScene = toScene))
+ .isTrue()
+ }
+ }
+
+ @Test
+ fun userInput() =
+ testScope.runTest {
+ assertThat(kosmos.fakePowerRepository.userTouchRegistered).isFalse()
+ underTest.onMotionEvent(mock())
+ assertThat(kosmos.fakePowerRepository.userTouchRegistered).isTrue()
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/PrivacyChipRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/PrivacyChipRepositoryTest.kt
new file mode 100644
index 000000000000..613f256113f3
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/PrivacyChipRepositoryTest.kt
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.data.repository
+
+import android.content.Intent
+import android.safetycenter.SafetyCenterManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.broadcastDispatcher
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.privacy.PrivacyApplication
+import com.android.systemui.privacy.PrivacyConfig
+import com.android.systemui.privacy.PrivacyItem
+import com.android.systemui.privacy.PrivacyItemController
+import com.android.systemui.privacy.PrivacyType
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.kotlinArgumentCaptor
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.mockito.withArgCaptor
+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.Mock
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations.initMocks
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class PrivacyChipRepositoryTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val broadcastDispatcher = kosmos.broadcastDispatcher
+
+ @Mock private lateinit var privacyConfig: PrivacyConfig
+ @Mock private lateinit var privacyItemController: PrivacyItemController
+ @Mock private lateinit var safetyCenterManager: SafetyCenterManager
+
+ lateinit var underTest: PrivacyChipRepositoryImpl
+
+ @Before
+ fun setUp() {
+ initMocks(this)
+ setUpUnderTest()
+ }
+
+ @Test
+ fun isSafetyCenterEnabled_startEnabled() =
+ testScope.runTest {
+ setUpUnderTest(true)
+
+ val actual by collectLastValue(underTest.isSafetyCenterEnabled)
+ runCurrent()
+
+ assertThat(actual).isTrue()
+ }
+
+ @Test
+ fun isSafetyCenterEnabled_startDisabled() =
+ testScope.runTest {
+ setUpUnderTest(false)
+
+ val actual by collectLastValue(underTest.isSafetyCenterEnabled)
+
+ assertThat(actual).isFalse()
+ }
+
+ @Test
+ fun isSafetyCenterEnabled_updates() =
+ testScope.runTest {
+ val actual by collectLastValue(underTest.isSafetyCenterEnabled)
+ runCurrent()
+
+ assertThat(actual).isFalse()
+
+ whenever(safetyCenterManager.isSafetyCenterEnabled).thenReturn(true)
+
+ broadcastDispatcher.sendIntentToMatchingReceiversOnly(
+ context,
+ Intent(SafetyCenterManager.ACTION_SAFETY_CENTER_ENABLED_CHANGED),
+ )
+
+ runCurrent()
+
+ assertThat(actual).isTrue()
+ }
+
+ @Test
+ fun privacyItems_updates() =
+ testScope.runTest {
+ val actual by collectLastValue(underTest.privacyItems)
+ runCurrent()
+
+ val callback =
+ withArgCaptor<PrivacyItemController.Callback> {
+ verify(privacyItemController).addCallback(capture())
+ }
+
+ callback.onPrivacyItemsChanged(emptyList())
+ assertThat(actual).isEmpty()
+
+ val privacyItems =
+ listOf(
+ PrivacyItem(
+ privacyType = PrivacyType.TYPE_CAMERA,
+ application = PrivacyApplication("", 0)
+ ),
+ )
+ callback.onPrivacyItemsChanged(privacyItems)
+ assertThat(actual).isEqualTo(privacyItems)
+ }
+
+ @Test
+ fun isMicCameraIndicationEnabled_updates() =
+ testScope.runTest {
+ val actual by collectLastValue(underTest.isMicCameraIndicationEnabled)
+ runCurrent()
+
+ val captor = kotlinArgumentCaptor<PrivacyConfig.Callback>()
+ verify(privacyConfig, times(2)).addCallback(captor.capture())
+ val callback = captor.allValues[0]
+
+ callback.onFlagMicCameraChanged(false)
+ assertThat(actual).isFalse()
+
+ callback.onFlagMicCameraChanged(true)
+ assertThat(actual).isTrue()
+ }
+
+ @Test
+ fun isLocationIndicationEnabled_updates() =
+ testScope.runTest {
+ val actual by collectLastValue(underTest.isLocationIndicationEnabled)
+ runCurrent()
+
+ val captor = kotlinArgumentCaptor<PrivacyConfig.Callback>()
+ verify(privacyConfig, times(2)).addCallback(captor.capture())
+ val callback = captor.allValues[1]
+
+ callback.onFlagLocationChanged(false)
+ assertThat(actual).isFalse()
+
+ callback.onFlagLocationChanged(true)
+ assertThat(actual).isTrue()
+ }
+
+ private fun setUpUnderTest(isSafetyCenterEnabled: Boolean = false) {
+ whenever(safetyCenterManager.isSafetyCenterEnabled).thenReturn(isSafetyCenterEnabled)
+
+ underTest =
+ PrivacyChipRepositoryImpl(
+ applicationScope = kosmos.applicationCoroutineScope,
+ privacyConfig = privacyConfig,
+ privacyItemController = privacyItemController,
+ backgroundDispatcher = kosmos.testDispatcher,
+ broadcastDispatcher = broadcastDispatcher,
+ safetyCenterManager = safetyCenterManager,
+ )
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/PrivacyChipInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/PrivacyChipInteractorTest.kt
new file mode 100644
index 000000000000..f0293a8efc8a
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/PrivacyChipInteractorTest.kt
@@ -0,0 +1,154 @@
+/*
+ * 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.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.kosmos.testScope
+import com.android.systemui.privacy.OngoingPrivacyChip
+import com.android.systemui.privacy.PrivacyApplication
+import com.android.systemui.privacy.PrivacyItem
+import com.android.systemui.privacy.PrivacyType
+import com.android.systemui.privacy.privacyDialogController
+import com.android.systemui.privacy.privacyDialogControllerV2
+import com.android.systemui.shade.data.repository.fakePrivacyChipRepository
+import com.android.systemui.shade.data.repository.privacyChipRepository
+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
+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.initMocks
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class PrivacyChipInteractorTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val privacyChipRepository = kosmos.fakePrivacyChipRepository
+ private val privacyDialogController = kosmos.privacyDialogController
+ private val privacyDialogControllerV2 = kosmos.privacyDialogControllerV2
+ @Mock private lateinit var privacyChip: OngoingPrivacyChip
+
+ val underTest = kosmos.privacyChipInteractor
+
+ @Before
+ fun setUp() {
+ initMocks(this)
+ whenever(privacyChip.context).thenReturn(this.context)
+ }
+
+ @Test
+ fun isChipVisible_updates() =
+ testScope.runTest {
+ val actual by collectLastValue(underTest.isChipVisible)
+
+ privacyChipRepository.setPrivacyItems(emptyList())
+ runCurrent()
+
+ assertThat(actual).isFalse()
+
+ val privacyItems =
+ listOf(
+ PrivacyItem(
+ privacyType = PrivacyType.TYPE_CAMERA,
+ application = PrivacyApplication("", 0)
+ ),
+ )
+ privacyChipRepository.setPrivacyItems(privacyItems)
+ runCurrent()
+
+ assertThat(actual).isTrue()
+ }
+
+ @Test
+ fun isChipEnabled_noIndicationEnabled() =
+ testScope.runTest {
+ val actual by collectLastValue(underTest.isChipEnabled)
+
+ privacyChipRepository.setIsMicCameraIndicationEnabled(false)
+ privacyChipRepository.setIsLocationIndicationEnabled(false)
+
+ assertThat(actual).isFalse()
+ }
+
+ @Test
+ fun isChipEnabled_micCameraIndicationEnabled() =
+ testScope.runTest {
+ val actual by collectLastValue(underTest.isChipEnabled)
+
+ privacyChipRepository.setIsMicCameraIndicationEnabled(true)
+ privacyChipRepository.setIsLocationIndicationEnabled(false)
+
+ assertThat(actual).isTrue()
+ }
+
+ @Test
+ fun isChipEnabled_locationIndicationEnabled() =
+ testScope.runTest {
+ val actual by collectLastValue(underTest.isChipEnabled)
+
+ privacyChipRepository.setIsMicCameraIndicationEnabled(false)
+ privacyChipRepository.setIsLocationIndicationEnabled(true)
+
+ assertThat(actual).isTrue()
+ }
+
+ @Test
+ fun isChipEnabled_allIndicationEnabled() =
+ testScope.runTest {
+ val actual by collectLastValue(underTest.isChipEnabled)
+
+ privacyChipRepository.setIsMicCameraIndicationEnabled(true)
+ privacyChipRepository.setIsLocationIndicationEnabled(true)
+
+ assertThat(actual).isTrue()
+ }
+
+ @Test
+ fun onPrivacyChipClicked_safetyCenterEnabled() =
+ testScope.runTest {
+ privacyChipRepository.setIsSafetyCenterEnabled(true)
+
+ underTest.onPrivacyChipClicked(privacyChip)
+
+ verify(privacyDialogControllerV2).showDialog(any(), any())
+ verify(privacyDialogController, never()).showDialog(any())
+ }
+
+ @Test
+ fun onPrivacyChipClicked_safetyCenterDisabled() =
+ testScope.runTest {
+ privacyChipRepository.setIsSafetyCenterEnabled(false)
+
+ underTest.onPrivacyChipClicked(privacyChip)
+
+ verify(privacyDialogController).showDialog(any())
+ verify(privacyDialogControllerV2, never()).showDialog(any(), any())
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
index c0aaab3ad6e1..1ef307617c4e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
@@ -8,7 +8,7 @@ import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.flags.Flags
import com.android.systemui.kosmos.testScope
-import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.shade.domain.interactor.privacyChipInteractor
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
@@ -31,7 +31,6 @@ import org.mockito.MockitoAnnotations
class ShadeHeaderViewModelTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private val sceneInteractor by lazy { kosmos.sceneInteractor }
private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
@@ -62,9 +61,9 @@ class ShadeHeaderViewModelTest : SysuiTestCase() {
ShadeHeaderViewModel(
applicationScope = testScope.backgroundScope,
context = context,
- sceneInteractor = sceneInteractor,
mobileIconsInteractor = mobileIconsInteractor,
mobileIconsViewModel = mobileIconsViewModel,
+ privacyChipInteractor = kosmos.privacyChipInteractor,
broadcastDispatcher = fakeBroadcastDispatcher,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
index 799e8f054d51..33c2d4ef7b3b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
@@ -27,10 +27,11 @@ import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.flags.Flags
import com.android.systemui.kosmos.testScope
-import com.android.systemui.media.controls.pipeline.MediaDataManager
+import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.shade.domain.interactor.privacyChipInteractor
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
@@ -96,9 +97,9 @@ class ShadeSceneViewModelTest : SysuiTestCase() {
ShadeHeaderViewModel(
applicationScope = testScope.backgroundScope,
context = context,
- sceneInteractor = sceneInteractor,
mobileIconsInteractor = mobileIconsInteractor,
mobileIconsViewModel = mobileIconsViewModel,
+ privacyChipInteractor = kosmos.privacyChipInteractor,
broadcastDispatcher = fakeBroadcastDispatcher,
)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerMainThreadTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerMainThreadTest.java
deleted file mode 100644
index d0e05fa60114..000000000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerMainThreadTest.java
+++ /dev/null
@@ -1,614 +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.
- */
-
-package com.android.systemui.statusbar;
-
-import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED;
-import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS;
-import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS;
-import static android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS;
-import static android.provider.Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS;
-
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
-
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.app.ActivityManager;
-import android.app.KeyguardManager;
-import android.app.Notification;
-import android.app.admin.DevicePolicyManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.UserInfo;
-import android.database.ContentObserver;
-import android.net.Uri;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.provider.Settings;
-import android.util.SparseArray;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.widget.LockPatternUtils;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FakeFeatureFlagsClassic;
-import com.android.systemui.flags.Flags;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.recents.OverviewProxyService;
-import com.android.systemui.settings.UserTracker;
-import com.android.systemui.statusbar.NotificationLockscreenUserManager.NotificationStateChangedListener;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
-import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
-import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
-import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.util.settings.FakeSettings;
-
-import com.google.android.collect.Lists;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.concurrent.Executor;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class NotificationLockscreenUserManagerMainThreadTest extends SysuiTestCase {
- @Mock
- private NotificationPresenter mPresenter;
- @Mock
- private UserManager mUserManager;
- @Mock
- private UserTracker mUserTracker;
-
- // Dependency mocks:
- @Mock
- private NotificationVisibilityProvider mVisibilityProvider;
- @Mock
- private CommonNotifCollection mNotifCollection;
- @Mock
- private DevicePolicyManager mDevicePolicyManager;
- @Mock
- private NotificationClickNotifier mClickNotifier;
- @Mock
- private OverviewProxyService mOverviewProxyService;
- @Mock
- private KeyguardManager mKeyguardManager;
- @Mock
- private DeviceProvisionedController mDeviceProvisionedController;
- @Mock
- private StatusBarStateController mStatusBarStateController;
- @Mock
- private BroadcastDispatcher mBroadcastDispatcher;
- @Mock
- private KeyguardStateController mKeyguardStateController;
-
- private UserInfo mCurrentUser;
- private UserInfo mSecondaryUser;
- private UserInfo mWorkUser;
- private UserInfo mCommunalUser;
- private FakeSettings mSettings;
- private TestNotificationLockscreenUserManager mLockscreenUserManager;
- private NotificationEntry mCurrentUserNotif;
- private NotificationEntry mSecondaryUserNotif;
- private NotificationEntry mWorkProfileNotif;
- private final FakeFeatureFlagsClassic mFakeFeatureFlags = new FakeFeatureFlagsClassic();
- private Executor mMainExecutor = Runnable::run; // Direct executor
- private Executor mBackgroundExecutor = Runnable::run; // Direct executor
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
-
- mFakeFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, false);
-
- int currentUserId = ActivityManager.getCurrentUser();
- when(mUserTracker.getUserId()).thenReturn(currentUserId);
- mSettings = new FakeSettings();
- mSettings.setUserId(ActivityManager.getCurrentUser());
- mCurrentUser = new UserInfo(currentUserId, "", 0);
- mSecondaryUser = new UserInfo(currentUserId + 1, "", 0);
- mWorkUser = new UserInfo(currentUserId + 2, "" /* name */, null /* iconPath */, 0,
- UserManager.USER_TYPE_PROFILE_MANAGED);
- mCommunalUser = new UserInfo(currentUserId + 3, "" /* name */, null /* iconPath */, 0,
- UserManager.USER_TYPE_PROFILE_COMMUNAL);
-
- when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(true);
- when(mUserManager.getProfiles(currentUserId)).thenReturn(Lists.newArrayList(
- mCurrentUser, mWorkUser));
- when(mUserManager.getProfilesIncludingCommunal(currentUserId)).thenReturn(
- Lists.newArrayList(mCurrentUser, mWorkUser, mCommunalUser));
- when(mUserManager.getProfiles(mSecondaryUser.id)).thenReturn(Lists.newArrayList(
- mSecondaryUser));
- when(mUserManager.getProfilesIncludingCommunal(mSecondaryUser.id)).thenReturn(
- Lists.newArrayList(mSecondaryUser, mCommunalUser));
-
- Notification notifWithPrivateVisibility = new Notification();
- notifWithPrivateVisibility.visibility = Notification.VISIBILITY_PRIVATE;
- mCurrentUserNotif = new NotificationEntryBuilder()
- .setNotification(notifWithPrivateVisibility)
- .setUser(new UserHandle(mCurrentUser.id))
- .build();
- mSecondaryUserNotif = new NotificationEntryBuilder()
- .setNotification(notifWithPrivateVisibility)
- .setUser(new UserHandle(mSecondaryUser.id))
- .build();
- mWorkProfileNotif = new NotificationEntryBuilder()
- .setNotification(notifWithPrivateVisibility)
- .setUser(new UserHandle(mWorkUser.id))
- .build();
-
- mLockscreenUserManager = new TestNotificationLockscreenUserManager(mContext);
- mLockscreenUserManager.setUpWithPresenter(mPresenter);
- }
-
- private void changeSetting(String setting) {
- final Collection<Uri> lockScreenUris = new ArrayList<>();
- lockScreenUris.add(Settings.Secure.getUriFor(setting));
- mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false,
- lockScreenUris, 0);
- }
-
- @Test
- public void testGetCurrentProfiles() {
- final SparseArray<UserInfo> expectedCurProfiles = new SparseArray<>();
- expectedCurProfiles.put(mCurrentUser.id, mCurrentUser);
- expectedCurProfiles.put(mWorkUser.id, mWorkUser);
- if (android.multiuser.Flags.supportCommunalProfile()) {
- expectedCurProfiles.put(mCommunalUser.id, mCommunalUser);
- }
- assertTrue(mLockscreenUserManager.getCurrentProfiles().contentEquals(expectedCurProfiles));
-
- mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext);
-
- final SparseArray<UserInfo> expectedSecProfiles = new SparseArray<>();
- expectedSecProfiles.put(mSecondaryUser.id, mSecondaryUser);
- if (android.multiuser.Flags.supportCommunalProfile()) {
- expectedSecProfiles.put(mCommunalUser.id, mCommunalUser);
- }
- assertTrue(mLockscreenUserManager.getCurrentProfiles().contentEquals(expectedSecProfiles));
- }
-
- @Test
- public void testLockScreenShowNotificationsFalse() {
- mSettings.putInt(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0);
- changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
- assertFalse(mLockscreenUserManager.shouldShowLockscreenNotifications());
- }
-
- @Test
- public void testLockScreenShowNotificationsTrue() {
- mSettings.putInt(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1);
- changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
- assertTrue(mLockscreenUserManager.shouldShowLockscreenNotifications());
- }
-
- @Test
- public void testLockScreenAllowPrivateNotificationsTrue() {
- mSettings.putInt(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1);
- changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
- assertTrue(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mCurrentUser.id));
- }
-
- @Test
- public void testLockScreenAllowPrivateNotificationsFalse() {
- mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
- mCurrentUser.id);
- changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
- assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mCurrentUser.id));
- }
-
- @Test
- public void testLockScreenAllowsWorkPrivateNotificationsFalse() {
- mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
- mWorkUser.id);
- changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
- assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mWorkUser.id));
- }
-
- @Test
- public void testLockScreenAllowsWorkPrivateNotificationsTrue() {
- mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
- mWorkUser.id);
- changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
- assertTrue(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(mWorkUser.id));
- }
-
- @Test
- public void testCurrentUserPrivateNotificationsNotRedacted() {
- // GIVEN current user doesn't allow private notifications to show
- mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
- mCurrentUser.id);
- changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
-
- // THEN current user's notification is redacted
- assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
- }
-
- @Test
- public void testCurrentUserPrivateNotificationsRedacted() {
- // GIVEN current user allows private notifications to show
- mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
- mCurrentUser.id);
- changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
-
- // THEN current user's notification isn't redacted
- assertFalse(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
- }
-
- @Test
- public void testWorkPrivateNotificationsRedacted() {
- // GIVEN work profile doesn't private notifications to show
- mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
- mWorkUser.id);
- changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
-
- // THEN work profile notification is redacted
- assertTrue(mLockscreenUserManager.needsRedaction(mWorkProfileNotif));
- assertFalse(mLockscreenUserManager.allowsManagedPrivateNotificationsInPublic());
- }
-
- @Test
- public void testWorkPrivateNotificationsNotRedacted() {
- // GIVEN work profile allows private notifications to show
- mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
- mWorkUser.id);
- changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
-
- // THEN work profile notification isn't redacted
- assertFalse(mLockscreenUserManager.needsRedaction(mWorkProfileNotif));
- assertTrue(mLockscreenUserManager.allowsManagedPrivateNotificationsInPublic());
- }
-
- @Test
- public void testWorkPrivateNotificationsNotRedacted_otherUsersRedacted() {
- // GIVEN work profile allows private notifications to show but the other users don't
- mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
- mWorkUser.id);
- mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
- mCurrentUser.id);
- mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
- mSecondaryUser.id);
- changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
- changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
- changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
-
- // THEN the work profile notification doesn't need to be redacted
- assertFalse(mLockscreenUserManager.needsRedaction(mWorkProfileNotif));
-
- // THEN the current user and secondary user notifications do need to be redacted
- assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
- assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif));
- }
-
- @Test
- public void testWorkProfileRedacted_otherUsersNotRedacted() {
- // GIVEN work profile doesn't allow private notifications to show but the other users do
- mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
- mWorkUser.id);
- mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
- mCurrentUser.id);
- mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
- mSecondaryUser.id);
- changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
- changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
- changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
-
- // THEN the work profile notification needs to be redacted
- assertTrue(mLockscreenUserManager.needsRedaction(mWorkProfileNotif));
-
- // THEN the current user and secondary user notifications don't need to be redacted
- assertFalse(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
- assertFalse(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif));
- }
-
- @Test
- public void testSecondaryUserNotRedacted_currentUserRedacted() {
- // GIVEN secondary profile allows private notifications to show but the current user
- // doesn't allow private notifications to show
- mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
- mCurrentUser.id);
- mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
- mSecondaryUser.id);
- changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
- changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
-
- // THEN the secondary profile notification still needs to be redacted because the current
- // user's setting takes precedence
- assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif));
- }
-
- @Test
- public void testUserSwitchedCallsOnUserSwitching() {
- mLockscreenUserManager.getUserTrackerCallbackForTest().onUserChanging(mSecondaryUser.id,
- mContext);
- verify(mPresenter, times(1)).onUserSwitched(mSecondaryUser.id);
- }
-
- @Test
- public void testIsLockscreenPublicMode() {
- assertFalse(mLockscreenUserManager.isLockscreenPublicMode(mCurrentUser.id));
- mLockscreenUserManager.setLockscreenPublicMode(true, mCurrentUser.id);
- assertTrue(mLockscreenUserManager.isLockscreenPublicMode(mCurrentUser.id));
- }
-
- @Test
- public void testUpdateIsPublicMode() {
- when(mKeyguardStateController.isMethodSecure()).thenReturn(true);
-
- NotificationStateChangedListener listener = mock(NotificationStateChangedListener.class);
- mLockscreenUserManager.addNotificationStateChangedListener(listener);
- mLockscreenUserManager.mCurrentProfiles.append(0, mock(UserInfo.class));
-
- // first call explicitly sets user 0 to not public; notifies
- mLockscreenUserManager.updatePublicMode();
- assertFalse(mLockscreenUserManager.isLockscreenPublicMode(0));
- verify(listener).onNotificationStateChanged();
- clearInvocations(listener);
-
- // calling again has no changes; does not notify
- mLockscreenUserManager.updatePublicMode();
- assertFalse(mLockscreenUserManager.isLockscreenPublicMode(0));
- verify(listener, never()).onNotificationStateChanged();
-
- // Calling again with keyguard now showing makes user 0 public; notifies
- when(mKeyguardStateController.isShowing()).thenReturn(true);
- mLockscreenUserManager.updatePublicMode();
- assertTrue(mLockscreenUserManager.isLockscreenPublicMode(0));
- verify(listener).onNotificationStateChanged();
- clearInvocations(listener);
-
- // calling again has no changes; does not notify
- mLockscreenUserManager.updatePublicMode();
- assertTrue(mLockscreenUserManager.isLockscreenPublicMode(0));
- verify(listener, never()).onNotificationStateChanged();
- }
-
- @Test
- public void testDevicePolicyDoesNotAllowNotifications() {
- // User allows them
- mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
- changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
-
- // DevicePolicy hides notifs on lockscreen
- when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
- .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
-
- BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
- 0, null, null, 0, true, false, null, mCurrentUser.id, 0);
- mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
- mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
- new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
-
- assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
- }
-
- @Test
- public void testDevicePolicyDoesNotAllowNotifications_secondary() {
- Mockito.clearInvocations(mDevicePolicyManager);
- // User allows notifications
- mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id);
- changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
-
- // DevicePolicy hides notifications
- when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mSecondaryUser.id))
- .thenReturn(KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
-
- BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
- 0, null, null, 0, true, false, null, mSecondaryUser.id, 0);
- mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
- mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
- new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
-
- assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
- }
-
- @Test
- public void testDevicePolicy_noPrivateNotifications() {
- Mockito.clearInvocations(mDevicePolicyManager);
- // User allows notifications
- mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
- changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
-
- // DevicePolicy hides sensitive content
- when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
- .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
-
- BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
- 0, null, null, 0, true, false, null, mCurrentUser.id, 0);
- mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
- mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
- new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
-
- assertTrue(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
- }
-
- @Test
- public void testDevicePolicy_noPrivateNotifications_userAll() {
- // User allows notifications
- mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
- changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
-
- // DevicePolicy hides sensitive content
- when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
- .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
-
- BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
- 0, null, null, 0, true, false, null, mCurrentUser.id, 0);
- mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
- mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
- new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
-
- assertTrue(mLockscreenUserManager.needsRedaction(new NotificationEntryBuilder()
- .setNotification(new Notification())
- .setUser(UserHandle.ALL)
- .build()));
- }
-
- @Test
- public void testDevicePolicyPrivateNotifications_secondary() {
- Mockito.clearInvocations(mDevicePolicyManager);
- // User allows notifications
- mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id);
- changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
-
- // DevicePolicy hides sensitive content
- when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mSecondaryUser.id))
- .thenReturn(KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
-
- BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
- 0, null, null, 0, true, false, null, mSecondaryUser.id, 0);
- mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
- mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
- new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
-
- mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext);
- assertTrue(mLockscreenUserManager.needsRedaction(mSecondaryUserNotif));
- }
-
- @Test
- public void testHideNotifications_primary() {
- mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mCurrentUser.id);
- changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
-
- assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
- }
-
- @Test
- public void testHideNotifications_secondary() {
- mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mSecondaryUser.id);
- changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
-
- assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
- }
-
- @Test
- public void testHideNotifications_secondary_userSwitch() {
- mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mSecondaryUser.id);
- changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
-
- mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext);
-
- assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
- }
-
- @Test
- public void testShowNotifications_secondary_userSwitch() {
- mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mSecondaryUser.id);
- changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
-
- mLockscreenUserManager.mUserChangedCallback.onUserChanging(mSecondaryUser.id, mContext);
-
- assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(mSecondaryUser.id));
- }
-
- @Test
- public void testUserAllowsNotificationsInPublic_keyguardManagerNoPrivateNotifications() {
- // DevicePolicy allows notifications
- when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, mCurrentUser.id))
- .thenReturn(0);
- BroadcastReceiver.PendingResult pr = new BroadcastReceiver.PendingResult(
- 0, null, null, 0, true, false, null, mCurrentUser.id, 0);
- mLockscreenUserManager.mAllUsersReceiver.setPendingResult(pr);
- mLockscreenUserManager.mAllUsersReceiver.onReceive(mContext,
- new Intent(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED));
-
- // KeyguardManager does not allow notifications
- when(mKeyguardManager.getPrivateNotificationsAllowed()).thenReturn(false);
-
- // User allows notifications
- mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
- // We shouldn't need to call this method, but getPrivateNotificationsAllowed has no
- // callback, so it's only updated when the setting is
- changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
-
- assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
- }
-
- @Test
- public void testUserAllowsNotificationsInPublic_settingsChange() {
- // User allows notifications
- mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, mCurrentUser.id);
- changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
-
- assertTrue(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
-
- // User disables
- mSettings.putIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, mCurrentUser.id);
- changeSetting(LOCK_SCREEN_SHOW_NOTIFICATIONS);
-
- assertFalse(mLockscreenUserManager.userAllowsNotificationsInPublic(mCurrentUser.id));
- }
-
- private class TestNotificationLockscreenUserManager
- extends NotificationLockscreenUserManagerImpl {
- public TestNotificationLockscreenUserManager(Context context) {
- super(
- context,
- mBroadcastDispatcher,
- mDevicePolicyManager,
- mUserManager,
- mUserTracker,
- (() -> mVisibilityProvider),
- (() -> mNotifCollection),
- mClickNotifier,
- (() -> mOverviewProxyService),
- NotificationLockscreenUserManagerMainThreadTest.this.mKeyguardManager,
- mStatusBarStateController,
- mMainExecutor,
- mBackgroundExecutor,
- mDeviceProvisionedController,
- mKeyguardStateController,
- mSettings,
- mock(DumpManager.class),
- mock(LockPatternUtils.class),
- mFakeFeatureFlags);
- }
-
- public BroadcastReceiver getBaseBroadcastReceiverForTest() {
- return mBaseBroadcastReceiver;
- }
-
- public UserTracker.Callback getUserTrackerCallbackForTest() {
- return mUserChangedCallback;
- }
-
- public ContentObserver getLockscreenSettingsObserverForTest() {
- return mLockscreenSettingsObserver;
- }
-
- public ContentObserver getSettingsObserverForTest() {
- return mSettingsObserver;
- }
- }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
index bcc0710359cc..d505b27a9969 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -172,8 +172,6 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
public void setUp() {
MockitoAnnotations.initMocks(this);
- mFakeFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, true);
-
int currentUserId = ActivityManager.getCurrentUser();
when(mUserTracker.getUserId()).thenReturn(currentUserId);
mSettings = new FakeSettings();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index b9b872254b06..0641c610aaff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -52,6 +52,7 @@ import com.android.systemui.statusbar.policy.splitShadeStateController
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.whenever
+import com.google.common.collect.Range
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
@@ -274,8 +275,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() {
)
)
runCurrent()
- // Expected alpha is inverse of progress as notifications are fading away
- assertThat(alpha).isEqualTo(1 - progress)
+ assertThat(alpha).isIn(Range.closed(0f, 1f))
// Finish transition to glanceable hub
keyguardTransitionRepository.sendTransitionStep(
@@ -681,7 +681,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() {
@Test
fun shadeCollapseFadeIn() =
testScope.runTest {
- val fadeIn by collectLastValue(underTest.shadeCollpaseFadeIn)
+ val fadeIn by collectLastValue(underTest.shadeCollapseFadeIn)
// Start on lockscreen without the shade
underTest.setShadeCollapseFadeInComplete(false)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
index aa6b4ac4970f..c804fc6990ae 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
@@ -1138,7 +1138,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
private fun setCameraProtectionBounds(protectionBounds: Rect) {
val protectionInfo =
- mock<CameraProtectionInfo> { whenever(this.cutoutBounds).thenReturn(protectionBounds) }
+ mock<CameraProtectionInfo> { whenever(this.bounds).thenReturn(protectionBounds) }
whenever(sysUICutout.cameraProtection).thenReturn(protectionInfo)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
index 4c824c0d130a..87d25ddcc75c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
@@ -46,12 +46,12 @@ import android.content.Intent;
import android.graphics.Region;
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
-import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.testing.UiEventLoggerFake;
@@ -75,8 +75,8 @@ import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
@SmallTest
-@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
+@RunWith(AndroidJUnit4.class)
public class BaseHeadsUpManagerTest extends SysuiTestCase {
@Rule
public MockitoRule rule = MockitoJUnit.rule();
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/OWNERS b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/OWNERS
new file mode 100644
index 000000000000..1f07df9f1e8e
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/OWNERS
@@ -0,0 +1 @@
+include /packages/SystemUI/src/com/android/systemui/volume/OWNERS \ No newline at end of file
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/data/repository/AncSliceRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/data/repository/AncSliceRepositoryTest.kt
new file mode 100644
index 000000000000..e31cdcd82e7e
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/data/repository/AncSliceRepositoryTest.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.anc.data.repository
+
+import android.bluetooth.BluetoothDevice
+import android.net.Uri
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.media.BluetoothMediaDevice
+import com.android.settingslib.media.MediaDevice
+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.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.volume.localMediaRepository
+import com.android.systemui.volume.localMediaRepositoryFactory
+import com.android.systemui.volume.panel.component.anc.FakeSliceFactory
+import com.android.systemui.volume.panel.component.anc.sliceViewManager
+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
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AncSliceRepositoryTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+
+ private lateinit var underTest: AncSliceRepository
+
+ @Before
+ fun setup() {
+ with(kosmos) {
+ val slice = FakeSliceFactory.createSlice(hasError = false, hasSliceItem = true)
+ whenever(sliceViewManager.bindSlice(any<Uri>())).thenReturn(slice)
+
+ underTest =
+ AncSliceRepositoryImpl(
+ localMediaRepositoryFactory,
+ testScope.testScheduler,
+ sliceViewManager,
+ )
+ }
+ }
+
+ @Test
+ fun noConnectedDevice_noSlice() {
+ with(kosmos) {
+ testScope.runTest {
+ localMediaRepository.updateCurrentConnectedDevice(null)
+
+ val slice by collectLastValue(underTest.ancSlice(1))
+ runCurrent()
+
+ assertThat(slice).isNull()
+ }
+ }
+ }
+
+ @Test
+ fun connectedDevice_sliceReturned() {
+ with(kosmos) {
+ testScope.runTest {
+ localMediaRepository.updateCurrentConnectedDevice(createMediaDevice())
+
+ val slice by collectLastValue(underTest.ancSlice(1))
+ runCurrent()
+
+ assertThat(slice).isNotNull()
+ }
+ }
+ }
+
+ private fun createMediaDevice(sliceUri: String = "content://test.slice"): MediaDevice {
+ val bluetoothDevice: BluetoothDevice = mock {
+ whenever(getMetadata(any()))
+ .thenReturn(
+ ("<HEARABLE_CONTROL_SLICE_WITH_WIDTH>" +
+ sliceUri +
+ "</HEARABLE_CONTROL_SLICE_WITH_WIDTH>")
+ .toByteArray()
+ )
+ }
+ val cachedBluetoothDevice: CachedBluetoothDevice = mock {
+ whenever(device).thenReturn(bluetoothDevice)
+ }
+ return mock<BluetoothMediaDevice> {
+ whenever(cachedDevice).thenReturn(cachedBluetoothDevice)
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/domain/AncAvailabilityCriteriaTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/domain/AncAvailabilityCriteriaTest.kt
new file mode 100644
index 000000000000..553aed8cfb05
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/domain/AncAvailabilityCriteriaTest.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.anc.domain
+
+import android.net.Uri
+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.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.volume.panel.component.anc.FakeSliceFactory
+import com.android.systemui.volume.panel.component.anc.ancSliceInteractor
+import com.android.systemui.volume.panel.component.anc.ancSliceRepository
+import com.android.systemui.volume.panel.component.anc.sliceViewManager
+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
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AncAvailabilityCriteriaTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+
+ private lateinit var underTest: AncAvailabilityCriteria
+
+ @Before
+ fun setup() {
+ with(kosmos) {
+ whenever(sliceViewManager.bindSlice(any<Uri>())).thenReturn(mock {})
+
+ underTest = AncAvailabilityCriteria(ancSliceInteractor)
+ }
+ }
+
+ @Test
+ fun noSlice_unavailable() {
+ with(kosmos) {
+ testScope.runTest {
+ ancSliceRepository.putSlice(1, null)
+
+ val isAvailable by collectLastValue(underTest.isAvailable())
+ runCurrent()
+
+ assertThat(isAvailable).isFalse()
+ }
+ }
+ }
+
+ @Test
+ fun hasSlice_available() {
+ with(kosmos) {
+ testScope.runTest {
+ ancSliceRepository.putSlice(
+ 1,
+ FakeSliceFactory.createSlice(hasError = false, hasSliceItem = true)
+ )
+
+ val isAvailable by collectLastValue(underTest.isAvailable())
+ runCurrent()
+
+ assertThat(isAvailable).isTrue()
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/domain/interactor/AncSliceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/domain/interactor/AncSliceInteractorTest.kt
new file mode 100644
index 000000000000..53f0bc9ddb51
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/domain/interactor/AncSliceInteractorTest.kt
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.anc.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.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.volume.panel.component.anc.FakeSliceFactory
+import com.android.systemui.volume.panel.component.anc.ancSliceRepository
+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
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AncSliceInteractorTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+
+ private lateinit var underTest: AncSliceInteractor
+
+ @Before
+ fun setup() {
+ with(kosmos) {
+ underTest = AncSliceInteractor(ancSliceRepository, testScope.backgroundScope)
+ }
+ }
+
+ @Test
+ fun errorSlice_returnsNull() {
+ with(kosmos) {
+ testScope.runTest {
+ ancSliceRepository.putSlice(
+ 1,
+ FakeSliceFactory.createSlice(hasError = true, hasSliceItem = true)
+ )
+
+ val slice by collectLastValue(underTest.ancSlice)
+ runCurrent()
+
+ assertThat(slice).isNull()
+ }
+ }
+ }
+
+ @Test
+ fun noSliceItem_returnsNull() {
+ with(kosmos) {
+ testScope.runTest {
+ ancSliceRepository.putSlice(
+ 1,
+ FakeSliceFactory.createSlice(hasError = false, hasSliceItem = false)
+ )
+
+ val slice by collectLastValue(underTest.ancSlice)
+ runCurrent()
+
+ assertThat(slice).isNull()
+ }
+ }
+ }
+
+ @Test
+ fun sliceItem_noError_returnsSlice() {
+ with(kosmos) {
+ testScope.runTest {
+ ancSliceRepository.putSlice(
+ 1,
+ FakeSliceFactory.createSlice(hasError = false, hasSliceItem = true)
+ )
+
+ val slice by collectLastValue(underTest.ancSlice)
+ runCurrent()
+
+ assertThat(slice).isNotNull()
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/MediaOutputAvailabilityCriteriaTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/MediaOutputAvailabilityCriteriaTest.kt
index ec37925af0f3..ec55c75d4ae5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/MediaOutputAvailabilityCriteriaTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/MediaOutputAvailabilityCriteriaTest.kt
@@ -17,8 +17,6 @@
package com.android.systemui.volume.panel.component.mediaoutput.domain
import android.media.AudioManager
-import android.media.session.MediaSession
-import android.media.session.PlaybackState
import android.testing.TestableLooper.RunWithLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -26,14 +24,8 @@ 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.mockito.mock
-import com.android.systemui.util.mockito.whenever
import com.android.systemui.volume.audioModeInteractor
import com.android.systemui.volume.audioRepository
-import com.android.systemui.volume.localMediaRepository
-import com.android.systemui.volume.mediaController
-import com.android.systemui.volume.mediaControllerRepository
-import com.android.systemui.volume.mediaOutputInteractor
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
@@ -54,23 +46,14 @@ class MediaOutputAvailabilityCriteriaTest : SysuiTestCase() {
@Before
fun setup() {
- with(kosmos) {
- whenever(mediaController.packageName).thenReturn("test.pkg")
- whenever(mediaController.sessionToken).thenReturn(MediaSession.Token(0, mock {}))
- whenever(mediaController.playbackState).thenReturn(PlaybackState.Builder().build())
-
- mediaControllerRepository.setActiveLocalMediaController(mediaController)
-
- underTest = MediaOutputAvailabilityCriteria(mediaOutputInteractor, audioModeInteractor)
- }
+ underTest = MediaOutputAvailabilityCriteria(kosmos.audioModeInteractor)
}
@Test
- fun notInCallAndHasDevices_isAvailable_true() {
+ fun notInCall_isAvailable_true() {
with(kosmos) {
testScope.runTest {
audioRepository.setMode(AudioManager.MODE_NORMAL)
- localMediaRepository.updateMediaDevices(listOf(mock {}))
val isAvailable by collectLastValue(underTest.isAvailable())
runCurrent()
@@ -79,27 +62,12 @@ class MediaOutputAvailabilityCriteriaTest : SysuiTestCase() {
}
}
}
- @Test
- fun inCallAndHasDevices_isAvailable_false() {
- with(kosmos) {
- testScope.runTest {
- audioRepository.setMode(AudioManager.MODE_IN_CALL)
- localMediaRepository.updateMediaDevices(listOf(mock {}))
-
- val isAvailable by collectLastValue(underTest.isAvailable())
- runCurrent()
-
- assertThat(isAvailable).isFalse()
- }
- }
- }
@Test
- fun notInCallAndHasDevices_isAvailable_false() {
+ fun inCall_isAvailable_false() {
with(kosmos) {
testScope.runTest {
- audioRepository.setMode(AudioManager.MODE_NORMAL)
- localMediaRepository.updateMediaDevices(emptyList())
+ audioRepository.setMode(AudioManager.MODE_IN_CALL)
val isAvailable by collectLastValue(underTest.isAvailable())
runCurrent()
diff --git a/packages/SystemUI/proguard_common.flags b/packages/SystemUI/proguard_common.flags
index f9546c46e915..162d8aebfc62 100644
--- a/packages/SystemUI/proguard_common.flags
+++ b/packages/SystemUI/proguard_common.flags
@@ -17,10 +17,6 @@
<1> *;
}
--keepclasseswithmembers class * {
- public <init>(android.content.Context, android.util.AttributeSet);
-}
-
-keep class androidx.core.app.CoreComponentFactory
# Keep the wm shell lib
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_presentation.xml b/packages/SystemUI/res-keyguard/layout/keyguard_presentation.xml
index f4d34f4ca141..8a0dd125d88b 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_presentation.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_presentation.xml
@@ -27,7 +27,7 @@
<com.android.keyguard.KeyguardStatusView
android:id="@+id/clock"
android:orientation="vertical"
- android:layout_width="410dp"
+ android:layout_width="@dimen/keyguard_presentation_width"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index 4e540de245dd..186bd7cc48c5 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -173,4 +173,6 @@
<dimen name="sfps_progress_bar_thickness">6dp</dimen>
<!-- Padding from the edge of the screen for the progress bar -->
<dimen name="sfps_progress_bar_padding_from_edge">7dp</dimen>
+
+ <dimen name="keyguard_presentation_width">410dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/drawable/ic_noise_aware.xml b/packages/SystemUI/res/drawable/ic_noise_aware.xml
new file mode 100644
index 000000000000..54826414f3f7
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_noise_aware.xml
@@ -0,0 +1,26 @@
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M440,82Q450,81 460,80.5Q470,80 480,80Q491,80 500.5,80.5Q510,81 520,82L520,162Q510,160 500.5,160Q491,160 480,160Q469,160 459.5,160Q450,160 440,162L440,82ZM272,138Q289,127 306.5,119Q324,111 343,104L378,176Q358,182 340.5,190.5Q323,199 306,210L272,138ZM654,210Q637,199 619.5,190.5Q602,182 582,176L617,104Q636,111 653.5,119Q671,127 688,138L654,210ZM753,311Q742,294 729,278.5Q716,263 702,249L765,199Q779,213 792,228.5Q805,244 816,261L753,311ZM143,263Q154,246 166.5,230.5Q179,215 193,201L256,251Q242,265 229.5,280.5Q217,296 206,313L143,263ZM83,428Q85,408 90,388.5Q95,369 101,350L180,368Q173,387 168.5,406.5Q164,426 162,446L83,428ZM799,449Q797,429 792.5,409Q788,389 781,370L859,352Q865,371 870,390.5Q875,410 877,430L799,449ZM781,590Q788,571 792,552Q796,533 798,513L877,531Q875,551 870,570.5Q865,590 859,609L781,590ZM162,514Q164,534 168.5,553.5Q173,573 180,592L101,610Q95,591 90,571.5Q85,552 83,532L162,514ZM705,708Q719,694 731,678.5Q743,663 754,646L818,696Q807,713 794.5,728.5Q782,744 768,758L705,708ZM194,760Q180,746 167.5,730Q155,714 144,697L206,647Q217,664 229.5,680Q242,696 256,710L194,760ZM583,783Q603,776 620,768Q637,760 654,749L689,821Q672,832 654.5,840.5Q637,849 618,856L583,783ZM344,857Q325,850 307,841.5Q289,833 272,822L307,750Q324,761 341.5,769.5Q359,778 379,784L344,857ZM480,880Q470,880 460,879.5Q450,879 440,878L440,798Q453,800 480,800Q491,800 500.5,800Q510,800 520,798L520,878Q510,879 500.5,879.5Q491,880 480,880ZM520,720Q482,720 450.5,697Q419,674 406,638Q403,629 399.5,620.5Q396,612 389,605L334,550Q308,524 294,490.5Q280,457 280,420Q280,345 332.5,292.5Q385,240 460,240Q529,240 580,285.5Q631,331 639,400L558,400Q551,365 523.5,342.5Q496,320 460,320Q418,320 389,349Q360,378 360,420Q360,440 368,459.5Q376,479 391,494L445,548Q459,562 467.5,578.5Q476,595 482,612Q487,625 497,632.5Q507,640 520,640Q537,640 548.5,628.5Q560,617 560,600L640,600Q640,650 605.5,685Q571,720 520,720ZM540,560Q515,560 497.5,542.5Q480,525 480,500Q480,474 497.5,457Q515,440 540,440Q566,440 583,457Q600,474 600,500Q600,525 583,542.5Q566,560 540,560Z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/media_squiggly_progress.xml b/packages/SystemUI/res/drawable/media_squiggly_progress.xml
index 9cd3f6288b1d..ae797f763b77 100644
--- a/packages/SystemUI/res/drawable/media_squiggly_progress.xml
+++ b/packages/SystemUI/res/drawable/media_squiggly_progress.xml
@@ -14,4 +14,4 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<com.android.systemui.media.controls.ui.SquigglyProgress /> \ No newline at end of file
+<com.android.systemui.media.controls.ui.drawable.SquigglyProgress /> \ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/qs_media_background.xml b/packages/SystemUI/res/drawable/qs_media_background.xml
index 217656dab022..830c882e1010 100644
--- a/packages/SystemUI/res/drawable/qs_media_background.xml
+++ b/packages/SystemUI/res/drawable/qs_media_background.xml
@@ -14,7 +14,7 @@
~ See the License for the specific language governing permissions and
~ limitations under the License
-->
-<com.android.systemui.media.controls.ui.IlluminationDrawable
+<com.android.systemui.media.controls.ui.drawable.IlluminationDrawable
xmlns:systemui="http://schemas.android.com/apk/res-auto"
systemui:highlight="15"
systemui:cornerRadius="@dimen/notification_corner_radius" /> \ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/qs_media_light_source.xml b/packages/SystemUI/res/drawable/qs_media_light_source.xml
index 849349a5f100..0b42dbab6ced 100644
--- a/packages/SystemUI/res/drawable/qs_media_light_source.xml
+++ b/packages/SystemUI/res/drawable/qs_media_light_source.xml
@@ -14,7 +14,7 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<com.android.systemui.media.controls.ui.LightSourceDrawable
+<com.android.systemui.media.controls.ui.drawable.LightSourceDrawable
xmlns:systemui="http://schemas.android.com/apk/res-auto"
systemui:rippleMinSize="25dp"
systemui:rippleMaxSize="135dp" /> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/anc_slice.xml b/packages/SystemUI/res/layout/anc_slice.xml
new file mode 100644
index 000000000000..71752f217a4f
--- /dev/null
+++ b/packages/SystemUI/res/layout/anc_slice.xml
@@ -0,0 +1,20 @@
+<?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.
+ -->
+<androidx.slice.widget.SliceView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/slice_view"
+ style="@style/Widget.SliceView.Panel.Slider"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" /> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/media_carousel.xml b/packages/SystemUI/res/layout/media_carousel.xml
index 715c86957e02..825ece856ed2 100644
--- a/packages/SystemUI/res/layout/media_carousel.xml
+++ b/packages/SystemUI/res/layout/media_carousel.xml
@@ -24,7 +24,7 @@
android:clipToPadding="false"
android:forceHasOverlappingRendering="false"
android:theme="@style/MediaPlayer">
- <com.android.systemui.media.controls.ui.MediaScrollView
+ <com.android.systemui.media.controls.ui.view.MediaScrollView
android:id="@+id/media_carousel_scroller"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -42,7 +42,7 @@
>
<!-- QSMediaPlayers will be added here dynamically -->
</LinearLayout>
- </com.android.systemui.media.controls.ui.MediaScrollView>
+ </com.android.systemui.media.controls.ui.view.MediaScrollView>
<com.android.systemui.qs.PageIndicator
android:id="@+id/media_page_indicator"
android:layout_width="wrap_content"
diff --git a/packages/SystemUI/res/layout/scene_window_root.xml b/packages/SystemUI/res/layout/scene_window_root.xml
index 0dcd15b429c1..bb8de4c32e76 100644
--- a/packages/SystemUI/res/layout/scene_window_root.xml
+++ b/packages/SystemUI/res/layout/scene_window_root.xml
@@ -24,7 +24,7 @@
android:id="@+id/scene_window_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:fitsSystemWindows="true">
+ android:fitsSystemWindows="false">
<include layout="@layout/super_notification_shade"
android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index a681da3adc4e..5436642dc5a1 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -608,8 +608,6 @@
<dimen name="volume_panel_slice_horizontal_padding">24dp</dimen>
<dimen name="volume_panel_corner_radius">52dp</dimen>
- <dimen name="volume_panel_bottom_bar_horizontal_padding">24dp</dimen>
- <dimen name="volume_panel_bottom_bar_bottom_padding">20dp</dimen>
<!-- Size of each item in the ringer selector drawer. -->
<dimen name="volume_ringer_drawer_item_size">42dp</dimen>
@@ -1514,6 +1512,12 @@
<!-- The amount of vertical offset for the keyguard during the full shade transition. -->
<dimen name="lockscreen_shade_keyguard_transition_vertical_offset">0dp</dimen>
+ <!-- LOCKSCREEN -> GLANCEABLE_HUB transition: Amount to shift lockscreen content on entering -->
+ <dimen name="lockscreen_to_hub_transition_lockscreen_translation_x">-824dp</dimen>
+
+ <!-- GLANCEABLE_HUB -> LOCKSCREEN transition: Amount to shift lockscreen content on entering -->
+ <dimen name="hub_to_lockscreen_transition_lockscreen_translation_x">824dp</dimen>
+
<!-- Distance that the full shade transition takes in order for media to fully transition to
the shade -->
<dimen name="lockscreen_shade_media_transition_distance">120dp</dimen>
@@ -1857,6 +1861,7 @@
<dimen name="dream_overlay_y_offset">80dp</dimen>
<dimen name="dream_overlay_entry_y_offset">40dp</dimen>
<dimen name="dream_overlay_exit_y_offset">40dp</dimen>
+ <dimen name="dream_overlay_exit_x_offset">824dp</dimen>
<dimen name="status_view_margin_horizontal">0dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index a7d93e70fda3..495f20f27996 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1522,6 +1522,9 @@
<string name="volume_stream_content_description_vibrate_a11y">%1$s. Tap to set to vibrate.</string>
<string name="volume_stream_content_description_mute_a11y">%1$s. Tap to mute.</string>
+ <!-- Label for button to enabled/disable live caption [CHAR_LIMIT=30] -->
+ <string name="volume_panel_noise_control_title">Noise Control</string>
+
<string name="volume_ringer_change">Tap to change ringer mode</string>
<!-- Hint for accessibility. For example: double tap to mute [CHAR_LIMIT=NONE] -->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index f1d4d71d1cc4..617eadbd447c 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -953,8 +953,11 @@
<item name="wallpaperTextColor">@*android:color/primary_text_material_dark</item>
</style>
- <style name="Theme.VolumePanelActivity"
- parent="@android:style/Theme.DeviceDefault.NoActionBar.Fullscreen">
+ <style name="Theme.VolumePanelActivity" parent="@android:style/Theme.DeviceDefault.DayNight">
+ <item name="android:windowFullscreen">true</item>
+ <item name="android:windowContentOverlay">@null</item>
+ <item name="android:windowActionBar">false</item>
+ <item name="android:windowNoTitle">true</item>
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:backgroundDimEnabled">true</item>
@@ -962,6 +965,12 @@
<item name="android:windowAnimationStyle">@android:style/Animation.Dialog</item>
</style>
+ <style name="Theme.VolumePanelActivity.Popup" parent="@style/Theme.SystemUI.Dialog">
+ <item name="android:dialogCornerRadius">44dp</item>
+ <item name="android:colorBackground">?androidprv:attr/materialColorSurfaceContainerHigh
+ </item>
+ </style>
+
<style name="Theme.UserSwitcherFullscreenDialog" parent="@android:style/Theme.DeviceDefault.NoActionBar.Fullscreen">
<item name="android:statusBarColor">@color/user_switcher_fullscreen_bg</item>
<item name="android:windowBackground">@color/user_switcher_fullscreen_bg</item>
diff --git a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricUserInfo.kt b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricUserInfo.kt
index fdac37b7a2c8..b0d66110deb5 100644
--- a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricUserInfo.kt
+++ b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricUserInfo.kt
@@ -21,8 +21,12 @@ package com.android.systemui.biometrics.shared.model
*
* If the user's fallback credential is owned by another profile user the [deviceCredentialOwnerId]
* will differ from the user's [userId].
+ *
+ * If prompt requests to use the user's parent profile for device credential,
+ * [userIdForPasswordEntry] might differ from the user's [userId].
*/
data class BiometricUserInfo(
val userId: Int,
val deviceCredentialOwnerId: Int = userId,
+ val userIdForPasswordEntry: Int = userId,
)
diff --git a/packages/SystemUI/src/com/android/keyguard/ConnectedDisplayKeyguardPresentation.kt b/packages/SystemUI/src/com/android/keyguard/ConnectedDisplayKeyguardPresentation.kt
index dec7d7992596..630610d1a85f 100644
--- a/packages/SystemUI/src/com/android/keyguard/ConnectedDisplayKeyguardPresentation.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ConnectedDisplayKeyguardPresentation.kt
@@ -19,13 +19,22 @@ package com.android.keyguard
import android.app.Presentation
import android.content.Context
import android.graphics.Color
+import android.graphics.Rect
import android.os.Bundle
import android.view.Display
+import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
+import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import android.view.WindowManager
+import android.widget.FrameLayout
+import android.widget.FrameLayout.LayoutParams
import com.android.keyguard.dagger.KeyguardStatusViewComponent
+import com.android.systemui.Flags.migrateClocksToBlueprint
+import com.android.systemui.plugins.clocks.ClockController
+import com.android.systemui.plugins.clocks.ClockFaceController
import com.android.systemui.res.R
+import com.android.systemui.shared.clocks.ClockRegistry
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@@ -37,6 +46,8 @@ constructor(
@Assisted display: Display,
context: Context,
private val keyguardStatusViewComponentFactory: KeyguardStatusViewComponent.Factory,
+ private val clockRegistry: ClockRegistry,
+ private val clockEventController: ClockEventController,
) :
Presentation(
context,
@@ -45,31 +56,74 @@ constructor(
WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG
) {
+ private lateinit var rootView: FrameLayout
+ private var clock: View? = null
private lateinit var keyguardStatusViewController: KeyguardStatusViewController
- private lateinit var clock: KeyguardStatusView
+ private lateinit var faceController: ClockFaceController
+ private lateinit var clockFrame: FrameLayout
+
+ private val clockChangedListener =
+ object : ClockRegistry.ClockChangeListener {
+ override fun onCurrentClockChanged() {
+ setClock(clockRegistry.createCurrentClock())
+ }
+
+ override fun onAvailableClocksChanged() {}
+ }
+
+ private val layoutChangeListener =
+ object : View.OnLayoutChangeListener {
+ override fun onLayoutChange(
+ view: View,
+ left: Int,
+ top: Int,
+ right: Int,
+ bottom: Int,
+ oldLeft: Int,
+ oldTop: Int,
+ oldRight: Int,
+ oldBottom: Int
+ ) {
+ clock?.let {
+ faceController.events.onTargetRegionChanged(
+ Rect(it.left, it.top, it.width, it.height)
+ )
+ }
+ }
+ }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+ if (migrateClocksToBlueprint()) {
+ onCreateV2()
+ } else {
+ onCreate()
+ }
+ }
+
+ fun onCreateV2() {
+ rootView = FrameLayout(getContext(), null)
+ rootView.setClipChildren(false)
+ setContentView(rootView)
+
+ setFullscreen()
+
+ setClock(clockRegistry.createCurrentClock())
+ }
+
+ fun onCreate() {
setContentView(
LayoutInflater.from(context)
.inflate(R.layout.keyguard_clock_presentation, /* root= */ null)
)
- val window = window ?: error("no window available.")
- // Logic to make the lock screen fullscreen
- window.decorView.systemUiVisibility =
- (View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
- View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
- View.SYSTEM_UI_FLAG_LAYOUT_STABLE)
- window.attributes.fitInsetsTypes = 0
- window.isNavigationBarContrastEnforced = false
- window.navigationBarColor = Color.TRANSPARENT
+ setFullscreen()
clock = requireViewById(R.id.clock)
keyguardStatusViewController =
keyguardStatusViewComponentFactory
- .build(clock, display)
+ .build(clock as KeyguardStatusView, display)
.keyguardStatusViewController
.apply {
setDisplayedOnSecondaryDisplay()
@@ -77,6 +131,61 @@ constructor(
}
}
+ override fun onAttachedToWindow() {
+ if (migrateClocksToBlueprint()) {
+ clockRegistry.registerClockChangeListener(clockChangedListener)
+ clockEventController.registerListeners(clock!!)
+
+ faceController.animations.enter()
+ }
+ }
+
+ override fun onDetachedFromWindow() {
+ if (migrateClocksToBlueprint()) {
+ clockEventController.unregisterListeners()
+ clockRegistry.unregisterClockChangeListener(clockChangedListener)
+ }
+
+ super.onDetachedFromWindow()
+ }
+
+ override fun onDisplayChanged() {
+ val window = window ?: error("no window available.")
+ window.getDecorView().requestLayout()
+ }
+
+ private fun setClock(clockController: ClockController) {
+ clock?.removeOnLayoutChangeListener(layoutChangeListener)
+ rootView.removeAllViews()
+
+ faceController = clockController.largeClock
+ clock = faceController.view.also { it.addOnLayoutChangeListener(layoutChangeListener) }
+ rootView.addView(
+ clock,
+ FrameLayout.LayoutParams(
+ context.resources.getDimensionPixelSize(R.dimen.keyguard_presentation_width),
+ WRAP_CONTENT,
+ Gravity.CENTER,
+ )
+ )
+
+ clockEventController.clock = clockController
+ clockEventController.setLargeClockOnSecondaryDisplay(true)
+ faceController.events.onSecondaryDisplayChanged(true)
+ }
+
+ private fun setFullscreen() {
+ val window = window ?: error("no window available.")
+ // Logic to make the lock screen fullscreen
+ window.decorView.systemUiVisibility =
+ (View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
+ View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
+ View.SYSTEM_UI_FLAG_LAYOUT_STABLE)
+ window.attributes.fitInsetsTypes = 0
+ window.isNavigationBarContrastEnforced = false
+ window.navigationBarColor = Color.TRANSPARENT
+ }
+
/** [ConnectedDisplayKeyguardPresentation] factory. */
@AssistedFactory
interface Factory {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
index 8a6f101b6c6c..0bb5c174444f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
@@ -17,13 +17,10 @@ package com.android.keyguard;
import android.app.Presentation;
import android.content.Context;
-import android.graphics.Color;
-import android.graphics.Rect;
import android.hardware.devicestate.DeviceStateManager;
import android.hardware.display.DisplayManager;
import android.media.MediaRouter;
import android.media.MediaRouter.RouteInfo;
-import android.os.Bundle;
import android.os.Trace;
import android.text.TextUtils;
import android.util.Log;
@@ -31,20 +28,14 @@ import android.util.SparseArray;
import android.view.Display;
import android.view.DisplayAddress;
import android.view.DisplayInfo;
-import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import androidx.annotation.Nullable;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.keyguard.dagger.KeyguardStatusViewComponent;
-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.UiBackground;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.navigationbar.NavigationBarController;
import com.android.systemui.navigationbar.NavigationBarView;
import com.android.systemui.settings.DisplayTracker;
@@ -66,10 +57,8 @@ public class KeyguardDisplayManager {
private final DisplayManager mDisplayService;
private final DisplayTracker mDisplayTracker;
private final Lazy<NavigationBarController> mNavigationBarControllerLazy;
- private final KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
private final ConnectedDisplayKeyguardPresentation.Factory
mConnectedDisplayKeyguardPresentationFactory;
- private final FeatureFlags mFeatureFlags;
private final Context mContext;
private boolean mShowing;
@@ -106,18 +95,15 @@ public class KeyguardDisplayManager {
@Inject
public KeyguardDisplayManager(Context context,
Lazy<NavigationBarController> navigationBarControllerLazy,
- KeyguardStatusViewComponent.Factory keyguardStatusViewComponentFactory,
DisplayTracker displayTracker,
@Main Executor mainExecutor,
@UiBackground Executor uiBgExecutor,
DeviceStateHelper deviceStateHelper,
KeyguardStateController keyguardStateController,
ConnectedDisplayKeyguardPresentation.Factory
- connectedDisplayKeyguardPresentationFactory,
- FeatureFlags featureFlags) {
+ connectedDisplayKeyguardPresentationFactory) {
mContext = context;
mNavigationBarControllerLazy = navigationBarControllerLazy;
- mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory;
uiBgExecutor.execute(() -> mMediaRouter = mContext.getSystemService(MediaRouter.class));
mDisplayService = mContext.getSystemService(DisplayManager.class);
mDisplayTracker = displayTracker;
@@ -125,7 +111,6 @@ public class KeyguardDisplayManager {
mDeviceStateHelper = deviceStateHelper;
mKeyguardStateController = keyguardStateController;
mConnectedDisplayKeyguardPresentationFactory = connectedDisplayKeyguardPresentationFactory;
- mFeatureFlags = featureFlags;
}
private boolean isKeyguardShowable(Display display) {
@@ -197,11 +182,7 @@ public class KeyguardDisplayManager {
}
Presentation createPresentation(Display display) {
- if (mFeatureFlags.isEnabled(Flags.ENABLE_CLOCK_KEYGUARD_PRESENTATION)) {
- return mConnectedDisplayKeyguardPresentationFactory.create(display);
- } else {
- return new KeyguardPresentation(mContext, display, mKeyguardStatusViewComponentFactory);
- }
+ return mConnectedDisplayKeyguardPresentationFactory.create(display);
}
/**
@@ -347,92 +328,4 @@ public class KeyguardDisplayManager {
&& mRearDisplayPhysicalAddress.equals(display.getAddress());
}
}
-
-
- @VisibleForTesting
- static final class KeyguardPresentation extends Presentation {
- private static final int VIDEO_SAFE_REGION = 80; // Percentage of display width & height
- private static final int MOVE_CLOCK_TIMEOUT = 10000; // 10s
- private final KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
- private KeyguardClockSwitchController mKeyguardClockSwitchController;
- private View mClock;
- private int mUsableWidth;
- private int mUsableHeight;
- private int mMarginTop;
- private int mMarginLeft;
- Runnable mMoveTextRunnable = new Runnable() {
- @Override
- public void run() {
- int x = mMarginLeft + (int) (Math.random() * (mUsableWidth - mClock.getWidth()));
- int y = mMarginTop + (int) (Math.random() * (mUsableHeight - mClock.getHeight()));
- mClock.setTranslationX(x);
- mClock.setTranslationY(y);
- mClock.postDelayed(mMoveTextRunnable, MOVE_CLOCK_TIMEOUT);
- }
- };
-
- KeyguardPresentation(Context context, Display display,
- KeyguardStatusViewComponent.Factory keyguardStatusViewComponentFactory) {
- super(context, display, R.style.Theme_SystemUI_KeyguardPresentation,
- WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
- mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory;
- setCancelable(false);
- }
-
- @Override
- public void cancel() {
- // Do not allow anything to cancel KeyguardPresentation except KeyguardDisplayManager.
- }
-
- @Override
- public void onDetachedFromWindow() {
- mClock.removeCallbacks(mMoveTextRunnable);
- }
-
- @Override
- public void onDisplayChanged() {
- updateBounds();
- getWindow().getDecorView().requestLayout();
- }
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- updateBounds();
-
- setContentView(LayoutInflater.from(getContext())
- .inflate(R.layout.keyguard_presentation, null));
-
- // Logic to make the lock screen fullscreen
- getWindow().getDecorView().setSystemUiVisibility(
- View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
- | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
- | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
- getWindow().getAttributes().setFitInsetsTypes(0 /* types */);
- getWindow().setNavigationBarContrastEnforced(false);
- getWindow().setNavigationBarColor(Color.TRANSPARENT);
-
- mClock = findViewById(R.id.clock);
-
- // Avoid screen burn in
- mClock.post(mMoveTextRunnable);
-
- mKeyguardClockSwitchController = mKeyguardStatusViewComponentFactory
- .build(findViewById(R.id.clock), getDisplay())
- .getKeyguardClockSwitchController();
-
- mKeyguardClockSwitchController.setOnlyClock(true);
- mKeyguardClockSwitchController.init();
- }
-
- private void updateBounds() {
- final Rect bounds = getWindow().getWindowManager().getMaximumWindowMetrics()
- .getBounds();
- mUsableWidth = VIDEO_SAFE_REGION * bounds.width() / 100;
- mUsableHeight = VIDEO_SAFE_REGION * bounds.height() / 100;
- mMarginLeft = (100 - VIDEO_SAFE_REGION) * bounds.width() / 200;
- mMarginTop = (100 - VIDEO_SAFE_REGION) * bounds.height() / 200;
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java
index a9928d80117b..63088aaf7ff4 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java
@@ -20,6 +20,7 @@ import static android.app.slice.Slice.HINT_LIST_ITEM;
import android.app.PendingIntent;
import android.net.Uri;
+import android.os.Handler;
import android.os.Trace;
import android.provider.Settings;
import android.util.Log;
@@ -39,6 +40,9 @@ import androidx.slice.widget.SliceLiveData;
import com.android.keyguard.dagger.KeyguardStatusViewScope;
import com.android.systemui.Dumpable;
+import com.android.systemui.Flags;
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.KeyguardSliceProvider;
import com.android.systemui.plugins.ActivityStarter;
@@ -60,6 +64,8 @@ public class KeyguardSliceViewController extends ViewController<KeyguardSliceVie
Dumpable {
private static final String TAG = "KeyguardSliceViewCtrl";
+ private final Handler mHandler;
+ private final Handler mBgHandler;
private final ActivityStarter mActivityStarter;
private final ConfigurationController mConfigurationController;
private final TunerService mTunerService;
@@ -105,6 +111,8 @@ public class KeyguardSliceViewController extends ViewController<KeyguardSliceVie
@Inject
public KeyguardSliceViewController(
+ @Main Handler handler,
+ @Background Handler bgHandler,
KeyguardSliceView keyguardSliceView,
ActivityStarter activityStarter,
ConfigurationController configurationController,
@@ -112,6 +120,8 @@ public class KeyguardSliceViewController extends ViewController<KeyguardSliceVie
DumpManager dumpManager,
DisplayTracker displayTracker) {
super(keyguardSliceView);
+ mHandler = handler;
+ mBgHandler = bgHandler;
mActivityStarter = activityStarter;
mConfigurationController = configurationController;
mTunerService = tunerService;
@@ -182,24 +192,34 @@ public class KeyguardSliceViewController extends ViewController<KeyguardSliceVie
* Update contents of the view.
*/
public void refresh() {
- Slice slice;
+
Trace.beginSection("KeyguardSliceViewController#refresh");
- // We can optimize performance and avoid binder calls when we know that we're bound
- // to a Slice on the same process.
- if (KeyguardSliceProvider.KEYGUARD_SLICE_URI.equals(mKeyguardSliceUri.toString())) {
- KeyguardSliceProvider instance = KeyguardSliceProvider.getAttachedInstance();
- if (instance != null) {
- slice = instance.onBindSlice(mKeyguardSliceUri);
+ try {
+ Slice slice;
+ if (KeyguardSliceProvider.KEYGUARD_SLICE_URI.equals(mKeyguardSliceUri.toString())) {
+ KeyguardSliceProvider instance = KeyguardSliceProvider.getAttachedInstance();
+ if (instance != null) {
+ if (Flags.sliceManagerBinderCallBackground()) {
+ mBgHandler.post(() -> {
+ Slice _slice = instance.onBindSlice(mKeyguardSliceUri);
+ mHandler.post(() -> mObserver.onChanged(_slice));
+ });
+ return;
+ }
+ slice = instance.onBindSlice(mKeyguardSliceUri);
+ } else {
+ Log.w(TAG, "Keyguard slice not bound yet?");
+ slice = null;
+ }
} else {
- Log.w(TAG, "Keyguard slice not bound yet?");
- slice = null;
+ // TODO: Make SliceViewManager injectable
+ slice = SliceViewManager.getInstance(mView.getContext()).bindSlice(
+ mKeyguardSliceUri);
}
- } else {
- // TODO: Make SliceViewManager injectable
- slice = SliceViewManager.getInstance(mView.getContext()).bindSlice(mKeyguardSliceUri);
+ mObserver.onChanged(slice);
+ } finally {
+ Trace.endSection();
}
- mObserver.onChanged(slice);
- Trace.endSection();
}
void showSlice(Slice slice) {
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt
index 2d854d934ca2..02ee2c951453 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt
@@ -16,18 +16,8 @@
package com.android.keyguard.logging
-import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.LogLevel.DEBUG
-import com.android.systemui.log.dagger.BiometricLog
-import javax.inject.Inject
-
-/** Helper class for logging for [com.android.systemui.biometrics.FaceHelpMessageDeferral] */
-@SysUISingleton
-class FaceMessageDeferralLogger
-@Inject
-constructor(@BiometricLog private val logBuffer: LogBuffer) :
- BiometricMessageDeferralLogger(logBuffer, "FaceMessageDeferralLogger")
open class BiometricMessageDeferralLogger(
private val logBuffer: LogBuffer,
diff --git a/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt b/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt
index e8499d3ca9a9..ca83724aaf07 100644
--- a/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt
@@ -143,7 +143,7 @@ class CameraAvailabilityListener(
private fun notifyCameraActive(info: CameraProtectionInfo) {
listeners.forEach {
- it.onApplyCameraProtection(info.cutoutProtectionPath, info.cutoutBounds)
+ it.onApplyCameraProtection(info.cutoutProtectionPath, info.bounds)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/CameraProtectionInfo.kt b/packages/SystemUI/src/com/android/systemui/CameraProtectionInfo.kt
index 6314bd9a5615..9357056a0850 100644
--- a/packages/SystemUI/src/com/android/systemui/CameraProtectionInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/CameraProtectionInfo.kt
@@ -23,6 +23,6 @@ data class CameraProtectionInfo(
val logicalCameraId: String,
val physicalCameraId: String?,
val cutoutProtectionPath: Path,
- val cutoutBounds: Rect,
+ val bounds: Rect,
val displayUniqueId: String?,
)
diff --git a/packages/SystemUI/src/com/android/systemui/SysUICutoutProvider.kt b/packages/SystemUI/src/com/android/systemui/SysUICutoutProvider.kt
index aad934124dfb..7309599d9c04 100644
--- a/packages/SystemUI/src/com/android/systemui/SysUICutoutProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/SysUICutoutProvider.kt
@@ -17,8 +17,12 @@
package com.android.systemui
import android.content.Context
+import android.graphics.Rect
+import android.util.RotationUtils
+import android.view.Display
import android.view.DisplayCutout
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.display.naturalBounds
import javax.inject.Inject
@SysUISingleton
@@ -33,15 +37,43 @@ constructor(
cameraProtectionLoader.loadCameraProtectionInfoList()
}
- fun cutoutInfoForCurrentDisplay(): SysUICutoutInformation? {
+ /**
+ * Returns the [SysUICutoutInformation] for the current display and the current rotation.
+ *
+ * This means that the bounds of the display cutout and the camera protection will be
+ * adjusted/rotated for the current rotation.
+ */
+ fun cutoutInfoForCurrentDisplayAndRotation(): SysUICutoutInformation? {
val display = context.display
val displayCutout: DisplayCutout = display.cutout ?: return null
+ return SysUICutoutInformation(displayCutout, getCameraProtectionForDisplay(display))
+ }
+
+ private fun getCameraProtectionForDisplay(display: Display): CameraProtectionInfo? {
val displayUniqueId: String? = display.uniqueId
if (displayUniqueId.isNullOrEmpty()) {
- return SysUICutoutInformation(displayCutout, cameraProtection = null)
+ return null
}
- val cameraProtection: CameraProtectionInfo? =
+ val cameraProtection: CameraProtectionInfo =
cameraProtectionList.firstOrNull { it.displayUniqueId == displayUniqueId }
- return SysUICutoutInformation(displayCutout, cameraProtection)
+ ?: return null
+ val adjustedBoundsForRotation =
+ calculateCameraProtectionBoundsForRotation(display, cameraProtection.bounds)
+ return cameraProtection.copy(bounds = adjustedBoundsForRotation)
+ }
+
+ private fun calculateCameraProtectionBoundsForRotation(
+ display: Display,
+ originalProtectionBounds: Rect,
+ ): Rect {
+ val displayNaturalBounds = display.naturalBounds
+ val rotatedBoundsOut = Rect(originalProtectionBounds)
+ RotationUtils.rotateBounds(
+ /* inOutBounds = */ rotatedBoundsOut,
+ /* parentWidth = */ displayNaturalBounds.width(),
+ /* parentHeight = */ displayNaturalBounds.height(),
+ /* rotation = */ display.rotation
+ )
+ return rotatedBoundsOut
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
index 9fd060255e9b..e0f73a63113a 100644
--- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
@@ -229,6 +229,9 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon
private void fetchCurrentActiveOps() {
List<AppOpsManager.PackageOps> packageOps = mAppOps.getPackagesForOps(OPS);
+ if (packageOps == null) {
+ return;
+ }
for (AppOpsManager.PackageOps op : packageOps) {
for (AppOpsManager.OpEntry entry : op.getOps()) {
for (Map.Entry<String, AppOpsManager.AttributedOpEntry> attributedOpEntry :
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/FaceHelpMessageDeferral.kt b/packages/SystemUI/src/com/android/systemui/biometrics/FaceHelpMessageDeferral.kt
index 8c68eac84963..90d06fb0bec1 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/FaceHelpMessageDeferral.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/FaceHelpMessageDeferral.kt
@@ -18,14 +18,16 @@ package com.android.systemui.biometrics
import android.content.res.Resources
import com.android.keyguard.logging.BiometricMessageDeferralLogger
-import com.android.keyguard.logging.FaceMessageDeferralLogger
import com.android.systemui.Dumpable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.dagger.BiometricLog
import com.android.systemui.res.R
import java.io.PrintWriter
import java.util.Objects
+import java.util.UUID
import javax.inject.Inject
@SysUISingleton
@@ -33,14 +35,16 @@ class FaceHelpMessageDeferralFactory
@Inject
constructor(
@Main private val resources: Resources,
- private val logBuffer: FaceMessageDeferralLogger,
+ @BiometricLog private val logBuffer: LogBuffer,
private val dumpManager: DumpManager
) {
fun create(): FaceHelpMessageDeferral {
+ val id = UUID.randomUUID().toString()
return FaceHelpMessageDeferral(
resources = resources,
- logBuffer = logBuffer,
+ logBuffer = BiometricMessageDeferralLogger(logBuffer, "FaceHelpMessageDeferral[$id]"),
dumpManager = dumpManager,
+ id = id,
)
}
}
@@ -51,15 +55,17 @@ constructor(
*/
class FaceHelpMessageDeferral(
resources: Resources,
- logBuffer: FaceMessageDeferralLogger,
- dumpManager: DumpManager
+ logBuffer: BiometricMessageDeferralLogger,
+ dumpManager: DumpManager,
+ val id: String,
) :
BiometricMessageDeferral(
resources.getIntArray(R.array.config_face_help_msgs_defer_until_timeout).toHashSet(),
resources.getIntArray(R.array.config_face_help_msgs_ignore).toHashSet(),
resources.getFloat(R.dimen.config_face_help_msgs_defer_until_timeout_threshold),
logBuffer,
- dumpManager
+ dumpManager,
+ id,
)
/**
@@ -72,7 +78,8 @@ open class BiometricMessageDeferral(
private val acquiredInfoToIgnore: Set<Int>,
private val threshold: Float,
private val logBuffer: BiometricMessageDeferralLogger,
- dumpManager: DumpManager
+ dumpManager: DumpManager,
+ id: String,
) : Dumpable {
private val acquiredInfoToFrequency: MutableMap<Int, Int> = HashMap()
private val acquiredInfoToHelpString: MutableMap<Int, String> = HashMap()
@@ -80,7 +87,10 @@ open class BiometricMessageDeferral(
private var totalFrames = 0
init {
- dumpManager.registerDumpable(this.javaClass.name, this)
+ dumpManager.registerNormalDumpable(
+ "${this.javaClass.name}[$id]",
+ this,
+ )
}
override fun dump(pw: PrintWriter, args: Array<out String>) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt
index 191533c8f377..49973708487b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt
@@ -7,9 +7,9 @@ import android.os.UserManager
import com.android.internal.widget.LockPatternUtils
import com.android.internal.widget.LockscreenCredential
import com.android.internal.widget.VerifyCredentialResponse
-import com.android.systemui.res.R
import com.android.systemui.biometrics.domain.model.BiometricPromptRequest
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.res.R
import com.android.systemui.util.time.SystemClock
import javax.inject.Inject
import kotlinx.coroutines.delay
@@ -29,6 +29,9 @@ interface CredentialInteractor {
/** Get the effective user id (profile owner, if one exists) */
fun getCredentialOwnerOrSelfId(userId: Int): Int
+ /** Get parent user profile (if exists) */
+ fun getParentProfileIdOrSelfId(userId: Int): Int
+
/**
* Verifies a credential and returns a stream of results.
*
@@ -58,6 +61,9 @@ constructor(
override fun getCredentialOwnerOrSelfId(userId: Int): Int =
userManager.getCredentialOwnerProfile(userId)
+ override fun getParentProfileIdOrSelfId(userId: Int): Int =
+ userManager.getProfileParent(userId)?.id ?: userManager.getCredentialOwnerProfile(userId)
+
override fun verifyCredential(
request: BiometricPromptRequest.Credential,
credential: LockscreenCredential,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt
index 359e2e703ee6..e3facff9af12 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt
@@ -75,20 +75,32 @@ constructor(
PromptKind.Pin ->
BiometricPromptRequest.Credential.Pin(
info = promptInfo,
- userInfo = userInfo(userId),
+ userInfo =
+ userInfo(
+ userId,
+ promptInfo.shouldUseParentProfileForDeviceCredential()
+ ),
operationInfo = operationInfo(challenge)
)
PromptKind.Pattern ->
BiometricPromptRequest.Credential.Pattern(
info = promptInfo,
- userInfo = userInfo(userId),
+ userInfo =
+ userInfo(
+ userId,
+ promptInfo.shouldUseParentProfileForDeviceCredential()
+ ),
operationInfo = operationInfo(challenge),
stealthMode = credentialInteractor.isStealthModeActive(userId)
)
PromptKind.Password ->
BiometricPromptRequest.Credential.Password(
info = promptInfo,
- userInfo = userInfo(userId),
+ userInfo =
+ userInfo(
+ userId,
+ promptInfo.shouldUseParentProfileForDeviceCredential()
+ ),
operationInfo = operationInfo(challenge)
)
else -> null
@@ -96,10 +108,17 @@ constructor(
}
.distinctUntilChanged()
- private fun userInfo(userId: Int): BiometricUserInfo =
+ private fun userInfo(
+ userId: Int,
+ useParentProfileForDeviceCredential: Boolean
+ ): BiometricUserInfo =
BiometricUserInfo(
userId = userId,
- deviceCredentialOwnerId = credentialInteractor.getCredentialOwnerOrSelfId(userId)
+ deviceCredentialOwnerId = credentialInteractor.getCredentialOwnerOrSelfId(userId),
+ userIdForPasswordEntry =
+ if (useParentProfileForDeviceCredential)
+ credentialInteractor.getParentProfileIdOrSelfId(userId)
+ else credentialInteractor.getCredentialOwnerOrSelfId(userId),
)
private fun operationInfo(challenge: Long): BiometricOperationInfo =
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt
index 0ad07ba924a0..4ed786be99c9 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt
@@ -12,12 +12,12 @@ import android.window.OnBackInvokedDispatcher
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
-import com.android.systemui.res.R
import com.android.systemui.biometrics.ui.CredentialPasswordView
import com.android.systemui.biometrics.ui.CredentialView
import com.android.systemui.biometrics.ui.IPinPad
import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.res.R
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.firstOrNull
@@ -42,7 +42,7 @@ object CredentialPasswordViewBinder {
view.repeatWhenAttached {
// the header info never changes - do it early
val header = viewModel.header.first()
- passwordField.setTextOperationUser(UserHandle.of(header.user.deviceCredentialOwnerId))
+ passwordField.setTextOperationUser(UserHandle.of(header.user.userIdForPasswordEntry))
viewModel.inputFlags.firstOrNull()?.let { flags -> passwordField.inputType = flags }
if (requestFocusForInput) {
passwordField.requestFocus()
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
index 25ccc1658744..357eca37ba37 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
@@ -93,11 +93,13 @@ public class BrightLineFalsingManager implements FalsingManager {
@Override
public void onSessionEnded() {
mLastProximityEvent = null;
+ mHistoryTracker.removeBeliefListener(mBeliefListener);
mClassifiers.forEach(FalsingClassifier::onSessionEnded);
}
@Override
public void onSessionStarted() {
+ mHistoryTracker.addBeliefListener(mBeliefListener);
mClassifiers.forEach(FalsingClassifier::onSessionStarted);
}
};
@@ -200,7 +202,6 @@ public class BrightLineFalsingManager implements FalsingManager {
mDataProvider.addSessionListener(mSessionListener);
mDataProvider.addGestureCompleteListener(mGestureFinalizedListener);
- mHistoryTracker.addBeliefListener(mBeliefListener);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
index 5b1082acd59f..3819e614aca0 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
@@ -27,6 +27,7 @@ import androidx.annotation.VisibleForTesting;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.systemui.communal.domain.interactor.CommunalInteractor;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dock.DockManager;
@@ -39,6 +40,7 @@ import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChang
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.android.systemui.util.kotlin.BooleanFlowOperators;
import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.sensors.ProximitySensor;
import com.android.systemui.util.sensors.ThresholdSensor;
@@ -67,6 +69,7 @@ class FalsingCollectorImpl implements FalsingCollector {
private final StatusBarStateController mStatusBarStateController;
private final KeyguardStateController mKeyguardStateController;
private final Lazy<ShadeInteractor> mShadeInteractorLazy;
+ private final Lazy<CommunalInteractor> mCommunalInteractorLazy;
private final BatteryController mBatteryController;
private final DockManager mDockManager;
private final DelayableExecutor mMainExecutor;
@@ -76,6 +79,7 @@ class FalsingCollectorImpl implements FalsingCollector {
private int mState;
private boolean mShowingAod;
+ private boolean mShowingCommunalHub;
private boolean mScreenOn;
private boolean mSessionStarted;
private MotionEvent mPendingDownEvent;
@@ -145,7 +149,8 @@ class FalsingCollectorImpl implements FalsingCollector {
@Main DelayableExecutor mainExecutor,
JavaAdapter javaAdapter,
SystemClock systemClock,
- Lazy<SelectedUserInteractor> userInteractor) {
+ Lazy<SelectedUserInteractor> userInteractor,
+ Lazy<CommunalInteractor> communalInteractorLazy) {
mFalsingDataProvider = falsingDataProvider;
mFalsingManager = falsingManager;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
@@ -160,6 +165,7 @@ class FalsingCollectorImpl implements FalsingCollector {
mJavaAdapter = javaAdapter;
mSystemClock = systemClock;
mUserInteractor = userInteractor;
+ mCommunalInteractorLazy = communalInteractorLazy;
}
@Override
@@ -176,6 +182,13 @@ class FalsingCollectorImpl implements FalsingCollector {
mShadeInteractorLazy.get().isQsExpanded(),
this::onQsExpansionChanged
);
+ final CommunalInteractor communalInteractor = mCommunalInteractorLazy.get();
+ mJavaAdapter.alwaysCollectFlow(
+ BooleanFlowOperators.INSTANCE.and(
+ communalInteractor.isCommunalEnabled(),
+ communalInteractor.isCommunalShowing()),
+ this::onShowingCommunalHubChanged
+ );
mBatteryController.addCallback(mBatteryListener);
mDockManager.addListener(mDockEventListener);
@@ -205,6 +218,12 @@ class FalsingCollectorImpl implements FalsingCollector {
}
}
+ private void onShowingCommunalHubChanged(boolean isShowing) {
+ logDebug("REAL: onShowingCommunalHubChanged(" + isShowing + ")");
+ mShowingCommunalHub = isShowing;
+ updateSessionActive();
+ }
+
@Override
public boolean shouldEnforceBouncer() {
return false;
@@ -333,7 +352,10 @@ class FalsingCollectorImpl implements FalsingCollector {
}
private boolean shouldSessionBeActive() {
- return mScreenOn && (mState == StatusBarState.KEYGUARD) && !mShowingAod;
+ return mScreenOn
+ && (mState == StatusBarState.KEYGUARD)
+ && !mShowingAod
+ && !mShowingCommunalHub;
}
private void updateSessionActive() {
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/domain/interactor/FalsingInteractor.kt b/packages/SystemUI/src/com/android/systemui/classifier/domain/interactor/FalsingInteractor.kt
index 2e861c399ee9..57e9ade30bb5 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/domain/interactor/FalsingInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/classifier/domain/interactor/FalsingInteractor.kt
@@ -17,23 +17,27 @@
package com.android.systemui.classifier.domain.interactor
import android.view.MotionEvent
+import com.android.systemui.classifier.Classifier
import com.android.systemui.classifier.FalsingClassifier
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.classifier.FalsingCollectorActual
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.plugins.FalsingManager
import javax.inject.Inject
/**
- * Exposes the subset of the [FalsingCollector] API that's required by external callers.
+ * Exposes the subset of the [FalsingCollector] and [FalsingManager] APIs that's required by
+ * external callers.
*
- * E.g. methods of [FalsingCollector] that are not exposed by this class don't need to be invoked by
- * external callers as they're already called by the scene framework.
+ * E.g. methods of the above APIs that are not exposed by this class either don't need to be invoked
+ * by external callers (as they're already called by the scene framework) or haven't been added yet.
*/
@SysUISingleton
class FalsingInteractor
@Inject
constructor(
@FalsingCollectorActual private val collector: FalsingCollector,
+ private val manager: FalsingManager,
) {
/**
* Notifies of a [MotionEvent] that passed through the UI.
@@ -62,4 +66,9 @@ constructor(
fun updateFalseConfidence(
result: FalsingClassifier.Result,
) = collector.updateFalseConfidence(result)
+
+ /** Returns `true` if the gesture should be rejected. */
+ fun isFalseTouch(
+ @Classifier.InteractionType interactionType: Int,
+ ): Boolean = manager.isFalseTouch(interactionType)
}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
index e0ce3db39403..c7a47b18f467 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
@@ -18,12 +18,14 @@ package com.android.systemui.clipboardoverlay;
import static android.content.ClipDescription.CLASSIFICATION_COMPLETE;
+import static com.android.systemui.Flags.clipboardNoninteractiveOnLockscreen;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ENTERED;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_UPDATED;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_TOAST_SHOWN;
import static com.google.android.setupcompat.util.WizardManagerHelper.SETTINGS_SECURE_USER_SETUP_COMPLETE;
+import android.app.KeyguardManager;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
@@ -57,6 +59,7 @@ public class ClipboardListener implements
private final Provider<ClipboardOverlayController> mOverlayProvider;
private final ClipboardToast mClipboardToast;
private final ClipboardManager mClipboardManager;
+ private final KeyguardManager mKeyguardManager;
private final UiEventLogger mUiEventLogger;
private ClipboardOverlay mClipboardOverlay;
@@ -65,11 +68,13 @@ public class ClipboardListener implements
Provider<ClipboardOverlayController> clipboardOverlayControllerProvider,
ClipboardToast clipboardToast,
ClipboardManager clipboardManager,
+ KeyguardManager keyguardManager,
UiEventLogger uiEventLogger) {
mContext = context;
mOverlayProvider = clipboardOverlayControllerProvider;
mClipboardToast = clipboardToast;
mClipboardManager = clipboardManager;
+ mKeyguardManager = keyguardManager;
mUiEventLogger = uiEventLogger;
}
@@ -92,7 +97,9 @@ public class ClipboardListener implements
return;
}
- if (!isUserSetupComplete() // user should not access intents from this state
+ // user should not access intents before setup or while device is locked
+ if ((clipboardNoninteractiveOnLockscreen() && mKeyguardManager.isDeviceLocked())
+ || !isUserSetupComplete()
|| clipData == null // shouldn't happen, but just in case
|| clipData.getItemCount() == 0) {
if (shouldShowToast(clipData)) {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt
index 201be51b873c..e2fed6d0ea20 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt
@@ -21,8 +21,8 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.log.dagger.CommunalTableLog
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.logDiffsForTable
-import com.android.systemui.media.controls.models.player.MediaData
-import com.android.systemui.media.controls.pipeline.MediaDataManager
+import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
+import com.android.systemui.media.controls.shared.model.MediaData
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
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 9e68ff88622c..f4a3bcb7a0fa 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
@@ -22,7 +22,6 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
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 javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -33,14 +32,10 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
/** Encapsulates the state of communal mode. */
interface CommunalRepository {
- /** Whether the communal hub is showing. */
- val isCommunalHubShowing: Flow<Boolean>
-
/**
* Target scene as requested by the underlying [SceneTransitionLayout] or through
* [setDesiredScene].
@@ -99,11 +94,4 @@ constructor(
override fun setTransitionState(transitionState: Flow<ObservableCommunalTransitionState>?) {
_transitionState.value = transitionState
}
-
- override val isCommunalHubShowing: Flow<Boolean> =
- if (sceneContainerFlags.isEnabled()) {
- sceneContainerRepository.currentScene.map { sceneKey -> sceneKey == SceneKey.Communal }
- } else {
- desiredScene.map { sceneKey -> sceneKey == CommunalSceneKey.Communal }
- }
}
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 636ea42ac88e..d0044a4c029e 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
@@ -42,6 +42,9 @@ import com.android.systemui.log.dagger.CommunalLog
import com.android.systemui.log.dagger.CommunalTableLog
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.logDiffsForTable
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.smartspace.data.repository.SmartspaceRepository
import com.android.systemui.util.kotlin.BooleanFlowOperators.and
import com.android.systemui.util.kotlin.BooleanFlowOperators.not
@@ -57,6 +60,7 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
@@ -75,9 +79,11 @@ constructor(
mediaRepository: CommunalMediaRepository,
smartspaceRepository: SmartspaceRepository,
keyguardInteractor: KeyguardInteractor,
- private val communalSettingsInteractor: CommunalSettingsInteractor,
+ communalSettingsInteractor: CommunalSettingsInteractor,
private val appWidgetHost: CommunalAppWidgetHost,
private val editWidgetsActivityStarter: EditWidgetsActivityStarter,
+ sceneInteractor: SceneInteractor,
+ sceneContainerFlags: SceneContainerFlags,
@CommunalLog logBuffer: LogBuffer,
@CommunalTableLog tableLogBuffer: TableLogBuffer,
) {
@@ -89,8 +95,7 @@ constructor(
val editModeOpen: StateFlow<Boolean> = _editModeOpen.asStateFlow()
/** Whether communal features are enabled. */
- val isCommunalEnabled: Boolean
- get() = communalSettingsInteractor.isCommunalEnabled.value
+ val isCommunalEnabled: StateFlow<Boolean> = communalSettingsInteractor.isCommunalEnabled
/** Whether communal features are enabled and available. */
val isCommunalAvailable: Flow<Boolean> =
@@ -173,8 +178,14 @@ constructor(
*/
// TODO(b/323215860): rename to something more appropriate after cleaning up usages
val isCommunalShowing: Flow<Boolean> =
- communalRepository.desiredScene
- .map { it == CommunalSceneKey.Communal }
+ flow { emit(sceneContainerFlags.isEnabled()) }
+ .flatMapLatest { sceneContainerEnabled ->
+ if (sceneContainerEnabled) {
+ sceneInteractor.currentScene.map { it == SceneKey.Communal }
+ } else {
+ desiredScene.map { it == CommunalSceneKey.Communal }
+ }
+ }
.distinctUntilChanged()
.onEach { showing ->
logger.i({ "Communal is ${if (bool1) "showing" else "gone"}" }) { bool1 = showing }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt
index 25dfc02a5d98..2b7db148582d 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractor.kt
@@ -17,7 +17,6 @@
package com.android.systemui.communal.domain.interactor
import android.provider.Settings
-import com.android.systemui.communal.data.repository.CommunalRepository
import com.android.systemui.communal.data.repository.CommunalTutorialRepository
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -51,7 +50,6 @@ constructor(
@Application private val scope: CoroutineScope,
private val communalTutorialRepository: CommunalTutorialRepository,
keyguardInteractor: KeyguardInteractor,
- private val communalRepository: CommunalRepository,
private val communalSettingsInteractor: CommunalSettingsInteractor,
communalInteractor: CommunalInteractor,
@CommunalTableLog tableLogBuffer: TableLogBuffer,
@@ -92,7 +90,7 @@ constructor(
if (tutorialSettingState == Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED) {
return@flatMapLatest flowOf(null)
}
- communalRepository.isCommunalHubShowing.map { isCommunalShowing ->
+ communalInteractor.isCommunalShowing.map { isCommunalShowing ->
nextStateAfterTransition(
tutorialSettingState,
isCommunalShowing,
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/CommunalTutorialIndicatorSection.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/CommunalTutorialIndicatorSection.kt
index 2d9dd50b6d11..1a323a75f8e7 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/CommunalTutorialIndicatorSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/CommunalTutorialIndicatorSection.kt
@@ -47,7 +47,7 @@ constructor(
private var communalTutorialIndicatorHandle: DisposableHandle? = null
override fun addViews(constraintLayout: ConstraintLayout) {
- if (!communalInteractor.isCommunalEnabled) {
+ if (!communalInteractor.isCommunalEnabled.value) {
return
}
val padding =
@@ -79,7 +79,7 @@ constructor(
}
override fun bindData(constraintLayout: ConstraintLayout) {
- if (!communalInteractor.isCommunalEnabled) {
+ if (!communalInteractor.isCommunalEnabled.value) {
return
}
communalTutorialIndicatorHandle =
@@ -90,7 +90,7 @@ constructor(
}
override fun applyConstraints(constraintSet: ConstraintSet) {
- if (!communalInteractor.isCommunalEnabled) {
+ if (!communalInteractor.isCommunalEnabled.value) {
return
}
val tutorialIndicatorId = R.id.communal_tutorial_indicator
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index dee7a0c4f717..8a7b5ebedb7a 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -23,7 +23,7 @@ import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
import com.android.systemui.communal.widgets.WidgetConfigurator
-import com.android.systemui.media.controls.ui.MediaHost
+import com.android.systemui.media.controls.ui.view.MediaHost
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
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 efbb11e824c9..bfe751af7154 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
@@ -25,7 +25,7 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
import com.android.systemui.log.dagger.CommunalLog
-import com.android.systemui.media.controls.ui.MediaHost
+import com.android.systemui.media.controls.ui.view.MediaHost
import com.android.systemui.media.dagger.MediaModule
import javax.inject.Inject
import javax.inject.Named
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index febfd4c95db1..fc9a7df4744d 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -24,9 +24,9 @@ import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
import com.android.systemui.log.dagger.CommunalLog
-import com.android.systemui.media.controls.ui.MediaHierarchyManager
-import com.android.systemui.media.controls.ui.MediaHost
-import com.android.systemui.media.controls.ui.MediaHostState
+import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
+import com.android.systemui.media.controls.ui.view.MediaHost
+import com.android.systemui.media.controls.ui.view.MediaHostState
import com.android.systemui.media.dagger.MediaModule
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.util.kotlin.BooleanFlowOperators.not
diff --git a/packages/SystemUI/src/com/android/systemui/complication/DreamMediaEntryComplication.java b/packages/SystemUI/src/com/android/systemui/complication/DreamMediaEntryComplication.java
index 6a7278529a54..bdefc4d2cbba 100644
--- a/packages/SystemUI/src/com/android/systemui/complication/DreamMediaEntryComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/complication/DreamMediaEntryComplication.java
@@ -28,7 +28,7 @@ import com.android.systemui.ActivityIntentHelper;
import com.android.systemui.complication.dagger.DreamMediaEntryComplicationComponent;
import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.media.controls.ui.MediaCarouselController;
+import com.android.systemui.media.controls.ui.controller.MediaCarouselController;
import com.android.systemui.media.dream.MediaDreamComplication;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/NotifInflation.kt b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/NotifInflation.kt
new file mode 100644
index 000000000000..231fb2dd16c8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/NotifInflation.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.dagger.qualifiers
+
+import javax.inject.Qualifier
+
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class NotifInflation
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/BiometricMessageInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/BiometricMessageInteractor.kt
index 55d2bfcc8911..6bfe8d91b5fc 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/BiometricMessageInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/BiometricMessageInteractor.kt
@@ -29,6 +29,7 @@ import com.android.systemui.deviceentry.shared.model.FingerprintMessage
import com.android.systemui.deviceentry.shared.model.HelpFaceAuthenticationStatus
import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
import com.android.systemui.res.R
+import com.android.systemui.util.kotlin.Utils.Companion.toTriple
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -41,9 +42,8 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
/**
- * BiometricMessage business logic. Filters biometric error/acquired/fail/success events for
- * authentication events that should never surface a message to the user at the current device
- * state.
+ * BiometricMessage business logic. Filters biometric error/fail/success events for authentication
+ * events that should never surface a message to the user at the current device state.
*/
@ExperimentalCoroutinesApi
@SysUISingleton
@@ -54,7 +54,8 @@ constructor(
fingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor,
fingerprintPropertyInteractor: FingerprintPropertyInteractor,
faceAuthInteractor: DeviceEntryFaceAuthInteractor,
- biometricSettingsInteractor: DeviceEntryBiometricSettingsInteractor,
+ private val biometricSettingsInteractor: DeviceEntryBiometricSettingsInteractor,
+ faceHelpMessageDeferralInteractor: FaceHelpMessageDeferralInteractor,
) {
private val faceHelp: Flow<HelpFaceAuthenticationStatus> =
faceAuthInteractor.authenticationStatus.filterIsInstance<HelpFaceAuthenticationStatus>()
@@ -130,19 +131,24 @@ constructor(
)
private val faceHelpMessage: Flow<FaceMessage> =
- biometricSettingsInteractor.fingerprintAndFaceEnrolledAndEnabled
- .flatMapLatest { fingerprintAndFaceEnrolledAndEnabled ->
+ faceHelp
+ .filterNot {
+ // Message deferred to potentially show at face timeout error instead
+ faceHelpMessageDeferralInteractor.shouldDefer(it.msgId)
+ }
+ .sample(biometricSettingsInteractor.fingerprintAndFaceEnrolledAndEnabled, ::Pair)
+ .filter { (faceAuthHelpStatus, fingerprintAndFaceEnrolledAndEnabled) ->
if (fingerprintAndFaceEnrolledAndEnabled) {
- faceHelp.filter { faceAuthHelpStatus ->
- coExFaceAcquisitionMsgIdsToShow.contains(faceAuthHelpStatus.msgId)
- }
+ // Show only some face help messages if fingerprint is also enrolled
+ coExFaceAcquisitionMsgIdsToShow.contains(faceAuthHelpStatus.msgId)
} else {
- faceHelp
+ // Show all face help messages if only face is enrolled
+ true
}
}
- .sample(biometricSettingsInteractor.faceAuthCurrentlyAllowed, ::Pair)
- .filter { (_, faceAuthCurrentlyAllowed) -> faceAuthCurrentlyAllowed }
- .map { (status, _) -> FaceMessage(status.msg) }
+ .sample(biometricSettingsInteractor.faceAuthCurrentlyAllowed, ::toTriple)
+ .filter { (_, _, faceAuthCurrentlyAllowed) -> faceAuthCurrentlyAllowed }
+ .map { (status, _, _) -> FaceMessage(status.msg) }
private val faceFailureMessage: Flow<FaceMessage> =
faceFailure
@@ -159,12 +165,18 @@ constructor(
}
.map { (status, _) ->
when {
- status.isTimeoutError() -> FaceTimeoutMessage(status.msg)
+ status.isTimeoutError() -> {
+ val deferredMessage = faceHelpMessageDeferralInteractor.getDeferredMessage()
+ if (deferredMessage != null) {
+ FaceMessage(deferredMessage.toString())
+ } else {
+ FaceTimeoutMessage(status.msg)
+ }
+ }
else -> FaceMessage(status.msg)
}
}
- // TODO(b/317215391): support showing face acquired messages on timeout + face lockout errors
val faceMessage: Flow<FaceMessage> =
merge(
faceHelpMessage,
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricSettingsInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricSettingsInteractor.kt
index 4515fcb545b3..96171aa6566e 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricSettingsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricSettingsInteractor.kt
@@ -33,6 +33,7 @@ constructor(
) {
val fingerprintAuthCurrentlyAllowed: Flow<Boolean> =
repository.isFingerprintAuthCurrentlyAllowed
+ val faceAuthEnrolledAndEnabled: Flow<Boolean> = repository.isFaceAuthEnrolledAndEnabled
val faceAuthCurrentlyAllowed: Flow<Boolean> = repository.isFaceAuthCurrentlyAllowed
/** Whether both fingerprint and face are enrolled and enabled for device entry. */
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/FaceHelpMessageDeferralInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/FaceHelpMessageDeferralInteractor.kt
new file mode 100644
index 000000000000..fd6fbc9610f4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/FaceHelpMessageDeferralInteractor.kt
@@ -0,0 +1,112 @@
+/*
+ * 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 android.hardware.face.FaceManager
+import com.android.systemui.biometrics.FaceHelpMessageDeferralFactory
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
+import com.android.systemui.deviceentry.shared.model.AcquiredFaceAuthenticationStatus
+import com.android.systemui.deviceentry.shared.model.HelpFaceAuthenticationStatus
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.filterIsInstance
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.launch
+
+/**
+ * FaceHelpMessageDeferral business logic. Processes face acquired and face help authentication
+ * events to determine whether a face auth event should be displayed to the user immediately or when
+ * a [FaceManager.FACE_ERROR_TIMEOUT] is received.
+ */
+@ExperimentalCoroutinesApi
+@SysUISingleton
+class FaceHelpMessageDeferralInteractor
+@Inject
+constructor(
+ @Application private val scope: CoroutineScope,
+ faceAuthInteractor: DeviceEntryFaceAuthInteractor,
+ private val biometricSettingsInteractor: DeviceEntryBiometricSettingsInteractor,
+ faceHelpMessageDeferralFactory: FaceHelpMessageDeferralFactory,
+) {
+ private val faceHelpMessageDeferral = faceHelpMessageDeferralFactory.create()
+ private val faceAcquired: Flow<AcquiredFaceAuthenticationStatus> =
+ faceAuthInteractor.authenticationStatus.filterIsInstance<AcquiredFaceAuthenticationStatus>()
+ private val faceHelp: Flow<HelpFaceAuthenticationStatus> =
+ faceAuthInteractor.authenticationStatus.filterIsInstance<HelpFaceAuthenticationStatus>()
+
+ init {
+ if (DeviceEntryUdfpsRefactor.isEnabled) {
+ startUpdatingFaceHelpMessageDeferral()
+ }
+ }
+
+ /**
+ * If the given [HelpFaceAuthenticationStatus] msgId should be deferred to
+ * [FaceManager.FACE_ERROR_TIMEOUT].
+ */
+ fun shouldDefer(msgId: Int): Boolean {
+ return faceHelpMessageDeferral.shouldDefer(msgId)
+ }
+
+ /**
+ * Message that was deferred to show at [FaceManager.FACE_ERROR_TIMEOUT], if any. Returns null
+ * if there are currently no valid deferred messages.
+ */
+ fun getDeferredMessage(): CharSequence? {
+ return faceHelpMessageDeferral.getDeferredMessage()
+ }
+
+ private fun startUpdatingFaceHelpMessageDeferral() {
+ scope.launch {
+ biometricSettingsInteractor.faceAuthEnrolledAndEnabled
+ .flatMapLatest { faceEnrolledAndEnabled ->
+ if (faceEnrolledAndEnabled) {
+ faceAcquired
+ } else {
+ emptyFlow()
+ }
+ }
+ .collect {
+ if (it.acquiredInfo == FaceManager.FACE_ACQUIRED_START) {
+ faceHelpMessageDeferral.reset()
+ }
+ faceHelpMessageDeferral.processFrame(it.acquiredInfo)
+ }
+ }
+
+ scope.launch {
+ biometricSettingsInteractor.faceAuthEnrolledAndEnabled
+ .flatMapLatest { faceEnrolledAndEnabled ->
+ if (faceEnrolledAndEnabled) {
+ faceHelp
+ } else {
+ emptyFlow()
+ }
+ }
+ .collect { helpAuthenticationStatus ->
+ helpAuthenticationStatus.msg?.let { msg ->
+ faceHelpMessageDeferral.updateMessage(helpAuthenticationStatus.msgId, msg)
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/shared/model/BiometricMessageModels.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/shared/model/BiometricMessageModels.kt
index 118215c6ba15..59c3f7f8aded 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/shared/model/BiometricMessageModels.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/shared/model/BiometricMessageModels.kt
@@ -27,6 +27,7 @@ sealed class BiometricMessage(
/** Face biometric message */
open class FaceMessage(faceMessage: String?) : BiometricMessage(faceMessage)
+/** Face timeout message. */
data class FaceTimeoutMessage(
private val faceTimeoutMessage: String?,
) : FaceMessage(faceTimeoutMessage)
diff --git a/packages/SystemUI/src/com/android/systemui/display/DisplayExtensions.kt b/packages/SystemUI/src/com/android/systemui/display/DisplayExtensions.kt
new file mode 100644
index 000000000000..0482bd8730f9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/display/DisplayExtensions.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.display
+
+import android.graphics.Rect
+import android.view.Display
+import android.view.DisplayInfo
+
+val Display.naturalBounds: Rect
+ get() {
+ val outDisplayInfo = DisplayInfo()
+ getDisplayInfo(outDisplayInfo)
+ return Rect(
+ /* left = */ 0,
+ /* top = */ 0,
+ /* right = */ outDisplayInfo.naturalWidth,
+ /* bottom = */ outDisplayInfo.naturalHeight
+ )
+ }
+
+val Display.naturalWidth: Int
+ get() {
+ val outDisplayInfo = DisplayInfo()
+ getDisplayInfo(outDisplayInfo)
+ return outDisplayInfo.naturalWidth
+ }
+
+val Display.naturalHeight: Int
+ get() {
+ val outDisplayInfo = DisplayInfo()
+ getDisplayInfo(outDisplayInfo)
+ return outDisplayInfo.naturalHeight
+ }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
index 557ad132bc9f..9000da33312c 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
@@ -33,20 +33,16 @@ import com.android.systemui.complication.ComplicationLayoutParams.POSITION_BOTTO
import com.android.systemui.complication.ComplicationLayoutParams.POSITION_TOP
import com.android.systemui.complication.ComplicationLayoutParams.Position
import com.android.systemui.dreams.dagger.DreamOverlayModule
-import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel
+import com.android.systemui.dreams.ui.viewmodel.DreamOverlayViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
import com.android.systemui.log.dagger.DreamLog
-import com.android.systemui.res.R
import com.android.systemui.statusbar.BlurUtils
import com.android.systemui.statusbar.CrossFadeHelper
import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
import javax.inject.Inject
import javax.inject.Named
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.launch
/** Controller for dream overlay animations. */
@@ -58,7 +54,7 @@ constructor(
private val mStatusBarViewController: DreamOverlayStatusBarViewController,
private val mOverlayStateController: DreamOverlayStateController,
@Named(DreamOverlayModule.DREAM_BLUR_RADIUS) private val mDreamBlurRadius: Int,
- private val transitionViewModel: DreamingToLockscreenTransitionViewModel,
+ private val dreamOverlayViewModel: DreamOverlayViewModel,
private val configController: ConfigurationController,
@Named(DreamOverlayModule.DREAM_IN_BLUR_ANIMATION_DURATION)
private val mDreamInBlurAnimDurationMs: Long,
@@ -91,59 +87,45 @@ constructor(
this.view = view
view.repeatWhenAttached {
- val configurationBasedDimensions = MutableStateFlow(loadFromResources(view))
- val configCallback =
- object : ConfigurationListener {
- override fun onDensityOrFontScaleChanged() {
- configurationBasedDimensions.value = loadFromResources(view)
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ launch {
+ dreamOverlayViewModel.dreamOverlayTranslationY.collect { px ->
+ ComplicationLayoutParams.iteratePositions(
+ { position: Int -> setElementsTranslationYAtPosition(px, position) },
+ POSITION_TOP or POSITION_BOTTOM
+ )
}
}
- configController.addCallback(configCallback)
-
- try {
- repeatOnLifecycle(Lifecycle.State.CREATED) {
- /* Translation animations, when moving from DREAMING->LOCKSCREEN state */
- launch {
- configurationBasedDimensions
- .flatMapLatest {
- transitionViewModel.dreamOverlayTranslationY(it.translationYPx)
- }
- .collect { px ->
- ComplicationLayoutParams.iteratePositions(
- { position: Int ->
- setElementsTranslationYAtPosition(px, position)
- },
- POSITION_TOP or POSITION_BOTTOM
- )
- }
+ launch {
+ dreamOverlayViewModel.dreamOverlayTranslationX.collect { px ->
+ ComplicationLayoutParams.iteratePositions(
+ { position: Int -> setElementsTranslationXAtPosition(px, position) },
+ POSITION_TOP or POSITION_BOTTOM
+ )
}
+ }
- /* Alpha animations, when moving from DREAMING->LOCKSCREEN state */
- launch {
- transitionViewModel.dreamOverlayAlpha.collect { alpha ->
- ComplicationLayoutParams.iteratePositions(
- { position: Int ->
- setElementsAlphaAtPosition(
- alpha = alpha,
- position = position,
- fadingOut = true,
- )
- },
- POSITION_TOP or POSITION_BOTTOM
- )
- }
+ launch {
+ dreamOverlayViewModel.dreamOverlayAlpha.collect { alpha ->
+ ComplicationLayoutParams.iteratePositions(
+ { position: Int ->
+ setElementsAlphaAtPosition(
+ alpha = alpha,
+ position = position,
+ fadingOut = true,
+ )
+ },
+ POSITION_TOP or POSITION_BOTTOM
+ )
}
+ }
- launch {
- transitionViewModel.transitionEnded.collect { _ ->
- mOverlayStateController.setExitAnimationsRunning(false)
- }
+ launch {
+ dreamOverlayViewModel.transitionEnded.collect { _ ->
+ mOverlayStateController.setExitAnimationsRunning(false)
}
}
- } finally {
- // Ensure the callback is removed when cancellation happens
- configController.removeCallback(configCallback)
}
}
}
@@ -373,14 +355,10 @@ constructor(
}
}
- private fun loadFromResources(view: View): ConfigurationBasedDimensions {
- return ConfigurationBasedDimensions(
- translationYPx =
- view.resources.getDimensionPixelSize(R.dimen.dream_overlay_exit_y_offset),
- )
+ /** Sets x translation of complications at the specified position. */
+ private fun setElementsTranslationXAtPosition(translationX: Float, position: Int) {
+ mComplicationHostViewController.getViewsAtPosition(position).forEach { v ->
+ v.translationX = translationX
+ }
}
-
- private data class ConfigurationBasedDimensions(
- val translationYPx: Int,
- )
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamOverlayViewModel.kt b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamOverlayViewModel.kt
new file mode 100644
index 000000000000..dd67a4c8706c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamOverlayViewModel.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.dreams.ui.viewmodel
+
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.ui.viewmodel.DreamingToGlanceableHubTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel
+import com.android.systemui.res.R
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.merge
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class DreamOverlayViewModel
+@Inject
+constructor(
+ configurationInteractor: ConfigurationInteractor,
+ private val toGlanceableHubTransitionViewModel: DreamingToGlanceableHubTransitionViewModel,
+ private val toLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
+) {
+
+ val dreamOverlayTranslationX: Flow<Float> =
+ configurationInteractor
+ .dimensionPixelSize(R.dimen.dream_overlay_exit_x_offset)
+ .flatMapLatest { px: Int ->
+ toGlanceableHubTransitionViewModel.dreamOverlayTranslationX(px)
+ }
+
+ val dreamOverlayTranslationY: Flow<Float> =
+ configurationInteractor
+ .dimensionPixelSize(R.dimen.dream_overlay_exit_y_offset)
+ .flatMapLatest { px: Int ->
+ toLockscreenTransitionViewModel.dreamOverlayTranslationY(px)
+ }
+
+ val dreamOverlayAlpha: Flow<Float> =
+ merge(
+ toLockscreenTransitionViewModel.dreamOverlayAlpha,
+ toGlanceableHubTransitionViewModel.dreamOverlayAlpha,
+ )
+
+ val transitionEnded = toLockscreenTransitionViewModel.transitionEnded
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt
index 7150d69e130d..9876fe4482c0 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt
@@ -222,13 +222,18 @@ constructor(
val buffers = dumpManager.getLogBuffers()
val tableBuffers = dumpManager.getTableLogBuffers()
- targets.forEach { target ->
- findTargetInCollection(target, dumpables, buffers, tableBuffers)?.dump(pw, args)
- }
+ val matches =
+ if (args.matchAll) {
+ findAllMatchesInCollection(targets, dumpables, buffers, tableBuffers)
+ } else {
+ findBestMatchesInCollection(targets, dumpables, buffers, tableBuffers)
+ }
+ matches.forEach { it.dump(pw, args) }
} else {
if (args.listOnly) {
val dumpables = dumpManager.getDumpables()
val buffers = dumpManager.getLogBuffers()
+ val tableBuffers = dumpManager.getTableLogBuffers()
pw.println("Dumpables:")
listTargetNames(dumpables, pw)
@@ -236,18 +241,23 @@ constructor(
pw.println("Buffers:")
listTargetNames(buffers, pw)
+ pw.println()
+
+ pw.println("TableBuffers:")
+ listTargetNames(tableBuffers, pw)
} else {
pw.println("Nothing to dump :(")
}
}
}
+ /** Finds the best match for a particular target */
private fun findTargetInCollection(
target: String,
dumpables: Collection<DumpableEntry>,
logBuffers: Collection<LogBufferEntry>,
tableBuffers: Collection<TableLogBufferEntry>,
- ) =
+ ): DumpsysEntry? =
sequence {
findBestTargetMatch(dumpables, target)?.let { yield(it) }
findBestTargetMatch(logBuffers, target)?.let { yield(it) }
@@ -256,6 +266,31 @@ constructor(
.sortedBy { it.name }
.minByOrNull { it.name.length }
+ /** Finds the best match for each target, if any, in the order of the targets */
+ private fun findBestMatchesInCollection(
+ targets: List<String>,
+ dumpables: Collection<DumpableEntry>,
+ logBuffers: Collection<LogBufferEntry>,
+ tableBuffers: Collection<TableLogBufferEntry>,
+ ): List<DumpsysEntry> =
+ targets.mapNotNull { target ->
+ findTargetInCollection(target, dumpables, logBuffers, tableBuffers)
+ }
+
+ /** Finds all matches for any target, returning in the --list order. */
+ private fun findAllMatchesInCollection(
+ targets: List<String>,
+ dumpables: Collection<DumpableEntry>,
+ logBuffers: Collection<LogBufferEntry>,
+ tableBuffers: Collection<TableLogBufferEntry>,
+ ): List<DumpsysEntry> =
+ sequence {
+ yieldAll(dumpables.filter { it.matchesAny(targets) })
+ yieldAll(logBuffers.filter { it.matchesAny(targets) })
+ yieldAll(tableBuffers.filter { it.matchesAny(targets) })
+ }
+ .sortedBy { it.name }.toList()
+
private fun dumpConfig(pw: PrintWriter) {
config.dump(pw, arrayOf())
}
@@ -272,6 +307,11 @@ constructor(
pw.println("etc.")
pw.println()
+ pw.println("Print all matches, instead of the best match:")
+ pw.println("$ <invocation> --all <targets>")
+ pw.println("$ <invocation> --all Log")
+ pw.println()
+
pw.println("Special commands:")
pw.println("$ <invocation> dumpables")
pw.println("$ <invocation> buffers")
@@ -325,9 +365,10 @@ constructor(
"--help" -> {
pArgs.command = "help"
}
- // This flag is passed as part of the proto dump in Bug reports, we can ignore
- // it because this is our default behavior.
- "-a" -> {}
+ "-a",
+ "--all" -> {
+ pArgs.matchAll = true
+ }
else -> {
throw ArgParseException("Unknown flag: $arg")
}
@@ -386,15 +427,19 @@ constructor(
const val DUMPSYS_DUMPABLE_DIVIDER =
"----------------------------------------------------------------------------"
+ private fun DumpsysEntry.matches(target: String) = name.endsWith(target)
+ private fun DumpsysEntry.matchesAny(targets: Collection<String>) =
+ targets.any { matches(it) }
+
private fun findBestTargetMatch(c: Collection<DumpsysEntry>, target: String) =
- c.asSequence().filter { it.name.endsWith(target) }.minByOrNull { it.name.length }
+ c.asSequence().filter { it.matches(target) }.minByOrNull { it.name.length }
private fun findBestProtoTargetMatch(
c: Collection<DumpableEntry>,
target: String
): ProtoDumpable? =
c.asSequence()
- .filter { it.name.endsWith(target) }
+ .filter { it.matches(target) }
.filter { it.dumpable is ProtoDumpable }
.minByOrNull { it.name.length }
?.dumpable as? ProtoDumpable
@@ -440,40 +485,34 @@ constructor(
}
/**
- * Utility to write a [DumpableEntry] to the given [PrintWriter] in a
- * dumpsys-appropriate format.
+ * Utility to write a [DumpableEntry] to the given [PrintWriter] in a dumpsys-appropriate
+ * format.
*/
private fun dumpDumpable(
- entry: DumpableEntry,
- pw: PrintWriter,
- args: Array<String> = arrayOf(),
- ) = pw.wrapSection(entry) {
- entry.dumpable.dump(pw, args)
- }
+ entry: DumpableEntry,
+ pw: PrintWriter,
+ args: Array<String> = arrayOf(),
+ ) = pw.wrapSection(entry) { entry.dumpable.dump(pw, args) }
/**
- * Utility to write a [LogBufferEntry] to the given [PrintWriter] in a
- * dumpsys-appropriate format.
+ * Utility to write a [LogBufferEntry] to the given [PrintWriter] in a dumpsys-appropriate
+ * format.
*/
private fun dumpBuffer(
- entry: LogBufferEntry,
- pw: PrintWriter,
- tailLength: Int = 0,
- ) = pw.wrapSection(entry) {
- entry.buffer.dump(pw, tailLength)
- }
+ entry: LogBufferEntry,
+ pw: PrintWriter,
+ tailLength: Int = 0,
+ ) = pw.wrapSection(entry) { entry.buffer.dump(pw, tailLength) }
/**
* Utility to write a [TableLogBufferEntry] to the given [PrintWriter] in a
* dumpsys-appropriate format.
*/
private fun dumpTableBuffer(
- entry: TableLogBufferEntry,
- pw: PrintWriter,
- args: Array<String> = arrayOf(),
- ) = pw.wrapSection(entry) {
- entry.table.dump(pw, args)
- }
+ entry: TableLogBufferEntry,
+ pw: PrintWriter,
+ args: Array<String> = arrayOf(),
+ ) = pw.wrapSection(entry) { entry.table.dump(pw, args) }
/**
* Zero-arg utility to write a [DumpsysEntry] to the given [PrintWriter] in a
@@ -513,6 +552,7 @@ private class ParsedArgs(val rawArgs: Array<String>, val nonFlagArgs: List<Strin
var tailLength: Int = 0
var command: String? = null
var listOnly = false
+ var matchAll = false
var proto = false
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependenciesBase.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependenciesBase.kt
index efbd59f6ce17..2fe1dd450088 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependenciesBase.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependenciesBase.kt
@@ -19,6 +19,7 @@ package com.android.systemui.flags
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
+import android.app.NotificationManager.IMPORTANCE_DEFAULT
import android.content.Context
import android.util.Log
import com.android.systemui.CoreStartable
@@ -150,7 +151,7 @@ constructor(
val title = "Invalid flag dependencies: ${unmet.size}"
val details = unmet.joinToString("\n") { it.shortUnmetString() }
Log.e("FlagDependencies", "$title:\n$details")
- val channel = NotificationChannel("FLAGS", "Flags", NotificationManager.IMPORTANCE_DEFAULT)
+ val channel = NotificationChannel(CHANNEL_ID, CHANNEL_NAME, IMPORTANCE_DEFAULT)
val notification =
Notification.Builder(context, channel.id)
.setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
@@ -160,7 +161,18 @@ constructor(
.setVisibility(Notification.VISIBILITY_PUBLIC)
.build()
notifManager.createNotificationChannel(channel)
- notifManager.notify("flags", 0, notification)
+ notifManager.notify(NOTIF_TAG, NOTIF_ID, notification)
+ }
+
+ override fun onCollected(all: List<FlagDependenciesBase.Dependency>) {
+ notifManager.cancel(NOTIF_TAG, NOTIF_ID)
+ }
+
+ companion object {
+ private const val CHANNEL_ID = "FLAGS"
+ private const val CHANNEL_NAME = "Flags"
+ private const val NOTIF_TAG = "FlagDependenciesNotifier"
+ private const val NOTIF_ID = 0
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 6eff79284847..33a69bf0d774 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -102,11 +102,6 @@ object Flags {
default = true
)
- // TODO(b/301955929)
- @JvmField
- val NOTIF_LS_BACKGROUND_THREAD =
- releasedFlag("notification_lockscreen_mgr_bg_thread")
-
// 200 - keyguard/lockscreen
// ** Flag retired **
// public static final BooleanFlag KEYGUARD_LAYOUT =
@@ -329,9 +324,6 @@ object Flags {
// TODO(b/254512673): Tracking Bug
@JvmField val DREAM_MEDIA_TAP_TO_OPEN = unreleasedFlag("dream_media_tap_to_open")
- // TODO(b/263272731): Tracking Bug
- val MEDIA_TTT_RECEIVER_SUCCESS_RIPPLE = releasedFlag("media_ttt_receiver_success_ripple")
-
// TODO(b/266157412): Tracking Bug
val MEDIA_RETAIN_SESSIONS = unreleasedFlag("media_retain_sessions")
@@ -482,11 +474,6 @@ object Flags {
val WARN_ON_BLOCKING_BINDER_TRANSACTIONS =
unreleasedFlag("warn_on_blocking_binder_transactions")
- // TODO(b/283071711): Tracking bug
- @JvmField
- val TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK =
- unreleasedFlag("trim_resources_with_background_trim_on_lock")
-
// TODO:(b/283203305): Tracking bug
@JvmField val TRIM_FONT_CACHES_AT_UNLOCK = unreleasedFlag("trim_font_caches_on_unlock")
@@ -555,10 +542,6 @@ object Flags {
@JvmField
val ENABLE_NEW_PRIVACY_DIALOG = releasedFlag("enable_new_privacy_dialog")
- // TODO(b/302087895): Tracking Bug
- @JvmField val CALL_LAYOUT_ASYNC_SET_DATA =
- unreleasedFlag("call_layout_async_set_data", teamfood = true)
-
// TODO(b/302144438): Tracking Bug
@JvmField val DECOUPLE_REMOTE_INPUT_DELEGATE_AND_CALLBACK_UPDATE =
unreleasedFlag("decouple_remote_input_delegate_and_callback_update")
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index afcb03da39da..0bc29a8d0f16 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -122,6 +122,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.globalactions.domain.interactor.GlobalActionsInteractor;
import com.android.systemui.plugins.GlobalActions.GlobalActionsManager;
import com.android.systemui.plugins.GlobalActionsPanelPlugin;
import com.android.systemui.scrim.ScrimDrawable;
@@ -257,6 +258,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
private final ShadeController mShadeController;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final DialogTransitionAnimator mDialogTransitionAnimator;
+ private final GlobalActionsInteractor mInteractor;
@VisibleForTesting
public enum GlobalActionsEvent implements UiEventLogger.UiEventEnum {
@@ -368,7 +370,8 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
ShadeController shadeController,
KeyguardUpdateMonitor keyguardUpdateMonitor,
DialogTransitionAnimator dialogTransitionAnimator,
- SelectedUserInteractor selectedUserInteractor) {
+ SelectedUserInteractor selectedUserInteractor,
+ GlobalActionsInteractor interactor) {
mContext = context;
mWindowManagerFuncs = windowManagerFuncs;
mAudioManager = audioManager;
@@ -404,6 +407,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mDialogTransitionAnimator = dialogTransitionAnimator;
mSelectedUserInteractor = selectedUserInteractor;
+ mInteractor = interactor;
// receive broadcasts
IntentFilter filter = new IntentFilter();
@@ -1333,6 +1337,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
mUiEventLogger.log(GlobalActionsEvent.GA_POWER_MENU_CLOSE);
mWindowManagerFuncs.onGlobalActionsHidden();
mLifecycle.setCurrentState(Lifecycle.State.CREATED);
+ mInteractor.onDismissed();
}
/**
@@ -1342,6 +1347,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
public void onShow(DialogInterface dialog) {
mMetricsLogger.visible(MetricsEvent.POWER_MENU);
mUiEventLogger.log(GlobalActionsEvent.GA_POWER_MENU_OPEN);
+ mInteractor.onShown();
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/data/repository/GlobalActionsRepository.kt b/packages/SystemUI/src/com/android/systemui/globalactions/data/repository/GlobalActionsRepository.kt
new file mode 100644
index 000000000000..2550504cdc42
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/data/repository/GlobalActionsRepository.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.globalactions.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/** Encapsulates application state for global actions. */
+@SysUISingleton
+class GlobalActionsRepository @Inject constructor() {
+ private val _isVisible: MutableStateFlow<Boolean> = MutableStateFlow(false)
+ /** Is the global actions dialog visible. */
+ val isVisible = _isVisible.asStateFlow()
+
+ /** Sets whether the global actions dialog is visible. */
+ fun setVisible(isVisible: Boolean) {
+ _isVisible.value = isVisible
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/domain/interactor/GlobalActionsInteractor.kt b/packages/SystemUI/src/com/android/systemui/globalactions/domain/interactor/GlobalActionsInteractor.kt
new file mode 100644
index 000000000000..c484a48a4058
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/domain/interactor/GlobalActionsInteractor.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.globalactions.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.globalactions.data.repository.GlobalActionsRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.StateFlow
+
+@SysUISingleton
+class GlobalActionsInteractor
+@Inject
+constructor(
+ private val repository: GlobalActionsRepository,
+) {
+ /** Is the global actions dialog visible. */
+ val isVisible: StateFlow<Boolean> = repository.isVisible
+
+ /** Notifies that the global actions dialog is shown. */
+ fun onShown() {
+ repository.setVisible(true)
+ }
+
+ /** Notifies that the global actions dialog has been dismissed. */
+ fun onDismissed() {
+ repository.setVisible(false)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/shared/model/StickyKey.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/shared/model/StickyKey.kt
index 72a81cbac9d5..0f1cc99bfef7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/shared/model/StickyKey.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/shared/model/StickyKey.kt
@@ -20,8 +20,8 @@ package com.android.systemui.keyboard.stickykeys.shared.model
value class Locked(val locked: Boolean)
enum class ModifierKey(val displayedText: String) {
- ALT("ALT LEFT"),
- ALT_GR("ALT RIGHT"),
+ ALT("ALT"),
+ ALT_GR("ALT"),
CTRL("CTRL"),
META("ACTION"),
SHIFT("SHIFT"),
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt
index c52ca68ee37f..e101b0ab64aa 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt
@@ -32,13 +32,13 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInterac
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.utils.GlobalWindowManager
+import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
-import javax.inject.Inject
/**
* Releases cached resources on allocated by keyguard.
@@ -62,7 +62,7 @@ constructor(
override fun start() {
Log.d(LOG_TAG, "Resource trimmer registered.")
- if (featureFlags.isEnabled(Flags.TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK)) {
+ if (com.android.systemui.Flags.trimResourcesWithBackgroundTrimAtLock()) {
applicationScope.launch(bgDispatcher) {
// We need to wait for the AoD transition (and animation) to complete.
// This means we're waiting for isDreaming (== implies isDoze) and dozeAmount == 1f
@@ -107,19 +107,16 @@ constructor(
@WorkerThread
private fun onWakefulnessUpdated(
- isAsleep: Boolean,
- isDreaming: Boolean,
- isDozingFully: Boolean
+ isAsleep: Boolean,
+ isDreaming: Boolean,
+ isDozingFully: Boolean
) {
- if (!featureFlags.isEnabled(Flags.TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK)) {
+ if (!com.android.systemui.Flags.trimResourcesWithBackgroundTrimAtLock()) {
return
}
if (DEBUG) {
- Log.d(
- LOG_TAG,
- "isAsleep: $isAsleep Dreaming: $isDreaming DozeAmount: $isDozingFully"
- )
+ Log.d(LOG_TAG, "isAsleep: $isAsleep Dreaming: $isDreaming DozeAmount: $isDozingFully")
}
// There are three scenarios:
// * No dozing and no AoD at all - where we go directly to ASLEEP with isDreaming = false
@@ -129,8 +126,7 @@ constructor(
// * AoD - where we go to ASLEEP with iDreaming = true and dozeAmount slowly increases
// to 1f
val dozeDisabledAndScreenOff = isAsleep && !isDreaming
- val dozeEnabledAndDozeAnimationCompleted =
- isAsleep && isDreaming && isDozingFully
+ val dozeEnabledAndDozeAnimationCompleted = isAsleep && isDreaming && isDozingFully
if (dozeDisabledAndScreenOff || dozeEnabledAndDozeAnimationCompleted) {
Trace.beginSection("ResourceTrimmer#trimMemory")
Log.d(LOG_TAG, "SysUI asleep, trimming memory.")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt
index 42f14f1eb317..9b3f13d16911 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt
@@ -151,9 +151,13 @@ constructor(
awaitClose { revealAmountAnimator.removeUpdateListener(updateListener) }
}
+ private var willBeOrIsRevealed: Boolean? = null
+
override fun startRevealAmountAnimator(reveal: Boolean) {
+ if (reveal == willBeOrIsRevealed) return
+ willBeOrIsRevealed = reveal
if (reveal) revealAmountAnimator.start() else revealAmountAnimator.reverse()
- scrimLogger.d(TAG, "startRevealAmountAnimator, reveal", reveal)
+ scrimLogger.d(TAG, "startRevealAmountAnimator, reveal: ", reveal)
}
override val revealEffect =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index 13ffd6396cda..c6594ef317d6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
@@ -18,6 +18,7 @@ package com.android.systemui.keyguard.domain.interactor
import android.animation.ValueAnimator
import com.android.app.animation.Interpolators
+import com.android.systemui.Flags.communalHub
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
@@ -44,6 +45,7 @@ constructor(
@Background bgDispatcher: CoroutineDispatcher,
@Main mainDispatcher: CoroutineDispatcher,
private val keyguardInteractor: KeyguardInteractor,
+ private val glanceableHubTransitions: GlanceableHubTransitions,
) :
TransitionInteractor(
fromState = KeyguardState.DREAMING,
@@ -57,6 +59,17 @@ constructor(
listenForDreamingToGone()
listenForDreamingToAodOrDozing()
listenForTransitionToCamera(scope, keyguardInteractor)
+ listenForDreamingToGlanceableHub()
+ }
+
+ private fun listenForDreamingToGlanceableHub() {
+ if (!communalHub()) return
+ glanceableHubTransitions.listenForGlanceableHubTransition(
+ transitionName = "listenForDreamingToGlanceableHub",
+ transitionOwnerName = TAG,
+ fromState = KeyguardState.DREAMING,
+ toState = KeyguardState.GLANCEABLE_HUB,
+ )
}
fun startToLockscreenTransition() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
index 71d941ad8d22..fbf195eb0952 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
@@ -20,7 +20,6 @@ import android.animation.ValueAnimator
import com.android.app.animation.Interpolators
import com.android.app.tracing.coroutines.launch
import com.android.systemui.Flags
-import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
@@ -31,7 +30,7 @@ import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleMultiple
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
-import kotlin.time.Duration.Companion.milliseconds
+import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
@@ -71,7 +70,11 @@ constructor(
override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
return ValueAnimator().apply {
interpolator = Interpolators.LINEAR
- duration = DEFAULT_DURATION.inWholeMilliseconds
+ duration =
+ when (toState) {
+ KeyguardState.LOCKSCREEN -> TO_LOCKSCREEN_DURATION
+ else -> DEFAULT_DURATION
+ }.inWholeMilliseconds
}
}
@@ -80,10 +83,11 @@ constructor(
* transition.
*/
private fun listenForHubToLockscreen() {
- glanceableHubTransitions.listenForLockscreenAndHubTransition(
+ glanceableHubTransitions.listenForGlanceableHubTransition(
transitionName = "listenForHubToLockscreen",
transitionOwnerName = TAG,
- toScene = CommunalSceneKey.Blank,
+ fromState = KeyguardState.GLANCEABLE_HUB,
+ toState = KeyguardState.LOCKSCREEN,
)
}
@@ -175,7 +179,7 @@ constructor(
companion object {
const val TAG = "FromGlanceableHubTransitionInteractor"
- val DEFAULT_DURATION = 400.milliseconds
+ val DEFAULT_DURATION = 1.seconds
val TO_LOCKSCREEN_DURATION = DEFAULT_DURATION
}
}
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 57e9ac707965..40b2c638823d 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
@@ -19,7 +19,6 @@ package com.android.systemui.keyguard.domain.interactor
import android.animation.ValueAnimator
import android.util.MathUtils
import com.android.app.animation.Interpolators
-import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
@@ -39,6 +38,7 @@ import com.android.systemui.util.kotlin.sample
import java.util.UUID
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
+import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
@@ -361,10 +361,11 @@ constructor(
return
}
- glanceableHubTransitions.listenForLockscreenAndHubTransition(
+ glanceableHubTransitions.listenForGlanceableHubTransition(
transitionName = "listenForLockscreenToGlanceableHub",
transitionOwnerName = TAG,
- toScene = CommunalSceneKey.Communal
+ fromState = KeyguardState.LOCKSCREEN,
+ toState = KeyguardState.GLANCEABLE_HUB,
)
}
@@ -380,6 +381,7 @@ constructor(
KeyguardState.AOD -> TO_AOD_DURATION
KeyguardState.DOZING -> TO_DOZING_DURATION
KeyguardState.DREAMING_LOCKSCREEN_HOSTED -> TO_DREAMING_HOSTED_DURATION
+ KeyguardState.GLANCEABLE_HUB -> TO_GLANCEABLE_HUB_DURATION
else -> DEFAULT_DURATION
}.inWholeMilliseconds
}
@@ -395,6 +397,6 @@ constructor(
val TO_AOD_DURATION = 500.milliseconds
val TO_PRIMARY_BOUNCER_DURATION = DEFAULT_DURATION
val TO_GONE_DURATION = DEFAULT_DURATION
- val TO_GLANCEABLE_HUB_DURATION = DEFAULT_DURATION
+ val TO_GLANCEABLE_HUB_DURATION = 1.seconds
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt
index ca661536d988..809c0aee9882 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt
@@ -52,20 +52,18 @@ constructor(
* externally. The progress is used for both transitions caused by user touch input or by
* programmatic changes.
*/
- fun listenForLockscreenAndHubTransition(
+ fun listenForGlanceableHubTransition(
transitionName: String,
transitionOwnerName: String,
- toScene: CommunalSceneKey
+ fromState: KeyguardState,
+ toState: KeyguardState,
) {
- val fromState: KeyguardState
- val toState: KeyguardState
- if (toScene == CommunalSceneKey.Blank) {
- fromState = KeyguardState.GLANCEABLE_HUB
- toState = KeyguardState.LOCKSCREEN
- } else {
- fromState = KeyguardState.LOCKSCREEN
- toState = KeyguardState.GLANCEABLE_HUB
- }
+ val toScene =
+ if (toState == KeyguardState.GLANCEABLE_HUB) {
+ CommunalSceneKey.Communal
+ } else {
+ CommunalSceneKey.Blank
+ }
var transitionId: UUID? = null
scope.launch("$transitionOwnerName#$transitionName") {
communalInteractor
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
index c7f262a2ac80..4d731eccd9bb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
@@ -21,7 +21,6 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.data.repository.LightRevealScrimRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.power.shared.model.ScreenPowerState
import com.android.systemui.statusbar.LightRevealEffect
@@ -53,11 +52,9 @@ constructor(
scope.launch {
transitionInteractor.startedKeyguardTransitionStep.collect {
scrimLogger.d(TAG, "listenForStartedKeyguardTransitionStep", it)
- if (willTransitionChangeEndState(it)) {
- lightRevealScrimRepository.startRevealAmountAnimator(
- willBeRevealedInState(it.to)
- )
- }
+ lightRevealScrimRepository.startRevealAmountAnimator(
+ willBeRevealedInState(it.to),
+ )
}
}
}
@@ -92,33 +89,25 @@ constructor(
companion object {
- /**
- * Whether the transition requires a change in the reveal amount of the light reveal scrim.
- * If not, we don't care about the transition and don't need to listen to it.
- */
- fun willTransitionChangeEndState(transition: TransitionStep): Boolean {
- return willBeRevealedInState(transition.from) != willBeRevealedInState(transition.to)
- }
-
- /**
- * Whether the light reveal scrim will be fully revealed (revealAmount = 1.0f) in the given
- * state after the transition is complete. If false, scrim will be fully hidden.
- */
- fun willBeRevealedInState(state: KeyguardState): Boolean {
- return when (state) {
- KeyguardState.OFF -> false
- KeyguardState.DOZING -> false
- KeyguardState.AOD -> false
- KeyguardState.DREAMING -> true
- KeyguardState.DREAMING_LOCKSCREEN_HOSTED -> true
- KeyguardState.GLANCEABLE_HUB -> true
- KeyguardState.ALTERNATE_BOUNCER -> true
- KeyguardState.PRIMARY_BOUNCER -> true
- KeyguardState.LOCKSCREEN -> true
- KeyguardState.GONE -> true
- KeyguardState.OCCLUDED -> true
- }
+ /**
+ * Whether the light reveal scrim will be fully revealed (revealAmount = 1.0f) in the given
+ * state after the transition is complete. If false, scrim will be fully hidden.
+ */
+ private fun willBeRevealedInState(state: KeyguardState): Boolean {
+ return when (state) {
+ KeyguardState.OFF -> false
+ KeyguardState.DOZING -> false
+ KeyguardState.AOD -> false
+ KeyguardState.DREAMING -> true
+ KeyguardState.DREAMING_LOCKSCREEN_HOSTED -> true
+ KeyguardState.GLANCEABLE_HUB -> true
+ KeyguardState.ALTERNATE_BOUNCER -> true
+ KeyguardState.PRIMARY_BOUNCER -> true
+ KeyguardState.LOCKSCREEN -> true
+ KeyguardState.GONE -> true
+ KeyguardState.OCCLUDED -> true
}
+ }
val TAG = LightRevealScrimInteractor::class.simpleName!!
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
index 8b278cdb9a6c..b8ba09801ee8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
@@ -185,13 +185,16 @@ constructor(
return getOrCreateFlow(edge)
.map { step ->
StateToValue(
- step.transitionState,
- when (step.transitionState) {
- STARTED -> stepToValue(step)
- RUNNING -> stepToValue(step)
- CANCELED -> onCancel?.invoke()
- FINISHED -> onFinish?.invoke()
- }
+ from = step.from,
+ to = step.to,
+ transitionState = step.transitionState,
+ value =
+ when (step.transitionState) {
+ STARTED -> stepToValue(step)
+ RUNNING -> stepToValue(step)
+ CANCELED -> onCancel?.invoke()
+ FINISHED -> onFinish?.invoke()
+ }
)
.also { logger.logTransitionStep(name, step, it.value) }
}
@@ -208,6 +211,10 @@ constructor(
}
data class StateToValue(
+ val from: KeyguardState? = null,
+ val to: KeyguardState? = null,
val transitionState: TransitionState = TransitionState.FINISHED,
val value: Float? = 0f,
-)
+) {
+ fun isToOrFrom(state: KeyguardState) = from == state || to == state
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index c58a03c05a09..dc1f33d53853 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -25,6 +25,7 @@ import android.graphics.Rect
import android.view.HapticFeedbackConstants
import android.view.View
import android.view.View.OnLayoutChangeListener
+import android.view.View.VISIBLE
import android.view.ViewGroup
import android.view.ViewGroup.OnHierarchyChangeListener
import android.view.ViewPropertyAnimator
@@ -43,6 +44,7 @@ import com.android.systemui.common.shared.model.Text
import com.android.systemui.common.shared.model.TintedIcon
import com.android.systemui.common.ui.ConfigurationState
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
@@ -65,6 +67,7 @@ import com.android.systemui.util.ui.isAnimating
import com.android.systemui.util.ui.stopAnimating
import com.android.systemui.util.ui.value
import javax.inject.Provider
+import kotlin.math.min
import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.coroutineScope
@@ -101,6 +104,10 @@ object KeyguardRootViewBinder {
val burnInLayerId = R.id.burn_in_layer
val aodNotificationIconContainerId = R.id.aod_notification_icon_container
val largeClockId = R.id.lockscreen_clock_view_large
+ val indicationArea = R.id.keyguard_indication_area
+ val startButton = R.id.start_button
+ val endButton = R.id.end_button
+ val lockIcon = R.id.lock_icon_view
if (keyguardBottomAreaRefactor()) {
view.setOnTouchListener { _, event ->
@@ -200,10 +207,29 @@ object KeyguardRootViewBinder {
launch {
burnInParams
.flatMapLatest { params -> viewModel.translationX(params) }
- .collect { x ->
- childViews[burnInLayerId]?.translationX = x
- childViews[largeClockId]?.translationX = x
- childViews[aodNotificationIconContainerId]?.translationX = x
+ .collect { state ->
+ val px = state.value ?: return@collect
+ when {
+ state.isToOrFrom(KeyguardState.AOD) -> {
+ childViews[largeClockId]?.translationX = px
+ childViews[burnInLayerId]?.translationX = px
+ childViews[aodNotificationIconContainerId]
+ ?.translationX = px
+ }
+ state.isToOrFrom(KeyguardState.GLANCEABLE_HUB) -> {
+ for ((key, childView) in childViews.entries) {
+ when (key) {
+ indicationArea,
+ startButton,
+ endButton,
+ lockIcon -> {
+ // Do not move these views
+ }
+ else -> childView.translationX = px
+ }
+ }
+ }
+ }
}
}
@@ -321,7 +347,7 @@ object KeyguardRootViewBinder {
}
}
- onLayoutChangeListener = OnLayoutChange(viewModel, burnInParams)
+ onLayoutChangeListener = OnLayoutChange(viewModel, childViews, burnInParams)
view.addOnLayoutChangeListener(onLayoutChangeListener)
// Views will be added or removed after the call to bind(). This is needed to avoid many
@@ -381,6 +407,7 @@ object KeyguardRootViewBinder {
private class OnLayoutChange(
private val viewModel: KeyguardRootViewModel,
+ private val childViews: Map<Int, View>,
private val burnInParams: MutableStateFlow<BurnInParameters>,
) : OnLayoutChangeListener {
override fun onLayoutChange(
@@ -394,7 +421,7 @@ object KeyguardRootViewBinder {
oldRight: Int,
oldBottom: Int
) {
- view.findViewById<View>(R.id.nssl_placeholder)?.let { notificationListPlaceholder ->
+ childViews[R.id.nssl_placeholder]?.let { notificationListPlaceholder ->
// After layout, ensure the notifications are positioned correctly
viewModel.onNotificationContainerBoundsChanged(
notificationListPlaceholder.top.toFloat(),
@@ -402,10 +429,34 @@ object KeyguardRootViewBinder {
)
}
- view.findViewById<View>(R.id.keyguard_status_view)?.let { statusView ->
- burnInParams.update { current -> current.copy(statusViewTop = statusView.top) }
+ burnInParams.update { current ->
+ current.copy(
+ minViewY =
+ if (migrateClocksToBlueprint()) {
+ // To ensure burn-in doesn't enroach the top inset, get the min top Y
+ childViews.entries.fold(Int.MAX_VALUE) { currentMin, (viewId, view) ->
+ min(
+ currentMin,
+ if (!isUserVisible(view)) {
+ Int.MAX_VALUE
+ } else {
+ view.getTop()
+ }
+ )
+ }
+ } else {
+ childViews[R.id.keyguard_status_view]?.top ?: 0
+ }
+ )
}
}
+
+ private fun isUserVisible(view: View): Boolean {
+ return view.id != R.id.burn_in_layer &&
+ view.visibility == VISIBLE &&
+ view.width > 0 &&
+ view.height > 0
+ }
}
suspend fun bindAodNotifIconVisibility(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt
index 390b39f1e202..6e8605bde864 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt
@@ -34,7 +34,7 @@ import com.android.keyguard.dagger.KeyguardStatusViewComponent
import com.android.systemui.Flags.migrateClocksToBlueprint
import com.android.systemui.keyguard.KeyguardViewConfigurator
import com.android.systemui.keyguard.shared.model.KeyguardSection
-import com.android.systemui.media.controls.ui.KeyguardMediaController
+import com.android.systemui.media.controls.ui.controller.KeyguardMediaController
import com.android.systemui.res.R
import com.android.systemui.shade.NotificationPanelView
import com.android.systemui.shade.NotificationPanelViewController
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeMediaSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeMediaSection.kt
index b12a8a811955..21e945582aff 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeMediaSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeMediaSection.kt
@@ -30,7 +30,7 @@ import androidx.constraintlayout.widget.ConstraintSet.START
import androidx.constraintlayout.widget.ConstraintSet.TOP
import com.android.systemui.Flags.migrateClocksToBlueprint
import com.android.systemui.keyguard.shared.model.KeyguardSection
-import com.android.systemui.media.controls.ui.KeyguardMediaController
+import com.android.systemui.media.controls.ui.controller.KeyguardMediaController
import com.android.systemui.res.R
import com.android.systemui.shade.NotificationPanelView
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerMessageAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerMessageAreaViewModel.kt
index 49c64bdcdd23..9edb4d159d4a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerMessageAreaViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerMessageAreaViewModel.kt
@@ -40,23 +40,17 @@ constructor(
alternateBouncerInteractor: AlternateBouncerInteractor,
) {
- private val faceHelp: Flow<FaceMessage> =
- biometricMessageInteractor.faceMessage.filterNot { faceMessage ->
- faceMessage !is FaceTimeoutMessage
- }
- private val fingerprintMessages: Flow<FingerprintMessage> =
- biometricMessageInteractor.fingerprintMessage.filterNot { fingerprintMessage ->
- // On lockout, the device will show the bouncer. Let's not show the message
- // before the transition or else it'll look flickery.
- fingerprintMessage is FingerprintLockoutMessage
- }
+ private val faceMessage: Flow<FaceMessage> =
+ biometricMessageInteractor.faceMessage.filterNot { it is FaceTimeoutMessage }
+ private val fingerprintMessage: Flow<FingerprintMessage> =
+ biometricMessageInteractor.fingerprintMessage.filterNot { it is FingerprintLockoutMessage }
val message: Flow<BiometricMessage?> =
alternateBouncerInteractor.isVisible.flatMapLatest { isVisible ->
if (isVisible) {
merge(
- faceHelp,
- fingerprintMessages,
+ faceMessage,
+ fingerprintMessage,
)
} else {
flowOf(null)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModel.kt
index f208e85bde3e..8a3b57ba027f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModel.kt
@@ -18,7 +18,9 @@
package com.android.systemui.keyguard.ui.viewmodel
+import com.android.systemui.Flags.migrateClocksToBlueprint
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
@@ -38,6 +40,7 @@ constructor(
keyguardTransitionInteractor: KeyguardTransitionInteractor,
goneToAodTransitionViewModel: GoneToAodTransitionViewModel,
goneToDozingTransitionViewModel: GoneToDozingTransitionViewModel,
+ keyguardInteractor: KeyguardInteractor,
) {
/** The alpha level for the entire lockscreen while in AOD. */
@@ -46,7 +49,8 @@ constructor(
keyguardTransitionInteractor.transitions,
goneToAodTransitionViewModel.enterFromTopAnimationAlpha.onStart { emit(0f) },
goneToDozingTransitionViewModel.lockscreenAlpha.onStart { emit(0f) },
- ) { step, goneToAodAlpha, goneToDozingAlpha ->
+ keyguardInteractor.keyguardAlpha.onStart { emit(1f) },
+ ) { step, goneToAodAlpha, goneToDozingAlpha, keyguardAlpha ->
if (step.to == GONE) {
// When transitioning to GONE, only emit a value when complete as other
// transitions may be controlling the alpha fade
@@ -57,6 +61,8 @@ constructor(
emit(goneToAodAlpha)
} else if (step.from == GONE && step.to == DOZING) {
emit(goneToDozingAlpha)
+ } else if (!migrateClocksToBlueprint()) {
+ emit(keyguardAlpha)
}
}
.distinctUntilChanged()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt
index 6fcbf48eab82..8665aabc3ef7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt
@@ -158,9 +158,9 @@ constructor(
val burnInY = MathUtils.lerp(0, burnIn.translationY, interpolated).toInt()
val translationY =
if (Flags.migrateClocksToBlueprint()) {
- burnInY
+ max(params.topInset - params.minViewY, burnInY)
} else {
- max(params.topInset, params.statusViewTop + burnInY) - params.statusViewTop
+ max(params.topInset, params.minViewY + burnInY) - params.minViewY
}
BurnInModel(
@@ -194,8 +194,8 @@ data class BurnInParameters(
val clockControllerProvider: Provider<ClockController>? = null,
/** System insets that keyguard needs to stay out of */
val topInset: Int = 0,
- /** Status view top, without translation added in */
- val statusViewTop: Int = 0,
+ /** The min y-value of the visible elements on lockscreen */
+ val minViewY: Int = Int.MAX_VALUE,
/** The current y translation of the view */
val translationY: () -> Float? = { null }
)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt
index a0a77fbaee86..c40902871388 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt
@@ -25,10 +25,13 @@ import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.StateToValue
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.map
/**
* Breaks down AOD->LOCKSCREEN transition into discrete steps for corresponding views to consume.
@@ -39,6 +42,7 @@ class AodToLockscreenTransitionViewModel
@Inject
constructor(
deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
+ shadeInteractor: ShadeInteractor,
animationFlow: KeyguardTransitionAnimationFlow,
) : DeviceEntryIconTransition {
@@ -73,11 +77,22 @@ constructor(
}
val notificationAlpha: Flow<Float> =
- transitionAnimation.sharedFlow(
- duration = 500.milliseconds,
- onStep = { it },
- onCancel = { 1f },
- )
+ combine(
+ shadeInteractor.shadeExpansion.map { it > 0f },
+ shadeInteractor.qsExpansion.map { it > 0f },
+ transitionAnimation.sharedFlow(
+ duration = 500.milliseconds,
+ onStep = { it },
+ onCancel = { 1f },
+ ),
+ ) { isShadeExpanded, isQsExpanded, alpha ->
+ if (isShadeExpanded || isQsExpanded) {
+ // One example of this happening is dragging a notification while pulsing on AOD
+ 1f
+ } else {
+ alpha
+ }
+ }
val shortcutsAlpha: Flow<Float> =
transitionAnimation.sharedFlow(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt
new file mode 100644
index 000000000000..374a93275ff2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt
@@ -0,0 +1,59 @@
+/*
+ * 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 com.android.app.animation.Interpolators.EMPHASIZED
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
+import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.flow.Flow
+
+@SysUISingleton
+class DreamingToGlanceableHubTransitionViewModel
+@Inject
+constructor(animationFlow: KeyguardTransitionAnimationFlow) {
+
+ private val transitionAnimation =
+ animationFlow.setup(
+ duration = TO_GLANCEABLE_HUB_DURATION,
+ from = KeyguardState.DREAMING,
+ to = KeyguardState.GLANCEABLE_HUB,
+ )
+
+ fun dreamOverlayTranslationX(translatePx: Int): Flow<Float> {
+ return transitionAnimation.sharedFlow(
+ duration = TO_GLANCEABLE_HUB_DURATION,
+ onStep = { it * -translatePx },
+ interpolator = EMPHASIZED,
+ name = "DREAMING->GLANCEABLE_HUB: overlayTranslationX",
+ )
+ }
+
+ val dreamOverlayAlpha: Flow<Float> =
+ transitionAnimation.sharedFlow(
+ duration = 167.milliseconds,
+ onStep = { 1f - it },
+ name = "DREAMING->GLANCEABLE_HUB: dreamOverlayAlpha",
+ )
+
+ private companion object {
+ val TO_GLANCEABLE_HUB_DURATION = 1.seconds
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt
index 6aa2ecabae75..e5b596419efe 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt
@@ -16,13 +16,22 @@
package com.android.systemui.keyguard.ui.viewmodel
+import com.android.app.animation.Interpolators.EMPHASIZED
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.domain.interactor.FromGlanceableHubTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.FromGlanceableHubTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.android.systemui.keyguard.ui.StateToValue
+import com.android.systemui.res.R
import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
/**
* Breaks down GLANCEABLE_HUB->LOCKSCREEN transition into discrete steps for corresponding views to
@@ -33,32 +42,43 @@ import kotlinx.coroutines.flow.Flow
class GlanceableHubToLockscreenTransitionViewModel
@Inject
constructor(
+ configurationInteractor: ConfigurationInteractor,
animationFlow: KeyguardTransitionAnimationFlow,
) {
private val transitionAnimation =
animationFlow.setup(
- duration = FromGlanceableHubTransitionInteractor.TO_LOCKSCREEN_DURATION,
+ duration = TO_LOCKSCREEN_DURATION,
from = KeyguardState.GLANCEABLE_HUB,
to = KeyguardState.LOCKSCREEN,
)
- // TODO(b/315205222): implement full animation spec instead of just a simple fade.
val keyguardAlpha: Flow<Float> =
- transitionAnimation.sharedFlow(
- duration = FromGlanceableHubTransitionInteractor.TO_LOCKSCREEN_DURATION,
- onStep = { it },
- onFinish = { 1f },
- onCancel = { 0f },
- name = "GLANCEABLE_HUB->LOCKSCREEN: keyguardAlpha",
- )
+ transitionAnimation
+ .sharedFlow(
+ duration = 167.milliseconds,
+ startTime = 167.milliseconds,
+ onStep = { it },
+ onFinish = { 1f },
+ onCancel = { 0f },
+ name = "GLANCEABLE_HUB->LOCKSCREEN: keyguardAlpha",
+ )
+ .onStart { emit(0f) }
- // TODO(b/315205216): implement full animation spec instead of just a simple fade.
- val notificationAlpha: Flow<Float> =
- transitionAnimation.sharedFlow(
- duration = FromGlanceableHubTransitionInteractor.TO_LOCKSCREEN_DURATION,
- onStep = { it },
- onFinish = { 1f },
- onCancel = { 0f },
- name = "GLANCEABLE_HUB->LOCKSCREEN: notificationAlpha",
- )
+ val keyguardTranslationX: Flow<StateToValue> =
+ configurationInteractor
+ .dimensionPixelSize(R.dimen.hub_to_lockscreen_transition_lockscreen_translation_x)
+ .flatMapLatest { translatePx: Int ->
+ transitionAnimation.sharedFlowWithState(
+ duration = TO_LOCKSCREEN_DURATION,
+ onStep = { value -> -translatePx + value * translatePx },
+ interpolator = EMPHASIZED,
+ onCancel = { -translatePx.toFloat() },
+ name = "GLANCEABLE_HUB->LOCKSCREEN: keyguardTranslationX"
+ )
+ }
+
+ val notificationAlpha: Flow<Float> = keyguardAlpha
+
+ val notificationTranslationX: Flow<Float> =
+ keyguardTranslationX.map { it.value }.filterNotNull()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
index 188be244be4a..4db942cc460c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
@@ -20,7 +20,9 @@ package com.android.systemui.keyguard.ui.viewmodel
import androidx.annotation.VisibleForTesting
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
import com.android.systemui.shade.domain.interactor.ShadeInteractor
@@ -57,6 +59,7 @@ constructor(
lockscreenToGoneTransitionViewModel: LockscreenToGoneTransitionViewModel,
lockscreenToOccludedTransitionViewModel: LockscreenToOccludedTransitionViewModel,
lockscreenToPrimaryBouncerTransitionViewModel: LockscreenToPrimaryBouncerTransitionViewModel,
+ transitionInteractor: KeyguardTransitionInteractor,
) {
data class PreviewMode(
@@ -71,6 +74,24 @@ constructor(
*/
private val previewMode = MutableStateFlow(PreviewMode())
+ private val showingLockscreen: Flow<Boolean> =
+ transitionInteractor.finishedKeyguardState.map { keyguardState ->
+ keyguardState == KeyguardState.LOCKSCREEN
+ }
+
+ /** The only time the expansion is important is while lockscreen is actively displayed */
+ private val shadeExpansionAlpha =
+ combine(
+ showingLockscreen,
+ shadeInteractor.anyExpansion,
+ ) { showingLockscreen, expansion ->
+ if (showingLockscreen) {
+ 1 - expansion
+ } else {
+ 0f
+ }
+ }
+
/**
* ID of the slot that's currently selected in the preview that renders exclusively in the
* wallpaper picker application. This is ignored for the actual, real lock screen experience.
@@ -101,7 +122,7 @@ constructor(
lockscreenToGoneTransitionViewModel.shortcutsAlpha,
lockscreenToOccludedTransitionViewModel.shortcutsAlpha,
lockscreenToPrimaryBouncerTransitionViewModel.shortcutsAlpha,
- shadeInteractor.qsExpansion.map { 1 - it },
+ shadeExpansionAlpha,
)
/** The source of truth of alpha for all of the quick affordances on lockscreen */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index f790d356620d..921eb66cd3cc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -18,6 +18,7 @@
package com.android.systemui.keyguard.ui.viewmodel
import android.graphics.Point
+import android.util.MathUtils
import android.view.View.VISIBLE
import com.android.systemui.Flags.newAodTransition
import com.android.systemui.common.shared.model.NotificationContainerBounds
@@ -32,6 +33,8 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
+import com.android.systemui.keyguard.ui.StateToValue
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor
import com.android.systemui.statusbar.phone.DozeParameters
import com.android.systemui.statusbar.phone.ScreenOffAnimationController
@@ -42,6 +45,7 @@ import com.android.systemui.util.ui.AnimatedValue
import com.android.systemui.util.ui.toAnimatedValueFlow
import com.android.systemui.util.ui.zip
import javax.inject.Inject
+import kotlin.math.max
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
@@ -62,7 +66,7 @@ constructor(
private val dozeParameters: DozeParameters,
private val keyguardInteractor: KeyguardInteractor,
private val communalInteractor: CommunalInteractor,
- keyguardTransitionInteractor: KeyguardTransitionInteractor,
+ private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val notificationsKeyguardInteractor: NotificationsKeyguardInteractor,
private val alternateBouncerToGoneTransitionViewModel:
AlternateBouncerToGoneTransitionViewModel,
@@ -86,6 +90,7 @@ constructor(
private val screenOffAnimationController: ScreenOffAnimationController,
private val aodBurnInViewModel: AodBurnInViewModel,
private val aodAlphaViewModel: AodAlphaViewModel,
+ private val shadeInteractor: ShadeInteractor,
) {
val burnInLayerVisibility: Flow<Int> =
@@ -101,6 +106,16 @@ constructor(
.onStart { emit(false) }
.distinctUntilChanged()
+ private val alphaOnShadeExpansion: Flow<Float> =
+ combine(
+ shadeInteractor.qsExpansion,
+ shadeInteractor.shadeExpansion,
+ ) { qsExpansion, shadeExpansion ->
+ // Fade out quickly as the shade expands
+ 1f - MathUtils.constrainedMap(0f, 1f, 0f, 0.2f, max(qsExpansion, shadeExpansion))
+ }
+ .distinctUntilChanged()
+
/** Last point that the root view was tapped */
val lastRootViewTapPosition: Flow<Point?> = keyguardInteractor.lastRootViewTapPosition
@@ -118,10 +133,12 @@ constructor(
fun alpha(viewState: ViewStateAccessor): Flow<Float> {
return combine(
communalInteractor.isIdleOnCommunal,
+ keyguardTransitionInteractor.transitionValue(GONE).onStart { emit(0f) },
// The transitions are mutually exclusive, so they are safe to merge to get the last
// value emitted by any of them. Do not add flows that cannot make this guarantee.
merge(
aodAlphaViewModel.alpha,
+ alphaOnShadeExpansion,
keyguardInteractor.dismissAlpha.filterNotNull(),
alternateBouncerToGoneTransitionViewModel.lockscreenAlpha,
aodToLockscreenTransitionViewModel.lockscreenAlpha(viewState),
@@ -139,11 +156,12 @@ constructor(
primaryBouncerToLockscreenTransitionViewModel.lockscreenAlpha,
)
.onStart { emit(1f) }
- ) { isIdleOnCommunal, alpha ->
- if (isIdleOnCommunal) {
+ ) { isIdleOnCommunal, goneValue, alpha ->
+ if (isIdleOnCommunal || goneValue == 1f) {
// Keyguard should not show while the communal hub is fully visible. This check
// is added since at the moment, closing the notification shade will cause the
- // keyguard alpha to be set back to 1.
+ // keyguard alpha to be set back to 1. Also ensure keyguard is never visible
+ // when GONE.
0f
} else {
alpha
@@ -165,8 +183,12 @@ constructor(
return aodBurnInViewModel.translationY(params)
}
- fun translationX(params: BurnInParameters): Flow<Float> {
- return aodBurnInViewModel.translationX(params)
+ fun translationX(params: BurnInParameters): Flow<StateToValue> {
+ return merge(
+ aodBurnInViewModel.translationX(params).map { StateToValue(to = AOD, value = it) },
+ lockscreenToGlanceableHubTransitionViewModel.keyguardTranslationX,
+ glanceableHubToLockscreenTransitionViewModel.keyguardTranslationX,
+ )
}
fun scale(params: BurnInParameters): Flow<BurnInScaleViewModel> {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt
index 3afa49e50167..978e71e2a825 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt
@@ -16,13 +16,22 @@
package com.android.systemui.keyguard.ui.viewmodel
+import com.android.app.animation.Interpolators.EMPHASIZED
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.android.systemui.keyguard.ui.StateToValue
+import com.android.systemui.res.R
import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
/**
* Breaks down LOCKSCREEN->GLANCEABLE_HUB transition into discrete steps for corresponding views to
@@ -33,6 +42,7 @@ import kotlinx.coroutines.flow.Flow
class LockscreenToGlanceableHubTransitionViewModel
@Inject
constructor(
+ configurationInteractor: ConfigurationInteractor,
animationFlow: KeyguardTransitionAnimationFlow,
) {
private val transitionAnimation =
@@ -42,23 +52,35 @@ constructor(
to = KeyguardState.GLANCEABLE_HUB,
)
- // TODO(b/315205222): implement full animation spec instead of just a simple fade.
val keyguardAlpha: Flow<Float> =
- transitionAnimation.sharedFlow(
- duration = FromLockscreenTransitionInteractor.TO_GLANCEABLE_HUB_DURATION,
- onStep = { 1f - it },
- onFinish = { 0f },
- onCancel = { 1f },
- name = "LOCKSCREEN->GLANCEABLE_HUB: keyguardAlpha",
- )
+ transitionAnimation
+ .sharedFlow(
+ duration = 167.milliseconds,
+ onStep = { 1f - it },
+ onFinish = { 0f },
+ onCancel = { 1f },
+ name = "LOCKSCREEN->GLANCEABLE_HUB: keyguardAlpha",
+ )
+ .onStart { emit(1f) }
- // TODO(b/315205216): implement full animation spec instead of just a simple fade.
- val notificationAlpha: Flow<Float> =
- transitionAnimation.sharedFlow(
- duration = FromLockscreenTransitionInteractor.TO_GLANCEABLE_HUB_DURATION,
- onStep = { 1f - it },
- onFinish = { 0f },
- onCancel = { 1f },
- name = "LOCKSCREEN->GLANCEABLE_HUB: notificationAlpha",
- )
+ val keyguardTranslationX: Flow<StateToValue> =
+ configurationInteractor
+ .dimensionPixelSize(R.dimen.lockscreen_to_hub_transition_lockscreen_translation_x)
+ .flatMapLatest { translatePx: Int ->
+ transitionAnimation.sharedFlowWithState(
+ duration = FromLockscreenTransitionInteractor.TO_GLANCEABLE_HUB_DURATION,
+ onStep = { value -> value * translatePx },
+ // Move notifications back to their original position since they can be
+ // accessed from the shade.
+ onFinish = { 0f },
+ onCancel = { 0f },
+ interpolator = EMPHASIZED,
+ name = "LOCKSCREEN->GLANCEABLE_HUB: keyguardTranslationX"
+ )
+ }
+
+ val notificationAlpha: Flow<Float> = keyguardAlpha
+
+ val notificationTranslationX: Flow<Float> =
+ keyguardTranslationX.map { it.value }.filterNotNull()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
index 4f28b4608e49..378ce52b4331 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
@@ -108,6 +108,7 @@ constructor(
0f
}
},
+ onFinish = { 0f },
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index ac579d6d2491..cc3729b5b4d1 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -333,7 +333,7 @@ public class LogModule {
/**
* Provides a buffer for our connections and disconnections to MediaBrowserService.
*
- * See {@link com.android.systemui.media.controls.resume.ResumeMediaBrowser}.
+ * See {@link com.android.systemui.media.controls.domain.resume.ResumeMediaBrowser}.
*/
@Provides
@SysUISingleton
@@ -345,7 +345,7 @@ public class LogModule {
/**
* Provides a buffer for updates to the media carousel.
*
- * See {@link com.android.systemui.media.controls.ui.MediaCarouselController}.
+ * See {@link com.android.systemui.media.controls.ui.controller.MediaCarouselController}.
*/
@Provides
@SysUISingleton
@@ -637,4 +637,11 @@ public class LogModule {
return factory.create("NavBarButtonClick", 50);
}
+ /** Provides a {@link LogBuffer} for NavBar Orientation Tracking. */
+ @Provides
+ @SysUISingleton
+ @NavbarOrientationTrackingLog
+ public static LogBuffer provideNavbarOrientationTrackingLogBuffer(LogBufferFactory factory) {
+ return factory.create("NavbarOrientationTrackingLog", 50);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaBrowserLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaBrowserLog.java
index 1c00c93f4e38..901559bd3d8b 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaBrowserLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaBrowserLog.java
@@ -26,7 +26,8 @@ import java.lang.annotation.Retention;
import javax.inject.Qualifier;
/**
- * A {@link LogBuffer} for {@link com.android.systemui.media.controls.resume.ResumeMediaBrowser}
+ * A {@link LogBuffer} for
+ * {@link com.android.systemui.media.controls.domain.resume.ResumeMediaBrowser}
*/
@Qualifier
@Documented
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaCarouselControllerLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaCarouselControllerLog.java
index 86a916ef6541..abbfd4fa32ed 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaCarouselControllerLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaCarouselControllerLog.java
@@ -26,7 +26,8 @@ import java.lang.annotation.Retention;
import javax.inject.Qualifier;
/**
- * A {@link LogBuffer} for {@link com.android.systemui.media.controls.ui.MediaCarouselController}
+ * A {@link LogBuffer} for
+ * {@link com.android.systemui.media.controls.ui.controller.MediaCarouselController}
*/
@Qualifier
@Documented
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTimeoutListenerLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTimeoutListenerLog.java
index 98e6556d7f53..0239caa39544 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTimeoutListenerLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaTimeoutListenerLog.java
@@ -26,7 +26,8 @@ import java.lang.annotation.Retention;
import javax.inject.Qualifier;
/**
- * A {@link LogBuffer} for {@link com.android.systemui.media.controls.pipeline.MediaTimeoutLogger}
+ * A {@link LogBuffer} for
+ * {@link com.android.systemui.media.controls.domain.pipeline.MediaTimeoutLogger}
*/
@Qualifier
@Documented
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaViewLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaViewLog.java
index dde0ee0796d4..27a6a64b1302 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/MediaViewLog.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/MediaViewLog.java
@@ -26,7 +26,7 @@ import java.lang.annotation.Retention;
import javax.inject.Qualifier;
/**
- * A {@link LogBuffer} for {@link com.android.systemui.media.controls.ui.MediaViewLogger}
+ * A {@link LogBuffer} for {@link com.android.systemui.media.controls.ui.controller.MediaViewLogger}
*/
@Qualifier
@Documented
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NavbarOrientationTrackingLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NavbarOrientationTrackingLog.java
new file mode 100644
index 000000000000..46790a66cbb6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NavbarOrientationTrackingLog.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.log.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.android.systemui.log.LogBuffer;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+/** A {@link LogBuffer} for {@link com.android.systemui.navigationbar.NavigationBar}. */
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface NavbarOrientationTrackingLog {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatest.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatest.kt
index 789ef407ea9d..ad70db5a3300 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatest.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatest.kt
@@ -14,11 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.media.controls.pipeline
+package com.android.systemui.media.controls.domain.pipeline
-import com.android.systemui.media.controls.models.player.MediaData
-import com.android.systemui.media.controls.models.player.MediaDeviceData
-import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
+import com.android.systemui.media.controls.shared.model.MediaData
+import com.android.systemui.media.controls.shared.model.MediaDeviceData
+import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
import javax.inject.Inject
/** Combines [MediaDataManager.Listener] events with [MediaDeviceManager.Listener] events. */
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilter.kt
index 185a78369a9e..bc539efdfe69 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilter.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media.controls.pipeline
+package com.android.systemui.media.controls.domain.pipeline
import android.content.Context
import android.content.pm.UserInfo
@@ -24,9 +24,9 @@ import com.android.internal.annotations.KeepForWeakReference
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.broadcast.BroadcastSender
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.media.controls.models.player.MediaData
-import com.android.systemui.media.controls.models.recommendation.EXTRA_KEY_TRIGGER_RESUME
-import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
+import com.android.systemui.media.controls.shared.model.EXTRA_KEY_TRIGGER_RESUME
+import com.android.systemui.media.controls.shared.model.MediaData
+import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.media.controls.util.MediaUiEventLogger
import com.android.systemui.settings.UserTracker
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataManager.kt
index 47df3b79b8bb..6fc22ea60a9e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataManager.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media.controls.pipeline
+package com.android.systemui.media.controls.domain.pipeline
import android.annotation.SuppressLint
import android.app.BroadcastOptions
@@ -66,17 +66,17 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
-import com.android.systemui.media.controls.models.player.MediaAction
-import com.android.systemui.media.controls.models.player.MediaButton
-import com.android.systemui.media.controls.models.player.MediaData
-import com.android.systemui.media.controls.models.player.MediaDeviceData
-import com.android.systemui.media.controls.models.player.MediaViewHolder
-import com.android.systemui.media.controls.models.recommendation.EXTRA_KEY_TRIGGER_SOURCE
-import com.android.systemui.media.controls.models.recommendation.EXTRA_VALUE_TRIGGER_PERIODIC
-import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
-import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaDataProvider
-import com.android.systemui.media.controls.resume.MediaResumeListener
-import com.android.systemui.media.controls.resume.ResumeMediaBrowser
+import com.android.systemui.media.controls.domain.resume.MediaResumeListener
+import com.android.systemui.media.controls.domain.resume.ResumeMediaBrowser
+import com.android.systemui.media.controls.shared.model.EXTRA_KEY_TRIGGER_SOURCE
+import com.android.systemui.media.controls.shared.model.EXTRA_VALUE_TRIGGER_PERIODIC
+import com.android.systemui.media.controls.shared.model.MediaAction
+import com.android.systemui.media.controls.shared.model.MediaButton
+import com.android.systemui.media.controls.shared.model.MediaData
+import com.android.systemui.media.controls.shared.model.MediaDeviceData
+import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
+import com.android.systemui.media.controls.shared.model.SmartspaceMediaDataProvider
+import com.android.systemui.media.controls.ui.view.MediaViewHolder
import com.android.systemui.media.controls.util.MediaControllerFactory
import com.android.systemui.media.controls.util.MediaDataUtils
import com.android.systemui.media.controls.util.MediaFlags
@@ -583,6 +583,10 @@ class MediaDataManager(
}
return
}
+ // Update last active if media was still active.
+ if (it.active) {
+ it.lastActive = systemClock.elapsedRealtime()
+ }
it.active = !timedOut
if (DEBUG) Log.d(TAG, "Updating $key timedOut: $timedOut")
onMediaDataLoaded(key, key, it)
@@ -1520,6 +1524,12 @@ class MediaDataManager(
context.packageManager.getLaunchIntentForPackage(data.packageName)?.let {
PendingIntent.getActivity(context, 0, it, PendingIntent.FLAG_IMMUTABLE)
}
+ val lastActive =
+ if (data.active) {
+ systemClock.elapsedRealtime()
+ } else {
+ data.lastActive
+ }
val updated =
data.copy(
token = null,
@@ -1531,6 +1541,7 @@ class MediaDataManager(
isPlaying = false,
isClearable = true,
clickIntent = launcherIntent,
+ lastActive = lastActive,
)
val pkg = data.packageName
val migrate = mediaEntries.put(pkg, updated) == null
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt
index dcbf670460ef..42d68bab49f8 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media.controls.pipeline
+package com.android.systemui.media.controls.domain.pipeline
import android.bluetooth.BluetoothLeBroadcast
import android.bluetooth.BluetoothLeBroadcastMetadata
@@ -37,8 +37,9 @@ import com.android.systemui.Dumpable
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
-import com.android.systemui.media.controls.models.player.MediaData
-import com.android.systemui.media.controls.models.player.MediaDeviceData
+import com.android.systemui.media.controls.shared.model.MediaData
+import com.android.systemui.media.controls.shared.model.MediaDeviceData
+import com.android.systemui.media.controls.util.LocalMediaManagerFactory
import com.android.systemui.media.controls.util.MediaControllerFactory
import com.android.systemui.media.controls.util.MediaDataUtils
import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManager
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaSessionBasedFilter.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilter.kt
index 6a8ffb7d8c42..b2a8f2e71cc6 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaSessionBasedFilter.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilter.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media.controls.pipeline
+package com.android.systemui.media.controls.domain.pipeline
import android.content.ComponentName
import android.content.Context
@@ -25,8 +25,8 @@ import android.media.session.MediaSessionManager
import android.util.Log
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.media.controls.models.player.MediaData
-import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
+import com.android.systemui.media.controls.shared.model.MediaData
+import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
import com.android.systemui.statusbar.phone.NotificationListenerWithPlugins
import java.util.concurrent.Executor
import javax.inject.Inject
@@ -199,9 +199,7 @@ constructor(
packageControllers.put(controller.packageName, tokens)
}
}
- controllers?.map { TokenId(it.sessionToken) }?.let {
- tokensWithNotifications.retainAll(it)
- }
+ controllers?.map { TokenId(it.sessionToken) }?.let { tokensWithNotifications.retainAll(it) }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListener.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListener.kt
index ed4eef9eaa2b..29f396700831 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListener.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media.controls.pipeline
+package com.android.systemui.media.controls.domain.pipeline
import android.media.session.MediaController
import android.media.session.MediaSession
@@ -23,8 +23,8 @@ import android.os.SystemProperties
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.media.controls.models.player.MediaData
-import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
+import com.android.systemui.media.controls.shared.model.MediaData
+import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
import com.android.systemui.media.controls.util.MediaControllerFactory
import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.plugins.statusbar.StatusBarStateController
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutLogger.kt
index 534241edb253..c50c46a853a5 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutLogger.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media.controls.pipeline
+package com.android.systemui.media.controls.domain.pipeline
import android.media.session.PlaybackState
import com.android.systemui.dagger.SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaBrowserFactory.java b/packages/SystemUI/src/com/android/systemui/media/controls/domain/resume/MediaBrowserFactory.java
index 00620b5b2575..2c45ddb5542a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaBrowserFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/resume/MediaBrowserFactory.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media.controls.resume;
+package com.android.systemui.media.controls.domain.resume;
import android.content.ComponentName;
import android.content.Context;
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/resume/MediaResumeListener.kt
index 23ee00d88fdc..e4047e5f96c5 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/resume/MediaResumeListener.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media.controls.resume
+package com.android.systemui.media.controls.domain.resume
import android.content.BroadcastReceiver
import android.content.ComponentName
@@ -34,9 +34,9 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
-import com.android.systemui.media.controls.models.player.MediaData
-import com.android.systemui.media.controls.pipeline.MediaDataManager
-import com.android.systemui.media.controls.pipeline.RESUME_MEDIA_TIMEOUT
+import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
+import com.android.systemui.media.controls.domain.pipeline.RESUME_MEDIA_TIMEOUT
+import com.android.systemui.media.controls.shared.model.MediaData
import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.settings.UserTracker
import com.android.systemui.tuner.TunerService
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowser.java b/packages/SystemUI/src/com/android/systemui/media/controls/domain/resume/ResumeMediaBrowser.java
index ceaccafd8f40..b2960cd287c6 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowser.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/resume/ResumeMediaBrowser.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media.controls.resume;
+package com.android.systemui.media.controls.domain.resume;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserFactory.java b/packages/SystemUI/src/com/android/systemui/media/controls/domain/resume/ResumeMediaBrowserFactory.java
index e37419127f5b..50eb77642632 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/resume/ResumeMediaBrowserFactory.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media.controls.resume;
+package com.android.systemui.media.controls.domain.resume;
import android.annotation.UserIdInt;
import android.content.ComponentName;
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/resume/ResumeMediaBrowserLogger.kt
index 888b9c7cc901..ce2a0803bbfb 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/resume/ResumeMediaBrowserLogger.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media.controls.resume
+package com.android.systemui.media.controls.domain.resume
import android.content.ComponentName
import com.android.systemui.dagger.SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt b/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaData.kt
index 5caa27f02bd3..4fa7cb54431f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaData.kt
@@ -11,10 +11,10 @@
* 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
+ * limitations under the License.
*/
-package com.android.systemui.media.controls.models.player
+package com.android.systemui.media.controls.shared.model
import android.app.PendingIntent
import android.graphics.drawable.Drawable
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaData.kt b/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/SmartspaceMediaData.kt
index ae03f27b32cd..52c605f55665 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaData.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/SmartspaceMediaData.kt
@@ -11,10 +11,10 @@
* 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
+ * limitations under the License.
*/
-package com.android.systemui.media.controls.models.recommendation
+package com.android.systemui.media.controls.shared.model
import android.app.smartspace.SmartspaceAction
import android.content.Context
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaDataProvider.kt b/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/SmartspaceMediaDataProvider.kt
index cacb3e2bbe4d..8726d8193800 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaDataProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/SmartspaceMediaDataProvider.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media.controls.models.recommendation
+package com.android.systemui.media.controls.shared.model
import android.app.smartspace.SmartspaceTarget
import android.util.Log
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/AnimationBindHandler.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/animation/AnimationBindHandler.kt
index f5cc04331f94..82580590a11c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/AnimationBindHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/animation/AnimationBindHandler.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media.controls.ui
+package com.android.systemui.media.controls.ui.animation
import android.graphics.drawable.Animatable2
import android.graphics.drawable.Drawable
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/animation/ColorSchemeTransition.kt
index 952f9b8711f0..21407f3bd6d4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/animation/ColorSchemeTransition.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media.controls.ui
+package com.android.systemui.media.controls.ui.animation
import android.animation.ArgbEvaluator
import android.animation.ValueAnimator
@@ -27,7 +27,7 @@ import android.graphics.drawable.RippleDrawable
import com.android.internal.R
import com.android.internal.annotations.VisibleForTesting
import com.android.settingslib.Utils
-import com.android.systemui.media.controls.models.player.MediaViewHolder
+import com.android.systemui.media.controls.ui.view.MediaViewHolder
import com.android.systemui.monet.ColorScheme
import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect
import com.android.systemui.surfaceeffects.ripple.MultiRippleController
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaColorSchemes.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/animation/MediaColorSchemes.kt
index 2a8362b64cd6..3c57c83ff9fe 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaColorSchemes.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/animation/MediaColorSchemes.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media.controls.ui
+package com.android.systemui.media.controls.ui.animation
import com.android.systemui.monet.ColorScheme
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MetadataAnimationHandler.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/animation/MetadataAnimationHandler.kt
index 1cdcf5ed2702..98202c51706a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MetadataAnimationHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/animation/MetadataAnimationHandler.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media.controls.ui
+package com.android.systemui.media.controls.ui.animation
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarObserver.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/SeekBarObserver.kt
index 8d918e76b1e1..34f7c4dcaec0 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarObserver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/SeekBarObserver.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media.controls.models.player
+package com.android.systemui.media.controls.ui.binder
import android.animation.Animator
import android.animation.ObjectAnimator
@@ -24,7 +24,9 @@ import androidx.lifecycle.Observer
import com.android.app.animation.Interpolators
import com.android.app.tracing.TraceStateLogger
import com.android.internal.annotations.VisibleForTesting
-import com.android.systemui.media.controls.ui.SquigglyProgress
+import com.android.systemui.media.controls.ui.drawable.SquigglyProgress
+import com.android.systemui.media.controls.ui.view.MediaViewHolder
+import com.android.systemui.media.controls.ui.viewmodel.SeekBarViewModel
import com.android.systemui.res.R
private const val TAG = "SeekBarObserver"
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaController.kt
index e15e03822610..9206af28eeff 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaController.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media.controls.ui
+package com.android.systemui.media.controls.ui.controller
import android.content.Context
import android.content.res.Configuration
@@ -31,6 +31,8 @@ import com.android.systemui.Flags.migrateClocksToBlueprint
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
+import com.android.systemui.media.controls.ui.view.MediaHost
+import com.android.systemui.media.controls.ui.view.MediaHostState
import com.android.systemui.media.dagger.MediaModule.KEYGUARD
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.StatusBarState
@@ -230,18 +232,12 @@ constructor(
val currentAllowMediaPlayerOnLockScreen = allowMediaPlayerOnLockScreen
val useSplitShade = useSplitShade
val shouldBeVisibleForSplitShade = shouldBeVisibleForSplitShade()
-
visible =
isMediaHostVisible &&
isBypassNotEnabled &&
keyguardOrUserSwitcher &&
currentAllowMediaPlayerOnLockScreen &&
shouldBeVisibleForSplitShade
- if (visible) {
- showMediaPlayer()
- } else {
- hideMediaPlayer()
- }
logger.logRefreshMediaPosition(
reason = reason,
visible = visible,
@@ -251,8 +247,17 @@ constructor(
mediaHostVisible = isMediaHostVisible,
bypassNotEnabled = isBypassNotEnabled,
currentAllowMediaPlayerOnLockScreen = currentAllowMediaPlayerOnLockScreen,
- shouldBeVisibleForSplitShade = shouldBeVisibleForSplitShade
+ shouldBeVisibleForSplitShade = shouldBeVisibleForSplitShade,
)
+ val currActiveContainer = activeContainer
+
+ logger.logActiveMediaContainer("before refreshMediaPosition", currActiveContainer)
+ if (visible) {
+ showMediaPlayer()
+ } else {
+ hideMediaPlayer()
+ }
+ logger.logActiveMediaContainer("after refreshMediaPosition", currActiveContainer)
lastUsedStatusBarState = currentState
}
@@ -293,9 +298,11 @@ constructor(
}
private fun setVisibility(view: ViewGroup?, newVisibility: Int) {
- val previousVisibility = view?.visibility
- view?.visibility = newVisibility
- if (previousVisibility != newVisibility) {
+ val currentMediaContainer = view ?: return
+
+ val previousVisibility = currentMediaContainer.visibility
+ currentMediaContainer.visibility = newVisibility
+ if (previousVisibility != newVisibility && currentMediaContainer is MediaContainerView) {
visibilityChangedListener?.invoke(newVisibility == View.VISIBLE)
}
}
@@ -325,4 +332,7 @@ constructor(
}
}
}
+
+ private val activeContainer: ViewGroup? =
+ if (useSplitShade) splitShadeContainer else singlePaneContainer
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaControllerLogger.kt
index 41fef88645a2..c0d9dc23a6d5 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaControllerLogger.kt
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.media.controls.ui
+package com.android.systemui.media.controls.ui.controller
+import android.view.ViewGroup
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.LogLevel.DEBUG
import com.android.systemui.log.dagger.KeyguardMediaControllerLog
@@ -36,8 +37,8 @@ constructor(@KeyguardMediaControllerLog private val logBuffer: LogBuffer) {
mediaHostVisible: Boolean,
bypassNotEnabled: Boolean,
currentAllowMediaPlayerOnLockScreen: Boolean,
- shouldBeVisibleForSplitShade: Boolean
- ) =
+ shouldBeVisibleForSplitShade: Boolean,
+ ) {
logBuffer.log(
TAG,
DEBUG,
@@ -63,6 +64,19 @@ constructor(@KeyguardMediaControllerLog private val logBuffer: LogBuffer) {
"shouldBeVisibleForSplitShade=$str3)"
}
)
+ }
+
+ fun logActiveMediaContainer(reason: String, activeContainer: ViewGroup?) {
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ {
+ str1 = reason
+ str2 = activeContainer.toString()
+ },
+ { "activeMediaContainerVisibility(reason=$str1, activeContainer=$str2)" }
+ )
+ }
private companion object {
private const val TAG = "KeyguardMediaControllerLog"
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
index 992eeca77e54..b721236eab01 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media.controls.ui
+package com.android.systemui.media.controls.ui.controller
import android.app.PendingIntent
import android.content.Context
@@ -46,12 +46,15 @@ import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.lifecycle.repeatWhenAttached
-import com.android.systemui.media.controls.models.player.MediaData
-import com.android.systemui.media.controls.models.player.MediaViewHolder
-import com.android.systemui.media.controls.models.recommendation.RecommendationViewHolder
-import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
-import com.android.systemui.media.controls.pipeline.MediaDataManager
-import com.android.systemui.media.controls.ui.MediaControlPanel.SMARTSPACE_CARD_DISMISS_EVENT
+import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
+import com.android.systemui.media.controls.shared.model.MediaData
+import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
+import com.android.systemui.media.controls.ui.controller.MediaControlPanel.SMARTSPACE_CARD_DISMISS_EVENT
+import com.android.systemui.media.controls.ui.view.MediaCarouselScrollHandler
+import com.android.systemui.media.controls.ui.view.MediaHostState
+import com.android.systemui.media.controls.ui.view.MediaScrollView
+import com.android.systemui.media.controls.ui.view.MediaViewHolder
+import com.android.systemui.media.controls.ui.view.RecommendationViewHolder
import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.media.controls.util.MediaUiEventLogger
import com.android.systemui.media.controls.util.SmallHash
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerLogger.kt
index 3dc00004e900..ebf1c6a10703 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselControllerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerLogger.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media.controls.ui
+package com.android.systemui.media.controls.ui.controller
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.log.LogBuffer
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
index e97c9d3d8c0b..e8ad4d325591 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
@@ -14,11 +14,12 @@
* limitations under the License.
*/
-package com.android.systemui.media.controls.ui;
+package com.android.systemui.media.controls.ui.controller;
import static android.provider.Settings.ACTION_MEDIA_CONTROLS_SETTINGS;
-import static com.android.systemui.media.controls.models.recommendation.SmartspaceMediaDataKt.NUM_REQUIRED_RECOMMENDATIONS;
+import static com.android.systemui.Flags.legacyLeAudioSharing;
+import static com.android.systemui.media.controls.shared.model.SmartspaceMediaDataKt.NUM_REQUIRED_RECOMMENDATIONS;
import android.animation.Animator;
import android.animation.AnimatorInflater;
@@ -89,17 +90,21 @@ import com.android.systemui.bluetooth.BroadcastDialogController;
import com.android.systemui.broadcast.BroadcastSender;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.media.controls.models.GutsViewHolder;
-import com.android.systemui.media.controls.models.player.MediaAction;
-import com.android.systemui.media.controls.models.player.MediaButton;
-import com.android.systemui.media.controls.models.player.MediaData;
-import com.android.systemui.media.controls.models.player.MediaDeviceData;
-import com.android.systemui.media.controls.models.player.MediaViewHolder;
-import com.android.systemui.media.controls.models.player.SeekBarObserver;
-import com.android.systemui.media.controls.models.player.SeekBarViewModel;
-import com.android.systemui.media.controls.models.recommendation.RecommendationViewHolder;
-import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData;
-import com.android.systemui.media.controls.pipeline.MediaDataManager;
+import com.android.systemui.media.controls.domain.pipeline.MediaDataManager;
+import com.android.systemui.media.controls.shared.model.MediaAction;
+import com.android.systemui.media.controls.shared.model.MediaButton;
+import com.android.systemui.media.controls.shared.model.MediaData;
+import com.android.systemui.media.controls.shared.model.MediaDeviceData;
+import com.android.systemui.media.controls.shared.model.SmartspaceMediaData;
+import com.android.systemui.media.controls.ui.animation.AnimationBindHandler;
+import com.android.systemui.media.controls.ui.animation.ColorSchemeTransition;
+import com.android.systemui.media.controls.ui.animation.MediaColorSchemesKt;
+import com.android.systemui.media.controls.ui.animation.MetadataAnimationHandler;
+import com.android.systemui.media.controls.ui.binder.SeekBarObserver;
+import com.android.systemui.media.controls.ui.view.GutsViewHolder;
+import com.android.systemui.media.controls.ui.view.MediaViewHolder;
+import com.android.systemui.media.controls.ui.view.RecommendationViewHolder;
+import com.android.systemui.media.controls.ui.viewmodel.SeekBarViewModel;
import com.android.systemui.media.controls.util.MediaDataUtils;
import com.android.systemui.media.controls.util.MediaFlags;
import com.android.systemui.media.controls.util.MediaUiEventLogger;
@@ -597,7 +602,9 @@ public class MediaControlPanel {
// Show the broadcast dialog button only when the le audio is enabled.
mShowBroadcastDialogButton =
- data.getDevice() != null && data.getDevice().getShowBroadcastButton();
+ legacyLeAudioSharing()
+ && data.getDevice() != null
+ && data.getDevice().getShowBroadcastButton();
bindOutputSwitcherAndBroadcastButton(mShowBroadcastDialogButton, data);
bindGutsMenuForPlayer(data);
bindPlayerContentDescription(data);
@@ -1930,3 +1937,4 @@ public class MediaControlPanel {
interactedSubcardCardinality);
}
}
+
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt
index 35e0271c1b8f..3b989d935cbd 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media.controls.ui
+package com.android.systemui.media.controls.ui.controller
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
@@ -42,7 +42,8 @@ import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dreams.DreamOverlayStateController
import com.android.systemui.keyguard.WakefulnessLifecycle
-import com.android.systemui.media.controls.pipeline.MediaDataManager
+import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
+import com.android.systemui.media.controls.ui.view.MediaHost
import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.media.dream.MediaDreamComplication
import com.android.systemui.plugins.statusbar.StatusBarStateController
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHostStatesManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHostStatesManager.kt
index 1f711cfdd966..8660d12bcb85 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHostStatesManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHostStatesManager.kt
@@ -14,10 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.media.controls.ui
+package com.android.systemui.media.controls.ui.controller
import com.android.app.tracing.traceSection
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.media.controls.ui.view.MediaHostState
import com.android.systemui.util.animation.MeasurementOutput
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
index 962764c028fc..ad7990b92931 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
@@ -11,10 +11,10 @@
* 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
+ * limitations under the License.
*/
-package com.android.systemui.media.controls.ui
+package com.android.systemui.media.controls.ui.controller
import android.content.Context
import android.content.res.Configuration
@@ -22,10 +22,11 @@ import androidx.annotation.VisibleForTesting
import androidx.constraintlayout.widget.ConstraintSet
import androidx.constraintlayout.widget.ConstraintSet.MATCH_CONSTRAINT
import com.android.app.tracing.traceSection
-import com.android.systemui.media.controls.models.GutsViewHolder
-import com.android.systemui.media.controls.models.player.MediaViewHolder
-import com.android.systemui.media.controls.models.recommendation.RecommendationViewHolder
-import com.android.systemui.media.controls.ui.MediaCarouselController.Companion.calculateAlpha
+import com.android.systemui.media.controls.ui.controller.MediaCarouselController.Companion.calculateAlpha
+import com.android.systemui.media.controls.ui.view.GutsViewHolder
+import com.android.systemui.media.controls.ui.view.MediaHostState
+import com.android.systemui.media.controls.ui.view.MediaViewHolder
+import com.android.systemui.media.controls.ui.view.RecommendationViewHolder
import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.ConfigurationController
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewLogger.kt
index 3ff2315956ad..1514db38d882 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewLogger.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media.controls.ui
+package com.android.systemui.media.controls.ui.controller
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.log.LogBuffer
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/IlluminationDrawable.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/drawable/IlluminationDrawable.kt
index 5aa6824a98b1..260d30061296 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/IlluminationDrawable.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/drawable/IlluminationDrawable.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media.controls.ui
+package com.android.systemui.media.controls.ui.drawable
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/LightSourceDrawable.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/drawable/LightSourceDrawable.kt
index 6ee072d41ecc..e4ce1353d30f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/LightSourceDrawable.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/drawable/LightSourceDrawable.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media.controls.ui
+package com.android.systemui.media.controls.ui.drawable
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/SquigglyProgress.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/drawable/SquigglyProgress.kt
index 47df021eaf83..c417fe60219a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/SquigglyProgress.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/drawable/SquigglyProgress.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media.controls.ui
+package com.android.systemui.media.controls.ui.drawable
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/GutsViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/GutsViewHolder.kt
index f5f5d388a278..a667c5819062 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/GutsViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/GutsViewHolder.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media.controls.models
+package com.android.systemui.media.controls.ui.view
import android.content.res.ColorStateList
import android.util.Log
@@ -22,11 +22,11 @@ import android.view.View
import android.view.ViewGroup
import android.widget.ImageButton
import android.widget.TextView
-import com.android.systemui.res.R
-import com.android.systemui.media.controls.ui.accentPrimaryFromScheme
-import com.android.systemui.media.controls.ui.surfaceFromScheme
-import com.android.systemui.media.controls.ui.textPrimaryFromScheme
+import com.android.systemui.media.controls.ui.animation.accentPrimaryFromScheme
+import com.android.systemui.media.controls.ui.animation.surfaceFromScheme
+import com.android.systemui.media.controls.ui.animation.textPrimaryFromScheme
import com.android.systemui.monet.ColorScheme
+import com.android.systemui.res.R
/**
* A view holder for the guts menu of a media player. The guts are shown when the user long-presses
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandler.kt
index 038582c4e999..c033e466d7d7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandler.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media.controls.ui
+package com.android.systemui.media.controls.ui.view
import android.graphics.Outline
import android.util.MathUtils
@@ -31,6 +31,7 @@ import com.android.internal.annotations.VisibleForTesting
import com.android.settingslib.Utils
import com.android.systemui.Gefingerpoken
import com.android.systemui.classifier.Classifier.NOTIFICATION_DISMISS
+import com.android.systemui.media.controls.ui.controller.MediaControlPanel
import com.android.systemui.media.controls.util.MediaUiEventLogger
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.qs.PageIndicator
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHost.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaHost.kt
index 437218f9f440..d92168bf9fa4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHost.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaHost.kt
@@ -14,15 +14,18 @@
* limitations under the License.
*/
-package com.android.systemui.media.controls.ui
+package com.android.systemui.media.controls.ui.view
import android.graphics.Rect
import android.util.ArraySet
import android.view.View
import android.view.View.OnAttachStateChangeListener
-import com.android.systemui.media.controls.models.player.MediaData
-import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
-import com.android.systemui.media.controls.pipeline.MediaDataManager
+import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
+import com.android.systemui.media.controls.shared.model.MediaData
+import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
+import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
+import com.android.systemui.media.controls.ui.controller.MediaHostStatesManager
+import com.android.systemui.media.controls.ui.controller.MediaLocation
import com.android.systemui.util.animation.DisappearParameters
import com.android.systemui.util.animation.MeasurementInput
import com.android.systemui.util.animation.MeasurementOutput
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaScrollView.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaScrollView.kt
index 10512f1c4aed..b6259081e174 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaScrollView.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaScrollView.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media.controls.ui
+package com.android.systemui.media.controls.ui.view
import android.content.Context
import android.os.SystemClock
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaViewHolder.kt
index 898eacff6246..35309ea65a4c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaViewHolder.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media.controls.models.player
+package com.android.systemui.media.controls.ui.view
import android.view.LayoutInflater
import android.view.View
@@ -25,7 +25,6 @@ import android.widget.SeekBar
import android.widget.TextView
import androidx.constraintlayout.widget.Barrier
import com.android.internal.widget.CachingIconView
-import com.android.systemui.media.controls.models.GutsViewHolder
import com.android.systemui.res.R
import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffectView
import com.android.systemui.surfaceeffects.ripple.MultiRippleView
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/RecommendationViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/RecommendationViewHolder.kt
index 8ac8a2da4478..2d028d0213ff 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/RecommendationViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/RecommendationViewHolder.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media.controls.models.recommendation
+package com.android.systemui.media.controls.ui.view
import android.view.LayoutInflater
import android.view.View
@@ -23,9 +23,8 @@ import android.widget.ImageView
import android.widget.SeekBar
import android.widget.TextView
import com.android.internal.widget.CachingIconView
+import com.android.systemui.media.controls.ui.drawable.IlluminationDrawable
import com.android.systemui.res.R
-import com.android.systemui.media.controls.models.GutsViewHolder
-import com.android.systemui.media.controls.ui.IlluminationDrawable
import com.android.systemui.util.animation.TransitionLayout
private const val TAG = "RecommendationViewHolder"
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/SeekBarViewModel.kt
index 13d743f8a4e4..cef1e69e7b6a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/SeekBarViewModel.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media.controls.models.player
+package com.android.systemui.media.controls.ui.viewmodel
import android.media.MediaMetadata
import android.media.session.MediaController
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/LocalMediaManagerFactory.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/LocalMediaManagerFactory.kt
index 1d3cfd2569aa..5d113a97c42f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/LocalMediaManagerFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/LocalMediaManagerFactory.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media.controls.pipeline
+package com.android.systemui.media.controls.util
import android.content.Context
import com.android.settingslib.bluetooth.LocalBluetoothManager
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaUiEventLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaUiEventLogger.kt
index 16a703a6bfdd..f8c816ca0b52 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaUiEventLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaUiEventLogger.kt
@@ -21,9 +21,9 @@ import com.android.internal.logging.InstanceIdSequence
import com.android.internal.logging.UiEvent
import com.android.internal.logging.UiEventLogger
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.media.controls.models.player.MediaData
-import com.android.systemui.media.controls.ui.MediaHierarchyManager
-import com.android.systemui.media.controls.ui.MediaLocation
+import com.android.systemui.media.controls.shared.model.MediaData
+import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
+import com.android.systemui.media.controls.ui.controller.MediaLocation
import com.android.systemui.res.R
import java.lang.IllegalArgumentException
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
index 8f752e59e806..d84e5dde6967 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
@@ -19,10 +19,10 @@ package com.android.systemui.media.dagger;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.log.LogBuffer;
import com.android.systemui.log.LogBufferFactory;
-import com.android.systemui.media.controls.pipeline.MediaDataManager;
-import com.android.systemui.media.controls.ui.MediaHierarchyManager;
-import com.android.systemui.media.controls.ui.MediaHost;
-import com.android.systemui.media.controls.ui.MediaHostStatesManager;
+import com.android.systemui.media.controls.domain.pipeline.MediaDataManager;
+import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager;
+import com.android.systemui.media.controls.ui.controller.MediaHostStatesManager;
+import com.android.systemui.media.controls.ui.view.MediaHost;
import com.android.systemui.media.dream.dagger.MediaComplicationComponent;
import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper;
import com.android.systemui.media.taptotransfer.MediaTttFlags;
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
index 2f5f92586260..c379d0e68e76 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
@@ -16,6 +16,8 @@
package com.android.systemui.media.dialog;
+import static com.android.systemui.Flags.legacyLeAudioSharing;
+
import android.content.Context;
import android.os.Bundle;
import android.util.FeatureFlagUtils;
@@ -108,6 +110,7 @@ public class MediaOutputDialog extends MediaOutputBaseDialog {
@Override
public boolean isBroadcastSupported() {
+ if (!legacyLeAudioSharing()) return false;
boolean isBluetoothLeDevice = false;
boolean isBroadcastEnabled = false;
if (FeatureFlagUtils.isEnabled(mContext,
diff --git a/packages/SystemUI/src/com/android/systemui/media/dream/MediaComplicationViewController.java b/packages/SystemUI/src/com/android/systemui/media/dream/MediaComplicationViewController.java
index b4153d72e64c..7f6398be526d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dream/MediaComplicationViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dream/MediaComplicationViewController.java
@@ -21,9 +21,9 @@ import static com.android.systemui.media.dream.dagger.MediaComplicationComponent
import android.widget.FrameLayout;
-import com.android.systemui.media.controls.ui.MediaHierarchyManager;
-import com.android.systemui.media.controls.ui.MediaHost;
-import com.android.systemui.media.controls.ui.MediaHostState;
+import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager;
+import com.android.systemui.media.controls.ui.view.MediaHost;
+import com.android.systemui.media.controls.ui.view.MediaHostState;
import com.android.systemui.util.ViewController;
import javax.inject.Inject;
diff --git a/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java
index 08c626c9e0eb..88a5f78e407d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java
@@ -27,9 +27,9 @@ import com.android.systemui.CoreStartable;
import com.android.systemui.complication.DreamMediaEntryComplication;
import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.media.controls.models.player.MediaData;
-import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData;
-import com.android.systemui.media.controls.pipeline.MediaDataManager;
+import com.android.systemui.media.controls.domain.pipeline.MediaDataManager;
+import com.android.systemui.media.controls.shared.model.MediaData;
+import com.android.systemui.media.controls.shared.model.SmartspaceMediaData;
import javax.inject.Inject;
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttFlags.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttFlags.kt
index 8a565fa86b35..03bc9350674b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttFlags.kt
@@ -26,8 +26,4 @@ import javax.inject.Inject
class MediaTttFlags @Inject constructor(private val featureFlags: FeatureFlags) {
/** */
fun isMediaTttEnabled(): Boolean = featureFlags.isEnabled(Flags.MEDIA_TAP_TO_TRANSFER)
-
- /** Check whether the flag for the receiver success state is enabled. */
- fun isMediaTttReceiverSuccessRippleEnabled(): Boolean =
- featureFlags.isEnabled(Flags.MEDIA_TTT_RECEIVER_SUCCESS_RIPPLE)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
index 6e9485e5947c..416eae1b2d0c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
@@ -266,8 +266,7 @@ open class MediaTttChipControllerReceiver @Inject constructor(
// translation animation.
bounceAnimator.removeAllUpdateListeners()
bounceAnimator.cancel()
- if (removalReason == ChipStateReceiver.TRANSFER_TO_RECEIVER_SUCCEEDED.name &&
- mediaTttFlags.isMediaTttReceiverSuccessRippleEnabled()) {
+ if (removalReason == ChipStateReceiver.TRANSFER_TO_RECEIVER_SUCCEEDED.name) {
rippleController.expandToSuccessState(rippleView, onAnimationEnd)
animateViewTranslationAndFade(
iconContainerView,
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavbarOrientationTrackingLogger.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/NavbarOrientationTrackingLogger.kt
new file mode 100644
index 000000000000..b1bd2863695a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavbarOrientationTrackingLogger.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.navigationbar
+
+import android.view.Surface
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.dagger.NavbarOrientationTrackingLog
+import javax.inject.Inject
+
+class NavbarOrientationTrackingLogger
+@Inject
+constructor(@NavbarOrientationTrackingLog private val buffer: LogBuffer) {
+ fun logPrimaryAndSecondaryVisibility(
+ methodName: String,
+ isViewVisible: Boolean,
+ isImmersiveMode: Boolean,
+ isSecondaryHandleVisible: Boolean,
+ currentRotation: Int,
+ startingQuickSwitchRotation: Int
+ ) {
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = methodName
+ bool1 = isViewVisible
+ bool2 = isImmersiveMode
+ bool3 = isSecondaryHandleVisible
+ int1 = startingQuickSwitchRotation
+ int2 = currentRotation
+ },
+ {
+ "Caller Method: $str1\n" +
+ "\tNavbar Visible: $bool1\n" +
+ "\tImmersive Mode: $bool2\n" +
+ "\tSecondary Handle Visible: $bool3\n" +
+ "\tDelta Rotation: ${getDeltaRotation(int1, int2)}\n" +
+ "\tStarting QuickSwitch Rotation: $int1\n" +
+ "\tCurrent Rotation: $int2\n"
+ }
+ )
+ }
+
+ private fun getDeltaRotation(oldRotation: Int, newRotation: Int): String {
+ var rotation: String = "0"
+ when (deltaRotation(oldRotation, newRotation)) {
+ Surface.ROTATION_90 -> {
+ rotation = "90"
+ }
+ Surface.ROTATION_180 -> {
+ rotation = "180"
+ }
+ Surface.ROTATION_270 -> {
+ rotation = "270"
+ }
+ }
+ return rotation
+ }
+
+ private fun deltaRotation(oldRotation: Int, newRotation: Int): Int {
+ var delta = newRotation - oldRotation
+ if (delta < 0) delta += 4
+ return delta
+ }
+}
+
+private const val TAG = "NavbarOrientationTracking"
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 95b75ac7a875..f4903f1f054f 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -282,6 +282,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
private final Rect mSamplingBounds = new Rect();
private final Binder mInsetsSourceOwner = new Binder();
private final NavBarButtonClickLogger mNavBarButtonClickLogger;
+ private final NavbarOrientationTrackingLogger mNavbarOrientationTrackingLogger;
/**
* When quickswitching between apps of different orientations, we draw a secondary home handle
@@ -557,7 +558,8 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
WakefulnessLifecycle wakefulnessLifecycle,
TaskStackChangeListeners taskStackChangeListeners,
DisplayTracker displayTracker,
- NavBarButtonClickLogger navBarButtonClickLogger) {
+ NavBarButtonClickLogger navBarButtonClickLogger,
+ NavbarOrientationTrackingLogger navbarOrientationTrackingLogger) {
super(navigationBarView);
mFrame = navigationBarFrame;
mContext = context;
@@ -600,6 +602,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
mDisplayTracker = displayTracker;
mEdgeBackGestureHandler = navBarHelper.getEdgeBackGestureHandler();
mNavBarButtonClickLogger = navBarButtonClickLogger;
+ mNavbarOrientationTrackingLogger = navbarOrientationTrackingLogger;
mNavColorSampleMargin = getResources()
.getDimensionPixelSize(R.dimen.navigation_handle_sample_horizontal_margin);
@@ -906,6 +909,8 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
| WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT;
mWindowManager.addView(mOrientationHandle, mOrientationParams);
mOrientationHandle.setVisibility(View.GONE);
+
+ logNavbarOrientation("initSecondaryHomeHandleForRotation");
mOrientationParams.setFitInsetsTypes(0 /* types*/);
mOrientationHandleGlobalLayoutListener =
() -> {
@@ -966,6 +971,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
mWindowManager.updateViewLayout(mOrientationHandle, mOrientationParams);
mView.setVisibility(View.GONE);
mOrientationHandle.setVisibility(View.VISIBLE);
+ logNavbarOrientation("orientSecondaryHomeHandle");
}
private void resetSecondaryHandle() {
@@ -975,9 +981,23 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
mOrientationHandle.setVisibility(View.GONE);
}
mView.setVisibility(View.VISIBLE);
+ logNavbarOrientation("resetSecondaryHandle");
setOrientedHandleSamplingRegion(null);
}
+ /**
+ * Logging method for issues concerning Navbar/secondary handle visibility.
+ */
+ private void logNavbarOrientation(String methodName) {
+ boolean isViewVisible = (mView != null) && (mView.getVisibility() == View.VISIBLE);
+ boolean isSecondaryHandleVisible =
+ (mOrientationHandle != null) && (mOrientationHandle.getVisibility()
+ == View.VISIBLE);
+ mNavbarOrientationTrackingLogger.logPrimaryAndSecondaryVisibility(methodName, isViewVisible,
+ mShowOrientedHandleForImmersiveMode, isSecondaryHandleVisible, mCurrentRotation,
+ mStartingQuickSwitchRotation);
+ }
+
private void parseCurrentSysuiState() {
NavBarHelper.CurrentSysuiState state = mNavBarHelper.getCurrentSysuiState();
if (state.mWindowStateDisplayId == mDisplayId) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
index 8ff0e365f2ce..741336277119 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
@@ -51,7 +51,7 @@ import com.android.systemui.compose.ComposeFacade;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
-import com.android.systemui.media.controls.ui.MediaHost;
+import com.android.systemui.media.controls.ui.view.MediaHost;
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.plugins.qs.QSContainerController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -207,6 +207,9 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl
mFooterActionsViewBinder = footerActionsViewBinder;
mListeningAndVisibilityLifecycleOwner = new ListeningAndVisibilityLifecycleOwner();
mSceneContainerFlags = sceneContainerFlags;
+ if (mSceneContainerFlags.isEnabled()) {
+ mStatusBarState = StatusBarState.SHADE;
+ }
}
/**
@@ -506,10 +509,20 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl
}
}
- private boolean isKeyguardState() {
- // We want the freshest state here since otherwise we'll have some weirdness if earlier
- // listeners trigger updates
- return mStatusBarStateController.getCurrentOrUpcomingState() == KEYGUARD;
+ @VisibleForTesting
+ boolean isKeyguardState() {
+ if (mSceneContainerFlags.isEnabled()) {
+ return false;
+ } else {
+ // We want the freshest state here since otherwise we'll have some weirdness if earlier
+ // listeners trigger updates
+ return mStatusBarStateController.getCurrentOrUpcomingState() == KEYGUARD;
+ }
+ }
+
+ @VisibleForTesting
+ int getStatusBarState() {
+ return mStatusBarState;
}
private void updateShowCollapsedOnKeyguard() {
@@ -562,15 +575,17 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl
}
private void setKeyguardShowing(boolean keyguardShowing) {
- if (DEBUG) Log.d(TAG, "setKeyguardShowing " + keyguardShowing);
- mLastQSExpansion = -1;
+ if (!mSceneContainerFlags.isEnabled()) {
+ if (DEBUG) Log.d(TAG, "setKeyguardShowing " + keyguardShowing);
+ mLastQSExpansion = -1;
- if (mQSAnimator != null) {
- mQSAnimator.setOnKeyguard(keyguardShowing);
- }
+ if (mQSAnimator != null) {
+ mQSAnimator.setOnKeyguard(keyguardShowing);
+ }
- mFooter.setKeyguardShowing(keyguardShowing);
- updateQsState();
+ mFooter.setKeyguardShowing(keyguardShowing);
+ updateQsState();
+ }
}
@Override
@@ -971,7 +986,7 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl
@Override
public void onStateChanged(int newState) {
- if (newState == mStatusBarState) {
+ if (mSceneContainerFlags.isEnabled() || newState == mStatusBarState) {
return;
}
mStatusBarState = newState;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index c3f5086b0096..2440651555d7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -27,9 +27,9 @@ import android.view.View;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.media.controls.ui.MediaHierarchyManager;
-import com.android.systemui.media.controls.ui.MediaHost;
-import com.android.systemui.media.controls.ui.MediaHostState;
+import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager;
+import com.android.systemui.media.controls.ui.view.MediaHost;
+import com.android.systemui.media.controls.ui.view.MediaHostState;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.qs.customize.QSCustomizerController;
import com.android.systemui.qs.dagger.QSScope;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index 1c37510fde34..975c871bd006 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -32,7 +32,7 @@ import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.Dumpable;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.media.controls.ui.MediaHost;
+import com.android.systemui.media.controls.ui.view.MediaHost;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.qs.QSTileView;
import com.android.systemui.qs.customize.QSCustomizerController;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
index f278dce047e0..a8e88da5d288 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
@@ -25,8 +25,8 @@ import androidx.annotation.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.media.controls.ui.MediaHierarchyManager;
-import com.android.systemui.media.controls.ui.MediaHost;
+import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager;
+import com.android.systemui.media.controls.ui.view.MediaHost;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.qs.customize.QSCustomizerController;
import com.android.systemui.qs.dagger.QSScope;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt
index 4bad45f19673..16aa99e22ae2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt
@@ -25,6 +25,8 @@ import com.android.systemui.qs.pipeline.data.repository.DefaultTilesQSHostReposi
import com.android.systemui.qs.pipeline.data.repository.DefaultTilesRepository
import com.android.systemui.qs.pipeline.data.repository.InstalledTilesComponentRepository
import com.android.systemui.qs.pipeline.data.repository.InstalledTilesComponentRepositoryImpl
+import com.android.systemui.qs.pipeline.data.repository.MinimumTilesRepository
+import com.android.systemui.qs.pipeline.data.repository.MinimumTilesResourceRepository
import com.android.systemui.qs.pipeline.data.repository.QSSettingsRestoredBroadcastRepository
import com.android.systemui.qs.pipeline.data.repository.QSSettingsRestoredRepository
import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository
@@ -81,6 +83,11 @@ abstract class QSPipelineModule {
impl: QSSettingsRestoredBroadcastRepository
): QSSettingsRestoredRepository
+ @Binds
+ abstract fun provideMinimumTilesRepository(
+ impl: MinimumTilesResourceRepository
+ ): MinimumTilesRepository
+
companion object {
/**
* Provides a logging buffer for all logs related to the new Quick Settings pipeline to log
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/MinimumTilesRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/MinimumTilesRepository.kt
new file mode 100644
index 000000000000..3a005c0ebfed
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/MinimumTilesRepository.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.data.repository
+
+import android.content.res.Resources
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+/**
+ * Provides the minimum number of tiles required in QS. The default number of tiles should be at
+ * least this many.
+ */
+interface MinimumTilesRepository {
+ val minNumberOfTiles: Int
+}
+
+/**
+ * Minimum number of tiles using the corresponding resource. The value will be read once upon
+ * creation, as it's not expected to change.
+ */
+@SysUISingleton
+class MinimumTilesResourceRepository @Inject constructor(@Main resources: Resources) :
+ MinimumTilesRepository {
+ override val minNumberOfTiles: Int =
+ resources.getInteger(R.integer.quick_settings_min_num_tiles)
+}
+
+/** Provides a fixed minimum number of tiles. */
+class MinimumTilesFixedRepository(override val minNumberOfTiles: Int = 0) : MinimumTilesRepository
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
index 00ea0b5c5ed3..214e9f097642 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
@@ -19,12 +19,12 @@ package com.android.systemui.qs.pipeline.data.repository
import android.annotation.UserIdInt
import android.content.res.Resources
import android.util.SparseArray
-import com.android.systemui.res.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.qs.pipeline.data.model.RestoreData
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
+import com.android.systemui.res.R
import com.android.systemui.retail.data.repository.RetailModeRepository
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -68,6 +68,9 @@ interface TileSpecRepository {
suspend fun reconcileRestore(restoreData: RestoreData, currentAutoAdded: Set<TileSpec>)
+ /** Prepend the default list of tiles to the current set of tiles */
+ suspend fun prependDefault(@UserIdInt userId: Int)
+
companion object {
/** Position to indicate the end of the list */
const val POSITION_AT_END = -1
@@ -152,6 +155,12 @@ constructor(
?.reconcileRestore(restoreData, currentAutoAdded)
}
+ override suspend fun prependDefault(
+ userId: Int,
+ ) {
+ userTileRepositories.get(userId)?.prependDefault()
+ }
+
companion object {
private const val DELIMITER = TilesSettingConverter.DELIMITER
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepository.kt
index 152fd0f83811..8ad5cb2c0a34 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepository.kt
@@ -50,9 +50,8 @@ constructor(
private val defaultTiles: List<TileSpec>
get() = defaultTilesRepository.defaultTiles
- private val changeEvents = MutableSharedFlow<ChangeAction>(
- extraBufferCapacity = CHANGES_BUFFER_SIZE
- )
+ private val changeEvents =
+ MutableSharedFlow<ChangeAction>(extraBufferCapacity = CHANGES_BUFFER_SIZE)
private lateinit var _tiles: StateFlow<List<TileSpec>>
@@ -163,6 +162,10 @@ constructor(
changeEvents.emit(RestoreTiles(restoreData, currentAutoAdded))
}
+ suspend fun prependDefault() {
+ changeEvents.emit(PrependDefault(defaultTiles))
+ }
+
sealed interface ChangeAction {
fun apply(currentTiles: List<TileSpec>): List<TileSpec>
}
@@ -199,6 +202,12 @@ constructor(
}
}
+ private data class PrependDefault(val defaultTiles: List<TileSpec>) : ChangeAction {
+ override fun apply(currentTiles: List<TileSpec>): List<TileSpec> {
+ return defaultTiles + currentTiles
+ }
+ }
+
private data class RestoreTiles(
val restoreData: RestoreData,
val currentAutoAdded: Set<TileSpec>,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddable.kt
index b22119966460..3f619c08261d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddable.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddable.kt
@@ -64,7 +64,8 @@ constructor(
fun maybeSend(profiles: List<UserInfo>) {
if (profiles.any { it.id == userId }) {
// We are looking at the profiles of the correct user.
- if (profiles.any { it.isManagedProfile }) {
+ // They need to be a managed enabled profile.
+ if (profiles.any { it.isManagedProfile && it.isEnabled }) {
trySend(
AutoAddSignal.Add(
spec,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractor.kt
index cde38359a871..187b4445637b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractor.kt
@@ -35,6 +35,7 @@ import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.filterIsInstance
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.take
@@ -55,6 +56,7 @@ constructor(
) : Dumpable {
private val initialized = AtomicBoolean(false)
+ private lateinit var currentTilesInteractor: CurrentTilesInteractor
/** Start collection of signals following the user from [currentTilesInteractor]. */
fun init(currentTilesInteractor: CurrentTilesInteractor) {
@@ -62,56 +64,72 @@ constructor(
return
}
+ this.currentTilesInteractor = currentTilesInteractor
dumpManager.registerNormalDumpable(TAG, this)
scope.launch {
currentTilesInteractor.userId.collectLatest { userId ->
coroutineScope {
- val previouslyAdded = repository.autoAddedTiles(userId).stateIn(this)
+ launch { collectAutoAddSignalsForUser(userId) }
+ launch { markTrackIfNotAddedTilesThatAreCurrent(userId) }
+ }
+ }
+ }
+ }
- autoAddables
- .map { addable ->
- val autoAddSignal = addable.autoAddSignal(userId)
- when (val lifecycle = addable.autoAddTracking) {
- is AutoAddTracking.Always -> autoAddSignal
- is AutoAddTracking.Disabled -> emptyFlow()
- is AutoAddTracking.IfNotAdded -> {
- if (lifecycle.spec !in previouslyAdded.value) {
- autoAddSignal.filterIsInstance<AutoAddSignal.Add>().take(1)
- } else {
- emptyFlow()
- }
- }
- }
+ private suspend fun markTrackIfNotAddedTilesThatAreCurrent(userId: Int) {
+ val trackIfNotAddedSpecs =
+ autoAddables
+ .map { it.autoAddTracking }
+ .filterIsInstance<AutoAddTracking.IfNotAdded>()
+ .map { it.spec }
+ currentTilesInteractor.currentTiles
+ .map { tiles -> tiles.map { it.spec } }
+ .collect {
+ it.filter { it in trackIfNotAddedSpecs }
+ .forEach { spec -> repository.markTileAdded(userId, spec) }
+ }
+ }
+
+ private suspend fun CoroutineScope.collectAutoAddSignalsForUser(userId: Int) {
+ val previouslyAdded = repository.autoAddedTiles(userId).stateIn(this)
+
+ autoAddables
+ .map { addable ->
+ val autoAddSignal = addable.autoAddSignal(userId)
+ when (val lifecycle = addable.autoAddTracking) {
+ is AutoAddTracking.Always -> autoAddSignal
+ is AutoAddTracking.Disabled -> emptyFlow()
+ is AutoAddTracking.IfNotAdded -> {
+ if (lifecycle.spec !in previouslyAdded.value) {
+ autoAddSignal.filterIsInstance<AutoAddSignal.Add>().take(1)
+ } else {
+ emptyFlow()
}
- .merge()
- .collect { signal ->
- when (signal) {
- is AutoAddSignal.Add -> {
- if (signal.spec !in previouslyAdded.value) {
- currentTilesInteractor.addTile(signal.spec, signal.position)
- qsPipelineLogger.logTileAutoAdded(
- userId,
- signal.spec,
- signal.position
- )
- repository.markTileAdded(userId, signal.spec)
- }
- }
- is AutoAddSignal.Remove -> {
- currentTilesInteractor.removeTiles(setOf(signal.spec))
- qsPipelineLogger.logTileAutoRemoved(userId, signal.spec)
- repository.unmarkTileAdded(userId, signal.spec)
- }
- is AutoAddSignal.RemoveTracking -> {
- qsPipelineLogger.logTileUnmarked(userId, signal.spec)
- repository.unmarkTileAdded(userId, signal.spec)
- }
- }
+ }
+ }
+ }
+ .merge()
+ .collect { signal ->
+ when (signal) {
+ is AutoAddSignal.Add -> {
+ if (signal.spec !in previouslyAdded.value) {
+ currentTilesInteractor.addTile(signal.spec, signal.position)
+ qsPipelineLogger.logTileAutoAdded(userId, signal.spec, signal.position)
+ repository.markTileAdded(userId, signal.spec)
}
+ }
+ is AutoAddSignal.Remove -> {
+ currentTilesInteractor.removeTiles(setOf(signal.spec))
+ qsPipelineLogger.logTileAutoRemoved(userId, signal.spec)
+ repository.unmarkTileAdded(userId, signal.spec)
+ }
+ is AutoAddSignal.RemoveTracking -> {
+ qsPipelineLogger.logTileUnmarked(userId, signal.spec)
+ repository.unmarkTileAdded(userId, signal.spec)
+ }
}
}
- }
}
override fun dump(pw: PrintWriter, args: Array<out String>) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
index 957cb1eb95d1..61896f0a3816 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
@@ -35,6 +35,7 @@ import com.android.systemui.qs.external.TileLifecycleManager
import com.android.systemui.qs.external.TileServiceKey
import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository
import com.android.systemui.qs.pipeline.data.repository.InstalledTilesComponentRepository
+import com.android.systemui.qs.pipeline.data.repository.MinimumTilesRepository
import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository
import com.android.systemui.qs.pipeline.domain.model.TileModel
import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository
@@ -131,6 +132,7 @@ constructor(
private val tileSpecRepository: TileSpecRepository,
private val installedTilesComponentRepository: InstalledTilesComponentRepository,
private val userRepository: UserRepository,
+ private val minimumTilesRepository: MinimumTilesRepository,
private val customTileStatePersister: CustomTileStatePersister,
private val newQSTileFactory: Lazy<NewQSTileFactory>,
private val tileFactory: QSFactory,
@@ -255,17 +257,23 @@ constructor(
val resolvedSpecs = newTileMap.keys.toList()
specsToTiles.clear()
specsToTiles.putAll(newTileMap)
- _currentSpecsAndTiles.value =
+ val newResolvedTiles =
newTileMap
.filter { it.value is TileOrNotInstalled.Tile }
.map {
TileModel(it.key, (it.value as TileOrNotInstalled.Tile).tile)
}
+
+ _currentSpecsAndTiles.value = newResolvedTiles
logger.logTilesNotInstalled(
newTileMap.filter { it.value is TileOrNotInstalled.NotInstalled }.keys,
newUser
)
- if (resolvedSpecs != newTileList) {
+ if (newResolvedTiles.size < minimumTilesRepository.minNumberOfTiles) {
+ // We ended up with not enough tiles (some may be not installed).
+ // Prepend the default set of tiles
+ launch { tileSpecRepository.prependDefault(currentUser.value) }
+ } else if (resolvedSpecs != newTileList) {
// There were some tiles that couldn't be created. Change the value in
// the
// repository
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractor.kt
index cff95d8368a2..1b3e58524815 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractor.kt
@@ -68,8 +68,7 @@ constructor(
serviceInteractor.setUser(user)
// Wait for the CustomTileInteractor to become initialized first, because
- // binding
- // the service might access it
+ // binding the service might access it
customTileInteractor.initForUser(user)
// Bind the TileService for not active tile
serviceInteractor.bindOnStart()
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractor.kt
index fd96fc5b6693..3e507cda4805 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractor.kt
@@ -77,6 +77,13 @@ constructor(
suspend fun isTileToggleable(): Boolean = customTileRepository.isTileToggleable()
/**
+ * True if the tile is active and false the otherwise. This effectively is a value of the
+ * [android.service.quicksettings.TileService.META_DATA_ACTIVE_TILE]. This is not the same as
+ * [Tile.STATE_ACTIVE].
+ */
+ suspend fun isTileActive(): Boolean = customTileRepository.isTileActive()
+
+ /**
* Initializes the repository for the current user. Suspends until it's safe to call [getTile]
* which needs at least one of the following:
* - defaults are loaded;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileServiceInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileServiceInteractor.kt
index acff40f816a9..79e903c7bce9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileServiceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileServiceInteractor.kt
@@ -58,7 +58,7 @@ constructor(
private val activityStarter: ActivityStarter,
private val userActionInteractor: Lazy<CustomTileUserActionInteractor>,
private val customTileInteractor: CustomTileInteractor,
- private val userRepository: UserRepository,
+ userRepository: UserRepository,
private val qsTileLogger: QSTileLogger,
private val tileServices: TileServices,
@QSTileScope private val tileScope: CoroutineScope,
@@ -78,10 +78,10 @@ constructor(
get() = tileReceivingInterface.mutableRefreshEvents
/** Clears all pending binding for an active tile and binds not active one. */
- fun bindOnStart() {
+ suspend fun bindOnStart() {
try {
with(getTileServiceManager()) {
- if (isActiveTile) {
+ if (customTileInteractor.isTileActive()) {
clearPendingBind()
} else {
setBindRequested(true)
@@ -94,10 +94,10 @@ constructor(
}
/** Binds active tile WITHOUT CLEARING pending binds. */
- fun bindOnClick() {
+ suspend fun bindOnClick() {
try {
with(getTileServiceManager()) {
- if (isActiveTile) {
+ if (customTileInteractor.isTileActive()) {
setBindRequested(true)
tileServiceInterface.onStartListening()
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractor.kt
index c3e1feaa6dfe..a16ac360e7e4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractor.kt
@@ -77,7 +77,7 @@ constructor(
qsTileLogger.logCustomTileUserActionDelivered(tileSpec)
}
- private fun click(
+ private suspend fun click(
view: View?,
activityLaunchForClick: PendingIntent?,
) {
@@ -114,9 +114,6 @@ constructor(
}
fun startActivityAndCollapse(pendingIntent: PendingIntent) {
- if (!pendingIntent.isActivity) {
- return
- }
if (!isTokenGranted) {
return
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
index 6e4f72daaf9c..72a5c468ea94 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
@@ -84,18 +84,36 @@ interface QSSceneAdapter {
*/
val qsHeight: Int
- sealed class State(
- val isVisible: Boolean,
- val expansion: Float,
- ) {
- data object CLOSED : State(false, 0f)
- data class Expanding(val progress: Float) : State(true, progress)
+ sealed interface State {
+
+ val isVisible: Boolean
+ val expansion: Float
+ val squishiness: Float
+
+ data object CLOSED : State {
+ override val isVisible = false
+ override val expansion = 0f
+ override val squishiness = 1f
+ }
+
+ /** State for expanding between QQS and QS */
+ data class Expanding(override val expansion: Float) : State {
+ override val isVisible = true
+ override val squishiness = 1f
+ }
+
+ /** State for appearing QQS from Lockscreen or Gone */
+ data class Unsquishing(override val squishiness: Float) : State {
+ override val isVisible = true
+ override val expansion = 0f
+ }
companion object {
// These are special cases of the expansion.
val QQS = Expanding(0f)
val QS = Expanding(1f)
+ /** Collapsing from QS to QQS. [progress] is 0f in QS and 1f in QQS. */
fun Collapsing(progress: Float) = Expanding(1f - progress)
}
}
@@ -232,7 +250,7 @@ constructor(
setQsVisible(state.isVisible)
setExpanded(state.isVisible)
setListening(state.isVisible)
- setQsExpansion(state.expansion, 1f, 0f, 1f)
- setTransitionToFullShadeProgress(false, 1f, 1f)
+ setQsExpansion(state.expansion, 1f, 0f, state.squishiness)
+ setTransitionToFullShadeProgress(false, 1f, state.squishiness)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
index 494c86c7b8c8..0add4443fa7a 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
@@ -19,7 +19,6 @@ package com.android.systemui.scene.domain.interactor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor
-import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.scene.data.repository.SceneContainerRepository
import com.android.systemui.scene.shared.logger.SceneLogger
import com.android.systemui.scene.shared.model.ObservableTransitionState
@@ -50,7 +49,6 @@ class SceneInteractor
constructor(
@Application private val applicationScope: CoroutineScope,
private val repository: SceneContainerRepository,
- private val powerInteractor: PowerInteractor,
private val logger: SceneLogger,
private val deviceUnlockedInteractor: DeviceUnlockedInteractor,
) {
@@ -189,9 +187,4 @@ constructor(
fun setTransitionState(transitionState: Flow<ObservableTransitionState>?) {
repository.setTransitionState(transitionState)
}
-
- /** Handles a user input event. */
- fun onUserInput() {
- powerInteractor.onUserTouch()
- }
}
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 605a5d9b6772..b642d38289fe 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
@@ -41,6 +41,7 @@ import com.android.systemui.scene.shared.logger.SceneLogger
import com.android.systemui.scene.shared.model.ObservableTransitionState
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor
import com.android.systemui.statusbar.notification.stack.shared.flexiNotifsEnabled
import com.android.systemui.statusbar.phone.CentralSurfaces
import com.android.systemui.statusbar.policy.domain.interactor.DeviceProvisioningInteractor
@@ -87,6 +88,7 @@ constructor(
private val windowController: NotificationShadeWindowController,
private val deviceProvisioningInteractor: DeviceProvisioningInteractor,
private val centralSurfaces: CentralSurfaces,
+ private val headsUpInteractor: HeadsUpNotificationInteractor,
) : CoreStartable {
override fun start() {
@@ -147,6 +149,15 @@ constructor(
}
}
}
+ .combine(headsUpInteractor.isHeadsUpOrAnimatingAway) {
+ visibilityForTransitionState,
+ isHeadsUpOrAnimatingAway ->
+ if (isHeadsUpOrAnimatingAway) {
+ true to "showing a HUN"
+ } else {
+ visibilityForTransitionState
+ }
+ }
.distinctUntilChanged()
} else {
flowOf(false to "Device not provisioned or Factory Reset Protection active")
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
index 4ccb18fced56..8408c51c86dc 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
@@ -29,6 +29,7 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.FlagToken
import com.android.systemui.flags.Flags.SCENE_CONTAINER_ENABLED
import com.android.systemui.flags.RefactorFlagUtils
+import com.android.systemui.keyguard.shared.ComposeLockscreen
import com.android.systemui.media.controls.util.MediaInSceneContainerFlag
import dagger.Module
import dagger.Provides
@@ -45,6 +46,7 @@ object SceneContainerFlag {
sceneContainer() && // mainAconfigFlag
keyguardBottomAreaRefactor() &&
migrateClocksToBlueprint() &&
+ ComposeLockscreen.isEnabled &&
MediaInSceneContainerFlag.isEnabled &&
// NOTE: Changes should also be made in getSecondaryFlags and @EnableSceneContainer
ComposeFacade.isComposeAvailable()
@@ -65,6 +67,7 @@ object SceneContainerFlag {
sequenceOf(
FlagToken(FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR, keyguardBottomAreaRefactor()),
FlagToken(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT, migrateClocksToBlueprint()),
+ ComposeLockscreen.token,
MediaInSceneContainerFlag.token,
// NOTE: Changes should also be made in isEnabled and @EnableSceneContainer
)
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt
index 7cff7ff1fd71..4c2c97981702 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt
@@ -25,8 +25,8 @@ import android.view.View
import android.view.WindowInsets
import android.widget.FrameLayout
import androidx.core.view.updateMargins
-import com.android.systemui.res.R
import com.android.systemui.compose.ComposeFacade
+import com.android.systemui.res.R
/** A view that can serve as the root of the main SysUI window. */
open class WindowRootView(
@@ -71,6 +71,7 @@ open class WindowRootView(
override fun onApplyWindowInsets(windowInsets: WindowInsets): WindowInsets? {
val insets = windowInsets.getInsetsIgnoringVisibility(WindowInsets.Type.systemBars())
+ val displayCutout = rootWindowInsets.displayCutout
if (fitsSystemWindows) {
val paddingChanged = insets.top != paddingTop || insets.bottom != paddingBottom
@@ -78,22 +79,23 @@ open class WindowRootView(
if (paddingChanged) {
setPadding(0, 0, 0, 0)
}
+
+ val pairInsets: Pair<Int, Int> =
+ layoutInsetsController.getinsets(windowInsets, displayCutout)
+ leftInset = pairInsets.first
+ rightInset = pairInsets.second
+ applyMargins()
} else {
val changed =
paddingLeft != 0 || paddingRight != 0 || paddingTop != 0 || paddingBottom != 0
if (changed) {
setPadding(0, 0, 0, 0)
}
+
+ leftInset = 0
+ rightInset = 0
}
- leftInset = 0
- rightInset = 0
- val displayCutout = rootWindowInsets.displayCutout
- val pairInsets: Pair<Int, Int> =
- layoutInsetsController.getinsets(windowInsets, displayCutout)
- leftInset = pairInsets.first
- rightInset = pairInsets.second
- applyMargins()
return windowInsets
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
index 5d290cefcd55..4cd3baa7a52e 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
@@ -17,8 +17,10 @@
package com.android.systemui.scene.ui.viewmodel
import android.view.MotionEvent
+import com.android.systemui.classifier.Classifier
import com.android.systemui.classifier.domain.interactor.FalsingInteractor
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.model.ObservableTransitionState
import com.android.systemui.scene.shared.model.SceneKey
@@ -33,6 +35,7 @@ class SceneContainerViewModel
constructor(
private val sceneInteractor: SceneInteractor,
private val falsingInteractor: FalsingInteractor,
+ private val powerInteractor: PowerInteractor,
) {
/**
* Keys of all scenes in the container.
@@ -63,7 +66,7 @@ constructor(
* Call this before the [MotionEvent] starts to propagate through the UI hierarchy.
*/
fun onMotionEvent(event: MotionEvent) {
- sceneInteractor.onUserInput()
+ powerInteractor.onUserTouch()
falsingInteractor.onTouchEvent(event)
}
@@ -77,7 +80,33 @@ constructor(
falsingInteractor.onMotionEventComplete()
}
- companion object {
- private const val SCENE_TRANSITION_LOGGING_REASON = "user input"
+ /**
+ * Returns `true` if a change to [toScene] is currently allowed; `false` otherwise.
+ *
+ * This is invoked only for user-initiated transitions. The goal is to check with the falsing
+ * system whether the change from the current scene to the given scene should be rejected due to
+ * it being a false touch.
+ */
+ fun canChangeScene(toScene: SceneKey): Boolean {
+ val interactionTypeOrNull =
+ when (toScene) {
+ SceneKey.Bouncer -> Classifier.BOUNCER_UNLOCK
+ SceneKey.Gone -> Classifier.UNLOCK
+ SceneKey.Shade -> Classifier.NOTIFICATION_DRAG_DOWN
+ SceneKey.QuickSettings -> Classifier.QUICK_SETTINGS
+ else -> null
+ }
+
+ return interactionTypeOrNull?.let { interactionType ->
+ // It's important that the falsing system is always queried, even if no enforcement will
+ // occur. This helps build up the right signal in the system.
+ val isFalseTouch = falsingInteractor.isFalseTouch(interactionType)
+
+ // Only enforce falsing if moving from the lockscreen scene to a new scene.
+ val fromLockscreenScene = currentScene.value == SceneKey.Lockscreen
+
+ !fromLockscreenScene || !isFalseTouch
+ }
+ ?: true
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt
index d6f1ed9c3334..1a997a764055 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt
@@ -50,6 +50,9 @@ interface UserTracker : UserContentResolverProvider, UserContextProvider {
* List of profiles associated with the current user.
*
* Quiet work profiles will still appear here, but will have the `QUIET_MODE` flag.
+ *
+ * Disabled work profiles will also appear here. Listeners will be notified when profiles go
+ * from disabled to enabled (as UserInfo are immutable) with the updated list.
*/
val userProfiles: List<UserInfo>
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 95d9bc490caa..7068f5fcc32b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -149,9 +149,9 @@ import com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransition
import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel;
import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel;
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel;
-import com.android.systemui.media.controls.pipeline.MediaDataManager;
-import com.android.systemui.media.controls.ui.KeyguardMediaController;
-import com.android.systemui.media.controls.ui.MediaHierarchyManager;
+import com.android.systemui.media.controls.domain.pipeline.MediaDataManager;
+import com.android.systemui.media.controls.ui.controller.KeyguardMediaController;
+import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager;
import com.android.systemui.model.SysUiState;
import com.android.systemui.navigationbar.NavigationBarController;
import com.android.systemui.navigationbar.NavigationBarView;
@@ -165,6 +165,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController.StateList
import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.power.shared.model.WakefulnessModel;
import com.android.systemui.res.R;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.shade.data.repository.FlingInfo;
import com.android.systemui.shade.data.repository.ShadeRepository;
import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor;
@@ -4595,8 +4596,10 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
public void onViewAttachedToWindow(View v) {
mFragmentService.getFragmentHostManager(mView)
.addTagListener(QS.TAG, mQsController.getQsFragmentListener());
- mStatusBarStateController.addCallback(mStatusBarStateListener);
- mStatusBarStateListener.onStateChanged(mStatusBarStateController.getState());
+ if (!SceneContainerFlag.isEnabled()) {
+ mStatusBarStateController.addCallback(mStatusBarStateListener);
+ mStatusBarStateListener.onStateChanged(mStatusBarStateController.getState());
+ }
mConfigurationController.addCallback(mConfigurationListener);
// Theme might have changed between inflating this view and attaching it to the
// window, so
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index f7fed537a167..8f9cef37dac7 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -60,6 +60,7 @@ import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
import com.android.systemui.res.R;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.scene.shared.flag.SceneContainerFlags;
import com.android.systemui.scene.ui.view.WindowRootViewComponent;
import com.android.systemui.settings.UserTracker;
@@ -506,7 +507,7 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
private void applyFitsSystemWindows(NotificationShadeWindowState state) {
boolean fitsSystemWindows = !state.isKeyguardShowingAndNotOccluded();
- if (mWindowRootView != null
+ if (!SceneContainerFlag.isEnabled() && mWindowRootView != null
&& mWindowRootView.getFitsSystemWindows() != fitsSystemWindows) {
mWindowRootView.setFitsSystemWindows(fitsSystemWindows);
mWindowRootView.requestApplyInsets();
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 99e91c1d332f..e5771785409f 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -511,6 +511,12 @@ public class NotificationShadeWindowViewController implements Dumpable {
return true;
}
}
+ } else if (migrateClocksToBlueprint()) {
+ // This final check handles swipes on HUNs and when Pulsing
+ if (!bouncerShowing && didNotificationPanelInterceptEvent(ev)) {
+ mShadeLogger.d("NSWVC: intercepted for HUN/PULSING");
+ return true;
+ }
}
return false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
index f7b9e4e35ef8..e7f970050033 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
@@ -71,8 +71,8 @@ import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.fragments.FragmentHostManager;
-import com.android.systemui.media.controls.pipeline.MediaDataManager;
-import com.android.systemui.media.controls.ui.MediaHierarchyManager;
+import com.android.systemui.media.controls.domain.pipeline.MediaDataManager;
+import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.res.R;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt
index d393f0d0b72b..4054a86960d3 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeEmptyImplModule.kt
@@ -17,6 +17,8 @@
package com.android.systemui.shade
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.shade.data.repository.PrivacyChipRepository
+import com.android.systemui.shade.data.repository.PrivacyChipRepositoryImpl
import com.android.systemui.shade.data.repository.ShadeRepository
import com.android.systemui.shade.data.repository.ShadeRepositoryImpl
import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor
@@ -61,4 +63,8 @@ abstract class ShadeEmptyImplModule {
abstract fun bindsShadeAnimationInteractor(
sai: ShadeAnimationInteractorEmptyImpl
): ShadeAnimationInteractor
+
+ @Binds
+ @SysUISingleton
+ abstract fun bindsPrivacyChipRepository(impl: PrivacyChipRepositoryImpl): PrivacyChipRepository
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
index 86fdceea57ef..5632766f2633 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
@@ -18,6 +18,8 @@ package com.android.systemui.shade
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.shade.data.repository.PrivacyChipRepository
+import com.android.systemui.shade.data.repository.PrivacyChipRepositoryImpl
import com.android.systemui.shade.data.repository.ShadeRepository
import com.android.systemui.shade.data.repository.ShadeRepositoryImpl
import com.android.systemui.shade.domain.interactor.BaseShadeInteractor
@@ -124,4 +126,8 @@ abstract class ShadeModule {
abstract fun bindsShadeViewController(
notificationPanelViewController: NotificationPanelViewController
): ShadeViewController
+
+ @Binds
+ @SysUISingleton
+ abstract fun bindsPrivacyChipRepository(impl: PrivacyChipRepositoryImpl): PrivacyChipRepository
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/PrivacyChipRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/PrivacyChipRepository.kt
new file mode 100644
index 000000000000..91c92cc8cd2f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/PrivacyChipRepository.kt
@@ -0,0 +1,133 @@
+/*
+ * 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.data.repository
+
+import android.content.IntentFilter
+import android.os.UserHandle
+import android.safetycenter.SafetyCenterManager
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.privacy.PrivacyConfig
+import com.android.systemui.privacy.PrivacyItem
+import com.android.systemui.privacy.PrivacyItemController
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.stateIn
+
+interface PrivacyChipRepository {
+ /** Whether or not the Safety Center is enabled. */
+ val isSafetyCenterEnabled: StateFlow<Boolean>
+
+ /** The list of PrivacyItems to be displayed by the privacy chip. */
+ val privacyItems: StateFlow<List<PrivacyItem>>
+
+ /** Whether or not mic & camera indicators are enabled in the device privacy config. */
+ val isMicCameraIndicationEnabled: StateFlow<Boolean>
+
+ /** Whether or not location indicators are enabled in the device privacy config. */
+ val isLocationIndicationEnabled: StateFlow<Boolean>
+}
+
+@SysUISingleton
+class PrivacyChipRepositoryImpl
+@Inject
+constructor(
+ @Application applicationScope: CoroutineScope,
+ private val privacyConfig: PrivacyConfig,
+ private val privacyItemController: PrivacyItemController,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+ broadcastDispatcher: BroadcastDispatcher,
+ private val safetyCenterManager: SafetyCenterManager,
+) : PrivacyChipRepository {
+ override val isSafetyCenterEnabled: StateFlow<Boolean> =
+ broadcastDispatcher
+ .broadcastFlow(
+ filter =
+ IntentFilter().apply {
+ addAction(SafetyCenterManager.ACTION_SAFETY_CENTER_ENABLED_CHANGED)
+ },
+ user = UserHandle.SYSTEM,
+ map = { _, _ -> safetyCenterManager.isSafetyCenterEnabled }
+ )
+ .onStart { emit(safetyCenterManager.isSafetyCenterEnabled) }
+ .flowOn(backgroundDispatcher)
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = false,
+ )
+
+ override val privacyItems: StateFlow<List<PrivacyItem>> =
+ conflatedCallbackFlow {
+ val callback =
+ object : PrivacyItemController.Callback {
+ override fun onPrivacyItemsChanged(privacyItems: List<PrivacyItem>) {
+ trySend(privacyItems)
+ }
+ }
+ privacyItemController.addCallback(callback)
+ awaitClose { privacyItemController.removeCallback(callback) }
+ }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.Eagerly,
+ initialValue = emptyList(),
+ )
+
+ override val isMicCameraIndicationEnabled: StateFlow<Boolean> =
+ conflatedCallbackFlow {
+ val callback =
+ object : PrivacyConfig.Callback {
+ override fun onFlagMicCameraChanged(flag: Boolean) {
+ trySend(flag)
+ }
+ }
+ privacyConfig.addCallback(callback)
+ awaitClose { privacyConfig.removeCallback(callback) }
+ }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.Eagerly,
+ initialValue = privacyItemController.micCameraAvailable,
+ )
+
+ override val isLocationIndicationEnabled: StateFlow<Boolean> =
+ conflatedCallbackFlow {
+ val callback =
+ object : PrivacyConfig.Callback {
+ override fun onFlagLocationChanged(flag: Boolean) {
+ trySend(flag)
+ }
+ }
+ privacyConfig.addCallback(callback)
+ awaitClose { privacyConfig.removeCallback(callback) }
+ }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.Eagerly,
+ initialValue = privacyItemController.locationAvailable,
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PrivacyChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PrivacyChipInteractor.kt
new file mode 100644
index 000000000000..4c6c31809275
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PrivacyChipInteractor.kt
@@ -0,0 +1,88 @@
+/*
+ * 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.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.privacy.OngoingPrivacyChip
+import com.android.systemui.privacy.PrivacyDialogController
+import com.android.systemui.privacy.PrivacyDialogControllerV2
+import com.android.systemui.privacy.PrivacyItem
+import com.android.systemui.shade.data.repository.PrivacyChipRepository
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+@SysUISingleton
+class PrivacyChipInteractor
+@Inject
+constructor(
+ @Application applicationScope: CoroutineScope,
+ private val repository: PrivacyChipRepository,
+ private val privacyDialogController: PrivacyDialogController,
+ private val privacyDialogControllerV2: PrivacyDialogControllerV2,
+ private val deviceProvisionedController: DeviceProvisionedController,
+) {
+ /** The list of PrivacyItems to be displayed by the privacy chip. */
+ val privacyItems: StateFlow<List<PrivacyItem>> = repository.privacyItems
+
+ /** Whether or not mic & camera indicators are enabled in the device privacy config. */
+ val isMicCameraIndicationEnabled: StateFlow<Boolean> = repository.isMicCameraIndicationEnabled
+
+ /** Whether or not location indicators are enabled in the device privacy config. */
+ val isLocationIndicationEnabled: StateFlow<Boolean> = repository.isLocationIndicationEnabled
+
+ /** Whether or not the privacy chip should be visible. */
+ val isChipVisible: StateFlow<Boolean> =
+ privacyItems
+ .map { it.isNotEmpty() }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = false,
+ )
+
+ /** Whether or not the privacy chip is enabled in the device privacy config. */
+ val isChipEnabled: StateFlow<Boolean> =
+ combine(
+ isMicCameraIndicationEnabled,
+ isLocationIndicationEnabled,
+ ) { micCamera, location ->
+ micCamera || location
+ }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = false,
+ )
+
+ /** Notifies that the privacy chip was clicked. */
+ fun onPrivacyChipClicked(privacyChip: OngoingPrivacyChip) {
+ if (!deviceProvisionedController.isDeviceProvisioned) return
+
+ if (repository.isSafetyCenterEnabled.value) {
+ privacyDialogControllerV2.showDialog(privacyChip.context, privacyChip)
+ } else {
+ privacyDialogController.showDialog(privacyChip.context)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
index 314637e4b27e..700825dd639c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
@@ -25,8 +25,10 @@ import android.os.UserHandle
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.privacy.OngoingPrivacyChip
+import com.android.systemui.privacy.PrivacyItem
import com.android.systemui.res.R
-import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.shade.domain.interactor.PrivacyChipInteractor
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
import java.util.Date
@@ -50,9 +52,9 @@ class ShadeHeaderViewModel
constructor(
@Application private val applicationScope: CoroutineScope,
context: Context,
- sceneInteractor: SceneInteractor,
mobileIconsInteractor: MobileIconsInteractor,
val mobileIconsViewModel: MobileIconsViewModel,
+ private val privacyChipInteractor: PrivacyChipInteractor,
broadcastDispatcher: BroadcastDispatcher,
) {
/** True if there is exactly one mobile connection. */
@@ -64,6 +66,23 @@ constructor(
.map { list -> list.map { it.subscriptionId } }
.stateIn(applicationScope, SharingStarted.WhileSubscribed(), emptyList())
+ /** The list of PrivacyItems to be displayed by the privacy chip. */
+ val privacyItems: StateFlow<List<PrivacyItem>> = privacyChipInteractor.privacyItems
+
+ /** Whether or not mic & camera indicators are enabled in the device privacy config. */
+ val isMicCameraIndicationEnabled: StateFlow<Boolean> =
+ privacyChipInteractor.isMicCameraIndicationEnabled
+
+ /** Whether or not location indicators are enabled in the device privacy config. */
+ val isLocationIndicationEnabled: StateFlow<Boolean> =
+ privacyChipInteractor.isLocationIndicationEnabled
+
+ /** Whether or not the privacy chip should be visible. */
+ val isPrivacyChipVisible: StateFlow<Boolean> = privacyChipInteractor.isChipVisible
+
+ /** Whether or not the privacy chip is enabled in the device privacy config. */
+ val isPrivacyChipEnabled: StateFlow<Boolean> = privacyChipInteractor.isChipEnabled
+
private val longerPattern = context.getString(R.string.abbrev_wday_month_day_no_year_alarm)
private val shorterPattern = context.getString(R.string.abbrev_month_day_no_year)
private val longerDateFormat = MutableStateFlow(getFormatFromPattern(longerPattern))
@@ -97,6 +116,11 @@ constructor(
applicationScope.launch { updateDateTexts(false) }
}
+ /** Notifies that the privacy chip was clicked. */
+ fun onPrivacyChipClicked(privacyChip: OngoingPrivacyChip) {
+ privacyChipInteractor.onPrivacyChipClicked(privacyChip)
+ }
+
private fun updateDateTexts(invalidateFormats: Boolean) {
if (invalidateFormats) {
longerDateFormat.value = getFormatFromPattern(longerPattern)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
index 9af2d58910b6..abdfa536f63d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
@@ -19,7 +19,7 @@ package com.android.systemui.shade.ui.viewmodel
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
-import com.android.systemui.media.controls.pipeline.MediaDataManager
+import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
import com.android.systemui.qs.ui.adapter.QSSceneAdapter
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
diff --git a/packages/SystemUI/src/com/android/systemui/slice/SliceViewManagerExt.kt b/packages/SystemUI/src/com/android/systemui/slice/SliceViewManagerExt.kt
new file mode 100644
index 000000000000..384acc493c4a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/slice/SliceViewManagerExt.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.slice
+
+import android.net.Uri
+import androidx.slice.Slice
+import androidx.slice.SliceViewManager
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.launch
+
+/**
+ * Returns updating [Slice] for a [sliceUri]. It's null when there is no slice available for the
+ * provided Uri. This can change overtime because of external changes (like device being
+ * connected/disconnected).
+ */
+fun SliceViewManager.sliceForUri(sliceUri: Uri): Flow<Slice?> =
+ ConflatedCallbackFlow.conflatedCallbackFlow {
+ val callback = SliceViewManager.SliceCallback { launch { send(it) } }
+
+ val slice = bindSlice(sliceUri)
+ send(slice)
+ registerSliceCallback(sliceUri, callback)
+ awaitClose { unregisterSliceCallback(sliceUri, callback) }
+ }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index ffb11dd3cf92..3908edec7da5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -177,6 +177,7 @@ public class CommandQueue extends IStatusBar.Stub implements
private static final int MSG_CONFIRM_IMMERSIVE_PROMPT = 77 << MSG_SHIFT;
private static final int MSG_IMMERSIVE_CHANGED = 78 << MSG_SHIFT;
private static final int MSG_SET_QS_TILES = 79 << MSG_SHIFT;
+ private static final int MSG_ENTER_DESKTOP = 80 << MSG_SHIFT;
public static final int FLAG_EXCLUDE_NONE = 0;
public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0;
public static final int FLAG_EXCLUDE_RECENTS_PANEL = 1 << 1;
@@ -520,6 +521,11 @@ public class CommandQueue extends IStatusBar.Stub implements
* @see IStatusBar#immersiveModeChanged
*/
default void immersiveModeChanged(int rootDisplayAreaId, boolean isImmersiveMode) {}
+
+ /**
+ * @see IStatusBar#enterDesktop(int)
+ */
+ default void enterDesktop(int displayId) {}
}
@VisibleForTesting
@@ -609,14 +615,7 @@ public class CommandQueue extends IStatusBar.Stub implements
args.argi2 = state1;
args.argi3 = state2;
args.argi4 = animate ? 1 : 0;
- Message msg = mHandler.obtainMessage(MSG_DISABLE, args);
- if (Looper.myLooper() == mHandler.getLooper()) {
- // If its the right looper execute immediately so hides can be handled quickly.
- mHandler.handleMessage(msg);
- msg.recycle();
- } else {
- msg.sendToTarget();
- }
+ mHandler.obtainMessage(MSG_DISABLE, args).sendToTarget();
}
}
@@ -1420,6 +1419,13 @@ public class CommandQueue extends IStatusBar.Stub implements
mHandler.obtainMessage(MSG_GO_TO_FULLSCREEN_FROM_SPLIT).sendToTarget();
}
+ @Override
+ public void enterDesktop(int displayId) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = displayId;
+ mHandler.obtainMessage(MSG_ENTER_DESKTOP, args).sendToTarget();
+ }
+
private final class H extends Handler {
private H(Looper l) {
super(l);
@@ -1914,6 +1920,13 @@ public class CommandQueue extends IStatusBar.Stub implements
mCallbacks.get(i).immersiveModeChanged(rootDisplayAreaId, isImmersiveMode);
}
break;
+ case MSG_ENTER_DESKTOP:
+ args = (SomeArgs) msg.obj;
+ int displayId = args.argi1;
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ mCallbacks.get(i).enterDesktop(displayId);
+ }
+ break;
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
index e5982428fe14..a4741a509d72 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
@@ -25,8 +25,11 @@ import android.app.AppGlobals;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
import android.graphics.Matrix;
@@ -71,6 +74,7 @@ import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto;
import com.android.settingslib.Utils;
import com.android.systemui.res.R;
+import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.google.android.material.bottomsheet.BottomSheetBehavior;
import com.google.android.material.bottomsheet.BottomSheetDialog;
@@ -114,6 +118,7 @@ public final class KeyboardShortcutListSearch {
private Button mButtonInput;
private Button mButtonOpenApps;
private Button mButtonSpecificApp;
+ private CharSequence mCurrentAppPackageName;
private TextView mNoSearchResults;
private final SparseArray<String> mSpecialCharacterNames = new SparseArray<>();
@@ -412,8 +417,10 @@ public final class KeyboardShortcutListSearch {
mWindowManager.requestAppKeyboardShortcuts(result -> {
// Add specific app shortcuts
if (result.isEmpty()) {
+ mCurrentAppPackageName = null;
mKeySearchResultMap.put(SHORTCUT_SPECIFICAPP_INDEX, false);
} else {
+ mCurrentAppPackageName = result.get(0).getPackageName();
mSpecificAppGroup.addAll(reMapToKeyboardShortcutMultiMappingGroup(result));
mKeySearchResultMap.put(SHORTCUT_SPECIFICAPP_INDEX, true);
}
@@ -823,6 +830,7 @@ public final class KeyboardShortcutListSearch {
mNoSearchResults = keyboardShortcutsView.findViewById(R.id.shortcut_search_no_result);
mKeyboardShortcutsBottomSheetDialog.setContentView(keyboardShortcutsView);
setButtonsDefaultStatus(keyboardShortcutsView);
+ populateCurrentAppButton();
populateKeyboardShortcutSearchList(shortcutsContainer);
// Workaround for solve issue about dialog not full expanded when landscape.
@@ -1272,6 +1280,41 @@ public final class KeyboardShortcutListSearch {
mFullButtonList.add(mButtonSpecificApp);
}
+ private void resetCurrentAppButton() {
+ if (mButtonSpecificApp == null) {
+ return;
+ }
+ mButtonSpecificApp.setText(
+ mContext.getString(R.string.keyboard_shortcut_search_category_current_app));
+ // TODO(b/325252986): Reset icon once the icon is implemented
+ }
+
+ private void populateCurrentAppButton() {
+ if (mButtonSpecificApp == null) {
+ return;
+ }
+ if (mCurrentAppPackageName != null) {
+ final int userId = mContext.getUserId();
+ try {
+ PackageManager pmUser = CentralSurfaces.getPackageManagerForUser(
+ mContext,
+ userId);
+ ApplicationInfo appInfo = pmUser.getApplicationInfoAsUser(
+ mCurrentAppPackageName.toString(),
+ 0,
+ userId);
+ // According to the API, we will always get a label
+ mButtonSpecificApp.setText(pmUser.getApplicationLabel(appInfo));
+ // TODO(b/325252986): Show icon once it has been defined
+ } catch (NameNotFoundException e) {
+ Log.e(TAG, "Package name not found", e);
+ resetCurrentAppButton();
+ }
+ } else {
+ resetCurrentAppButton();
+ }
+ }
+
private void setButtonFocusColor(int i, boolean isFocused) {
if (isFocused) {
mFullButtonList.get(i).setTextColor(getColorOfTextColorOnAccent());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeKeyguardTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeKeyguardTransitionController.kt
index 0b470c179dbf..9f098e79f759 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeKeyguardTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeKeyguardTransitionController.kt
@@ -4,7 +4,7 @@ import android.content.Context
import android.util.IndentingPrintWriter
import android.util.MathUtils
import com.android.systemui.dump.DumpManager
-import com.android.systemui.media.controls.ui.MediaHierarchyManager
+import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
import com.android.systemui.res.R
import com.android.systemui.shade.ShadeLockscreenInteractor
import com.android.systemui.statusbar.policy.ConfigurationController
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index 1dbd87e0aa97..a59d753971f6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -24,7 +24,7 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.keyguard.domain.interactor.NaturalScrollingSettingObserver
-import com.android.systemui.media.controls.ui.MediaHierarchyManager
+import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
import com.android.systemui.navigationbar.gestural.Utilities.isTrackpadScroll
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.ActivityStarter.OnDismissAction
@@ -68,7 +68,7 @@ constructor(
private val mediaHierarchyManager: MediaHierarchyManager,
private val scrimTransitionController: LockscreenShadeScrimTransitionController,
private val keyguardTransitionControllerFactory:
- LockscreenShadeKeyguardTransitionController.Factory,
+ LockscreenShadeKeyguardTransitionController.Factory,
private val depthController: NotificationShadeDepthController,
private val context: Context,
private val splitShadeOverScrollerFactory: SplitShadeLockScreenOverScroller.Factory,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index 9916ef6ff9ee..1a06eec1cb4d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -156,14 +156,12 @@ public class NotificationLockscreenUserManagerImpl implements
final String action = intent.getAction();
if (ACTION_KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED.equals(action)) {
- if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
- mKeyguardAllowingNotifications =
- intent.getBooleanExtra(EXTRA_KM_PRIVATE_NOTIFS_ALLOWED, false);
- if (mCurrentUserId == getSendingUserId()) {
- boolean changed = updateLockscreenNotificationSetting();
- if (changed) {
- notifyNotificationStateChanged();
- }
+ mKeyguardAllowingNotifications =
+ intent.getBooleanExtra(EXTRA_KM_PRIVATE_NOTIFS_ALLOWED, false);
+ if (mCurrentUserId == getSendingUserId()) {
+ boolean changed = updateLockscreenNotificationSetting();
+ if (changed) {
+ notifyNotificationStateChanged();
}
}
}
@@ -176,36 +174,26 @@ public class NotificationLockscreenUserManagerImpl implements
final String action = intent.getAction();
if (ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED.equals(action)) {
- if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
- boolean changed = false;
- int sendingUserId = getSendingUserId();
- if (sendingUserId == USER_ALL) {
- // When a Device Owner triggers changes it's sent as USER_ALL. Normalize
- // the user before calling into DPM
- sendingUserId = mCurrentUserId;
- @SuppressLint("MissingPermission")
- List<UserInfo> users = mUserManager.getUsers();
- for (int i = users.size() - 1; i >= 0; i--) {
- changed |= updateDpcSettings(users.get(i).id);
- }
- } else {
- changed |= updateDpcSettings(sendingUserId);
- }
-
- if (mCurrentUserId == sendingUserId) {
- changed |= updateLockscreenNotificationSetting();
- }
- if (changed) {
- notifyNotificationStateChanged();
+ boolean changed = false;
+ int sendingUserId = getSendingUserId();
+ if (sendingUserId == USER_ALL) {
+ // When a Device Owner triggers changes it's sent as USER_ALL. Normalize
+ // the user before calling into DPM
+ sendingUserId = mCurrentUserId;
+ @SuppressLint("MissingPermission")
+ List<UserInfo> users = mUserManager.getUsers();
+ for (int i = users.size() - 1; i >= 0; i--) {
+ changed |= updateDpcSettings(users.get(i).id);
}
} else {
- if (isCurrentProfile(getSendingUserId())) {
- mUsersAllowingPrivateNotifications.clear();
- updateLockscreenNotificationSetting();
- // TODO(b/231976036): Consolidate pipeline invalidations related to this
- // event
- // notifyNotificationStateChanged();
- }
+ changed |= updateDpcSettings(sendingUserId);
+ }
+
+ if (mCurrentUserId == sendingUserId) {
+ changed |= updateLockscreenNotificationSetting();
+ }
+ if (changed) {
+ notifyNotificationStateChanged();
}
}
}
@@ -225,12 +213,10 @@ public class NotificationLockscreenUserManagerImpl implements
updateCurrentProfilesCache();
} else if (Objects.equals(action, Intent.ACTION_USER_ADDED)){
updateCurrentProfilesCache();
- if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
- final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL);
- mBackgroundExecutor.execute(() -> {
- initValuesForUser(userId);
- });
- }
+ final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL);
+ mBackgroundExecutor.execute(() -> {
+ initValuesForUser(userId);
+ });
} else if (profileAvailabilityActions(action)) {
updateCurrentProfilesCache();
} else if (Objects.equals(action, Intent.ACTION_USER_UNLOCKED)) {
@@ -360,28 +346,16 @@ public class NotificationLockscreenUserManagerImpl implements
}
private void init() {
- mLockscreenSettingsObserver = new ExecutorContentObserver(
- mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)
- ? mBackgroundExecutor
- : mMainExecutor) {
+ mLockscreenSettingsObserver = new ExecutorContentObserver(mBackgroundExecutor) {
@Override
public void onChange(boolean selfChange, Collection<Uri> uris, int flags) {
- if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
- @SuppressLint("MissingPermission")
- List<UserInfo> users = mUserManager.getUsers();
- for (int i = users.size() - 1; i >= 0; i--) {
- onChange(selfChange, uris, flags,users.get(i).getUserHandle());
- }
- } else {
- // We don't know which user changed LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS or
- // LOCK_SCREEN_SHOW_NOTIFICATIONS, so we just dump our cache ...
- mUsersAllowingPrivateNotifications.clear();
- mUsersAllowingNotifications.clear();
- // ... and refresh all the notifications
- updateLockscreenNotificationSetting();
- notifyNotificationStateChanged();
+ @SuppressLint("MissingPermission")
+ List<UserInfo> users = mUserManager.getUsers();
+ for (int i = users.size() - 1; i >= 0; i--) {
+ onChange(selfChange, uris, flags,users.get(i).getUserHandle());
}
+
}
// Note: even though this is an override, this method is not called by the OS
@@ -390,22 +364,20 @@ public class NotificationLockscreenUserManagerImpl implements
@Override
public void onChange(boolean selfChange, Collection<Uri> uris,
int flags, UserHandle user) {
- if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
- boolean changed = false;
- for (Uri uri: uris) {
- if (SHOW_LOCKSCREEN.equals(uri)) {
- changed |= updateUserShowSettings(user.getIdentifier());
- } else if (SHOW_PRIVATE_LOCKSCREEN.equals(uri)) {
- changed |= updateUserShowPrivateSettings(user.getIdentifier());
- }
+ boolean changed = false;
+ for (Uri uri: uris) {
+ if (SHOW_LOCKSCREEN.equals(uri)) {
+ changed |= updateUserShowSettings(user.getIdentifier());
+ } else if (SHOW_PRIVATE_LOCKSCREEN.equals(uri)) {
+ changed |= updateUserShowPrivateSettings(user.getIdentifier());
}
+ }
- if (mCurrentUserId == user.getIdentifier()) {
- changed |= updateLockscreenNotificationSetting();
- }
- if (changed) {
- notifyNotificationStateChanged();
- }
+ if (mCurrentUserId == user.getIdentifier()) {
+ changed |= updateLockscreenNotificationSetting();
+ }
+ if (changed) {
+ notifyNotificationStateChanged();
}
}
};
@@ -432,16 +404,10 @@ public class NotificationLockscreenUserManagerImpl implements
mLockscreenSettingsObserver,
USER_ALL);
- if (!mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
- mContext.getContentResolver().registerContentObserver(
- Settings.Global.getUriFor(Settings.Global.ZEN_MODE), false,
- mSettingsObserver);
- }
mBroadcastDispatcher.registerReceiver(mAllUsersReceiver,
new IntentFilter(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED),
- mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)
- ? mBackgroundExecutor : null, UserHandle.ALL);
+ mBackgroundExecutor, UserHandle.ALL);
if (keyguardPrivateNotifications()) {
mBroadcastDispatcher.registerReceiver(mKeyguardReceiver,
new IntentFilter(ACTION_KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED),
@@ -471,17 +437,13 @@ public class NotificationLockscreenUserManagerImpl implements
mCurrentUserId = mUserTracker.getUserId(); // in case we reg'd receiver too late
updateCurrentProfilesCache();
- if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
- // Set up
- mBackgroundExecutor.execute(() -> {
- @SuppressLint("MissingPermission") List<UserInfo> users = mUserManager.getUsers();
- for (int i = users.size() - 1; i >= 0; i--) {
- initValuesForUser(users.get(i).id);
- }
- });
- } else {
- mSettingsObserver.onChange(false); // set up
- }
+ // Set up
+ mBackgroundExecutor.execute(() -> {
+ @SuppressLint("MissingPermission") List<UserInfo> users = mUserManager.getUsers();
+ for (int i = users.size() - 1; i >= 0; i--) {
+ initValuesForUser(users.get(i).id);
+ }
+ });
}
private void initValuesForUser(@UserIdInt int userId) {
@@ -519,26 +481,15 @@ public class NotificationLockscreenUserManagerImpl implements
boolean show;
boolean allowedByDpm;
- if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
- if (keyguardPrivateNotifications()) {
- show = mUsersUsersAllowingNotifications.get(mCurrentUserId);
- } else {
- show = mUsersUsersAllowingNotifications.get(mCurrentUserId)
- && mKeyguardAllowingNotifications;
- }
- // If DPC never notified us about a user, that means they have no policy for the user,
- // and they allow the behavior
- allowedByDpm = mUsersDpcAllowingNotifications.get(mCurrentUserId, true);
+ if (keyguardPrivateNotifications()) {
+ show = mUsersUsersAllowingNotifications.get(mCurrentUserId);
} else {
- show = mSecureSettings.getIntForUser(
- LOCK_SCREEN_SHOW_NOTIFICATIONS,
- 1,
- mCurrentUserId) != 0;
- final int dpmFlags = mDevicePolicyManager.getKeyguardDisabledFeatures(
- null /* admin */, mCurrentUserId);
- allowedByDpm = (dpmFlags
- & KEYGUARD_DISABLE_SECURE_NOTIFICATIONS) == 0;
+ show = mUsersUsersAllowingNotifications.get(mCurrentUserId)
+ && mKeyguardAllowingNotifications;
}
+ // If DPC never notified us about a user, that means they have no policy for the user,
+ // and they allow the behavior
+ allowedByDpm = mUsersDpcAllowingNotifications.get(mCurrentUserId, true);
final boolean oldValue = mShowLockscreenNotifications;
setShowLockscreenNotifications(show && allowedByDpm);
@@ -600,42 +551,24 @@ public class NotificationLockscreenUserManagerImpl implements
* when the lockscreen is in "public" (secure & locked) mode?
*/
public boolean userAllowsPrivateNotificationsInPublic(int userHandle) {
- if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
- if (userHandle == USER_ALL) {
- userHandle = mCurrentUserId;
- }
- if (mUsersUsersAllowingPrivateNotifications.indexOfKey(userHandle) < 0) {
- Log.i(TAG, "Asking for redact notifs setting too early", new Throwable());
- return false;
- }
- if (mUsersDpcAllowingPrivateNotifications.indexOfKey(userHandle) < 0) {
- Log.i(TAG, "Asking for redact notifs dpm override too early", new Throwable());
- return false;
- }
- if (keyguardPrivateNotifications()) {
- return mUsersUsersAllowingPrivateNotifications.get(userHandle)
- && mUsersDpcAllowingPrivateNotifications.get(userHandle)
- && mKeyguardAllowingNotifications;
- } else {
- return mUsersUsersAllowingPrivateNotifications.get(userHandle)
- && mUsersDpcAllowingPrivateNotifications.get(userHandle);
- }
+ if (userHandle == USER_ALL) {
+ userHandle = mCurrentUserId;
+ }
+ if (mUsersUsersAllowingPrivateNotifications.indexOfKey(userHandle) < 0) {
+ Log.i(TAG, "Asking for redact notifs setting too early", new Throwable());
+ return false;
+ }
+ if (mUsersDpcAllowingPrivateNotifications.indexOfKey(userHandle) < 0) {
+ Log.i(TAG, "Asking for redact notifs dpm override too early", new Throwable());
+ return false;
+ }
+ if (keyguardPrivateNotifications()) {
+ return mUsersUsersAllowingPrivateNotifications.get(userHandle)
+ && mUsersDpcAllowingPrivateNotifications.get(userHandle)
+ && mKeyguardAllowingNotifications;
} else {
- if (userHandle == USER_ALL) {
- return true;
- }
-
- if (mUsersAllowingPrivateNotifications.indexOfKey(userHandle) < 0) {
- final boolean allowedByUser = 0 != mSecureSettings.getIntForUser(
- LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0, userHandle);
- final boolean allowedByDpm = adminAllowsKeyguardFeature(userHandle,
- KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS);
- final boolean allowed = allowedByUser && allowedByDpm;
- mUsersAllowingPrivateNotifications.append(userHandle, allowed);
- return allowed;
- }
-
- return mUsersAllowingPrivateNotifications.get(userHandle);
+ return mUsersUsersAllowingPrivateNotifications.get(userHandle)
+ && mUsersDpcAllowingPrivateNotifications.get(userHandle);
}
}
@@ -688,48 +621,30 @@ public class NotificationLockscreenUserManagerImpl implements
* "public" (secure & locked) mode?
*/
public boolean userAllowsNotificationsInPublic(int userHandle) {
- if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
- // Unlike 'show private', settings does not show a copy of this setting for each
- // profile, so it inherits from the parent user.
- if (userHandle == USER_ALL || mCurrentManagedProfiles.contains(userHandle)) {
- userHandle = mCurrentUserId;
- }
- if (mUsersUsersAllowingNotifications.indexOfKey(userHandle) < 0) {
- // TODO(b/301955929): STOP_SHIP (stop flag flip): remove this read and use a safe
- // default value before moving to 'released'
- Log.wtf(TAG, "Asking for show notifs setting too early", new Throwable());
- updateUserShowSettings(userHandle);
- }
- if (mUsersDpcAllowingNotifications.indexOfKey(userHandle) < 0) {
- // TODO(b/301955929): STOP_SHIP (stop flag flip): remove this read and use a safe
- // default value before moving to 'released'
- Log.wtf(TAG, "Asking for show notifs dpm override too early", new Throwable());
- updateDpcSettings(userHandle);
- }
- if (keyguardPrivateNotifications()) {
- return mUsersUsersAllowingNotifications.get(userHandle)
- && mUsersDpcAllowingNotifications.get(userHandle);
- } else {
- return mUsersUsersAllowingNotifications.get(userHandle)
- && mUsersDpcAllowingNotifications.get(userHandle)
- && mKeyguardAllowingNotifications;
- }
+ // Unlike 'show private', settings does not show a copy of this setting for each
+ // profile, so it inherits from the parent user.
+ if (userHandle == USER_ALL || mCurrentManagedProfiles.contains(userHandle)) {
+ userHandle = mCurrentUserId;
+ }
+ if (mUsersUsersAllowingNotifications.indexOfKey(userHandle) < 0) {
+ // TODO(b/301955929): STOP_SHIP (stop flag flip): remove this read and use a safe
+ // default value before moving to 'released'
+ Log.wtf(TAG, "Asking for show notifs setting too early", new Throwable());
+ updateUserShowSettings(userHandle);
+ }
+ if (mUsersDpcAllowingNotifications.indexOfKey(userHandle) < 0) {
+ // TODO(b/301955929): STOP_SHIP (stop flag flip): remove this read and use a safe
+ // default value before moving to 'released'
+ Log.wtf(TAG, "Asking for show notifs dpm override too early", new Throwable());
+ updateDpcSettings(userHandle);
+ }
+ if (keyguardPrivateNotifications()) {
+ return mUsersUsersAllowingNotifications.get(userHandle)
+ && mUsersDpcAllowingNotifications.get(userHandle);
} else {
- if (isCurrentProfile(userHandle) && userHandle != mCurrentUserId) {
- return true;
- }
-
- if (mUsersAllowingNotifications.indexOfKey(userHandle) < 0) {
- final boolean allowedByUser = 0 != mSecureSettings.getIntForUser(
- LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, userHandle);
- final boolean allowedByDpm = adminAllowsKeyguardFeature(userHandle,
- KEYGUARD_DISABLE_SECURE_NOTIFICATIONS);
- final boolean allowedBySystem = mKeyguardManager.getPrivateNotificationsAllowed();
- final boolean allowed = allowedByUser && allowedByDpm && allowedBySystem;
- mUsersAllowingNotifications.append(userHandle, allowed);
- return allowed;
- }
- return mUsersAllowingNotifications.get(userHandle);
+ return mUsersUsersAllowingNotifications.get(userHandle)
+ && mUsersDpcAllowingNotifications.get(userHandle)
+ && mKeyguardAllowingNotifications;
}
}
@@ -766,13 +681,7 @@ public class NotificationLockscreenUserManagerImpl implements
return true;
}
NotificationEntry entry = mCommonNotifCollectionLazy.get().getEntry(key);
- if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
- return entry != null && entry.isChannelVisibilityPrivate();
- } else {
- return entry != null
- && entry.getRanking().getLockscreenVisibilityOverride()
- == Notification.VISIBILITY_PRIVATE;
- }
+ return entry != null && entry.isChannelVisibilityPrivate();
}
@SuppressLint("MissingPermission")
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index 9c4625e91110..d465973a5d12 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -30,9 +30,9 @@ import android.util.Log;
import com.android.systemui.Dumpable;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.media.controls.models.player.MediaData;
-import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData;
-import com.android.systemui.media.controls.pipeline.MediaDataManager;
+import com.android.systemui.media.controls.domain.pipeline.MediaDataManager;
+import com.android.systemui.media.controls.shared.model.MediaData;
+import com.android.systemui.media.controls.shared.model.SmartspaceMediaData;
import com.android.systemui.statusbar.dagger.CentralSurfacesModule;
import com.android.systemui.statusbar.notification.collection.NotifCollection;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index 537f8a866fed..36fc9bb3a2da 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -18,6 +18,7 @@ package com.android.systemui.statusbar;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_TRANSITION_FROM_AOD;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_TRANSITION_TO_AOD;
+import static com.android.systemui.util.kotlin.JavaAdapterKt.combineFlows;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -43,19 +44,26 @@ import com.android.internal.logging.UiEventLogger;
import com.android.keyguard.KeyguardClockSwitch;
import com.android.systemui.DejankUtils;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
import com.android.systemui.res.R;
+import com.android.systemui.scene.domain.interactor.SceneInteractor;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
+import com.android.systemui.scene.shared.model.SceneKey;
import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
import com.android.systemui.statusbar.policy.CallbackController;
import com.android.systemui.util.Compile;
import com.android.systemui.util.kotlin.JavaAdapter;
+import com.google.common.base.Preconditions;
+
import dagger.Lazy;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Comparator;
+import java.util.Map;
import javax.inject.Inject;
@@ -97,6 +105,8 @@ public class StatusBarStateControllerImpl implements
private final InteractionJankMonitor mInteractionJankMonitor;
private final JavaAdapter mJavaAdapter;
private final Lazy<ShadeInteractor> mShadeInteractorLazy;
+ private final Lazy<DeviceUnlockedInteractor> mDeviceUnlockedInteractorLazy;
+ private final Lazy<SceneInteractor> mSceneInteractorLazy;
private int mState;
private int mLastState;
private int mUpcomingState;
@@ -160,11 +170,15 @@ public class StatusBarStateControllerImpl implements
UiEventLogger uiEventLogger,
InteractionJankMonitor interactionJankMonitor,
JavaAdapter javaAdapter,
- Lazy<ShadeInteractor> shadeInteractorLazy) {
+ Lazy<ShadeInteractor> shadeInteractorLazy,
+ Lazy<DeviceUnlockedInteractor> deviceUnlockedInteractorLazy,
+ Lazy<SceneInteractor> sceneInteractorLazy) {
mUiEventLogger = uiEventLogger;
mInteractionJankMonitor = interactionJankMonitor;
mJavaAdapter = javaAdapter;
mShadeInteractorLazy = shadeInteractorLazy;
+ mDeviceUnlockedInteractorLazy = deviceUnlockedInteractorLazy;
+ mSceneInteractorLazy = sceneInteractorLazy;
for (int i = 0; i < HISTORY_SIZE; i++) {
mHistoricalRecords[i] = new HistoricalState();
}
@@ -174,6 +188,15 @@ public class StatusBarStateControllerImpl implements
public void start() {
mJavaAdapter.alwaysCollectFlow(mShadeInteractorLazy.get().isAnyExpanded(),
this::onShadeOrQsExpanded);
+
+ if (SceneContainerFlag.isEnabled()) {
+ mJavaAdapter.alwaysCollectFlow(
+ combineFlows(
+ mDeviceUnlockedInteractorLazy.get().isDeviceUnlocked(),
+ mSceneInteractorLazy.get().getCurrentScene(),
+ this::calculateStateFromSceneFramework),
+ this::onStatusBarStateChanged);
+ }
}
@Override
@@ -183,6 +206,10 @@ public class StatusBarStateControllerImpl implements
@Override
public boolean setState(int state, boolean force) {
+ if (SceneContainerFlag.isEnabled()) {
+ return false;
+ }
+
if (state > MAX_STATE || state < MIN_STATE) {
throw new IllegalArgumentException("Invalid state " + state);
}
@@ -194,6 +221,14 @@ public class StatusBarStateControllerImpl implements
return false;
}
+ updateStateAndNotifyListeners(state);
+ return true;
+ }
+
+ /**
+ * Updates the {@link StatusBarState} and notifies registered listeners, if needed.
+ */
+ private void updateStateAndNotifyListeners(int state) {
if (state != mUpcomingState) {
Log.d(TAG, "setState: requested state " + StatusBarState.toString(state)
+ "!= upcomingState: " + StatusBarState.toString(mUpcomingState) + ". "
@@ -229,15 +264,16 @@ public class StatusBarStateControllerImpl implements
}
DejankUtils.stopDetectingBlockingIpcs(tag);
}
-
- return true;
}
@Override
public void setUpcomingState(int nextState) {
+ if (SceneContainerFlag.isEnabled()) {
+ return;
+ }
+
recordHistoricalState(nextState /* newState */, mState /* lastState */, true);
updateUpcomingState(nextState);
-
}
private void updateUpcomingState(int upcomingState) {
@@ -468,7 +504,7 @@ public class StatusBarStateControllerImpl implements
@Override
public boolean goingToFullShade() {
- return mState == StatusBarState.SHADE && mLeaveOpenOnKeyguardHide;
+ return getState() == StatusBarState.SHADE && mLeaveOpenOnKeyguardHide;
}
@Override
@@ -599,6 +635,37 @@ public class StatusBarStateControllerImpl implements
state.mUpcoming = upcoming;
}
+ private int calculateStateFromSceneFramework(
+ boolean isDeviceUnlocked,
+ SceneKey currentScene) {
+ SceneContainerFlag.isUnexpectedlyInLegacyMode();
+
+ if (isDeviceUnlocked) {
+ return StatusBarState.SHADE;
+ } else {
+ return Preconditions.checkNotNull(sStatusBarStateByLockedSceneKey.get(currentScene));
+ }
+ }
+
+ /** Notifies that the {@link StatusBarState} has changed to the given new state. */
+ private void onStatusBarStateChanged(int newState) {
+ SceneContainerFlag.isUnexpectedlyInLegacyMode();
+
+ if (newState == mState) {
+ return;
+ }
+
+ updateStateAndNotifyListeners(newState);
+ }
+
+ private static final Map<SceneKey, Integer> sStatusBarStateByLockedSceneKey = Map.of(
+ SceneKey.Lockscreen.INSTANCE, StatusBarState.KEYGUARD,
+ SceneKey.Bouncer.INSTANCE, StatusBarState.KEYGUARD,
+ SceneKey.Communal.INSTANCE, StatusBarState.KEYGUARD,
+ SceneKey.Shade.INSTANCE, StatusBarState.SHADE_LOCKED,
+ SceneKey.QuickSettings.INSTANCE, StatusBarState.SHADE_LOCKED
+ );
+
/**
* For keeping track of our previous state to help with debugging
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
index f6d99bdefb9f..f960fcafafc6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -33,7 +33,7 @@ import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpHandler;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.media.controls.pipeline.MediaDataManager;
+import com.android.systemui.media.controls.domain.pipeline.MediaDataManager;
import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.shade.NotificationPanelViewController;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt
index 6429815bcb9f..11636bdf0f17 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt
@@ -17,6 +17,8 @@
package com.android.systemui.statusbar.data.repository
import android.graphics.Rect
+import android.view.InsetsFlags
+import android.view.ViewDebug
import android.view.WindowInsets
import android.view.WindowInsetsController
import android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS
@@ -305,8 +307,8 @@ constructor(
letterboxDetails.isNotEmpty()
override fun dump(pw: PrintWriter, args: Array<out String>) {
- pw.println("originalStatusBarAttributes: ${_originalStatusBarAttributes.value}")
- pw.println("modifiedStatusBarAttributes: ${modifiedStatusBarAttributes.value}")
+ pw.println("${_originalStatusBarAttributes.value}")
+ pw.println("${modifiedStatusBarAttributes.value}")
pw.println("statusBarMode: ${statusBarMode.value}")
}
@@ -320,7 +322,20 @@ constructor(
val navbarColorManagedByIme: Boolean,
@WindowInsets.Type.InsetsType val requestedVisibleTypes: Int,
val letterboxDetails: List<LetterboxDetails>,
- )
+ ) {
+ override fun toString(): String {
+ return """
+ StatusBarAttributes(
+ appearance=${appearance.toAppearanceString()},
+ appearanceRegions=$appearanceRegions,
+ navbarColorManagedByIme=$navbarColorManagedByIme,
+ requestedVisibleTypes=${requestedVisibleTypes.toWindowInsetsString()},
+ letterboxDetails=$letterboxDetails
+ )
+ """
+ .trimIndent()
+ }
+ }
/**
* Internal class keeping track of how [StatusBarAttributes] were transformed into new
@@ -331,9 +346,31 @@ constructor(
val appearanceRegions: List<AppearanceRegion>,
val navbarColorManagedByIme: Boolean,
val statusBarBounds: BoundsPair,
- )
+ ) {
+ override fun toString(): String {
+ return """
+ ModifiedStatusBarAttributes(
+ appearance=${appearance.toAppearanceString()},
+ appearanceRegions=$appearanceRegions,
+ navbarColorManagedByIme=$navbarColorManagedByIme,
+ statusBarBounds=$statusBarBounds
+ )
+ """
+ .trimIndent()
+ }
+ }
}
+private fun @receiver:WindowInsets.Type.InsetsType Int.toWindowInsetsString() =
+ "[${WindowInsets.Type.toString(this).replace(" ", ", ")}]"
+
+private fun @receiver:Appearance Int.toAppearanceString() =
+ if (this == 0) {
+ "NONE"
+ } else {
+ ViewDebug.flagsToString(InsetsFlags::class.java, "appearance", this)
+ }
+
@AssistedFactory
interface StatusBarModePerDisplayRepositoryFactory {
fun create(@Assisted("displayId") displayId: Int): StatusBarModePerDisplayRepositoryImpl
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ColorUpdateLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ColorUpdateLogger.kt
index c8f996a0272f..c416d434a8fb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ColorUpdateLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ColorUpdateLogger.kt
@@ -60,7 +60,7 @@ constructor(
val didAppend = frames.lastOrNull()?.tryAddTrigger(event) == true
if (!didAppend) {
frames.add(Frame(event))
- if (frames.size > maxFrames) frames.removeFirst()
+ if (frames.size > maxFrames) frames.removeAt(0)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java
index 16af9d9c82ae..072f56d2429d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java
@@ -16,7 +16,7 @@
package com.android.systemui.statusbar.notification.collection.coordinator;
-import static com.android.systemui.media.controls.pipeline.MediaDataManagerKt.isMediaNotification;
+import static com.android.systemui.media.controls.domain.pipeline.MediaDataManagerKt.isMediaNotification;
import android.os.RemoteException;
import android.service.notification.StatusBarNotification;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt
index b187cf15cccd..e5e5292d9a94 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt
@@ -15,6 +15,9 @@
*/
package com.android.systemui.statusbar.notification.data
+import com.android.systemui.statusbar.notification.data.repository.HeadsUpNotificationRepository
+import com.android.systemui.statusbar.notification.data.repository.HeadsUpNotificationRepositoryImpl
+import dagger.Binds
import dagger.Module
@Module(
@@ -23,4 +26,9 @@ import dagger.Module
NotificationSettingsRepositoryModule::class,
]
)
-interface NotificationDataLayerModule
+interface NotificationDataLayerModule {
+ @Binds
+ fun bindHeadsUpNotificationRepository(
+ impl: HeadsUpNotificationRepositoryImpl
+ ): HeadsUpNotificationRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpNotificationRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpNotificationRepository.kt
new file mode 100644
index 000000000000..d60ee9896758
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpNotificationRepository.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.data.repository
+
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.policy.HeadsUpManager
+import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+class HeadsUpNotificationRepositoryImpl
+@Inject
+constructor(
+ headsUpManager: HeadsUpManager,
+) : HeadsUpNotificationRepository {
+ override val hasPinnedHeadsUp: Flow<Boolean> = conflatedCallbackFlow {
+ val listener =
+ object : OnHeadsUpChangedListener {
+ override fun onHeadsUpPinnedModeChanged(inPinnedMode: Boolean) {
+ trySend(headsUpManager.hasPinnedHeadsUp())
+ }
+
+ override fun onHeadsUpPinned(entry: NotificationEntry?) {
+ trySend(headsUpManager.hasPinnedHeadsUp())
+ }
+
+ override fun onHeadsUpUnPinned(entry: NotificationEntry?) {
+ trySend(headsUpManager.hasPinnedHeadsUp())
+ }
+
+ override fun onHeadsUpStateChanged(entry: NotificationEntry, isHeadsUp: Boolean) {
+ trySend(headsUpManager.hasPinnedHeadsUp())
+ }
+ }
+ trySend(headsUpManager.hasPinnedHeadsUp())
+ headsUpManager.addListener(listener)
+ awaitClose { headsUpManager.removeListener(listener) }
+ }
+}
+
+interface HeadsUpNotificationRepository {
+ val hasPinnedHeadsUp: Flow<Boolean>
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
new file mode 100644
index 000000000000..5c8f354de485
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.domain.interactor
+
+import com.android.systemui.statusbar.notification.data.repository.HeadsUpNotificationRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+class HeadsUpNotificationInteractor @Inject constructor(repository: HeadsUpNotificationRepository) {
+ val isHeadsUpOrAnimatingAway: Flow<Boolean> =
+ // TODO(b/296118689): Needs to include the animating away state.
+ repository.hasPinnedHeadsUp
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt
index 9fb453afb55e..65ab4fdeb77b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt
@@ -136,7 +136,7 @@ object FooterViewBinder {
}
launch {
- viewModel.clearAllButton.accessibilityDescriptionId.collect { textId ->
+ viewModel.manageOrHistoryButton.accessibilityDescriptionId.collect { textId ->
footer.setManageOrHistoryButtonDescription(textId)
}
}
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 d7fe36f75418..332ece428a6e 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
@@ -219,15 +219,11 @@ class KeyguardNotificationVisibilityProviderImpl @Inject constructor(
}
private fun isRankingVisibilitySecret(entry: NotificationEntry): Boolean {
- return if (featureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
- // ranking.lockscreenVisibilityOverride contains possibly out of date DPC and Setting
- // info, and NotificationLockscreenUserManagerImpl is already listening for updates
- // to those
- entry.ranking.channel != null && entry.ranking.channel.lockscreenVisibility ==
+ // ranking.lockscreenVisibilityOverride contains possibly out of date DPC and Setting
+ // info, and NotificationLockscreenUserManagerImpl is already listening for updates
+ // to those
+ return entry.ranking.channel != null && entry.ranking.channel.lockscreenVisibility ==
VISIBILITY_SECRET
- } else {
- entry.ranking.lockscreenVisibilityOverride == VISIBILITY_SECRET
- }
}
override fun dump(pw: PrintWriter, args: Array<out String>) = pw.asIndenting().run {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/CallLayoutSetDataAsyncFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/CallLayoutSetDataAsyncFactory.kt
deleted file mode 100644
index 4deebdb8de7d..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/CallLayoutSetDataAsyncFactory.kt
+++ /dev/null
@@ -1,37 +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.
- */
-
-package com.android.systemui.statusbar.notification.row
-
-import android.content.Context
-import android.util.AttributeSet
-import android.view.View
-import com.android.internal.widget.CallLayout
-import javax.inject.Inject
-
-class CallLayoutSetDataAsyncFactory @Inject constructor() : NotifRemoteViewsFactory {
- override fun instantiate(
- row: ExpandableNotificationRow,
- @NotificationRowContentBinder.InflationFlag layoutType: Int,
- parent: View?,
- name: String,
- context: Context,
- attrs: AttributeSet
- ): View? =
- if (name == CallLayout::class.java.name)
- CallLayout(context, attrs).apply { setSetDataAsyncEnabled(true) }
- else null
-}
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
index 195fe785b538..dab89c5235b0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt
@@ -31,7 +31,6 @@ constructor(
featureFlags: FeatureFlags,
precomputedTextViewFactory: PrecomputedTextViewFactory,
bigPictureLayoutInflaterFactory: BigPictureLayoutInflaterFactory,
- callLayoutSetDataAsyncFactory: CallLayoutSetDataAsyncFactory,
optimizedLinearLayoutFactory: NotificationOptimizedLinearLayoutFactory
) : NotifRemoteViewsFactoryContainer {
override val factories: Set<NotifRemoteViewsFactory> = buildSet {
@@ -39,9 +38,6 @@ constructor(
if (featureFlags.isEnabled(Flags.BIGPICTURE_NOTIFICATION_LAZY_LOADING)) {
add(bigPictureLayoutInflaterFactory)
}
- if (featureFlags.isEnabled(Flags.CALL_LAYOUT_ASYNC_SET_DATA)) {
- add(callLayoutSetDataAsyncFactory)
- }
if (notifLinearlayoutOptimized()) {
add(optimizedLinearLayoutFactory)
}
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 913d5f6d3848..e288e857bf4a 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
@@ -43,7 +43,7 @@ import android.widget.RemoteViews;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.widget.ImageMessageConsumer;
import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.NotifInflation;
import com.android.systemui.media.controls.util.MediaFeatureFlag;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.InflationTask;
@@ -82,7 +82,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
private final NotificationRemoteInputManager mRemoteInputManager;
private final NotifRemoteViewCache mRemoteViewCache;
private final ConversationNotificationProcessor mConversationProcessor;
- private final Executor mBgExecutor;
+ private final Executor mInflationExecutor;
private final SmartReplyStateInflater mSmartReplyStateInflater;
private final NotifLayoutInflaterFactory.Provider mNotifLayoutInflaterFactoryProvider;
private final NotificationContentInflaterLogger mLogger;
@@ -93,7 +93,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
NotificationRemoteInputManager remoteInputManager,
ConversationNotificationProcessor conversationProcessor,
MediaFeatureFlag mediaFeatureFlag,
- @Background Executor bgExecutor,
+ @NotifInflation Executor inflationExecutor,
SmartReplyStateInflater smartRepliesInflater,
NotifLayoutInflaterFactory.Provider notifLayoutInflaterFactoryProvider,
NotificationContentInflaterLogger logger) {
@@ -101,7 +101,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
mRemoteInputManager = remoteInputManager;
mConversationProcessor = conversationProcessor;
mIsMediaInQS = mediaFeatureFlag.getEnabled();
- mBgExecutor = bgExecutor;
+ mInflationExecutor = inflationExecutor;
mSmartReplyStateInflater = smartRepliesInflater;
mNotifLayoutInflaterFactoryProvider = notifLayoutInflaterFactoryProvider;
mLogger = logger;
@@ -138,7 +138,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
cancelContentViewFrees(row, contentToBind);
AsyncInflationTask task = new AsyncInflationTask(
- mBgExecutor,
+ mInflationExecutor,
mInflateSynchronously,
/* reInflateFlags = */ contentToBind,
mRemoteViewCache,
@@ -157,7 +157,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
if (mInflateSynchronously) {
task.onPostExecute(task.doInBackground());
} else {
- task.executeOnExecutor(mBgExecutor);
+ task.executeOnExecutor(mInflationExecutor);
}
}
@@ -208,7 +208,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
}
apply(
- mBgExecutor,
+ mInflationExecutor,
inflateSynchronously,
result,
reInflateFlags,
@@ -416,7 +416,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
}
private static CancellationSignal apply(
- Executor bgExecutor,
+ Executor inflationExecutor,
boolean inflateSynchronously,
InflationProgress result,
@InflationFlag int reInflateFlags,
@@ -447,7 +447,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
}
};
logger.logAsyncTaskProgress(entry, "applying contracted view");
- applyRemoteView(bgExecutor, inflateSynchronously, result, reInflateFlags, flag,
+ applyRemoteView(inflationExecutor, inflateSynchronously, result, reInflateFlags, flag,
remoteViewCache, entry, row, isNewView, remoteViewClickHandler, callback,
privateLayout, privateLayout.getContractedChild(),
privateLayout.getVisibleWrapper(
@@ -474,11 +474,10 @@ public class NotificationContentInflater implements NotificationRowContentBinder
}
};
logger.logAsyncTaskProgress(entry, "applying expanded view");
- applyRemoteView(bgExecutor, inflateSynchronously, result, reInflateFlags, flag,
- remoteViewCache, entry, row, isNewView, remoteViewClickHandler,
+ applyRemoteView(inflationExecutor, inflateSynchronously, result, reInflateFlags,
+ flag, remoteViewCache, entry, row, isNewView, remoteViewClickHandler,
callback, privateLayout, privateLayout.getExpandedChild(),
- privateLayout.getVisibleWrapper(
- NotificationContentView.VISIBLE_TYPE_EXPANDED), runningInflations,
+ privateLayout.getVisibleWrapper(VISIBLE_TYPE_EXPANDED), runningInflations,
applyCallback, logger);
}
}
@@ -502,11 +501,10 @@ public class NotificationContentInflater implements NotificationRowContentBinder
}
};
logger.logAsyncTaskProgress(entry, "applying heads up view");
- applyRemoteView(bgExecutor, inflateSynchronously, result, reInflateFlags, flag,
- remoteViewCache, entry, row, isNewView, remoteViewClickHandler,
+ applyRemoteView(inflationExecutor, inflateSynchronously, result, reInflateFlags,
+ flag, remoteViewCache, entry, row, isNewView, remoteViewClickHandler,
callback, privateLayout, privateLayout.getHeadsUpChild(),
- privateLayout.getVisibleWrapper(
- VISIBLE_TYPE_HEADSUP), runningInflations,
+ privateLayout.getVisibleWrapper(VISIBLE_TYPE_HEADSUP), runningInflations,
applyCallback, logger);
}
}
@@ -529,7 +527,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
}
};
logger.logAsyncTaskProgress(entry, "applying public view");
- applyRemoteView(bgExecutor, inflateSynchronously, result, reInflateFlags, flag,
+ applyRemoteView(inflationExecutor, inflateSynchronously, result, reInflateFlags, flag,
remoteViewCache, entry, row, isNewView, remoteViewClickHandler, callback,
publicLayout, publicLayout.getContractedChild(),
publicLayout.getVisibleWrapper(NotificationContentView.VISIBLE_TYPE_CONTRACTED),
@@ -551,7 +549,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
@VisibleForTesting
static void applyRemoteView(
- Executor bgExecutor,
+ Executor inflationExecutor,
boolean inflateSynchronously,
final InflationProgress result,
final @InflationFlag int reInflateFlags,
@@ -655,14 +653,14 @@ public class NotificationContentInflater implements NotificationRowContentBinder
cancellationSignal = newContentView.applyAsync(
result.packageContext,
parentLayout,
- bgExecutor,
+ inflationExecutor,
listener,
remoteViewClickHandler);
} else {
cancellationSignal = newContentView.reapplyAsync(
result.packageContext,
existingView,
- bgExecutor,
+ inflationExecutor,
listener,
remoteViewClickHandler);
}
@@ -918,7 +916,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
private final boolean mUsesIncreasedHeadsUpHeight;
private final @InflationFlag int mReInflateFlags;
private final NotifRemoteViewCache mRemoteViewCache;
- private final Executor mBgExecutor;
+ private final Executor mInflationExecutor;
private ExpandableNotificationRow mRow;
private Exception mError;
private RemoteViews.InteractionHandler mRemoteViewClickHandler;
@@ -930,7 +928,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
private final NotificationContentInflaterLogger mLogger;
private AsyncInflationTask(
- Executor bgExecutor,
+ Executor inflationExecutor,
boolean inflateSynchronously,
@InflationFlag int reInflateFlags,
NotifRemoteViewCache cache,
@@ -948,7 +946,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
NotificationContentInflaterLogger logger) {
mEntry = entry;
mRow = row;
- mBgExecutor = bgExecutor;
+ mInflationExecutor = inflationExecutor;
mInflateSynchronously = inflateSynchronously;
mReInflateFlags = reInflateFlags;
mRemoteViewCache = cache;
@@ -1067,7 +1065,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
if (mError == null) {
// Logged in detail in apply.
mCancellationSignal = apply(
- mBgExecutor,
+ mInflationExecutor,
mInflateSynchronously,
result,
mReInflateFlags,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
index cfc433a09c4d..d269eda6795a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
@@ -19,7 +19,7 @@ import android.annotation.ColorInt
import android.util.Log
import android.view.View
import com.android.internal.annotations.VisibleForTesting
-import com.android.systemui.media.controls.ui.KeyguardMediaController
+import com.android.systemui.media.controls.ui.controller.KeyguardMediaController
import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager
import com.android.systemui.statusbar.notification.SourceType
import com.android.systemui.statusbar.notification.collection.render.MediaContainerController
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 d2ff266319d0..8dfac8617ff1 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
@@ -74,7 +74,7 @@ import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository;
import com.android.systemui.keyguard.shared.model.KeyguardState;
import com.android.systemui.keyguard.shared.model.TransitionStep;
-import com.android.systemui.media.controls.ui.KeyguardMediaController;
+import com.android.systemui.media.controls.ui.controller.KeyguardMediaController;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
@@ -1039,6 +1039,11 @@ public class NotificationStackScrollLayoutController implements Dumpable {
mView.setTranslationY(translationY);
}
+ /** Set view x-translation */
+ public void setTranslationX(float translationX) {
+ mView.setTranslationX(translationX);
+ }
+
public int indexOfChild(View view) {
return mView.indexOfChild(view);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
index 30708b708f25..2d9c63efee53 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
@@ -23,7 +23,7 @@ import androidx.annotation.VisibleForTesting
import com.android.systemui.res.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.media.controls.pipeline.MediaDataManager
+import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
import com.android.systemui.statusbar.LockscreenShadeTransitionController
import com.android.systemui.statusbar.StatusBarState.KEYGUARD
import com.android.systemui.statusbar.SysuiStatusBarStateController
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStatsLoggerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStatsLoggerImpl.kt
index 4897b4275f75..534e5c3fda87 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStatsLoggerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStatsLoggerImpl.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.stack.ui.view
+import android.os.Trace
import android.service.notification.NotificationListenerService
import androidx.annotation.VisibleForTesting
import com.android.internal.statusbar.IStatusBarService
@@ -182,6 +183,8 @@ constructor(
maybeLogVisibilityChanges(newlyVisible, noLongerVisible, activeNotifCount)
updateExpansionStates(newlyVisible, noLongerVisible)
+ Trace.traceCounter(Trace.TRACE_TAG_APP, "Notifications [Active]", activeNotifCount)
+ Trace.traceCounter(Trace.TRACE_TAG_APP, "Notifications [Visible]", newVisibilities.size)
lastLoggedVisibilities.clear()
lastLoggedVisibilities.putAll(newVisibilities)
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 daea8af8f334..5191053b6f72 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
@@ -91,10 +91,10 @@ object SharedNotificationContainerBinder {
if (!sceneContainerFlags.flexiNotifsEnabled()) {
launch {
// Only temporarily needed, until flexi notifs go live
- viewModel.shadeCollpaseFadeIn.collect { fadeIn ->
+ viewModel.shadeCollapseFadeIn.collect { fadeIn ->
if (fadeIn) {
android.animation.ValueAnimator.ofFloat(0f, 1f).apply {
- duration = 350
+ duration = 250
addUpdateListener { animation ->
controller.setMaxAlphaForExpansion(
animation.getAnimatedFraction()
@@ -144,6 +144,8 @@ object SharedNotificationContainerBinder {
.collect { y -> controller.setTranslationY(y) }
}
+ launch { viewModel.translationX.collect { x -> controller.translationX = x } }
+
if (!sceneContainerFlags.isEnabled()) {
launch {
viewModel.expansionAlpha(viewState).collect {
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 f325157c598d..052e35c44bbe 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
@@ -133,6 +133,21 @@ constructor(
.distinctUntilChanged()
.onStart { emit(false) }
+ /**
+ * Shade locked is a legacy concept, but necessary to mimic current functionality. Listen for
+ * both SHADE_LOCKED and shade/qs expansion in order to determine lock state, as one can arrive
+ * before the other.
+ */
+ private val isShadeLocked: Flow<Boolean> =
+ combine(
+ keyguardInteractor.statusBarState.map { it == SHADE_LOCKED },
+ shadeInteractor.qsExpansion.map { it > 0f },
+ shadeInteractor.shadeExpansion.map { it > 0f },
+ ) { isShadeLocked, isQsExpanded, isShadeExpanded ->
+ isShadeLocked && (isQsExpanded || isShadeExpanded)
+ }
+ .distinctUntilChanged()
+
val shadeCollapseFadeInComplete = MutableStateFlow(false)
val configurationBasedDimensions: Flow<ConfigurationBasedDimensions> =
@@ -190,7 +205,7 @@ constructor(
)
/** Are we purely on the glanceable hub without the shade/qs? */
- internal val isOnGlanceableHubWithoutShade: Flow<Boolean> =
+ val isOnGlanceableHubWithoutShade: Flow<Boolean> =
combine(
communalInteractor.isIdleOnCommunal,
// Shade with notifications
@@ -208,12 +223,12 @@ constructor(
)
/** Fade in only for use after the shade collapses */
- val shadeCollpaseFadeIn: Flow<Boolean> =
+ val shadeCollapseFadeIn: Flow<Boolean> =
flow {
while (currentCoroutineContext().isActive) {
emit(false)
// Wait for shade to be fully expanded
- keyguardInteractor.statusBarState.first { it == SHADE_LOCKED }
+ isShadeLocked.first { it }
// ... and then for it to be collapsed
isOnLockscreenWithoutShade.first { it }
emit(true)
@@ -330,16 +345,16 @@ constructor(
// shade expansion or swipe to dismiss
combineTransform(
isOnLockscreenWithoutShade,
- shadeCollpaseFadeIn,
+ shadeCollapseFadeIn,
alphaForShadeAndQsExpansion,
keyguardInteractor.dismissAlpha,
) {
isOnLockscreenWithoutShade,
- shadeCollpaseFadeIn,
+ shadeCollapseFadeIn,
alphaForShadeAndQsExpansion,
dismissAlpha ->
if (isOnLockscreenWithoutShade) {
- if (!shadeCollpaseFadeIn && dismissAlpha != null) {
+ if (!shadeCollapseFadeIn && dismissAlpha != null) {
emit(dismissAlpha)
}
} else {
@@ -363,14 +378,9 @@ constructor(
lockscreenToGlanceableHubRunning,
glanceableHubToLockscreenRunning,
merge(
- lockscreenToGlanceableHubTransitionViewModel.notificationAlpha,
- glanceableHubToLockscreenTransitionViewModel.notificationAlpha,
- )
- .onStart {
- // Transition flows don't emit a value on start, kick things off so the
- // combine starts.
- emit(1f)
- }
+ lockscreenToGlanceableHubTransitionViewModel.notificationAlpha,
+ glanceableHubToLockscreenTransitionViewModel.notificationAlpha,
+ )
) { lockscreenToGlanceableHubRunning, glanceableHubToLockscreenRunning, alpha ->
if (isOnGlanceableHubWithoutShade) {
// Notifications should not be visible on the glanceable hub.
@@ -409,6 +419,16 @@ constructor(
}
/**
+ * The container may need to be translated in the x direction as the keyguard fades out, such as
+ * when swiping open the glanceable hub from the lockscreen.
+ */
+ val translationX: Flow<Float> =
+ merge(
+ lockscreenToGlanceableHubTransitionViewModel.notificationTranslationX,
+ glanceableHubToLockscreenTransitionViewModel.notificationTranslationX,
+ )
+
+ /**
* When on keyguard, there is limited space to display notifications so calculate how many could
* be shown. Otherwise, there is no limit since the vertical space will be scrollable.
*
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ComponentSystemUIDialog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ComponentSystemUIDialog.kt
index 4fe9c8ccca0b..f3a4f0e64924 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ComponentSystemUIDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ComponentSystemUIDialog.kt
@@ -24,6 +24,7 @@ import android.view.ViewGroup
import androidx.activity.OnBackPressedDispatcher
import androidx.activity.OnBackPressedDispatcherOwner
import androidx.activity.setViewTreeOnBackPressedDispatcherOwner
+import androidx.annotation.GravityInt
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry
@@ -56,6 +57,7 @@ class ComponentSystemUIDialog(
sysUiState: SysUiState,
broadcastDispatcher: BroadcastDispatcher,
dialogTransitionAnimator: DialogTransitionAnimator,
+ @GravityInt private val dialogGravity: Int?,
) :
SystemUIDialog(
context,
@@ -90,6 +92,7 @@ class ComponentSystemUIDialog(
@CallSuper
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+ dialogGravity?.let { window?.setGravity(it) }
onBackPressedDispatcher.setOnBackInvokedDispatcher(onBackInvokedDispatcher)
savedStateRegistryController.performRestore(savedInstanceState)
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
index 323ab8065a32..613efaa148f5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
@@ -185,7 +185,7 @@ constructor(
*/
fun getStatusBarContentInsetsForRotation(@Rotation rotation: Int): Insets =
traceSection(tag = "StatusBarContentInsetsProvider.getStatusBarContentInsetsForRotation") {
- val sysUICutout = sysUICutoutProvider.cutoutInfoForCurrentDisplay()
+ val sysUICutout = sysUICutoutProvider.cutoutInfoForCurrentDisplayAndRotation()
val displayCutout = sysUICutout?.cutout
val key = getCacheKey(rotation, displayCutout)
@@ -227,7 +227,7 @@ constructor(
*/
@JvmOverloads
fun getStatusBarContentAreaForRotation(@Rotation rotation: Int): Rect {
- val sysUICutout = sysUICutoutProvider.cutoutInfoForCurrentDisplay()
+ val sysUICutout = sysUICutoutProvider.cutoutInfoForCurrentDisplayAndRotation()
val displayCutout = sysUICutout?.cutout
val key = getCacheKey(rotation, displayCutout)
return insetsCache[key]
@@ -528,7 +528,7 @@ private fun getStatusBarContentBounds(
var leftMargin = minLeft
var rightMargin = minRight
for (cutoutRect in cutoutRects) {
- val protectionRect = sysUICutout.cameraProtection?.cutoutBounds
+ val protectionRect = sysUICutout.cameraProtection?.bounds
val actualCutoutRect =
if (protectionRect?.intersects(cutoutRect) == true) {
rectUnion(cutoutRect, protectionRect)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialogFactory.kt
index 553edf9b5d13..1edd4d11351c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialogFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialogFactory.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.phone
import android.content.Context
+import androidx.annotation.GravityInt
import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.qualifiers.Application
@@ -43,11 +44,14 @@ constructor(
* @param context the [Context] in which the dialog will be constructed.
* @param dismissOnDeviceLock whether the dialog should be automatically dismissed when the
* device is locked (true by default).
+ * @param dialogGravity is one of the [android.view.Gravity] and determines dialog position on
+ * the screen.
*/
fun create(
context: Context = this.applicationContext,
theme: Int = SystemUIDialog.DEFAULT_THEME,
dismissOnDeviceLock: Boolean = SystemUIDialog.DEFAULT_DISMISS_ON_DEVICE_LOCK,
+ @GravityInt dialogGravity: Int? = null,
): ComponentSystemUIDialog {
Assert.isMainThread()
@@ -59,6 +63,7 @@ constructor(
sysUiState,
broadcastDispatcher,
dialogTransitionAnimator,
+ dialogGravity,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
index 1c33d3fd0288..bef6b0ba483f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
@@ -18,15 +18,22 @@ package com.android.systemui.statusbar.pipeline.satellite.ui.viewmodel
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository
+import com.android.systemui.statusbar.pipeline.dagger.OemSatelliteInputLog
import com.android.systemui.statusbar.pipeline.satellite.domain.interactor.DeviceBasedSatelliteInteractor
import com.android.systemui.statusbar.pipeline.satellite.ui.model.SatelliteIconModel
import javax.inject.Inject
+import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.stateIn
@@ -42,25 +49,44 @@ constructor(
interactor: DeviceBasedSatelliteInteractor,
@Application scope: CoroutineScope,
airplaneModeRepository: AirplaneModeRepository,
+ @OemSatelliteInputLog logBuffer: LogBuffer,
) {
- private val shouldShowIcon: StateFlow<Boolean> =
- interactor.areAllConnectionsOutOfService
- .flatMapLatest { allOos ->
- if (!allOos) {
- flowOf(false)
+ private val shouldShowIcon: Flow<Boolean> =
+ interactor.areAllConnectionsOutOfService.flatMapLatest { allOos ->
+ if (!allOos) {
+ flowOf(false)
+ } else {
+ combine(interactor.isSatelliteAllowed, airplaneModeRepository.isAirplaneMode) {
+ isSatelliteAllowed,
+ isAirplaneMode ->
+ isSatelliteAllowed && !isAirplaneMode
+ }
+ }
+ }
+
+ // This adds a 10 seconds delay before showing the icon
+ private val shouldActuallyShowIcon: StateFlow<Boolean> =
+ shouldShowIcon
+ .distinctUntilChanged()
+ .flatMapLatest { shouldShow ->
+ if (shouldShow) {
+ logBuffer.log(
+ TAG,
+ LogLevel.INFO,
+ { long1 = DELAY_DURATION.inWholeSeconds },
+ { "Waiting $long1 seconds before showing the satellite icon" }
+ )
+ delay(DELAY_DURATION)
+ flowOf(true)
} else {
- combine(interactor.isSatelliteAllowed, airplaneModeRepository.isAirplaneMode) {
- isSatelliteAllowed,
- isAirplaneMode ->
- isSatelliteAllowed && !isAirplaneMode
- }
+ flowOf(false)
}
}
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
val icon: StateFlow<Icon?> =
combine(
- shouldShowIcon,
+ shouldActuallyShowIcon,
interactor.connectionState,
interactor.signalStrength,
) { shouldShow, state, signalStrength ->
@@ -71,4 +97,9 @@ constructor(
}
}
.stateIn(scope, SharingStarted.WhileSubscribed(), null)
+
+ companion object {
+ private const val TAG = "DeviceBasedSatelliteViewModel"
+ private val DELAY_DURATION = 10.seconds
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
index 1414150c5511..2c1780d3b304 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
@@ -158,7 +158,15 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
@Override
public void showNotification(@NonNull NotificationEntry entry) {
mLogger.logShowNotification(entry);
- addEntry(entry);
+
+ // Add new entry and begin managing it
+ HeadsUpEntry headsUpEntry = createHeadsUpEntry();
+ headsUpEntry.setEntry(entry);
+ mHeadsUpEntryMap.put(entry.getKey(), headsUpEntry);
+ onEntryAdded(headsUpEntry);
+ entry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+ entry.setIsHeadsUpEntry(true);
+
updateNotification(entry.getKey(), true /* shouldHeadsUpAgain */);
entry.setInterruption();
}
@@ -319,19 +327,6 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager {
}
/**
- * Add a new entry and begin managing it.
- * @param entry the entry to add
- */
- protected final void addEntry(@NonNull NotificationEntry entry) {
- HeadsUpEntry headsUpEntry = createHeadsUpEntry();
- headsUpEntry.setEntry(entry);
- mHeadsUpEntryMap.put(entry.getKey(), headsUpEntry);
- onEntryAdded(headsUpEntry);
- entry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
- entry.setIsHeadsUpEntry(true);
- }
-
- /**
* Manager-specific logic that should occur when an entry is added.
* @param headsUpEntry entry added
*/
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..600005b97610 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.policy;
+import static com.android.systemui.Flags.registerZenModeContentObserverBackground;
+
import android.app.AlarmManager;
import android.app.Flags;
import android.app.NotificationManager;
@@ -45,6 +47,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.Dumpable;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.settings.UserTracker;
@@ -104,6 +107,7 @@ public class ZenModeControllerImpl implements ZenModeController, Dumpable {
public ZenModeControllerImpl(
Context context,
@Main Handler handler,
+ @Background Handler bgHandler,
BroadcastDispatcher broadcastDispatcher,
DumpManager dumpManager,
GlobalSettings globalSettings,
@@ -134,9 +138,18 @@ public class ZenModeControllerImpl implements ZenModeController, Dumpable {
}
};
mNoMan = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
- globalSettings.registerContentObserver(Global.ZEN_MODE, modeContentObserver);
+ if (registerZenModeContentObserverBackground()) {
+ bgHandler.post(() -> {
+ globalSettings.registerContentObserver(Global.ZEN_MODE, modeContentObserver);
+ globalSettings.registerContentObserver(Global.ZEN_MODE_CONFIG_ETAG,
+ configContentObserver);
+ });
+ } else {
+ globalSettings.registerContentObserver(Global.ZEN_MODE, modeContentObserver);
+ globalSettings.registerContentObserver(Global.ZEN_MODE_CONFIG_ETAG,
+ configContentObserver);
+ }
updateZenMode(getModeSettingValueFromProvider());
- globalSettings.registerContentObserver(Global.ZEN_MODE_CONFIG_ETAG, configContentObserver);
updateZenModeConfig();
updateConsolidatedNotificationPolicy();
mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.java
index 7b652c11e9ec..6124f6383fff 100644
--- a/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.java
@@ -23,11 +23,13 @@ import android.os.HandlerThread;
import android.os.Looper;
import android.os.Process;
+import com.android.systemui.Flags;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.BroadcastRunning;
import com.android.systemui.dagger.qualifiers.LongRunning;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dagger.qualifiers.NotifInflation;
import dagger.Module;
import dagger.Provides;
@@ -50,6 +52,8 @@ public abstract class SysUIConcurrencyModule {
private static final Long LONG_SLOW_DELIVERY_THRESHOLD = 2500L;
private static final Long BROADCAST_SLOW_DISPATCH_THRESHOLD = 1000L;
private static final Long BROADCAST_SLOW_DELIVERY_THRESHOLD = 1000L;
+ private static final Long NOTIFICATION_INFLATION_SLOW_DISPATCH_THRESHOLD = 1000L;
+ private static final Long NOTIFICATION_INFLATION_SLOW_DELIVERY_THRESHOLD = 1000L;
/** Background Looper */
@Provides
@@ -90,6 +94,24 @@ public abstract class SysUIConcurrencyModule {
return thread.getLooper();
}
+ /** Notification inflation Looper */
+ @Provides
+ @SysUISingleton
+ @NotifInflation
+ public static Looper provideNotifInflationLooper(@Background Looper bgLooper) {
+ if (!Flags.dedicatedNotifInflationThread()) {
+ return bgLooper;
+ }
+
+ final HandlerThread thread = new HandlerThread("NotifInflation",
+ Process.THREAD_PRIORITY_BACKGROUND);
+ thread.start();
+ final Looper looper = thread.getLooper();
+ looper.setSlowLogThresholdMs(NOTIFICATION_INFLATION_SLOW_DISPATCH_THRESHOLD,
+ NOTIFICATION_INFLATION_SLOW_DELIVERY_THRESHOLD);
+ return looper;
+ }
+
/**
* Background Handler.
*
@@ -225,4 +247,12 @@ public abstract class SysUIConcurrencyModule {
thread.start();
return new Handler(thread.getLooper());
}
+
+ /** */
+ @Provides
+ @SysUISingleton
+ @NotifInflation
+ public static Executor provideNotifInflationExecutor(@NotifInflation Looper looper) {
+ return new ExecutorImpl(looper);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/FlowDumper.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/FlowDumper.kt
new file mode 100644
index 000000000000..e17274c435aa
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/FlowDumper.kt
@@ -0,0 +1,142 @@
+/*
+ * 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 android.util.IndentingPrintWriter
+import com.android.systemui.Dumpable
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.util.asIndenting
+import com.android.systemui.util.printCollection
+import java.io.PrintWriter
+import java.util.concurrent.ConcurrentHashMap
+import java.util.concurrent.atomic.AtomicBoolean
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.flow
+
+/**
+ * An interface which gives the implementing type flow extension functions which will register a
+ * given flow as a field in the Dumpable.
+ */
+interface FlowDumper : Dumpable {
+ /**
+ * Include the last emitted value of this Flow whenever it is being collected. Remove its value
+ * when collection ends.
+ *
+ * @param dumpName the name to use for this field in the dump output
+ */
+ fun <T> Flow<T>.dumpWhileCollecting(dumpName: String): Flow<T>
+
+ /**
+ * Include the [SharedFlow.replayCache] for this Flow in the dump.
+ *
+ * @param dumpName the name to use for this field in the dump output
+ */
+ fun <T, F : SharedFlow<T>> F.dumpReplayCache(dumpName: String): F
+
+ /**
+ * Include the [StateFlow.value] for this Flow in the dump.
+ *
+ * @param dumpName the name to use for this field in the dump output
+ */
+ fun <T, F : StateFlow<T>> F.dumpValue(dumpName: String): F
+
+ /** The default [Dumpable.dump] implementation which just calls [dumpFlows] */
+ override fun dump(pw: PrintWriter, args: Array<out String>) = dumpFlows(pw.asIndenting())
+
+ /** Dump all the values from any registered / active Flows. */
+ fun dumpFlows(pw: IndentingPrintWriter)
+}
+
+/**
+ * An implementation of [FlowDumper]. This be extended directly, or can be used to implement
+ * [FlowDumper] by delegation.
+ *
+ * @param dumpManager if provided, this will be used by the [FlowDumperImpl] to register and
+ * unregister itself when there is something to dump.
+ * @param tag a static name by which this [FlowDumperImpl] is registered. If not provided, this
+ * class's name will be used. If you're implementing by delegation, you probably want to provide
+ * this tag to get a meaningful dumpable name.
+ */
+open class FlowDumperImpl(private val dumpManager: DumpManager?, tag: String? = null) : FlowDumper {
+ private val stateFlowMap = ConcurrentHashMap<String, StateFlow<*>>()
+ private val sharedFlowMap = ConcurrentHashMap<String, SharedFlow<*>>()
+ private val flowCollectionMap = ConcurrentHashMap<Pair<String, String>, Any>()
+ override fun dumpFlows(pw: IndentingPrintWriter) {
+ pw.printCollection("StateFlow (value)", stateFlowMap.toSortedMap().entries) { (key, flow) ->
+ append(key).append('=').println(flow.value)
+ }
+ pw.printCollection("SharedFlow (replayCache)", sharedFlowMap.toSortedMap().entries) {
+ (key, flow) ->
+ append(key).append('=').println(flow.replayCache)
+ }
+ val comparator = compareBy<Pair<String, String>> { it.first }.thenBy { it.second }
+ pw.printCollection("Flow (latest)", flowCollectionMap.toSortedMap(comparator).entries) {
+ (pair, value) ->
+ append(pair.first).append('=').println(value)
+ }
+ }
+
+ private val Any.idString: String
+ get() = Integer.toHexString(System.identityHashCode(this))
+
+ override fun <T> Flow<T>.dumpWhileCollecting(dumpName: String): Flow<T> = flow {
+ val mapKey = dumpName to idString
+ try {
+ collect {
+ flowCollectionMap[mapKey] = it ?: "null"
+ updateRegistration(required = true)
+ emit(it)
+ }
+ } finally {
+ flowCollectionMap.remove(mapKey)
+ updateRegistration(required = false)
+ }
+ }
+
+ override fun <T, F : StateFlow<T>> F.dumpValue(dumpName: String): F {
+ stateFlowMap[dumpName] = this
+ return this
+ }
+
+ override fun <T, F : SharedFlow<T>> F.dumpReplayCache(dumpName: String): F {
+ sharedFlowMap[dumpName] = this
+ return this
+ }
+
+ private val dumpManagerName = tag ?: "[$idString] ${javaClass.simpleName}"
+ private var registered = AtomicBoolean(false)
+ private fun updateRegistration(required: Boolean) {
+ if (dumpManager == null) return
+ if (required && registered.get()) return
+ synchronized(registered) {
+ val shouldRegister =
+ stateFlowMap.isNotEmpty() ||
+ sharedFlowMap.isNotEmpty() ||
+ flowCollectionMap.isNotEmpty()
+ val wasRegistered = registered.getAndSet(shouldRegister)
+ if (wasRegistered != shouldRegister) {
+ if (shouldRegister) {
+ dumpManager.registerCriticalDumpable(dumpManagerName, this)
+ } else {
+ dumpManager.unregisterDumpable(dumpManagerName)
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/OWNERS b/packages/SystemUI/src/com/android/systemui/volume/OWNERS
index e627d610dc0a..4d2b639ba8fb 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/OWNERS
+++ b/packages/SystemUI/src/com/android/systemui/volume/OWNERS
@@ -1,4 +1,6 @@
-asc@google.com # send reviews here
+ethibodeau@google.com
+michaelmikhil@google.com
+apotapov@google.com
+asc@google.com
-juliacr@google.com
-tsuji@google.com
+juliacr@google.com #{LAST_RESORT_SUGGESTION}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/AncModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/AncModule.kt
new file mode 100644
index 000000000000..66df45ca4cfa
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/AncModule.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.content.Context
+import androidx.slice.SliceViewManager
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.volume.panel.component.anc.data.repository.AncSliceRepository
+import com.android.systemui.volume.panel.component.anc.data.repository.AncSliceRepositoryImpl
+import dagger.Module
+import dagger.Provides
+
+/** Dagger module that provides ANC controlling backend. */
+@Module
+interface AncModule {
+
+ companion object {
+ @Provides
+ @SysUISingleton
+ fun provideAncSliceRepository(
+ @Application context: Context,
+ implFactory: AncSliceRepositoryImpl.Factory
+ ): AncSliceRepository = implFactory.create(SliceViewManager.getInstance(context))
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
index 67d6a7f5d735..f6fd519ed723 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
@@ -27,8 +27,8 @@ import com.android.settingslib.statusbar.notification.data.repository.Notificati
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.settingslib.volume.shared.AudioManagerIntentsReceiver
-import com.android.settingslib.volume.shared.AudioManagerIntentsReceiverImpl
+import com.android.settingslib.volume.shared.AudioManagerEventsReceiver
+import com.android.settingslib.volume.shared.AudioManagerEventsReceiverImpl
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import dagger.Module
@@ -46,11 +46,11 @@ interface AudioModule {
fun provideAudioManagerIntentsReceiver(
@Application context: Context,
@Application coroutineScope: CoroutineScope,
- ): AudioManagerIntentsReceiver = AudioManagerIntentsReceiverImpl(context, coroutineScope)
+ ): AudioManagerEventsReceiver = AudioManagerEventsReceiverImpl(context, coroutineScope)
@Provides
fun provideAudioRepository(
- intentsReceiver: AudioManagerIntentsReceiver,
+ intentsReceiver: AudioManagerEventsReceiver,
audioManager: AudioManager,
@Background coroutineContext: CoroutineContext,
@Application coroutineScope: CoroutineScope,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt
index 9f99e9778ef2..bf9963d13959 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt
@@ -20,7 +20,7 @@ import android.media.session.MediaSessionManager
import com.android.settingslib.bluetooth.LocalBluetoothManager
import com.android.settingslib.volume.data.repository.MediaControllerRepository
import com.android.settingslib.volume.data.repository.MediaControllerRepositoryImpl
-import com.android.settingslib.volume.shared.AudioManagerIntentsReceiver
+import com.android.settingslib.volume.shared.AudioManagerEventsReceiver
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
@@ -45,7 +45,7 @@ interface MediaDevicesModule {
@Provides
@SysUISingleton
fun provideMediaDeviceSessionRepository(
- intentsReceiver: AudioManagerIntentsReceiver,
+ intentsReceiver: AudioManagerEventsReceiver,
mediaSessionManager: MediaSessionManager,
localBluetoothManager: LocalBluetoothManager?,
@Application coroutineScope: CoroutineScope,
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 32856373dbe9..c6aee428ce6a 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
@@ -57,6 +57,7 @@ import dagger.multibindings.IntoSet;
@Module(
includes = {
AudioModule.class,
+ AncModule.class,
CaptioningModule.class,
MediaDevicesModule.class
},
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/data/repository/AncSliceRepository.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/data/repository/AncSliceRepository.kt
new file mode 100644
index 000000000000..8f18aa8021ae
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/data/repository/AncSliceRepository.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.anc.data.repository
+
+import android.bluetooth.BluetoothDevice
+import android.net.Uri
+import androidx.slice.Slice
+import androidx.slice.SliceViewManager
+import com.android.settingslib.bluetooth.BluetoothUtils
+import com.android.settingslib.media.BluetoothMediaDevice
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.slice.sliceForUri
+import com.android.systemui.volume.panel.component.mediaoutput.data.repository.LocalMediaRepositoryFactory
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+
+/** Provides ANC slice data */
+interface AncSliceRepository {
+
+ /**
+ * ANC slice with a given width. Emits null when there is no ANC slice available. This can mean
+ * that:
+ * - there is no supported device connected;
+ * - there is no slice provider for the uri;
+ */
+ fun ancSlice(width: Int): Flow<Slice?>
+}
+
+@OptIn(ExperimentalCoroutinesApi::class)
+class AncSliceRepositoryImpl
+@AssistedInject
+constructor(
+ mediaRepositoryFactory: LocalMediaRepositoryFactory,
+ @Background private val backgroundCoroutineContext: CoroutineContext,
+ @Assisted private val sliceViewManager: SliceViewManager,
+) : AncSliceRepository {
+
+ private val localMediaRepository = mediaRepositoryFactory.create(null)
+
+ override fun ancSlice(width: Int): Flow<Slice?> {
+ return localMediaRepository.currentConnectedDevice
+ .map { (it as? BluetoothMediaDevice)?.cachedDevice?.device?.getExtraControlUri(width) }
+ .distinctUntilChanged()
+ .flatMapLatest { sliceUri ->
+ sliceUri ?: return@flatMapLatest flowOf(null)
+ sliceViewManager.sliceForUri(sliceUri)
+ }
+ .flowOn(backgroundCoroutineContext)
+ }
+
+ private fun BluetoothDevice.getExtraControlUri(width: Int): Uri? {
+ val uri: String? = BluetoothUtils.getControlUriMetaData(this)
+ uri ?: return null
+
+ return if (uri.isEmpty()) {
+ null
+ } else {
+ Uri.parse(
+ "$uri$width" +
+ "&version=${SliceParameters.VERSION}" +
+ "&is_collapsed=${SliceParameters.IS_COLLAPSED}"
+ )
+ }
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(sliceViewManager: SliceViewManager): AncSliceRepositoryImpl
+ }
+
+ private object SliceParameters {
+ /**
+ * Slice version
+ * 1) legacy slice
+ * 2) new slice
+ */
+ const val VERSION = 2
+
+ /**
+ * Collapsed slice shows a single button, and expanded shows a row buttons. Supported since
+ * [VERSION]==2.
+ */
+ const val IS_COLLAPSED = false
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/domain/AncAvailabilityCriteria.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/domain/AncAvailabilityCriteria.kt
new file mode 100644
index 000000000000..89b927480783
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/domain/AncAvailabilityCriteria.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.anc.domain
+
+import com.android.systemui.volume.panel.component.anc.domain.interactor.AncSliceInteractor
+import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+/** Determines if ANC component is available for the Volume Panel. */
+@VolumePanelScope
+class AncAvailabilityCriteria
+@Inject
+constructor(
+ private val ancSliceInteractor: AncSliceInteractor,
+) : ComponentAvailabilityCriteria {
+
+ override fun isAvailable(): Flow<Boolean> = ancSliceInteractor.ancSlice.map { it != null }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/domain/interactor/AncSliceInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/domain/interactor/AncSliceInteractor.kt
new file mode 100644
index 000000000000..91af622074a0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/domain/interactor/AncSliceInteractor.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.anc.domain.interactor
+
+import android.app.slice.Slice.HINT_ERROR
+import android.app.slice.SliceItem.FORMAT_SLICE
+import androidx.slice.Slice
+import com.android.systemui.volume.panel.component.anc.data.repository.AncSliceRepository
+import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.shareIn
+
+/** Provides a valid slice from [AncSliceRepository]. */
+@OptIn(ExperimentalCoroutinesApi::class)
+@VolumePanelScope
+class AncSliceInteractor
+@Inject
+constructor(
+ private val ancSliceRepository: AncSliceRepository,
+ scope: CoroutineScope,
+) {
+
+ // Start with a positive width to check is the Slice is available.
+ private val width = MutableStateFlow(1)
+
+ /** Provides a valid ANC slice. */
+ val ancSlice: SharedFlow<Slice?> =
+ width
+ .flatMapLatest { width -> ancSliceRepository.ancSlice(width) }
+ .map { slice ->
+ if (slice?.isValidSlice() == true) {
+ slice
+ } else {
+ null
+ }
+ }
+ .shareIn(scope, SharingStarted.Eagerly, replay = 1)
+
+ /** Updates the width of the [ancSlice] */
+ fun changeWidth(newWidth: Int) {
+ width.value = newWidth
+ }
+
+ private fun Slice.isValidSlice(): Boolean {
+ if (hints.contains(HINT_ERROR)) {
+ return false
+ }
+ for (item in items) {
+ if (item.format == FORMAT_SLICE) {
+ return true
+ }
+ }
+ return false
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/ui/viewmodel/AncViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/ui/viewmodel/AncViewModel.kt
new file mode 100644
index 000000000000..eb96f6cad8f2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/ui/viewmodel/AncViewModel.kt
@@ -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.systemui.volume.panel.component.anc.ui.viewmodel
+
+import android.content.Context
+import androidx.slice.Slice
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.res.R
+import com.android.systemui.volume.panel.component.anc.domain.interactor.AncSliceInteractor
+import com.android.systemui.volume.panel.component.button.ui.viewmodel.ButtonViewModel
+import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+/** Volume Panel ANC component view model. */
+@VolumePanelScope
+class AncViewModel
+@Inject
+constructor(
+ @Application private val context: Context,
+ @VolumePanelScope private val coroutineScope: CoroutineScope,
+ private val interactor: AncSliceInteractor,
+) {
+
+ /** ANC [Slice]. Null when there is no slice available for ANC. */
+ val slice: StateFlow<Slice?> =
+ interactor.ancSlice.stateIn(coroutineScope, SharingStarted.Eagerly, null)
+
+ /**
+ * ButtonViewModel to be shown in the VolumePanel. Null when there is no ANC Slice available.
+ */
+ val button: StateFlow<ButtonViewModel?> =
+ interactor.ancSlice
+ .map { slice ->
+ slice?.let {
+ ButtonViewModel(
+ Icon.Resource(R.drawable.ic_noise_aware, null),
+ context.getString(R.string.volume_panel_noise_control_title)
+ )
+ }
+ }
+ .stateIn(coroutineScope, SharingStarted.Eagerly, null)
+
+ /** Call this to update [slice] width in a reaction to container size change. */
+ fun changeSliceWidth(width: Int) {
+ interactor.changeWidth(width)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/button/ui/viewmodel/ButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/button/ui/viewmodel/ButtonViewModel.kt
new file mode 100644
index 000000000000..754d258b2b86
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/button/ui/viewmodel/ButtonViewModel.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.volume.panel.component.button.ui.viewmodel
+
+import com.android.systemui.common.shared.model.Icon
+
+/** Models base buttons appearance. */
+data class ButtonViewModel(
+ val icon: Icon,
+ val label: CharSequence,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/LocalMediaRepositoryFactory.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/LocalMediaRepositoryFactory.kt
index 1f52260bb20d..11b4690e59ee 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/LocalMediaRepositoryFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/LocalMediaRepositoryFactory.kt
@@ -18,10 +18,10 @@ package com.android.systemui.volume.panel.component.mediaoutput.data.repository
import android.media.MediaRouter2Manager
import com.android.settingslib.volume.data.repository.LocalMediaRepository
import com.android.settingslib.volume.data.repository.LocalMediaRepositoryImpl
-import com.android.settingslib.volume.shared.AudioManagerIntentsReceiver
+import com.android.settingslib.volume.shared.AudioManagerEventsReceiver
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.media.controls.pipeline.LocalMediaManagerFactory
+import com.android.systemui.media.controls.util.LocalMediaManagerFactory
import javax.inject.Inject
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
@@ -34,7 +34,7 @@ interface LocalMediaRepositoryFactory {
class LocalMediaRepositoryFactoryImpl
@Inject
constructor(
- private val intentsReceiver: AudioManagerIntentsReceiver,
+ private val eventsReceiver: AudioManagerEventsReceiver,
private val mediaRouter2Manager: MediaRouter2Manager,
private val localMediaManagerFactory: LocalMediaManagerFactory,
@Application private val coroutineScope: CoroutineScope,
@@ -43,7 +43,7 @@ constructor(
override fun create(packageName: String?): LocalMediaRepository =
LocalMediaRepositoryImpl(
- intentsReceiver,
+ eventsReceiver,
localMediaManagerFactory.create(packageName),
mediaRouter2Manager,
coroutineScope,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/MediaOutputAvailabilityCriteria.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/MediaOutputAvailabilityCriteria.kt
index 020ec64c0491..bac7d15235d0 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/MediaOutputAvailabilityCriteria.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/MediaOutputAvailabilityCriteria.kt
@@ -17,27 +17,19 @@
package com.android.systemui.volume.panel.component.mediaoutput.domain
import com.android.settingslib.volume.domain.interactor.AudioModeInteractor
-import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor
import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.map
/** Determines if the Media Output Volume Panel component is available. */
@VolumePanelScope
class MediaOutputAvailabilityCriteria
@Inject
constructor(
- private val mediaOutputInteractor: MediaOutputInteractor,
private val audioModeInteractor: AudioModeInteractor,
) : ComponentAvailabilityCriteria {
- override fun isAvailable(): Flow<Boolean> {
- return combine(mediaOutputInteractor.mediaDevices, audioModeInteractor.isOngoingCall) {
- devices,
- isOngoingCall ->
- !isOngoingCall && devices.isNotEmpty()
- }
- }
+ override fun isAvailable(): Flow<Boolean> = audioModeInteractor.isOngoingCall.map { !it }
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt
index 24cc29d8e1f9..0f5343701ac6 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt
@@ -95,10 +95,6 @@ constructor(
val currentConnectedDevice: Flow<MediaDevice?> =
localMediaRepository.flatMapLatest { it.currentConnectedDevice }
- /** A list of available [MediaDevice]s. */
- val mediaDevices: Flow<Collection<MediaDevice>> =
- localMediaRepository.flatMapLatest { it.mediaDevices }
-
private suspend fun getApplicationLabel(packageName: String): CharSequence? {
return try {
withContext(backgroundCoroutineContext) {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/DeviceIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/DeviceIconViewModel.kt
index e0718ace2c30..e518ed022792 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/DeviceIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/DeviceIconViewModel.kt
@@ -23,15 +23,18 @@ import com.android.systemui.common.shared.model.Icon
sealed interface DeviceIconViewModel {
val icon: Icon
+ val iconColor: Color
val backgroundColor: Color
class IsPlaying(
override val icon: Icon,
+ override val iconColor: Color,
override val backgroundColor: Color,
) : DeviceIconViewModel
class IsNotPlaying(
override val icon: Icon,
+ override val iconColor: Color,
override val backgroundColor: Color,
) : DeviceIconViewModel
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
index d14899294526..85d6c9e341ac 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
@@ -86,13 +86,21 @@ constructor(
null
)
DeviceIconViewModel.IsPlaying(
- icon,
- Color.Attribute(com.android.internal.R.attr.materialColorSecondary),
+ icon = icon,
+ iconColor =
+ Color.Attribute(com.android.internal.R.attr.materialColorSurface),
+ backgroundColor =
+ Color.Attribute(com.android.internal.R.attr.materialColorSecondary),
)
} else {
DeviceIconViewModel.IsNotPlaying(
- Icon.Resource(R.drawable.ic_media_home_devices, null),
- Color.Attribute(com.android.internal.R.attr.materialColorSurface),
+ icon = Icon.Resource(R.drawable.ic_media_home_devices, null),
+ iconColor =
+ Color.Attribute(
+ com.android.internal.R.attr.materialColorOnSurfaceVariant
+ ),
+ backgroundColor =
+ Color.Attribute(com.android.internal.R.attr.materialColorSurface),
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/shared/model/VolumePanelComponents.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/shared/model/VolumePanelComponents.kt
index 6c742ba7e5f9..f11ac5e1a8e4 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/shared/model/VolumePanelComponents.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/shared/model/VolumePanelComponents.kt
@@ -23,4 +23,5 @@ object VolumePanelComponents {
const val MEDIA_OUTPUT: VolumePanelComponentKey = "media_output"
const val BOTTOM_BAR: VolumePanelComponentKey = "bottom_bar"
const val CAPTIONING: VolumePanelComponentKey = "captioning"
+ const val ANC: VolumePanelComponentKey = "anc"
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt
index afd3f6170d3d..df4972aeafea 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt
@@ -16,6 +16,7 @@
package com.android.systemui.volume.panel.dagger
+import com.android.systemui.volume.panel.component.anc.AncModule
import com.android.systemui.volume.panel.component.bottombar.BottomBarModule
import com.android.systemui.volume.panel.component.captioning.CaptioningModule
import com.android.systemui.volume.panel.component.mediaoutput.MediaOutputModule
@@ -46,6 +47,7 @@ import kotlinx.coroutines.CoroutineScope
UiModule::class,
// Components modules
BottomBarModule::class,
+ AncModule::class,
CaptioningModule::class,
MediaOutputModule::class,
]
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/domain/DomainModule.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/domain/DomainModule.kt
index 55d8de5aeb95..0d65c429eac6 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/domain/DomainModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/domain/DomainModule.kt
@@ -50,6 +50,7 @@ interface DomainModule {
@VolumePanelScope
fun provideEnabledComponents(): Collection<VolumePanelComponentKey> {
return setOf(
+ VolumePanelComponents.ANC,
VolumePanelComponents.CAPTIONING,
VolumePanelComponents.MEDIA_OUTPUT,
VolumePanelComponents.BOTTOM_BAR,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/UiModule.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/UiModule.kt
index 867df4a87dd5..ec4da0692841 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/UiModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/UiModule.kt
@@ -47,7 +47,8 @@ interface UiModule {
@VolumePanelScope
@FooterComponents
fun provideFooterComponents(): Collection<VolumePanelComponentKey> {
- return setOf(
+ return listOf(
+ VolumePanelComponents.ANC,
VolumePanelComponents.CAPTIONING,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index e8325065c219..15e0965c16fe 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -356,6 +356,12 @@ public final class WMShell implements
// TODO(b/278084491): update sysui state for changes on other displays
}
}, mSysUiMainExecutor);
+ mCommandQueue.addCallback(new CommandQueue.Callbacks() {
+ @Override
+ public void enterDesktop(int displayId) {
+ desktopMode.enterDesktop(displayId);
+ }
+ });
}
@VisibleForTesting
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java
index 438f0f43acb6..cc36cfa6a30a 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java
@@ -18,8 +18,6 @@ package com.android.keyguard;
import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
-import static com.android.systemui.flags.Flags.ENABLE_CLOCK_KEYGUARD_PRESENTATION;
-
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
@@ -37,9 +35,7 @@ import android.view.DisplayInfo;
import androidx.test.filters.SmallTest;
-import com.android.keyguard.dagger.KeyguardStatusViewComponent;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.navigationbar.NavigationBarController;
import com.android.systemui.settings.FakeDisplayTracker;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -60,13 +56,9 @@ public class KeyguardDisplayManagerTest extends SysuiTestCase {
@Mock
private NavigationBarController mNavigationBarController;
@Mock
- private KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
- @Mock
private ConnectedDisplayKeyguardPresentation.Factory
mConnectedDisplayKeyguardPresentationFactory;
@Mock
- private KeyguardDisplayManager.KeyguardPresentation mKeyguardPresentation;
- @Mock
private ConnectedDisplayKeyguardPresentation mConnectedDisplayKeyguardPresentation;
@Mock
private KeyguardDisplayManager.DeviceStateHelper mDeviceStateHelper;
@@ -77,7 +69,6 @@ public class KeyguardDisplayManagerTest extends SysuiTestCase {
private Executor mBackgroundExecutor = Runnable::run;
private KeyguardDisplayManager mManager;
private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext);
- private FakeFeatureFlags mFakeFeatureFlags = new FakeFeatureFlags();
// The default and secondary displays are both in the default group
private Display mDefaultDisplay;
private Display mSecondaryDisplay;
@@ -88,15 +79,13 @@ public class KeyguardDisplayManagerTest extends SysuiTestCase {
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mFakeFeatureFlags.set(ENABLE_CLOCK_KEYGUARD_PRESENTATION, false);
mManager = spy(new KeyguardDisplayManager(mContext, () -> mNavigationBarController,
- mKeyguardStatusViewComponentFactory, mDisplayTracker, mMainExecutor,
- mBackgroundExecutor, mDeviceStateHelper, mKeyguardStateController,
- mConnectedDisplayKeyguardPresentationFactory, mFakeFeatureFlags));
- doReturn(mKeyguardPresentation).when(mManager).createPresentation(any());
+ mDisplayTracker, mMainExecutor, mBackgroundExecutor, mDeviceStateHelper,
+ mKeyguardStateController, mConnectedDisplayKeyguardPresentationFactory));
doReturn(mConnectedDisplayKeyguardPresentation).when(
mConnectedDisplayKeyguardPresentationFactory).create(any());
-
+ doReturn(mConnectedDisplayKeyguardPresentation).when(mManager)
+ .createPresentation(any());
mDefaultDisplay = new Display(DisplayManagerGlobal.getInstance(), Display.DEFAULT_DISPLAY,
new DisplayInfo(), DEFAULT_DISPLAY_ADJUSTMENTS);
mSecondaryDisplay = new Display(DisplayManagerGlobal.getInstance(),
@@ -152,9 +141,8 @@ public class KeyguardDisplayManagerTest extends SysuiTestCase {
}
@Test
- public void testShow_withClockPresentationFlagEnabled_presentationCreated() {
+ public void testShow_presentationCreated() {
when(mManager.createPresentation(any())).thenCallRealMethod();
- mFakeFeatureFlags.set(ENABLE_CLOCK_KEYGUARD_PRESENTATION, true);
mDisplayTracker.setAllDisplays(new Display[]{mDefaultDisplay, mSecondaryDisplay});
mManager.show();
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPresentationTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPresentationTest.java
deleted file mode 100644
index 5102957c71ed..000000000000
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPresentationTest.java
+++ /dev/null
@@ -1,116 +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.
- */
-
-package com.android.keyguard;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.hardware.display.DisplayManager;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.util.AttributeSet;
-import android.view.Display;
-import android.view.LayoutInflater;
-import android.view.View;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.keyguard.KeyguardDisplayManager.KeyguardPresentation;
-import com.android.keyguard.dagger.KeyguardStatusViewComponent;
-import com.android.systemui.res.R;
-import com.android.systemui.SysuiTestCase;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
-public class KeyguardPresentationTest extends SysuiTestCase {
-
- @Mock
- KeyguardClockSwitch mMockKeyguardClockSwitch;
- @Mock
- KeyguardSliceView mMockKeyguardSliceView;
- @Mock
- KeyguardStatusView mMockKeyguardStatusView;
- @Mock
- private KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
- @Mock
- private KeyguardStatusViewComponent mKeyguardStatusViewComponent;
- @Mock
- private KeyguardClockSwitchController mKeyguardClockSwitchController;
-
- LayoutInflater mLayoutInflater;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- when(mMockKeyguardClockSwitch.getContext()).thenReturn(mContext);
- when(mMockKeyguardSliceView.getContext()).thenReturn(mContext);
- when(mMockKeyguardStatusView.getContext()).thenReturn(mContext);
- when(mMockKeyguardStatusView.findViewById(R.id.clock)).thenReturn(mMockKeyguardStatusView);
- when(mKeyguardStatusViewComponentFactory.build(any(KeyguardStatusView.class),
- any(Display.class)))
- .thenReturn(mKeyguardStatusViewComponent);
- when(mKeyguardStatusViewComponent.getKeyguardClockSwitchController())
- .thenReturn(mKeyguardClockSwitchController);
-
- allowTestableLooperAsMainThread();
-
- mLayoutInflater = LayoutInflater.from(mContext);
- mLayoutInflater.setPrivateFactory(new LayoutInflater.Factory2() {
-
- @Override
- public View onCreateView(View parent, String name, Context context,
- AttributeSet attrs) {
- return onCreateView(name, context, attrs);
- }
-
- @Override
- public View onCreateView(String name, Context context, AttributeSet attrs) {
- if ("com.android.keyguard.KeyguardStatusView".equals(name)) {
- return mMockKeyguardStatusView;
- } else if ("com.android.keyguard.KeyguardClockSwitch".equals(name)) {
- return mMockKeyguardClockSwitch;
- } else if ("com.android.keyguard.KeyguardSliceView".equals(name)) {
- return mMockKeyguardStatusView;
- }
- return null;
- }
- });
- }
-
- @After
- public void tearDown() {
- disallowTestableLooperAsMainThread();
- }
-
- @Test
- public void testInflation_doesntCrash() {
- final Display display = mContext.getSystemService(DisplayManager.class).getDisplay(
- Display.DEFAULT_DISPLAY);
- KeyguardPresentation keyguardPresentation = new KeyguardPresentation(mContext, display,
- mKeyguardStatusViewComponentFactory);
- keyguardPresentation.onCreate(null /*savedInstanceState */);
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java
index 4d3243a2ccf6..edb910a3acc2 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewControllerTest.java
@@ -22,8 +22,10 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.pm.PackageManager;
+import android.os.Handler;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
import android.view.View;
@@ -57,17 +59,22 @@ public class KeyguardSliceViewControllerTest extends SysuiTestCase {
private ActivityStarter mActivityStarter;
private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext);
private DumpManager mDumpManager = new DumpManager();
-
+ private Handler mHandler;
+ private Handler mBgHandler;
private KeyguardSliceViewController mController;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
+ TestableLooper testableLooper = TestableLooper.get(this);
+ assert testableLooper != null;
+ mHandler = new Handler(testableLooper.getLooper());
+ mBgHandler = new Handler(testableLooper.getLooper());
when(mView.isAttachedToWindow()).thenReturn(true);
when(mView.getContext()).thenReturn(mContext);
- mController = new KeyguardSliceViewController(
- mView, mActivityStarter, mConfigurationController,
- mTunerService, mDumpManager, mDisplayTracker);
+ mController = new KeyguardSliceViewController(mHandler, mBgHandler, mView,
+ mActivityStarter, mConfigurationController, mTunerService, mDumpManager,
+ mDisplayTracker);
mController.setupUri(KeyguardSliceProvider.KEYGUARD_SLICE_URI);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/CameraProtectionLoaderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/CameraProtectionLoaderImplTest.kt
index a19a0c7d12a3..d2a17c2ccbb4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/CameraProtectionLoaderImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/CameraProtectionLoaderImplTest.kt
@@ -89,7 +89,7 @@ class CameraProtectionLoaderImplTest : SysuiTestCase() {
loader.loadCameraProtectionInfoList().map { it.toTestableVersion() }
private fun CameraProtectionInfo.toTestableVersion() =
- TestableProtectionInfo(logicalCameraId, physicalCameraId, cutoutBounds, displayUniqueId)
+ TestableProtectionInfo(logicalCameraId, physicalCameraId, bounds, displayUniqueId)
/**
* "Testable" version, because the original version contains a Path property, which doesn't
diff --git a/packages/SystemUI/tests/src/com/android/systemui/FakeCameraProtectionLoader.kt b/packages/SystemUI/tests/src/com/android/systemui/FakeCameraProtectionLoader.kt
index f769b4e5f2d0..6cb77cff4404 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/FakeCameraProtectionLoader.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/FakeCameraProtectionLoader.kt
@@ -16,6 +16,7 @@
package com.android.systemui
+import android.graphics.Rect
import com.android.systemui.res.R
class FakeCameraProtectionLoader(private val context: SysuiTestableContext) :
@@ -36,7 +37,10 @@ class FakeCameraProtectionLoader(private val context: SysuiTestableContext) :
addInnerCameraProtection()
}
- fun addOuterCameraProtection(displayUniqueId: String = "111") {
+ fun addOuterCameraProtection(
+ displayUniqueId: String = "111",
+ bounds: Rect = Rect(/* left = */ 0, /* top = */ 0, /* right = */ 10, /* bottom = */ 10)
+ ) {
context.orCreateTestableResources.addOverride(R.string.config_protectedCameraId, "1")
context.orCreateTestableResources.addOverride(
R.string.config_protectedPhysicalCameraId,
@@ -44,7 +48,7 @@ class FakeCameraProtectionLoader(private val context: SysuiTestableContext) :
)
context.orCreateTestableResources.addOverride(
R.string.config_frontBuiltInDisplayCutoutProtection,
- "M 0,0 H 10,10 V 10,10 H 0,10 Z"
+ bounds.asPath(),
)
context.orCreateTestableResources.addOverride(
R.string.config_protectedScreenUniqueId,
@@ -52,7 +56,10 @@ class FakeCameraProtectionLoader(private val context: SysuiTestableContext) :
)
}
- fun addInnerCameraProtection(displayUniqueId: String = "222") {
+ fun addInnerCameraProtection(
+ displayUniqueId: String = "222",
+ bounds: Rect = Rect(/* left = */ 0, /* top = */ 0, /* right = */ 20, /* bottom = */ 20)
+ ) {
context.orCreateTestableResources.addOverride(R.string.config_protectedInnerCameraId, "2")
context.orCreateTestableResources.addOverride(
R.string.config_protectedInnerPhysicalCameraId,
@@ -60,11 +67,13 @@ class FakeCameraProtectionLoader(private val context: SysuiTestableContext) :
)
context.orCreateTestableResources.addOverride(
R.string.config_innerBuiltInDisplayCutoutProtection,
- "M 0,0 H 20,20 V 20,20 H 0,20 Z"
+ bounds.asPath(),
)
context.orCreateTestableResources.addOverride(
R.string.config_protectedInnerScreenUniqueId,
displayUniqueId
)
}
+
+ private fun Rect.asPath() = "M $left, $top H $right V $bottom H $left Z"
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysUICutoutProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/SysUICutoutProviderTest.kt
index f37c4ae613ff..61c7e1d63e51 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/SysUICutoutProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/SysUICutoutProviderTest.kt
@@ -16,11 +16,16 @@
package com.android.systemui
+import android.graphics.Rect
import android.view.Display
import android.view.DisplayAdjustments
import android.view.DisplayCutout
+import android.view.DisplayInfo
+import android.view.Surface
+import android.view.Surface.Rotation
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
@@ -39,7 +44,7 @@ class SysUICutoutProviderTest : SysuiTestCase() {
val noCutoutDisplayContext = context.createDisplayContext(noCutoutDisplay)
val provider = SysUICutoutProvider(noCutoutDisplayContext, fakeProtectionLoader)
- val sysUICutout = provider.cutoutInfoForCurrentDisplay()
+ val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()
assertThat(sysUICutout).isNull()
}
@@ -50,7 +55,7 @@ class SysUICutoutProviderTest : SysuiTestCase() {
val cutoutDisplayContext = context.createDisplayContext(cutoutDisplay)
val provider = SysUICutoutProvider(cutoutDisplayContext, fakeProtectionLoader)
- val sysUICutout = provider.cutoutInfoForCurrentDisplay()!!
+ val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()!!
assertThat(sysUICutout.cutout).isEqualTo(cutoutDisplay.cutout)
}
@@ -61,7 +66,7 @@ class SysUICutoutProviderTest : SysuiTestCase() {
val cutoutDisplayContext = context.createDisplayContext(cutoutDisplay)
val provider = SysUICutoutProvider(cutoutDisplayContext, fakeProtectionLoader)
- val sysUICutout = provider.cutoutInfoForCurrentDisplay()!!
+ val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()!!
assertThat(sysUICutout.cameraProtection).isNull()
}
@@ -72,7 +77,7 @@ class SysUICutoutProviderTest : SysuiTestCase() {
val outerDisplayContext = context.createDisplayContext(OUTER_DISPLAY)
val provider = SysUICutoutProvider(outerDisplayContext, fakeProtectionLoader)
- val sysUICutout = provider.cutoutInfoForCurrentDisplay()!!
+ val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()!!
assertThat(sysUICutout.cameraProtection).isNotNull()
}
@@ -83,7 +88,7 @@ class SysUICutoutProviderTest : SysuiTestCase() {
val outerDisplayContext = context.createDisplayContext(OUTER_DISPLAY)
val provider = SysUICutoutProvider(outerDisplayContext, fakeProtectionLoader)
- val sysUICutout = provider.cutoutInfoForCurrentDisplay()!!
+ val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()!!
assertThat(sysUICutout.cameraProtection).isNull()
}
@@ -94,7 +99,7 @@ class SysUICutoutProviderTest : SysuiTestCase() {
val displayContext = context.createDisplayContext(createDisplay(uniqueId = null))
val provider = SysUICutoutProvider(displayContext, fakeProtectionLoader)
- val sysUICutout = provider.cutoutInfoForCurrentDisplay()!!
+ val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()!!
assertThat(sysUICutout.cameraProtection).isNull()
}
@@ -105,20 +110,170 @@ class SysUICutoutProviderTest : SysuiTestCase() {
val displayContext = context.createDisplayContext(createDisplay(uniqueId = ""))
val provider = SysUICutoutProvider(displayContext, fakeProtectionLoader)
- val sysUICutout = provider.cutoutInfoForCurrentDisplay()!!
+ val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()!!
assertThat(sysUICutout.cameraProtection).isNull()
}
+ @Test
+ fun cutoutInfo_rotation0_returnsOriginalProtectionBounds() {
+ val provider =
+ setUpProviderWithCameraProtection(
+ displayWidth = 500,
+ displayHeight = 1000,
+ rotation = Surface.ROTATION_0,
+ protectionBounds =
+ Rect(/* left = */ 440, /* top = */ 10, /* right = */ 490, /* bottom = */ 110)
+ )
+
+ val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()!!
+
+ assertThat(sysUICutout.cameraProtection!!.bounds)
+ .isEqualTo(
+ Rect(/* left = */ 440, /* top = */ 10, /* right = */ 490, /* bottom = */ 110)
+ )
+ }
+
+ @Test
+ fun cutoutInfo_rotation90_returnsRotatedProtectionBounds() {
+ val provider =
+ setUpProviderWithCameraProtection(
+ displayWidth = 500,
+ displayHeight = 1000,
+ rotation = Surface.ROTATION_90,
+ protectionBounds =
+ Rect(/* left = */ 440, /* top = */ 10, /* right = */ 490, /* bottom = */ 110)
+ )
+
+ val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()!!
+
+ assertThat(sysUICutout.cameraProtection!!.bounds)
+ .isEqualTo(Rect(/* left = */ 10, /* top = */ 10, /* right = */ 110, /* bottom = */ 60))
+ }
+
+ @Test
+ fun cutoutInfo_withRotation_doesNotMutateOriginalBounds() {
+ val displayNaturalWidth = 500
+ val displayNaturalHeight = 1000
+ val originalProtectionBounds =
+ Rect(/* left = */ 440, /* top = */ 10, /* right = */ 490, /* bottom = */ 110)
+ // Safe copy as we don't know at which layer the mutation could happen
+ val originalProtectionBoundsCopy = Rect(originalProtectionBounds)
+ val display =
+ createDisplay(
+ uniqueId = OUTER_DISPLAY_UNIQUE_ID,
+ rotation = Surface.ROTATION_180,
+ width = displayNaturalWidth,
+ height = displayNaturalHeight,
+ )
+ fakeProtectionLoader.addOuterCameraProtection(
+ displayUniqueId = OUTER_DISPLAY_UNIQUE_ID,
+ bounds = originalProtectionBounds
+ )
+ val provider =
+ SysUICutoutProvider(context.createDisplayContext(display), fakeProtectionLoader)
+
+ // Here we get the rotated bounds once
+ provider.cutoutInfoForCurrentDisplayAndRotation()
+
+ // Rotate display back to original rotation
+ whenever(display.rotation).thenReturn(Surface.ROTATION_0)
+
+ // Now the bounds should match the original ones. We are checking for mutation since Rect
+ // is mutable and has many methods that mutate the instance, and it is easy to do it by
+ // mistake.
+ val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()!!
+ assertThat(sysUICutout.cameraProtection!!.bounds).isEqualTo(originalProtectionBoundsCopy)
+ }
+
+ @Test
+ fun cutoutInfo_rotation180_returnsRotatedProtectionBounds() {
+ val provider =
+ setUpProviderWithCameraProtection(
+ displayWidth = 500,
+ displayHeight = 1000,
+ rotation = Surface.ROTATION_180,
+ protectionBounds =
+ Rect(/* left = */ 440, /* top = */ 10, /* right = */ 490, /* bottom = */ 110)
+ )
+
+ val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()!!
+
+ assertThat(sysUICutout.cameraProtection!!.bounds)
+ .isEqualTo(Rect(/* left = */ 10, /* top = */ 890, /* right = */ 60, /* bottom = */ 990))
+ }
+
+ @Test
+ fun cutoutInfo_rotation270_returnsRotatedProtectionBounds() {
+ val provider =
+ setUpProviderWithCameraProtection(
+ displayWidth = 500,
+ displayHeight = 1000,
+ rotation = Surface.ROTATION_270,
+ protectionBounds =
+ Rect(/* left = */ 440, /* top = */ 10, /* right = */ 490, /* bottom = */ 110)
+ )
+
+ val sysUICutout = provider.cutoutInfoForCurrentDisplayAndRotation()!!
+
+ assertThat(sysUICutout.cameraProtection!!.bounds)
+ .isEqualTo(
+ Rect(/* left = */ 890, /* top = */ 440, /* right = */ 990, /* bottom = */ 490)
+ )
+ }
+
+ private fun setUpProviderWithCameraProtection(
+ displayWidth: Int,
+ displayHeight: Int,
+ rotation: Int = Surface.ROTATION_0,
+ protectionBounds: Rect,
+ ): SysUICutoutProvider {
+ val display =
+ createDisplay(
+ uniqueId = OUTER_DISPLAY_UNIQUE_ID,
+ rotation = rotation,
+ width =
+ if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) {
+ displayWidth
+ } else {
+ displayHeight
+ },
+ height =
+ if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180)
+ displayHeight
+ else displayWidth,
+ )
+ fakeProtectionLoader.addOuterCameraProtection(
+ displayUniqueId = OUTER_DISPLAY_UNIQUE_ID,
+ bounds = protectionBounds
+ )
+ return SysUICutoutProvider(context.createDisplayContext(display), fakeProtectionLoader)
+ }
+
companion object {
private const val OUTER_DISPLAY_UNIQUE_ID = "outer"
private val OUTER_DISPLAY = createDisplay(uniqueId = OUTER_DISPLAY_UNIQUE_ID)
private fun createDisplay(
+ width: Int = 500,
+ height: Int = 1000,
+ @Rotation rotation: Int = Surface.ROTATION_0,
uniqueId: String? = "uniqueId",
cutout: DisplayCutout? = mock<DisplayCutout>()
) =
mock<Display> {
+ whenever(this.getDisplayInfo(any())).thenAnswer {
+ val displayInfo = it.arguments[0] as DisplayInfo
+ displayInfo.rotation = rotation
+ displayInfo.logicalWidth = width
+ displayInfo.logicalHeight = height
+ return@thenAnswer true
+ }
+ // Setting width and height to smaller values to simulate real behavior of this API
+ // not always returning the real display size
+ whenever(this.width).thenReturn(width - 5)
+ whenever(this.height).thenReturn(height - 10)
+ whenever(this.rotation).thenReturn(rotation)
whenever(this.displayAdjustments).thenReturn(DisplayAdjustments())
whenever(this.cutout).thenReturn(cutout)
whenever(this.uniqueId).thenReturn(uniqueId)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
index 0b0410aa1670..0d464cfd71f7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
@@ -180,6 +180,17 @@ public class AppOpsControllerTest extends SysuiTestCase {
assertThat(mController.getActiveAppOps()).isEmpty();
}
+ /** Regression test for b/324329757 */
+ @Test
+ public void startListening_fetchCurrentActive_nullPackageOps() {
+ when(mAppOpsManager.getPackagesForOps(AppOpsControllerImpl.OPS)).thenReturn(null);
+
+ mController.setListening(true);
+ mBgExecutor.runAllReady();
+
+ assertThat(mController.getActiveAppOps()).isEmpty();
+ }
+
/** Regression test for b/294104969. */
@Test
public void startListening_fetchesCurrentActive_oneActive() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt
index 5eda2b28175d..8690d4eb1f3e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt
@@ -89,6 +89,26 @@ class CredentialInteractorImplTest : SysuiTestCase() {
}
}
+ @Test
+ fun testParentProfile() {
+ for (value in listOf(12, 8, 4)) {
+ whenever(userManager.getProfileParent(eq(USER_ID)))
+ .thenReturn(UserInfo(value, "test", 0))
+
+ assertThat(interactor.getParentProfileIdOrSelfId(USER_ID)).isEqualTo(value)
+ }
+ }
+
+ @Test
+ fun useCredentialOwnerWhenParentProfileIsNull() {
+ val value = 1
+
+ whenever(userManager.getProfileParent(eq(USER_ID))).thenReturn(null)
+ whenever(userManager.getCredentialOwnerProfile(eq(USER_ID))).thenReturn(value)
+
+ assertThat(interactor.getParentProfileIdOrSelfId(USER_ID)).isEqualTo(value)
+ }
+
@Test fun pinCredentialWhenGood() = pinCredential(goodCredential())
@Test fun pinCredentialWhenBad() = pinCredential(badCredential())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
index f770a3885aad..6e00b70b5410 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
@@ -21,6 +21,8 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyDouble;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.testing.AndroidTestingRunner;
@@ -40,6 +42,8 @@ import com.android.systemui.statusbar.policy.KeyguardStateController;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -71,6 +75,10 @@ public class BrightLineFalsingManagerTest extends SysuiTestCase {
private KeyguardStateController mKeyguardStateController;
@Mock
private AccessibilityManager mAccessibilityManager;
+ @Captor
+ private ArgumentCaptor<FalsingDataProvider.SessionListener> mSessionListenerArgumentCaptor;
+ @Captor
+ private ArgumentCaptor<HistoryTracker.BeliefListener> mBeliefListenerArgumentCaptor;
private final FalsingClassifier.Result mPassedResult = FalsingClassifier.Result.passed(1);
private final FalsingClassifier.Result mFalsedResult =
@@ -194,4 +202,28 @@ public class BrightLineFalsingManagerTest extends SysuiTestCase {
when(mFalsingDataProvider.isFromTrackpad()).thenReturn(true);
assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isFalse();
}
+
+ @Test
+ public void testAddAndRemoveFalsingBeliefListener() {
+ verify(mHistoryTracker, never()).addBeliefListener(any());
+
+ // Session started
+ final FalsingDataProvider.SessionListener sessionListener = captureSessionListener();
+ sessionListener.onSessionStarted();
+
+ // Verify belief listener added when session started
+ verify(mHistoryTracker).addBeliefListener(mBeliefListenerArgumentCaptor.capture());
+ verify(mHistoryTracker, never()).removeBeliefListener(any());
+
+ // Session ended
+ sessionListener.onSessionEnded();
+
+ // Verify belief listener removed when session ended
+ verify(mHistoryTracker).removeBeliefListener(mBeliefListenerArgumentCaptor.getValue());
+ }
+
+ private FalsingDataProvider.SessionListener captureSessionListener() {
+ verify(mFalsingDataProvider).addSessionListener(mSessionListenerArgumentCaptor.capture());
+ return mSessionListenerArgumentCaptor.getValue();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
index fcb18f52086d..3f13033217b3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
@@ -33,6 +33,7 @@ import androidx.test.filters.SmallTest;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.communal.domain.interactor.CommunalInteractor;
import com.android.systemui.dock.DockManager;
import com.android.systemui.dock.DockManagerFake;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -85,6 +86,8 @@ public class FalsingCollectorImplTest extends SysuiTestCase {
private BatteryController mBatteryController;
@Mock
private SelectedUserInteractor mSelectedUserInteractor;
+ @Mock
+ private CommunalInteractor mCommunalInteractor;
private final DockManagerFake mDockManager = new DockManagerFake();
private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
private final FakeExecutor mFakeExecutor = new FakeExecutor(mFakeSystemClock);
@@ -102,7 +105,8 @@ public class FalsingCollectorImplTest extends SysuiTestCase {
mStatusBarStateController, mKeyguardStateController,
() -> mShadeInteractor, mBatteryController,
mDockManager, mFakeExecutor,
- mJavaAdapter, mFakeSystemClock, () -> mSelectedUserInteractor
+ mJavaAdapter, mFakeSystemClock, () -> mSelectedUserInteractor,
+ () -> mCommunalInteractor
);
mFalsingCollector.init();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
index 18515825967f..c65a1176a55b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
@@ -27,16 +27,20 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
+import android.app.KeyguardManager;
import android.content.ClipData;
import android.content.ClipDescription;
import android.content.ClipboardManager;
import android.os.PersistableBundle;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.provider.Settings;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.logging.UiEventLogger;
+import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
import org.junit.Before;
@@ -59,6 +63,8 @@ public class ClipboardListenerTest extends SysuiTestCase {
@Mock
private ClipboardManager mClipboardManager;
@Mock
+ private KeyguardManager mKeyguardManager;
+ @Mock
private ClipboardOverlayController mOverlayController;
@Mock
private ClipboardToast mClipboardToast;
@@ -96,7 +102,7 @@ public class ClipboardListenerTest extends SysuiTestCase {
when(mClipboardManager.getPrimaryClipSource()).thenReturn(mSampleSource);
mClipboardListener = new ClipboardListener(getContext(), mOverlayControllerProvider,
- mClipboardToast, mClipboardManager, mUiEventLogger);
+ mClipboardToast, mClipboardManager, mKeyguardManager, mUiEventLogger);
}
@@ -191,6 +197,34 @@ public class ClipboardListenerTest extends SysuiTestCase {
}
@Test
+ @EnableFlags(Flags.FLAG_CLIPBOARD_NONINTERACTIVE_ON_LOCKSCREEN)
+ public void test_deviceLocked_showsToast() {
+ when(mKeyguardManager.isDeviceLocked()).thenReturn(true);
+
+ mClipboardListener.start();
+ mClipboardListener.onPrimaryClipChanged();
+
+ verify(mUiEventLogger, times(1)).log(
+ ClipboardOverlayEvent.CLIPBOARD_TOAST_SHOWN, 0, mSampleSource);
+ verify(mClipboardToast, times(1)).showCopiedToast();
+ verifyZeroInteractions(mOverlayControllerProvider);
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_CLIPBOARD_NONINTERACTIVE_ON_LOCKSCREEN)
+ public void test_deviceLocked_legacyBehavior_showsInteractiveUI() {
+ when(mKeyguardManager.isDeviceLocked()).thenReturn(true);
+
+ mClipboardListener.start();
+ mClipboardListener.onPrimaryClipChanged();
+
+ verify(mUiEventLogger, times(1)).log(
+ ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ENTERED, 0, mSampleSource);
+ verify(mOverlayController).setClipData(mSampleClipData, mSampleSource);
+ verifyZeroInteractions(mClipboardToast);
+ }
+
+ @Test
public void test_nullClipData_showsNothing() {
when(mClipboardManager.getPrimaryClip()).thenReturn(null);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/complication/DreamMediaEntryComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/complication/DreamMediaEntryComplicationTest.java
index 2bf9ab264bcf..05b4a41d1b40 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/complication/DreamMediaEntryComplicationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/complication/DreamMediaEntryComplicationTest.java
@@ -37,7 +37,7 @@ import com.android.systemui.SysuiTestCase;
import com.android.systemui.complication.dagger.DreamMediaEntryComplicationComponent;
import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.media.controls.ui.MediaCarouselController;
+import com.android.systemui.media.controls.ui.controller.MediaCarouselController;
import com.android.systemui.media.dream.MediaDreamComplication;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/BiometricMessageInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/BiometricMessageInteractorTest.kt
index a53f8d43f323..5581f0c0314f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/BiometricMessageInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/BiometricMessageInteractorTest.kt
@@ -23,6 +23,7 @@ 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.biometrics.domain.faceHelpMessageDeferral
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.android.systemui.biometrics.shared.model.SensorStrength
import com.android.systemui.coroutines.collectLastValue
@@ -39,11 +40,13 @@ import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationS
import com.android.systemui.keyguard.shared.model.HelpFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -57,6 +60,7 @@ class BiometricMessageInteractorTest : SysuiTestCase() {
private val fingerprintAuthRepository = kosmos.deviceEntryFingerprintAuthRepository
private val faceAuthRepository = kosmos.fakeDeviceEntryFaceAuthRepository
private val biometricSettingsRepository = kosmos.biometricSettingsRepository
+ private val faceHelpMessageDeferral = kosmos.faceHelpMessageDeferral
@Test
fun fingerprintErrorMessage() =
@@ -266,11 +270,38 @@ class BiometricMessageInteractorTest : SysuiTestCase() {
)
)
- // THEN fingerprintFailedMessage is updated
+ // THEN fingerprintHelpMessage is updated
assertThat(faceHelpMessage).isNotNull()
}
@Test
+ fun faceHelpMessageShouldDefer() =
+ testScope.runTest {
+ val faceHelpMessage by collectLastValue(underTest.faceMessage)
+
+ // GIVEN face is allowed
+ biometricSettingsRepository.setIsFaceAuthCurrentlyAllowed(true)
+
+ // GIVEN face only enrolled
+ biometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
+ biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
+
+ // WHEN all face help messages should be deferred
+ whenever(faceHelpMessageDeferral.shouldDefer(anyInt())).thenReturn(true)
+
+ // WHEN authentication status help
+ faceAuthRepository.setAuthenticationStatus(
+ HelpFaceAuthenticationStatus(
+ msg = "Move left",
+ msgId = FaceManager.FACE_ACQUIRED_TOO_RIGHT,
+ )
+ )
+
+ // THEN fingerprintHelpMessage is NOT updated
+ assertThat(faceHelpMessage).isNull()
+ }
+
+ @Test
fun faceHelpMessage_coEx() =
testScope.runTest {
val faceHelpMessage by collectLastValue(underTest.faceMessage)
@@ -291,7 +322,7 @@ class BiometricMessageInteractorTest : SysuiTestCase() {
)
)
- // THEN fingerprintFailedMessage is NOT updated
+ // THEN fingerprintHelpMessage is NOT updated
assertThat(faceHelpMessage).isNull()
}
@@ -337,7 +368,7 @@ class BiometricMessageInteractorTest : SysuiTestCase() {
testScope.runTest {
val faceErrorMessage by collectLastValue(underTest.faceMessage)
- // WHEN authentication status error is FACE_ERROR_HW_UNAVAILABLE
+ // WHEN authentication status error is FACE_ERROR_TIMEOUT
faceAuthRepository.setAuthenticationStatus(
ErrorFaceAuthenticationStatus(msgId = FaceManager.FACE_ERROR_TIMEOUT, msg = "test")
)
@@ -349,4 +380,23 @@ class BiometricMessageInteractorTest : SysuiTestCase() {
assertThat(faceErrorMessage).isInstanceOf(FaceTimeoutMessage::class.java)
assertThat(faceErrorMessage?.message).isEqualTo("test")
}
+
+ @Test
+ fun faceTimeoutDeferredErrorMessage() =
+ testScope.runTest {
+ whenever(faceHelpMessageDeferral.getDeferredMessage()).thenReturn("deferredMessage")
+ val faceErrorMessage by collectLastValue(underTest.faceMessage)
+
+ // WHEN authentication status error is FACE_ERROR_TIMEOUT
+ faceAuthRepository.setAuthenticationStatus(
+ ErrorFaceAuthenticationStatus(msgId = FaceManager.FACE_ERROR_TIMEOUT, msg = "test")
+ )
+
+ // GIVEN face is allowed
+ biometricSettingsRepository.setIsFaceAuthCurrentlyAllowed(true)
+
+ // THEN faceErrorMessage is updated to deferred message instead of timeout message
+ assertThat(faceErrorMessage).isNotInstanceOf(FaceTimeoutMessage::class.java)
+ assertThat(faceErrorMessage?.message).isEqualTo("deferredMessage")
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
index 3f454925186c..77b30402c040 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
@@ -65,6 +65,8 @@ import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.DialogTransitionAnimator;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.colorextraction.SysuiColorExtractor;
+import com.android.systemui.globalactions.domain.interactor.GlobalActionsInteractor;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.plugins.GlobalActions;
import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.settings.UserTracker;
@@ -140,6 +142,8 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase {
@Captor private ArgumentCaptor<OnBackInvokedCallback> mOnBackInvokedCallback;
private TestableLooper mTestableLooper;
+ private KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
+ private GlobalActionsInteractor mInteractor;
@Before
public void setUp() throws Exception {
@@ -154,6 +158,7 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase {
mGlobalSettings = new FakeGlobalSettings();
mSecureSettings = new FakeSettings();
+ mInteractor = mKosmos.getGlobalActionsInteractor();
mGlobalActionsDialogLite = new GlobalActionsDialogLite(mContext,
mWindowManagerFuncs,
@@ -189,7 +194,8 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase {
mShadeController,
mKeyguardUpdateMonitor,
mDialogTransitionAnimator,
- mSelectedUserInteractor);
+ mSelectedUserInteractor,
+ mInteractor);
mGlobalActionsDialogLite.setZeroDialogPressDelayForTesting();
ColorExtractor.GradientColors backdropColors = new ColorExtractor.GradientColors();
@@ -623,6 +629,18 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase {
assertThat(bugReportAction.showBeforeProvisioning()).isFalse();
}
+ @Test
+ public void testInteractor_onShow() {
+ mGlobalActionsDialogLite.onShow(null);
+ assertThat(mInteractor.isVisible().getValue()).isTrue();
+ }
+
+ @Test
+ public void testInteractor_onDismiss() {
+ mGlobalActionsDialogLite.onDismiss(mGlobalActionsDialogLite.mDialog);
+ assertThat(mInteractor.isVisible().getValue()).isFalse();
+ }
+
private UserInfo mockCurrentUser(int flags) {
return new UserInfo(10, "A User", flags);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/data/repository/GlobalActionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/globalactions/data/repository/GlobalActionsRepositoryTest.kt
new file mode 100644
index 000000000000..e437c10c7b73
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/data/repository/GlobalActionsRepositoryTest.kt
@@ -0,0 +1,69 @@
+/*
+ * 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.globalactions.data.repository
+
+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.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
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class GlobalActionsRepositoryTest : SysuiTestCase() {
+ private lateinit var underTest: GlobalActionsRepository
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+
+ @Before
+ fun setUp() {
+ underTest = kosmos.globalActionsRepository
+ }
+
+ @Test
+ fun isVisible_initialValueFalse() {
+ testScope.runTest {
+ val isVisible by collectLastValue(underTest.isVisible)
+ runCurrent()
+
+ assertThat(isVisible).isFalse()
+ }
+ }
+
+ @Test
+ fun isVisible_onChange() {
+ testScope.runTest {
+ val isVisible by collectLastValue(underTest.isVisible)
+ runCurrent()
+
+ underTest.setVisible(true)
+ assertThat(isVisible).isTrue()
+
+ underTest.setVisible(false)
+ assertThat(isVisible).isFalse()
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/domain/interactor/GlobalActionsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/globalactions/domain/interactor/GlobalActionsInteractorTest.kt
new file mode 100644
index 000000000000..9275512009b2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/domain/interactor/GlobalActionsInteractorTest.kt
@@ -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.systemui.globalactions.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.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.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class GlobalActionsInteractorTest : SysuiTestCase() {
+ private lateinit var underTest: GlobalActionsInteractor
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+
+ @Before
+ fun setup() {
+ underTest = kosmos.globalActionsInteractor
+ }
+
+ @Test
+ fun OnDismissed() {
+ testScope.runTest {
+ val isVisible by collectLastValue(underTest.isVisible)
+ underTest.onDismissed()
+ runCurrent()
+
+ assertThat(isVisible).isFalse()
+ }
+ }
+
+ @Test
+ fun OnShown() {
+ testScope.runTest {
+ val isVisible by collectLastValue(underTest.isVisible)
+ underTest.onShown()
+ runCurrent()
+
+ assertThat(isVisible).isTrue()
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
index cf8fe79f70f0..2b51863117e9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
@@ -2,6 +2,9 @@ package com.android.systemui.keyguard
import android.content.ComponentCallbacks2
import android.graphics.HardwareRenderer
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -23,6 +26,7 @@ import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
@@ -48,10 +52,11 @@ class ResourceTrimmerTest : SysuiTestCase() {
@Mock private lateinit var globalWindowManager: GlobalWindowManager
private lateinit var resourceTrimmer: ResourceTrimmer
+ @Rule @JvmField public val setFlagsRule = SetFlagsRule()
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- featureFlags.set(Flags.TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK, true)
featureFlags.set(Flags.TRIM_FONT_CACHES_AT_UNLOCK, true)
keyguardRepository.setDozeAmount(0f)
keyguardRepository.setKeyguardGoingAway(false)
@@ -76,6 +81,7 @@ class ResourceTrimmerTest : SysuiTestCase() {
}
@Test
+ @EnableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK)
fun noChange_noOutputChanges() =
testScope.runTest {
testScope.runCurrent()
@@ -83,6 +89,7 @@ class ResourceTrimmerTest : SysuiTestCase() {
}
@Test
+ @EnableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK)
fun dozeAodDisabled_sleep_trimsMemory() =
testScope.runTest {
powerInteractor.setAsleepForTest()
@@ -93,6 +100,27 @@ class ResourceTrimmerTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK)
+ fun dozeAodDisabled_flagDisabled_sleep_doesntTrimMemory() =
+ testScope.runTest {
+ powerInteractor.setAsleepForTest()
+ testScope.runCurrent()
+ verifyZeroInteractions(globalWindowManager)
+ }
+
+ @Test
+ @DisableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK)
+ fun dozeEnabled_flagDisabled_sleepWithFullDozeAmount_doesntTrimMemory() =
+ testScope.runTest {
+ keyguardRepository.setDreaming(true)
+ keyguardRepository.setDozeAmount(1f)
+ powerInteractor.setAsleepForTest()
+ testScope.runCurrent()
+ verifyZeroInteractions(globalWindowManager)
+ }
+
+ @Test
+ @EnableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK)
fun dozeEnabled_sleepWithFullDozeAmount_trimsMemory() =
testScope.runTest {
keyguardRepository.setDreaming(true)
@@ -105,6 +133,7 @@ class ResourceTrimmerTest : SysuiTestCase() {
}
@Test
+ @EnableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK)
fun dozeEnabled_sleepWithoutFullDozeAmount_doesntTrimMemory() =
testScope.runTest {
keyguardRepository.setDreaming(true)
@@ -115,6 +144,7 @@ class ResourceTrimmerTest : SysuiTestCase() {
}
@Test
+ @EnableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK)
fun aodEnabled_sleepWithFullDozeAmount_trimsMemoryOnce() {
testScope.runTest {
keyguardRepository.setDreaming(true)
@@ -141,6 +171,7 @@ class ResourceTrimmerTest : SysuiTestCase() {
}
@Test
+ @EnableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK)
fun aodEnabled_deviceWakesHalfWayThrough_doesNotTrimMemory() {
testScope.runTest {
keyguardRepository.setDreaming(true)
@@ -172,6 +203,7 @@ class ResourceTrimmerTest : SysuiTestCase() {
}
@Test
+ @EnableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK)
fun keyguardTransitionsToGone_trimsFontCache() =
testScope.runTest {
keyguardTransitionRepository.sendTransitionSteps(
@@ -186,6 +218,7 @@ class ResourceTrimmerTest : SysuiTestCase() {
}
@Test
+ @EnableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK)
fun keyguardTransitionsToGone_flagDisabled_doesNotTrimFontCache() =
testScope.runTest {
featureFlags.set(Flags.TRIM_FONT_CACHES_AT_UNLOCK, false)
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 5b93df5a0bad..a5d577dceecb 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
@@ -57,7 +57,6 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceTimeBy
-import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -148,6 +147,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
keyguardInteractor = keyguardInteractor,
transitionRepository = transitionRepository,
transitionInteractor = transitionInteractor,
+ glanceableHubTransitions = glanceableHubTransitions,
)
.apply { start() }
@@ -1372,6 +1372,44 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
}
@Test
+ fun dreamingToGlanceableHub() =
+ testScope.runTest {
+ // GIVEN a prior transition has run to DREAMING
+ keyguardRepository.setDreaming(true)
+ runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.DREAMING)
+ runCurrent()
+
+ // WHEN a transition to the glanceable hub starts
+ val currentScene = CommunalSceneKey.Blank
+ val targetScene = CommunalSceneKey.Communal
+
+ val progress = MutableStateFlow(0f)
+ val transitionState =
+ MutableStateFlow<ObservableCommunalTransitionState>(
+ ObservableCommunalTransitionState.Transition(
+ fromScene = currentScene,
+ toScene = targetScene,
+ progress = progress,
+ isInitiatedByUserInput = false,
+ isUserInputOngoing = flowOf(false),
+ )
+ )
+ communalInteractor.setTransitionState(transitionState)
+ progress.value = .1f
+ runCurrent()
+
+ assertThat(transitionRepository)
+ .startedTransition(
+ ownerName = FromDreamingTransitionInteractor::class.simpleName,
+ from = KeyguardState.DREAMING,
+ to = KeyguardState.GLANCEABLE_HUB,
+ animatorAssertion = { it.isNull() }, // transition should be manually animated
+ )
+
+ coroutineContext.cancelChildren()
+ }
+
+ @Test
fun lockscreenToOccluded() =
testScope.runTest {
// GIVEN a prior transition has run to LOCKSCREEN
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt
index 0e9197ef8ac1..f0607f4b70e1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt
@@ -22,7 +22,8 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
+import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.kosmos.testScope
@@ -51,8 +52,8 @@ class KeyguardTransitionAnimationFlowTest : SysuiTestCase() {
underTest =
animationFlow.setup(
duration = 1000.milliseconds,
- from = KeyguardState.GONE,
- to = KeyguardState.DREAMING,
+ from = GONE,
+ to = DREAMING,
)
}
@@ -192,17 +193,65 @@ class KeyguardTransitionAnimationFlowTest : SysuiTestCase() {
runCurrent()
repository.sendTransitionStep(step(0f, TransitionState.STARTED))
- assertThat(animationValues).isEqualTo(StateToValue(TransitionState.STARTED, 0f))
+ assertThat(animationValues)
+ .isEqualTo(
+ StateToValue(
+ from = GONE,
+ to = DREAMING,
+ transitionState = TransitionState.STARTED,
+ value = 0f
+ )
+ )
repository.sendTransitionStep(step(0.3f, TransitionState.RUNNING))
- assertThat(animationValues).isEqualTo(StateToValue(TransitionState.RUNNING, 0.6f))
+ assertThat(animationValues)
+ .isEqualTo(
+ StateToValue(
+ from = GONE,
+ to = DREAMING,
+ transitionState = TransitionState.RUNNING,
+ value = 0.6f
+ )
+ )
repository.sendTransitionStep(step(0.6f, TransitionState.RUNNING))
- assertThat(animationValues).isEqualTo(StateToValue(TransitionState.RUNNING, 1.2f))
+ assertThat(animationValues)
+ .isEqualTo(
+ StateToValue(
+ from = GONE,
+ to = DREAMING,
+ transitionState = TransitionState.RUNNING,
+ value = 1.2f
+ )
+ )
repository.sendTransitionStep(step(0.8f, TransitionState.RUNNING))
- assertThat(animationValues).isEqualTo(StateToValue(TransitionState.RUNNING, 1.6f))
+ assertThat(animationValues)
+ .isEqualTo(
+ StateToValue(
+ from = GONE,
+ to = DREAMING,
+ transitionState = TransitionState.RUNNING,
+ value = 1.6f
+ )
+ )
repository.sendTransitionStep(step(1f, TransitionState.RUNNING))
- assertThat(animationValues).isEqualTo(StateToValue(TransitionState.RUNNING, 2f))
+ assertThat(animationValues)
+ .isEqualTo(
+ StateToValue(
+ from = GONE,
+ to = DREAMING,
+ transitionState = TransitionState.RUNNING,
+ value = 2f
+ )
+ )
repository.sendTransitionStep(step(1f, TransitionState.FINISHED))
- assertThat(animationValues).isEqualTo(StateToValue(TransitionState.FINISHED, null))
+ assertThat(animationValues)
+ .isEqualTo(
+ StateToValue(
+ from = GONE,
+ to = DREAMING,
+ transitionState = TransitionState.FINISHED,
+ value = null
+ )
+ )
}
@Test
@@ -251,8 +300,8 @@ class KeyguardTransitionAnimationFlowTest : SysuiTestCase() {
state: TransitionState = TransitionState.RUNNING
): TransitionStep {
return TransitionStep(
- from = KeyguardState.GONE,
- to = KeyguardState.DREAMING,
+ from = GONE,
+ to = DREAMING,
value = value,
transitionState = state,
ownerName = "GoneToDreamingTransitionViewModelTest"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelTest.kt
index bfa84335d670..716c40d59ccf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelTest.kt
@@ -59,7 +59,14 @@ class GoneToAodTransitionViewModelTest : SysuiTestCase() {
// The animation should only start > .4f way through
repository.sendTransitionStep(step(0f, TransitionState.STARTED))
assertThat(enterFromTopTranslationY)
- .isEqualTo(StateToValue(TransitionState.STARTED, pixels))
+ .isEqualTo(
+ StateToValue(
+ from = KeyguardState.GONE,
+ to = KeyguardState.AOD,
+ transitionState = TransitionState.STARTED,
+ value = pixels
+ )
+ )
repository.sendTransitionStep(step(.55f))
assertThat(enterFromTopTranslationY!!.value ?: -1f).isIn(Range.closed(pixels, 0f))
@@ -70,7 +77,14 @@ class GoneToAodTransitionViewModelTest : SysuiTestCase() {
// At the end, the translation should be complete and set to zero
repository.sendTransitionStep(step(1f))
assertThat(enterFromTopTranslationY)
- .isEqualTo(StateToValue(TransitionState.RUNNING, 0f))
+ .isEqualTo(
+ StateToValue(
+ from = KeyguardState.GONE,
+ to = KeyguardState.AOD,
+ transitionState = TransitionState.RUNNING,
+ value = 0f
+ )
+ )
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
index 18a34ba964cf..1f14afa1d00b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
@@ -44,6 +44,8 @@ import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepo
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger
@@ -75,6 +77,7 @@ import org.mockito.ArgumentMatchers
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.MockitoAnnotations
+import kotlin.test.assertEquals
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -130,6 +133,8 @@ class KeyguardQuickAffordancesCombinedViewModelTest : SysuiTestCase() {
@Mock
private lateinit var lockscreenToPrimaryBouncerTransitionViewModel:
LockscreenToPrimaryBouncerTransitionViewModel
+ @Mock
+ private lateinit var transitionInteractor: KeyguardTransitionInteractor
private lateinit var underTest: KeyguardQuickAffordancesCombinedViewModel
@@ -146,6 +151,8 @@ class KeyguardQuickAffordancesCombinedViewModelTest : SysuiTestCase() {
// the viewModel does a `map { 1 - it }` on this value, which is why it's different
private val intendedShadeAlphaMutableStateFlow: MutableStateFlow<Float> = MutableStateFlow(0f)
+ private val intendedFinishedKeyguardStateFlow = MutableStateFlow(KeyguardState.LOCKSCREEN)
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
@@ -242,6 +249,7 @@ class KeyguardQuickAffordancesCombinedViewModelTest : SysuiTestCase() {
intendedAlphaMutableStateFlow.value = 1f
intendedShadeAlphaMutableStateFlow.value = 0f
+ intendedFinishedKeyguardStateFlow.value = KeyguardState.LOCKSCREEN
whenever(aodToLockscreenTransitionViewModel.shortcutsAlpha)
.thenReturn(intendedAlphaMutableStateFlow)
whenever(dozingToLockscreenTransitionViewModel.shortcutsAlpha).thenReturn(emptyFlow())
@@ -263,7 +271,9 @@ class KeyguardQuickAffordancesCombinedViewModelTest : SysuiTestCase() {
whenever(lockscreenToOccludedTransitionViewModel.shortcutsAlpha).thenReturn(emptyFlow())
whenever(lockscreenToPrimaryBouncerTransitionViewModel.shortcutsAlpha)
.thenReturn(emptyFlow())
- whenever(shadeInteractor.qsExpansion).thenReturn(intendedShadeAlphaMutableStateFlow)
+ whenever(shadeInteractor.anyExpansion).thenReturn(intendedShadeAlphaMutableStateFlow)
+ whenever(transitionInteractor.finishedKeyguardState)
+ .thenReturn(intendedFinishedKeyguardStateFlow)
underTest =
KeyguardQuickAffordancesCombinedViewModel(
@@ -304,7 +314,8 @@ class KeyguardQuickAffordancesCombinedViewModelTest : SysuiTestCase() {
lockscreenToGoneTransitionViewModel = lockscreenToGoneTransitionViewModel,
lockscreenToOccludedTransitionViewModel = lockscreenToOccludedTransitionViewModel,
lockscreenToPrimaryBouncerTransitionViewModel =
- lockscreenToPrimaryBouncerTransitionViewModel
+ lockscreenToPrimaryBouncerTransitionViewModel,
+ transitionInteractor = transitionInteractor,
)
}
@@ -682,6 +693,30 @@ class KeyguardQuickAffordancesCombinedViewModelTest : SysuiTestCase() {
)
}
+ @Test
+ fun shadeExpansionAlpha_changes_whenOnLockscreen() =
+ testScope.runTest {
+ intendedFinishedKeyguardStateFlow.value = KeyguardState.LOCKSCREEN
+ intendedShadeAlphaMutableStateFlow.value = 0.25f
+ val underTest = collectLastValue(underTest.transitionAlpha)
+ assertEquals(0.75f, underTest())
+
+ intendedShadeAlphaMutableStateFlow.value = 0.3f
+ assertEquals(0.7f, underTest())
+ }
+
+ @Test
+ fun shadeExpansionAlpha_alwaysZero_whenNotOnLockscreen() =
+ testScope.runTest {
+ intendedFinishedKeyguardStateFlow.value = KeyguardState.GONE
+ intendedShadeAlphaMutableStateFlow.value = 0.5f
+ val underTest = collectLastValue(underTest.transitionAlpha)
+ assertEquals(0f, underTest())
+
+ intendedShadeAlphaMutableStateFlow.value = 0.25f
+ assertEquals(0f, underTest())
+ }
+
private suspend fun setUpQuickAffordanceModel(
position: KeyguardQuickAffordancePosition,
testConfig: TestConfig,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/MediaTestUtils.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/MediaTestUtils.kt
index 3437365d9902..4e976d0597cf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/MediaTestUtils.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/MediaTestUtils.kt
@@ -17,7 +17,7 @@
package com.android.systemui.media.controls
import com.android.internal.logging.InstanceId
-import com.android.systemui.media.controls.models.player.MediaData
+import com.android.systemui.media.controls.shared.model.MediaData
class MediaTestUtils {
companion object {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatestTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatestTest.java
index fb101dda6aaf..bb5b57287a9a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatestTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatestTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media.controls.pipeline;
+package com.android.systemui.media.controls.domain.pipeline;
import static com.google.common.truth.Truth.assertThat;
@@ -33,8 +33,8 @@ import androidx.test.filters.SmallTest;
import com.android.internal.logging.InstanceId;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.media.controls.models.player.MediaData;
-import com.android.systemui.media.controls.models.player.MediaDeviceData;
+import com.android.systemui.media.controls.shared.model.MediaData;
+import com.android.systemui.media.controls.shared.model.MediaDeviceData;
import org.junit.Before;
import org.junit.Rule;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterTest.kt
index 94b9fa4a6582..59eb7bb73de7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media.controls.pipeline
+package com.android.systemui.media.controls.domain.pipeline
import android.app.smartspace.SmartspaceAction
import android.os.Bundle
@@ -25,10 +25,10 @@ import com.android.internal.logging.InstanceId
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastSender
import com.android.systemui.media.controls.MediaTestUtils
-import com.android.systemui.media.controls.models.player.MediaData
-import com.android.systemui.media.controls.models.recommendation.EXTRA_KEY_TRIGGER_RESUME
-import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
-import com.android.systemui.media.controls.ui.MediaPlayerData
+import com.android.systemui.media.controls.shared.model.EXTRA_KEY_TRIGGER_RESUME
+import com.android.systemui.media.controls.shared.model.MediaData
+import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
+import com.android.systemui.media.controls.ui.controller.MediaPlayerData
import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.media.controls.util.MediaUiEventLogger
import com.android.systemui.settings.UserTracker
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataManagerTest.kt
index 59d81049ad46..61bfdb548b4f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataManagerTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media.controls.pipeline
+package com.android.systemui.media.controls.domain.pipeline
import android.app.IUriGrantsManager
import android.app.Notification
@@ -51,13 +51,13 @@ import com.android.systemui.InstanceIdSequenceFake
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dump.DumpManager
-import com.android.systemui.media.controls.models.player.MediaData
-import com.android.systemui.media.controls.models.recommendation.EXTRA_KEY_TRIGGER_SOURCE
-import com.android.systemui.media.controls.models.recommendation.EXTRA_VALUE_TRIGGER_PERIODIC
-import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
-import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaDataProvider
-import com.android.systemui.media.controls.resume.MediaResumeListener
-import com.android.systemui.media.controls.resume.ResumeMediaBrowser
+import com.android.systemui.media.controls.domain.resume.MediaResumeListener
+import com.android.systemui.media.controls.domain.resume.ResumeMediaBrowser
+import com.android.systemui.media.controls.shared.model.EXTRA_KEY_TRIGGER_SOURCE
+import com.android.systemui.media.controls.shared.model.EXTRA_VALUE_TRIGGER_PERIODIC
+import com.android.systemui.media.controls.shared.model.MediaData
+import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
+import com.android.systemui.media.controls.shared.model.SmartspaceMediaDataProvider
import com.android.systemui.media.controls.util.MediaControllerFactory
import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.media.controls.util.MediaUiEventLogger
@@ -1476,7 +1476,7 @@ class MediaDataManagerTest : SysuiTestCase() {
}
@Test
- fun testOnMediaDataTimedOut_doesNotUpdateLastActiveTime() {
+ fun testOnMediaDataTimedOut_updatesLastActiveTime() {
// GIVEN that the manager has a notification
mediaDataManager.onNotificationAdded(KEY, mediaNotification)
assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
@@ -1487,7 +1487,7 @@ class MediaDataManagerTest : SysuiTestCase() {
val currentTime = clock.elapsedRealtime()
mediaDataManager.setTimedOut(KEY, true, true)
- // THEN the last active time is not changed
+ // THEN the last active time is changed
verify(listener)
.onMediaDataLoaded(
eq(KEY),
@@ -1497,11 +1497,11 @@ class MediaDataManagerTest : SysuiTestCase() {
eq(0),
eq(false)
)
- assertThat(mediaDataCaptor.value.lastActive).isLessThan(currentTime)
+ assertThat(mediaDataCaptor.value.lastActive).isAtLeast(currentTime)
}
@Test
- fun testOnActiveMediaConverted_doesNotUpdateLastActiveTime() {
+ fun testOnActiveMediaConverted_updatesLastActiveTime() {
// GIVEN that the manager has a notification with a resume action
addNotificationAndLoad()
val data = mediaDataCaptor.value
@@ -1514,6 +1514,42 @@ class MediaDataManagerTest : SysuiTestCase() {
val currentTime = clock.elapsedRealtime()
mediaDataManager.onNotificationRemoved(KEY)
+ // THEN the last active time is changed
+ verify(listener)
+ .onMediaDataLoaded(
+ eq(PACKAGE_NAME),
+ eq(KEY),
+ capture(mediaDataCaptor),
+ eq(true),
+ eq(0),
+ eq(false)
+ )
+ assertThat(mediaDataCaptor.value.resumption).isTrue()
+ assertThat(mediaDataCaptor.value.lastActive).isAtLeast(currentTime)
+
+ // Log as a conversion event, not as a new resume control
+ verify(logger).logActiveConvertedToResume(anyInt(), eq(PACKAGE_NAME), eq(instanceId))
+ verify(logger, never()).logResumeMediaAdded(anyInt(), eq(PACKAGE_NAME), any())
+ }
+
+ @Test
+ fun testOnInactiveMediaConverted_doesNotUpdateLastActiveTime() {
+ // GIVEN that the manager has a notification with a resume action
+ addNotificationAndLoad()
+ val data = mediaDataCaptor.value
+ val instanceId = data.instanceId
+ assertThat(data.resumption).isFalse()
+ mediaDataManager.onMediaDataLoaded(
+ KEY,
+ null,
+ data.copy(resumeAction = Runnable {}, active = false)
+ )
+
+ // WHEN the notification is removed
+ clock.advanceTime(100)
+ val currentTime = clock.elapsedRealtime()
+ mediaDataManager.onNotificationRemoved(KEY)
+
// THEN the last active time is not changed
verify(listener)
.onMediaDataLoaded(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt
index e3c4c2858b3d..14fe18289401 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media.controls.pipeline
+package com.android.systemui.media.controls.domain.pipeline
import android.bluetooth.BluetoothLeBroadcast
import android.bluetooth.BluetoothLeBroadcastMetadata
@@ -39,8 +39,9 @@ import com.android.settingslib.media.PhoneMediaDevice
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.media.controls.MediaTestUtils
-import com.android.systemui.media.controls.models.player.MediaData
-import com.android.systemui.media.controls.models.player.MediaDeviceData
+import com.android.systemui.media.controls.shared.model.MediaData
+import com.android.systemui.media.controls.shared.model.MediaDeviceData
+import com.android.systemui.media.controls.util.LocalMediaManagerFactory
import com.android.systemui.media.controls.util.MediaControllerFactory
import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManager
import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManagerFactory
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaSessionBasedFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilterTest.kt
index 3099609d42f0..5a3c220b3d23 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaSessionBasedFilterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilterTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media.controls.pipeline
+package com.android.systemui.media.controls.domain.pipeline
import android.media.session.MediaController
import android.media.session.MediaController.PlaybackInfo
@@ -25,7 +25,7 @@ import android.testing.TestableLooper
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.media.controls.MediaTestUtils
-import com.android.systemui.media.controls.models.player.MediaData
+import com.android.systemui.media.controls.shared.model.MediaData
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.FakeSystemClock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt
index 8baa06ac0141..3cc65c9524a8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media.controls.pipeline
+package com.android.systemui.media.controls.domain.pipeline
import android.media.MediaMetadata
import android.media.session.MediaController
@@ -24,8 +24,8 @@ import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.media.controls.MediaTestUtils
-import com.android.systemui.media.controls.models.player.MediaData
-import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
+import com.android.systemui.media.controls.shared.model.MediaData
+import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
import com.android.systemui.media.controls.util.MediaControllerFactory
import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.plugins.statusbar.StatusBarStateController
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/resume/MediaResumeListenerTest.kt
index 530b86eb4978..55ff231ab6b6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/resume/MediaResumeListenerTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media.controls.resume
+package com.android.systemui.media.controls.domain.resume
import android.app.PendingIntent
import android.content.ComponentName
@@ -34,10 +34,10 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dump.DumpManager
import com.android.systemui.media.controls.MediaTestUtils
-import com.android.systemui.media.controls.models.player.MediaData
-import com.android.systemui.media.controls.models.player.MediaDeviceData
-import com.android.systemui.media.controls.pipeline.MediaDataManager
-import com.android.systemui.media.controls.pipeline.RESUME_MEDIA_TIMEOUT
+import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
+import com.android.systemui.media.controls.domain.pipeline.RESUME_MEDIA_TIMEOUT
+import com.android.systemui.media.controls.shared.model.MediaData
+import com.android.systemui.media.controls.shared.model.MediaDeviceData
import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.settings.UserTracker
import com.android.systemui.tuner.TunerService
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/resume/ResumeMediaBrowserTest.kt
index b45e66bfc31b..8dfa5b8e640c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/resume/ResumeMediaBrowserTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media.controls.resume
+package com.android.systemui.media.controls.domain.resume
import android.content.ComponentName
import android.content.Context
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaDataTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/shared/SmartspaceMediaDataTest.kt
index f7c20ac35957..473dc4712c25 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaDataTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/shared/SmartspaceMediaDataTest.kt
@@ -14,14 +14,16 @@
* limitations under the License.
*/
-package com.android.systemui.media.controls.models.recommendation
+package com.android.systemui.media.controls.shared
import android.app.smartspace.SmartspaceAction
import android.graphics.drawable.Icon
import androidx.test.filters.SmallTest
import com.android.internal.logging.InstanceId
-import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.media.controls.shared.model.NUM_REQUIRED_RECOMMENDATIONS
+import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
+import com.android.systemui.res.R
import com.google.common.truth.Truth.assertThat
import org.junit.Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaPlayerDataTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaPlayerDataTest.kt
index 32b822d798f8..b509e779a8c3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaPlayerDataTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaPlayerDataTest.kt
@@ -20,7 +20,9 @@ import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.media.controls.MediaTestUtils
-import com.android.systemui.media.controls.models.player.MediaData
+import com.android.systemui.media.controls.shared.model.MediaData
+import com.android.systemui.media.controls.ui.controller.MediaControlPanel
+import com.android.systemui.media.controls.ui.controller.MediaPlayerData
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import org.junit.Before
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/AnimationBindHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/AnimationBindHandlerTest.kt
index 99f56b16ab8b..eb885fd4ae41 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/AnimationBindHandlerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/AnimationBindHandlerTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media.controls.ui
+package com.android.systemui.media.controls.ui.animation
import android.graphics.drawable.Animatable2
import android.graphics.drawable.Drawable
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/ColorSchemeTransitionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/ColorSchemeTransitionTest.kt
index a94374680b91..aa297b537c49 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/ColorSchemeTransitionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/ColorSchemeTransitionTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media.controls.ui
+package com.android.systemui.media.controls.ui.animation
import android.animation.ValueAnimator
import android.graphics.Color
@@ -22,8 +22,8 @@ import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.media.controls.models.GutsViewHolder
-import com.android.systemui.media.controls.models.player.MediaViewHolder
+import com.android.systemui.media.controls.ui.view.GutsViewHolder
+import com.android.systemui.media.controls.ui.view.MediaViewHolder
import com.android.systemui.monet.ColorScheme
import com.android.systemui.surfaceeffects.ripple.MultiRippleController
import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseController
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MetadataAnimationHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/MetadataAnimationHandlerTest.kt
index 323b7818ed3d..711669eb2dd0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MetadataAnimationHandlerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/MetadataAnimationHandlerTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media.controls.ui
+package com.android.systemui.media.controls.ui.animation
import android.animation.Animator
import android.test.suitebuilder.annotation.SmallTest
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarObserverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/binder/SeekBarObserverTest.kt
index 4ec29ceb56d3..8a6b2722b1a2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarObserverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/binder/SeekBarObserverTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media.controls.models.player
+package com.android.systemui.media.controls.ui.binder
import android.animation.Animator
import android.animation.ObjectAnimator
@@ -25,7 +25,9 @@ import android.widget.SeekBar
import android.widget.TextView
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.media.controls.ui.SquigglyProgress
+import com.android.systemui.media.controls.ui.drawable.SquigglyProgress
+import com.android.systemui.media.controls.ui.view.MediaViewHolder
+import com.android.systemui.media.controls.ui.viewmodel.SeekBarViewModel
import com.android.systemui.res.R
import com.google.common.truth.Truth.assertThat
import org.junit.Before
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaControllerTest.kt
index 50f0eb4eca2c..9f5260c252e4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaControllerTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media.controls.ui
+package com.android.systemui.media.controls.ui.controller
import android.provider.Settings
import android.test.suitebuilder.annotation.SmallTest
@@ -25,6 +25,8 @@ import android.view.View.VISIBLE
import android.widget.FrameLayout
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
+import com.android.systemui.media.controls.ui.view.MediaHost
+import com.android.systemui.media.controls.ui.view.MediaHostState
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.SysuiStatusBarStateController
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt
index f3b9102da780..f755199b4c72 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media.controls.ui
+package com.android.systemui.media.controls.ui.controller
import android.app.PendingIntent
import android.content.res.ColorStateList
@@ -38,11 +38,13 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInterac
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.media.controls.MediaTestUtils
-import com.android.systemui.media.controls.models.player.MediaData
-import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
-import com.android.systemui.media.controls.pipeline.EMPTY_SMARTSPACE_MEDIA_DATA
-import com.android.systemui.media.controls.pipeline.MediaDataManager
-import com.android.systemui.media.controls.ui.MediaHierarchyManager.Companion.LOCATION_QS
+import com.android.systemui.media.controls.domain.pipeline.EMPTY_SMARTSPACE_MEDIA_DATA
+import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
+import com.android.systemui.media.controls.shared.model.MediaData
+import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
+import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager.Companion.LOCATION_QS
+import com.android.systemui.media.controls.ui.view.MediaHostState
+import com.android.systemui.media.controls.ui.view.MediaScrollView
import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.media.controls.util.MediaUiEventLogger
import com.android.systemui.plugins.ActivityStarter
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt
index c896486339b9..2e7829d4ea7b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media.controls.ui
+package com.android.systemui.media.controls.ui.controller
import android.animation.Animator
import android.animation.AnimatorSet
@@ -41,6 +41,8 @@ import android.media.session.MediaSession
import android.media.session.PlaybackState
import android.os.Bundle
import android.platform.test.annotations.EnableFlags
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
import android.provider.Settings
import android.provider.Settings.ACTION_MEDIA_CONTROLS_SETTINGS
import android.testing.AndroidTestingRunner
@@ -67,19 +69,19 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.bluetooth.BroadcastDialogController
import com.android.systemui.broadcast.BroadcastSender
import com.android.systemui.media.controls.MediaTestUtils
-import com.android.systemui.media.controls.models.GutsViewHolder
-import com.android.systemui.media.controls.models.player.MediaAction
-import com.android.systemui.media.controls.models.player.MediaButton
-import com.android.systemui.media.controls.models.player.MediaData
-import com.android.systemui.media.controls.models.player.MediaDeviceData
-import com.android.systemui.media.controls.models.player.MediaViewHolder
-import com.android.systemui.media.controls.models.player.SeekBarObserver
-import com.android.systemui.media.controls.models.player.SeekBarViewModel
-import com.android.systemui.media.controls.models.recommendation.KEY_SMARTSPACE_APP_NAME
-import com.android.systemui.media.controls.models.recommendation.RecommendationViewHolder
-import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData
-import com.android.systemui.media.controls.pipeline.EMPTY_SMARTSPACE_MEDIA_DATA
-import com.android.systemui.media.controls.pipeline.MediaDataManager
+import com.android.systemui.media.controls.domain.pipeline.EMPTY_SMARTSPACE_MEDIA_DATA
+import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
+import com.android.systemui.media.controls.shared.model.KEY_SMARTSPACE_APP_NAME
+import com.android.systemui.media.controls.shared.model.MediaAction
+import com.android.systemui.media.controls.shared.model.MediaButton
+import com.android.systemui.media.controls.shared.model.MediaData
+import com.android.systemui.media.controls.shared.model.MediaDeviceData
+import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
+import com.android.systemui.media.controls.ui.binder.SeekBarObserver
+import com.android.systemui.media.controls.ui.view.GutsViewHolder
+import com.android.systemui.media.controls.ui.view.MediaViewHolder
+import com.android.systemui.media.controls.ui.view.RecommendationViewHolder
+import com.android.systemui.media.controls.ui.viewmodel.SeekBarViewModel
import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.media.controls.util.MediaUiEventLogger
import com.android.systemui.media.dialog.MediaOutputDialogFactory
@@ -140,6 +142,7 @@ private const val APP_NAME = "APP_NAME"
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class MediaControlPanelTest : SysuiTestCase() {
+ @get:Rule val checkFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
private lateinit var player: MediaControlPanel
@@ -1244,6 +1247,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
fun bindBroadcastButton() {
initMediaViewHolderMocks()
initDeviceMediaData(true, APP_NAME)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt
index 87d093fe2dff..85291b814b3c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media.controls.ui
+package com.android.systemui.media.controls.ui.controller
import android.graphics.Rect
import android.provider.Settings
@@ -31,7 +31,10 @@ import com.android.systemui.controls.controller.ControlsControllerImplTest.Compa
import com.android.systemui.dreams.DreamOverlayStateController
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.kosmos.testScope
-import com.android.systemui.media.controls.pipeline.MediaDataManager
+import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
+import com.android.systemui.media.controls.ui.view.MediaCarouselScrollHandler
+import com.android.systemui.media.controls.ui.view.MediaHost
+import com.android.systemui.media.controls.ui.view.MediaHostState
import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.media.dream.MediaDreamComplication
import com.android.systemui.plugins.statusbar.StatusBarStateController
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaViewControllerTest.kt
index b701d7f315bc..a73bb2cdf79a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaViewControllerTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media.controls.ui
+package com.android.systemui.media.controls.ui.controller
import android.content.res.Configuration
import android.content.res.Configuration.ORIENTATION_LANDSCAPE
@@ -24,8 +24,9 @@ import android.view.View
import androidx.constraintlayout.widget.ConstraintSet
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.media.controls.models.player.MediaViewHolder
-import com.android.systemui.media.controls.models.recommendation.RecommendationViewHolder
+import com.android.systemui.media.controls.ui.view.MediaHost
+import com.android.systemui.media.controls.ui.view.MediaViewHolder
+import com.android.systemui.media.controls.ui.view.RecommendationViewHolder
import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.res.R
import com.android.systemui.util.animation.MeasurementInput
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/SquigglyProgressTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/drawable/SquigglyProgressTest.kt
index d6cff81c0aaa..0319aaaedd27 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/SquigglyProgressTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/drawable/SquigglyProgressTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media.controls.ui
+package com.android.systemui.media.controls.ui.drawable
import android.graphics.Canvas
import android.graphics.Color
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandlerTest.kt
index 74b3fce12790..120836959fdc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandlerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandlerTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media.controls.ui
+package com.android.systemui.media.controls.ui.view
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/MediaViewHolderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/view/MediaViewHolderTest.kt
index c829d4cbfb71..d3c703ccce46 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/MediaViewHolderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/view/MediaViewHolderTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media.controls.models.player
+package com.android.systemui.media.controls.ui.view
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/viewmodel/SeekBarViewModelTest.kt
index e3c8b052327c..e1c2d3f115ed 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/viewmodel/SeekBarViewModelTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media.controls.models.player
+package com.android.systemui.media.controls.ui.viewmodel
import android.media.MediaMetadata
import android.media.session.MediaController
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
index f7873aa3e40e..ca403e0addec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
@@ -19,6 +19,7 @@ package com.android.systemui.media.dialog;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -47,6 +48,7 @@ import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
+import com.android.settingslib.media.LocalMediaManager;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.DialogTransitionAnimator;
import com.android.systemui.broadcast.BroadcastSender;
@@ -126,6 +128,13 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase {
mNotifCollection, mDialogTransitionAnimator,
mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
mKeyguardManager, mFlags, mUserTracker);
+
+ // Using a fake package will cause routing operations to fail, so we intercept
+ // scanning-related operations.
+ mMediaOutputController.mLocalMediaManager = mock(LocalMediaManager.class);
+ doNothing().when(mMediaOutputController.mLocalMediaManager).startScan();
+ doNothing().when(mMediaOutputController.mLocalMediaManager).stopScan();
+
mMediaOutputBaseDialogImpl = new MediaOutputBaseDialogImpl(mContext, mBroadcastSender,
mMediaOutputController);
mMediaOutputBaseDialogImpl.onCreate(new Bundle());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
index 2b62f03c5c02..d9ddc8e6b23b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
@@ -35,6 +35,9 @@ import android.media.session.MediaSessionManager;
import android.media.session.PlaybackState;
import android.os.PowerExemptionManager;
import android.os.UserHandle;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.util.FeatureFlagUtils;
@@ -61,6 +64,7 @@ import com.android.systemui.statusbar.notification.collection.notifcollection.Co
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
@@ -76,6 +80,9 @@ public class MediaOutputDialogTest extends SysuiTestCase {
private static final String TEST_PACKAGE = "test_package";
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
// Mock
private final MediaSessionManager mMediaSessionManager = mock(MediaSessionManager.class);
private MediaController mMediaController = mock(MediaController.class);
@@ -170,6 +177,7 @@ public class MediaOutputDialogTest extends SysuiTestCase {
}
@Test
+ @RequiresFlagsEnabled(com.android.systemui.Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void getStopButtonVisibility_remoteBLEDevice_returnVisible() {
when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
mLocalBluetoothLeBroadcast);
@@ -181,6 +189,7 @@ public class MediaOutputDialogTest extends SysuiTestCase {
}
@Test
+ @RequiresFlagsEnabled(com.android.systemui.Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void getStopButtonVisibility_remoteNonBLEDevice_returnGone() {
when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
mLocalBluetoothLeBroadcast);
@@ -201,6 +210,7 @@ public class MediaOutputDialogTest extends SysuiTestCase {
}
@Test
+ @RequiresFlagsEnabled(com.android.systemui.Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void isBroadcastSupported_flagOnAndConnectBleDevice_returnsTrue() {
when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
mLocalBluetoothLeBroadcast);
@@ -213,6 +223,7 @@ public class MediaOutputDialogTest extends SysuiTestCase {
}
@Test
+ @RequiresFlagsEnabled(com.android.systemui.Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void isBroadcastSupported_flagOnAndNoBleDevice_returnsFalse() {
when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
mLocalBluetoothLeBroadcast);
@@ -225,6 +236,7 @@ public class MediaOutputDialogTest extends SysuiTestCase {
}
@Test
+ @RequiresFlagsEnabled(com.android.systemui.Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void isBroadcastSupported_notSupportBroadcastAndflagOn_returnsFalse() {
FeatureFlagUtils.setEnabled(mContext,
FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST, true);
@@ -233,6 +245,7 @@ public class MediaOutputDialogTest extends SysuiTestCase {
}
@Test
+ @RequiresFlagsEnabled(com.android.systemui.Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void isBroadcastSupported_flagOffAndConnectToBleDevice_returnsTrue() {
when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
mLocalBluetoothLeBroadcast);
@@ -245,6 +258,7 @@ public class MediaOutputDialogTest extends SysuiTestCase {
}
@Test
+ @RequiresFlagsEnabled(com.android.systemui.Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void isBroadcastSupported_flagOffAndNoBleDevice_returnsTrue() {
when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
mLocalBluetoothLeBroadcast);
@@ -257,6 +271,7 @@ public class MediaOutputDialogTest extends SysuiTestCase {
}
@Test
+ @RequiresFlagsEnabled(com.android.systemui.Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void isBroadcastSupported_noBleDeviceAndEnabledBroadcast_returnsTrue() {
when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
mLocalBluetoothLeBroadcast);
@@ -269,6 +284,7 @@ public class MediaOutputDialogTest extends SysuiTestCase {
}
@Test
+ @RequiresFlagsEnabled(com.android.systemui.Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void isBroadcastSupported_noBleDeviceAndDisabledBroadcast_returnsFalse() {
when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
mLocalBluetoothLeBroadcast);
@@ -281,6 +297,7 @@ public class MediaOutputDialogTest extends SysuiTestCase {
}
@Test
+ @RequiresFlagsEnabled(com.android.systemui.Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void getBroadcastIconVisibility_isBroadcasting_returnVisible() {
when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
mLocalBluetoothLeBroadcast);
@@ -292,6 +309,7 @@ public class MediaOutputDialogTest extends SysuiTestCase {
}
@Test
+ @RequiresFlagsEnabled(com.android.systemui.Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void getBroadcastIconVisibility_noBroadcasting_returnGone() {
when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
mLocalBluetoothLeBroadcast);
@@ -303,6 +321,7 @@ public class MediaOutputDialogTest extends SysuiTestCase {
}
@Test
+ @RequiresFlagsEnabled(com.android.systemui.Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void getBroadcastIconVisibility_remoteNonLeDevice_returnGone() {
when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
mLocalBluetoothLeBroadcast);
@@ -355,6 +374,7 @@ public class MediaOutputDialogTest extends SysuiTestCase {
}
@Test
+ @RequiresFlagsEnabled(com.android.systemui.Flags.FLAG_LEGACY_LE_AUDIO_SHARING)
public void getStopButtonText_supportsBroadcast_returnsBroadcastText() {
String stopText = mContext.getText(R.string.media_output_broadcast).toString();
MediaDevice mMediaDevice = mock(MediaDevice.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaComplicationViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaComplicationViewControllerTest.java
index ce885c0bba2a..a82884377602 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaComplicationViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaComplicationViewControllerTest.java
@@ -25,7 +25,7 @@ import android.widget.FrameLayout;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.media.controls.ui.MediaHost;
+import com.android.systemui.media.controls.ui.view.MediaHost;
import com.android.systemui.util.animation.UniqueObjectHostView;
import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java
index 8a316642a3b0..ff7c970960e9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java
@@ -33,8 +33,8 @@ import com.android.systemui.SysuiTestCase;
import com.android.systemui.complication.DreamMediaEntryComplication;
import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.media.controls.models.player.MediaData;
-import com.android.systemui.media.controls.pipeline.MediaDataManager;
+import com.android.systemui.media.controls.domain.pipeline.MediaDataManager;
+import com.android.systemui.media.controls.shared.model.MediaData;
import org.junit.Before;
import org.junit.Test;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
index e2be4cbab14d..27f59d292927 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
@@ -102,7 +102,6 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() {
fun setUp() {
MockitoAnnotations.initMocks(this)
whenever(mediaTttFlags.isMediaTttEnabled()).thenReturn(true)
- whenever(mediaTttFlags.isMediaTttReceiverSuccessRippleEnabled()).thenReturn(true)
fakeAppIconDrawable = context.getDrawable(R.drawable.ic_cake)!!
whenever(packageManager.getApplicationIcon(PACKAGE_NAME)).thenReturn(fakeAppIconDrawable)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
index 0d1e87433c60..31746a2a46a3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
@@ -184,6 +184,8 @@ public class NavigationBarTest extends SysuiTestCase {
@Mock
private NavBarButtonClickLogger mNavBarButtonClickLogger;
@Mock
+ private NavbarOrientationTrackingLogger mNavbarOrientationTrackingLogger;
+ @Mock
private ViewTreeObserver mViewTreeObserver;
NavBarHelper mNavBarHelper;
@Mock
@@ -599,7 +601,8 @@ public class NavigationBarTest extends SysuiTestCase {
mWakefulnessLifecycle,
mTaskStackChangeListeners,
new FakeDisplayTracker(mContext),
- mNavBarButtonClickLogger));
+ mNavBarButtonClickLogger,
+ mNavbarOrientationTrackingLogger));
}
private void processAllMessages() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java
index 563a3fe9fc7f..e4a4836bcd46 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java
@@ -20,6 +20,7 @@ import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.android.systemui.statusbar.StatusBarState.SHADE;
+import static com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED;
import static com.google.common.truth.Truth.assertThat;
@@ -57,7 +58,7 @@ import com.android.keyguard.BouncerPanelExpansionCalculator;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlagsClassic;
-import com.android.systemui.media.controls.ui.MediaHost;
+import com.android.systemui.media.controls.ui.view.MediaHost;
import com.android.systemui.qs.customize.QSCustomizerController;
import com.android.systemui.qs.dagger.QSComponent;
import com.android.systemui.qs.external.TileServiceRequestController;
@@ -511,6 +512,28 @@ public class QSImplTest extends SysuiTestCase {
);
}
+ @Test
+ public void testSceneContainerFlagsEnabled_statusBarStateIsShade() {
+ when(mSceneContainerFlags.isEnabled()).thenReturn(true);
+
+ mUnderTest.onStateChanged(KEYGUARD);
+ assertThat(mUnderTest.getStatusBarState()).isEqualTo(SHADE);
+
+ mUnderTest.onStateChanged(SHADE_LOCKED);
+ assertThat(mUnderTest.getStatusBarState()).isEqualTo(SHADE);
+ }
+
+ @Test
+ public void testSceneContainerFlagsEnabled_isKeyguardState_alwaysFalse() {
+ when(mSceneContainerFlags.isEnabled()).thenReturn(true);
+
+ mUnderTest.onStateChanged(KEYGUARD);
+ assertThat(mUnderTest.isKeyguardState()).isFalse();
+
+ when(mStatusBarStateController.getCurrentOrUpcomingState()).thenReturn(KEYGUARD);
+ assertThat(mUnderTest.isKeyguardState()).isFalse();
+ }
+
private QSImpl instantiate() {
setupQsComponent();
setUpViews();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
index da8d29c622d1..65ede89a1514 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
@@ -45,7 +45,7 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.logging.testing.UiEventLoggerFake;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.media.controls.ui.MediaHost;
+import com.android.systemui.media.controls.ui.view.MediaHost;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.qs.customize.QSCustomizerController;
import com.android.systemui.qs.logging.QSLogger;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
index 1f7a02962ce2..85d7d9865c7c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
@@ -10,8 +10,8 @@ import com.android.internal.logging.UiEventLogger
import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
-import com.android.systemui.media.controls.ui.MediaHost
-import com.android.systemui.media.controls.ui.MediaHostState
+import com.android.systemui.media.controls.ui.view.MediaHost
+import com.android.systemui.media.controls.ui.view.MediaHostState
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.qs.customize.QSCustomizerController
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
index 2db79c2dc527..2c1430844d12 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
@@ -25,8 +25,8 @@ import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
-import com.android.systemui.media.controls.ui.MediaHost
-import com.android.systemui.media.controls.ui.MediaHostState
+import com.android.systemui.media.controls.ui.view.MediaHost
+import com.android.systemui.media.controls.ui.view.MediaHostState
import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.qs.customize.QSCustomizerController
import com.android.systemui.qs.logging.QSLogger
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index db455cb31684..950a9dbc2ff3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -115,9 +115,9 @@ import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransition
import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel;
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel;
import com.android.systemui.kosmos.KosmosJavaAdapter;
-import com.android.systemui.media.controls.pipeline.MediaDataManager;
-import com.android.systemui.media.controls.ui.KeyguardMediaController;
-import com.android.systemui.media.controls.ui.MediaHierarchyManager;
+import com.android.systemui.media.controls.domain.pipeline.MediaDataManager;
+import com.android.systemui.media.controls.ui.controller.KeyguardMediaController;
+import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager;
import com.android.systemui.model.SysUiState;
import com.android.systemui.navigationbar.NavigationBarController;
import com.android.systemui.navigationbar.NavigationModeController;
@@ -439,8 +439,13 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
)
);
SystemClock systemClock = new FakeSystemClock();
- mStatusBarStateController = new StatusBarStateControllerImpl(mUiEventLogger,
- mInteractionJankMonitor, mJavaAdapter, () -> mShadeInteractor);
+ mStatusBarStateController = new StatusBarStateControllerImpl(
+ mUiEventLogger,
+ mInteractionJankMonitor,
+ mJavaAdapter,
+ () -> mShadeInteractor,
+ () -> mKosmos.getDeviceUnlockedInteractor(),
+ () -> mKosmos.getSceneInteractor());
KeyguardStatusView keyguardStatusView = new KeyguardStatusView(mContext);
keyguardStatusView.setId(R.id.keyguard_status_view);
@@ -603,9 +608,13 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
new NotificationWakeUpCoordinator(
mDumpManager,
mock(HeadsUpManager.class),
- new StatusBarStateControllerImpl(new UiEventLoggerFake(),
+ new StatusBarStateControllerImpl(
+ new UiEventLoggerFake(),
mInteractionJankMonitor,
- mJavaAdapter, () -> mShadeInteractor),
+ mJavaAdapter,
+ () -> mShadeInteractor,
+ () -> mKosmos.getDeviceUnlockedInteractor(),
+ () -> mKosmos.getSceneInteractor()),
mKeyguardBypassController,
mDozeParameters,
mScreenOffAnimationController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
index 61fee16f0431..3808d309b02b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -180,7 +180,6 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase {
mTestScope.getBackgroundScope(),
mKosmos.getFakeSceneContainerConfig(),
mKosmos.getSceneDataSource()),
- powerInteractor,
mock(SceneLogger.class),
mKosmos.getDeviceUnlockedInteractor());
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 c2267903440a..b426d1de0b00 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -450,7 +450,7 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
mSetFlagsRule.enableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
- // THEN touch should NOT be intercepted by NotificationShade
+ // THEN touch should be intercepted by NotificationShade
assertThat(interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)).isTrue()
}
@@ -469,7 +469,35 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
mSetFlagsRule.enableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
- // THEN touch should NOT be intercepted by NotificationShade
+ // THEN touch should be intercepted by NotificationShade
+ assertThat(interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)).isTrue()
+ }
+
+ @Test
+ fun shouldInterceptTouchEvent_dozingAndPulsing_touchIntercepted() {
+ // GIVEN dozing
+ whenever(sysuiStatusBarStateController.isDozing).thenReturn(true)
+ // AND pulsing
+ whenever(dozeServiceHost.isPulsing()).thenReturn(true)
+ // AND status bar doesn't want it
+ whenever(statusBarKeyguardViewManager.shouldInterceptTouchEvent(DOWN_EVENT))
+ .thenReturn(false)
+ // AND shade is not fully expanded
+ whenever(notificationPanelViewController.isFullyExpanded()).thenReturn(false)
+ // AND the lock icon does NOT want the touch
+ whenever(lockIconViewController.willHandleTouchWhileDozing(DOWN_EVENT)).thenReturn(false)
+ // AND quick settings controller DOES want it
+ whenever(quickSettingsController.shouldQuickSettingsIntercept(any(), any(), any()))
+ .thenReturn(true)
+ // AND bouncer is not showing
+ whenever(centralSurfaces.isBouncerShowing()).thenReturn(false)
+ // AND panel view controller wants it
+ whenever(notificationPanelViewController.handleExternalInterceptTouch(DOWN_EVENT))
+ .thenReturn(true)
+
+ mSetFlagsRule.enableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
+
+ // THEN touch should be intercepted by NotificationShade
assertThat(interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)).isTrue()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
index ab5e51c1b1a1..59fe813cf18e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -207,7 +207,7 @@ class NotificationShadeWindowViewTest : SysuiTestCase() {
@Test
fun testDragDownHelperCalledWhenDraggingDown() =
testScope.runTest {
- mSetFlagsRule.disableFlags(AConfigFlags.FLAG_KEYGUARD_SHADE_MIGRATION_NSSL)
+ mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
whenever(dragDownHelper.isDraggingDown).thenReturn(true)
val now = SystemClock.elapsedRealtime()
val ev = MotionEvent.obtain(now, now, MotionEvent.ACTION_UP, 0f, 0f, 0 /* meta */)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
index 0cda3fd2d8c2..81d0e06df1fc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
@@ -401,7 +401,7 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() {
@Test
fun testSplitShadeLayout_isAlignedToGuideline() {
- mSetFlagsRule.disableFlags(AConfigFlags.FLAG_KEYGUARD_SHADE_MIGRATION_NSSL)
+ mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
enableSplitShade()
underTest.updateResources()
assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd).isEqualTo(R.id.qs_edge_guideline)
@@ -411,7 +411,7 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() {
@Test
fun testSinglePaneLayout_childrenHaveEqualMargins() {
- mSetFlagsRule.disableFlags(AConfigFlags.FLAG_KEYGUARD_SHADE_MIGRATION_NSSL)
+ mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
disableSplitShade()
underTest.updateResources()
val qsStartMargin = getConstraintSetLayout(R.id.qs_frame).startMargin
@@ -428,7 +428,7 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() {
@Test
fun testSplitShadeLayout_childrenHaveInsideMarginsOfZero() {
- mSetFlagsRule.disableFlags(AConfigFlags.FLAG_KEYGUARD_SHADE_MIGRATION_NSSL)
+ mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
enableSplitShade()
underTest.updateResources()
assertThat(getConstraintSetLayout(R.id.qs_frame).endMargin).isEqualTo(0)
@@ -446,8 +446,8 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() {
@Test
fun testLargeScreenLayout_refactorFlagOff_qsAndNotifsTopMarginIsOfHeaderHeightResource() {
+ mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
- mSetFlagsRule.disableFlags(AConfigFlags.FLAG_KEYGUARD_SHADE_MIGRATION_NSSL)
setLargeScreen()
val largeScreenHeaderResourceHeight = 100
val largeScreenHeaderHelperHeight = 200
@@ -469,8 +469,8 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() {
@Test
fun testLargeScreenLayout_refactorFlagOn_qsAndNotifsTopMarginIsOfHeaderHeightHelper() {
+ mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
- mSetFlagsRule.disableFlags(AConfigFlags.FLAG_KEYGUARD_SHADE_MIGRATION_NSSL)
setLargeScreen()
val largeScreenHeaderResourceHeight = 100
val largeScreenHeaderHelperHeight = 200
@@ -492,7 +492,7 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() {
@Test
fun testSmallScreenLayout_qsAndNotifsTopMarginIsZero() {
- mSetFlagsRule.disableFlags(AConfigFlags.FLAG_KEYGUARD_SHADE_MIGRATION_NSSL)
+ mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
setSmallScreen()
underTest.updateResources()
assertThat(getConstraintSetLayout(R.id.qs_frame).topMargin).isEqualTo(0)
@@ -513,7 +513,7 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() {
@Test
fun testSinglePaneShadeLayout_isAlignedToParent() {
- mSetFlagsRule.disableFlags(AConfigFlags.FLAG_KEYGUARD_SHADE_MIGRATION_NSSL)
+ mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
disableSplitShade()
underTest.updateResources()
assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
index 061f88e8a592..0b49a954e848 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
@@ -54,8 +54,8 @@ import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransit
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.kosmos.KosmosJavaAdapter;
-import com.android.systemui.media.controls.pipeline.MediaDataManager;
-import com.android.systemui.media.controls.ui.MediaHierarchyManager;
+import com.android.systemui.media.controls.domain.pipeline.MediaDataManager;
+import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.power.domain.interactor.PowerInteractor;
@@ -208,7 +208,6 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase {
mTestScope.getBackgroundScope(),
mKosmos.getFakeSceneContainerConfig(),
mKosmos.getSceneDataSource()),
- powerInteractor,
mock(SceneLogger.class),
mKosmos.getDeviceUnlockedInteractor());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
index 0933425d2405..91701b17b5e6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
@@ -15,7 +15,7 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.FakeFeatureFlagsClassicModule
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.domain.interactor.NaturalScrollingSettingObserver
-import com.android.systemui.media.controls.ui.MediaHierarchyManager
+import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
import com.android.systemui.plugins.qs.QS
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.res.R
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
index 1396a430df61..fe16347fa298 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
@@ -14,20 +14,30 @@
* limitations under the License.
*/
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
package com.android.systemui.statusbar
import android.animation.ObjectAnimator
+import android.platform.test.annotations.DisableFlags
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
import com.android.systemui.classifier.FalsingCollectorFake
import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
+import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
+import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.FakeCommandQueue
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
@@ -38,13 +48,13 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.fromLockscreenTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.fromPrimaryBouncerTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
-import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.power.data.repository.FakePowerRepository
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags
+import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.shade.LargeScreenHeaderHelper
import com.android.systemui.shade.data.repository.FakeShadeRepository
import com.android.systemui.shade.domain.interactor.ShadeInteractor
@@ -56,8 +66,13 @@ import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository
import com.android.systemui.statusbar.policy.domain.interactor.deviceProvisioningInteractor
import com.android.systemui.testKosmos
+import com.android.systemui.util.kotlin.JavaAdapter
import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
@@ -71,8 +86,8 @@ import org.mockito.ArgumentMatchers.eq
import org.mockito.Mockito
import org.mockito.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)
@@ -81,7 +96,6 @@ class StatusBarStateControllerImplTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private val testDispatcher = kosmos.testDispatcher
private lateinit var shadeInteractor: ShadeInteractor
private lateinit var fromLockscreenTransitionInteractor: FromLockscreenTransitionInteractor
private lateinit var fromPrimaryBouncerTransitionInteractor:
@@ -91,7 +105,7 @@ class StatusBarStateControllerImplTest : SysuiTestCase() {
private val deviceEntryUdfpsInteractor = mock<DeviceEntryUdfpsInteractor>()
private val largeScreenHeaderHelper = mock<LargeScreenHeaderHelper>()
- private lateinit var controller: StatusBarStateControllerImpl
+ private lateinit var underTest: StatusBarStateControllerImpl
private lateinit var uiEventLogger: UiEventLoggerFake
@Before
@@ -101,13 +115,15 @@ class StatusBarStateControllerImplTest : SysuiTestCase() {
whenever(interactionJankMonitor.end(anyInt())).thenReturn(true)
uiEventLogger = UiEventLoggerFake()
- controller =
+ underTest =
object :
StatusBarStateControllerImpl(
uiEventLogger,
interactionJankMonitor,
- mock(),
- { shadeInteractor }
+ JavaAdapter(testScope.backgroundScope),
+ { shadeInteractor },
+ { kosmos.deviceUnlockedInteractor },
+ { kosmos.sceneInteractor },
) {
override fun createDarkAnimator(): ObjectAnimator {
return mockDarkAnimator
@@ -115,7 +131,7 @@ class StatusBarStateControllerImplTest : SysuiTestCase() {
}
val powerInteractor =
- PowerInteractor(FakePowerRepository(), FalsingCollectorFake(), mock(), controller)
+ PowerInteractor(FakePowerRepository(), FalsingCollectorFake(), mock(), underTest)
val keyguardRepository = FakeKeyguardRepository()
val keyguardTransitionRepository = FakeKeyguardTransitionRepository()
val featureFlags = FakeFeatureFlagsClassic()
@@ -168,11 +184,12 @@ class StatusBarStateControllerImplTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(FLAG_SCENE_CONTAINER)
fun testChangeState_logged() {
TestableLooper.get(this).runWithLooper {
- controller.state = StatusBarState.KEYGUARD
- controller.state = StatusBarState.SHADE
- controller.state = StatusBarState.SHADE_LOCKED
+ underTest.state = StatusBarState.KEYGUARD
+ underTest.state = StatusBarState.SHADE
+ underTest.state = StatusBarState.SHADE_LOCKED
}
val logs = uiEventLogger.logs
@@ -186,90 +203,199 @@ class StatusBarStateControllerImplTest : SysuiTestCase() {
@Test
fun testSetDozeAmountInternal_onlySetsOnce() {
val listener = mock(StatusBarStateController.StateListener::class.java)
- controller.addCallback(listener)
+ underTest.addCallback(listener)
- controller.setAndInstrumentDozeAmount(null, 0.5f, false /* animated */)
- controller.setAndInstrumentDozeAmount(null, 0.5f, false /* animated */)
+ underTest.setAndInstrumentDozeAmount(null, 0.5f, false /* animated */)
+ underTest.setAndInstrumentDozeAmount(null, 0.5f, false /* animated */)
verify(listener).onDozeAmountChanged(eq(0.5f), anyFloat())
}
@Test
+ @DisableFlags(FLAG_SCENE_CONTAINER)
fun testSetState_appliesState_sameStateButDifferentUpcomingState() {
- controller.state = StatusBarState.SHADE
- controller.setUpcomingState(StatusBarState.KEYGUARD)
+ underTest.state = StatusBarState.SHADE
+ underTest.setUpcomingState(StatusBarState.KEYGUARD)
- assertEquals(controller.state, StatusBarState.SHADE)
+ assertEquals(underTest.state, StatusBarState.SHADE)
// We should return true (state change was applied) despite going from SHADE to SHADE, since
// the upcoming state was set to KEYGUARD.
- assertTrue(controller.setState(StatusBarState.SHADE))
+ assertTrue(underTest.setState(StatusBarState.SHADE))
}
@Test
+ @DisableFlags(FLAG_SCENE_CONTAINER)
fun testSetState_appliesState_differentStateEqualToUpcomingState() {
- controller.state = StatusBarState.SHADE
- controller.setUpcomingState(StatusBarState.KEYGUARD)
+ underTest.state = StatusBarState.SHADE
+ underTest.setUpcomingState(StatusBarState.KEYGUARD)
- assertEquals(controller.state, StatusBarState.SHADE)
+ assertEquals(underTest.state, StatusBarState.SHADE)
// Make sure we apply a SHADE -> KEYGUARD state change when the upcoming state is KEYGUARD.
- assertTrue(controller.setState(StatusBarState.KEYGUARD))
+ assertTrue(underTest.setState(StatusBarState.KEYGUARD))
}
@Test
+ @DisableFlags(FLAG_SCENE_CONTAINER)
fun testSetState_doesNotApplyState_currentAndUpcomingStatesSame() {
- controller.state = StatusBarState.SHADE
- controller.setUpcomingState(StatusBarState.SHADE)
+ underTest.state = StatusBarState.SHADE
+ underTest.setUpcomingState(StatusBarState.SHADE)
- assertEquals(controller.state, StatusBarState.SHADE)
+ assertEquals(underTest.state, StatusBarState.SHADE)
// We're going from SHADE -> SHADE, and the upcoming state is also SHADE, this should not do
// anything.
- assertFalse(controller.setState(StatusBarState.SHADE))
+ assertFalse(underTest.setState(StatusBarState.SHADE))
// Double check that we can still force it to happen.
- assertTrue(controller.setState(StatusBarState.SHADE, true /* force */))
+ assertTrue(underTest.setState(StatusBarState.SHADE, true /* force */))
}
@Test
fun testSetDozeAmount_immediatelyChangesDozeAmount_lockscreenTransitionFromAod() {
// Put controller in AOD state
- controller.setAndInstrumentDozeAmount(null, 1f, false)
+ underTest.setAndInstrumentDozeAmount(null, 1f, false)
// When waking from doze, CentralSurfaces#updateDozingState will update the dozing state
// before the doze amount changes
- controller.setIsDozing(false)
+ underTest.setIsDozing(false)
// Animate the doze amount to 0f, as would normally happen
- controller.setAndInstrumentDozeAmount(null, 0f, true)
+ underTest.setAndInstrumentDozeAmount(null, 0f, true)
// Check that the doze amount is immediately set to a value slightly less than 1f. This is
// to ensure that any scrim implementation changes its opacity immediately rather than
// waiting an extra frame. Waiting an extra frame will cause a relayout (which is expensive)
// and cause us to drop a frame during the LOCKSCREEN_TRANSITION_FROM_AOD CUJ.
- assertEquals(0.99f, controller.dozeAmount, 0.009f)
+ assertEquals(0.99f, underTest.dozeAmount, 0.009f)
}
@Test
fun testSetDreamState_invokesCallback() {
val listener = mock(StatusBarStateController.StateListener::class.java)
- controller.addCallback(listener)
+ underTest.addCallback(listener)
- controller.setIsDreaming(true)
+ underTest.setIsDreaming(true)
verify(listener).onDreamingChanged(true)
Mockito.clearInvocations(listener)
- controller.setIsDreaming(false)
+ underTest.setIsDreaming(false)
verify(listener).onDreamingChanged(false)
}
@Test
fun testSetDreamState_getterReturnsCurrentState() {
- controller.setIsDreaming(true)
- assertTrue(controller.isDreaming())
+ underTest.setIsDreaming(true)
+ assertTrue(underTest.isDreaming())
- controller.setIsDreaming(false)
- assertFalse(controller.isDreaming())
+ underTest.setIsDreaming(false)
+ assertFalse(underTest.isDreaming())
}
+
+ @Test
+ @EnableSceneContainer
+ fun start_hydratesStatusBarState_whileLocked() =
+ testScope.runTest {
+ var statusBarState = underTest.state
+ val listener =
+ object : StatusBarStateController.StateListener {
+ override fun onStateChanged(newState: Int) {
+ statusBarState = newState
+ }
+ }
+ underTest.addCallback(listener)
+
+ val currentScene by collectLastValue(kosmos.sceneInteractor.currentScene)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Password
+ )
+ kosmos.fakeDeviceEntryRepository.setUnlocked(false)
+ runCurrent()
+ kosmos.sceneInteractor.changeScene(
+ toScene = SceneKey.Lockscreen,
+ loggingReason = "reason"
+ )
+ runCurrent()
+ assertThat(kosmos.deviceUnlockedInteractor.isDeviceUnlocked.value).isFalse()
+ assertThat(currentScene).isEqualTo(SceneKey.Lockscreen)
+
+ // Call start to begin hydrating based on the scene framework:
+ underTest.start()
+
+ kosmos.sceneInteractor.changeScene(toScene = SceneKey.Bouncer, loggingReason = "reason")
+ runCurrent()
+ assertThat(currentScene).isEqualTo(SceneKey.Bouncer)
+ assertThat(statusBarState).isEqualTo(StatusBarState.KEYGUARD)
+
+ kosmos.sceneInteractor.changeScene(toScene = SceneKey.Shade, loggingReason = "reason")
+ runCurrent()
+ assertThat(currentScene).isEqualTo(SceneKey.Shade)
+ assertThat(statusBarState).isEqualTo(StatusBarState.SHADE_LOCKED)
+
+ kosmos.sceneInteractor.changeScene(
+ toScene = SceneKey.QuickSettings,
+ loggingReason = "reason"
+ )
+ runCurrent()
+ assertThat(currentScene).isEqualTo(SceneKey.QuickSettings)
+ assertThat(statusBarState).isEqualTo(StatusBarState.SHADE_LOCKED)
+
+ kosmos.sceneInteractor.changeScene(
+ toScene = SceneKey.Communal,
+ loggingReason = "reason"
+ )
+ runCurrent()
+ assertThat(currentScene).isEqualTo(SceneKey.Communal)
+ assertThat(statusBarState).isEqualTo(StatusBarState.KEYGUARD)
+
+ kosmos.sceneInteractor.changeScene(
+ toScene = SceneKey.Lockscreen,
+ loggingReason = "reason"
+ )
+ runCurrent()
+ assertThat(currentScene).isEqualTo(SceneKey.Lockscreen)
+ assertThat(statusBarState).isEqualTo(StatusBarState.KEYGUARD)
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun start_hydratesStatusBarState_whileUnlocked() =
+ testScope.runTest {
+ var statusBarState = underTest.state
+ val listener =
+ object : StatusBarStateController.StateListener {
+ override fun onStateChanged(newState: Int) {
+ statusBarState = newState
+ }
+ }
+ underTest.addCallback(listener)
+
+ val currentScene by collectLastValue(kosmos.sceneInteractor.currentScene)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Password
+ )
+ kosmos.fakeDeviceEntryRepository.setUnlocked(true)
+ runCurrent()
+ kosmos.sceneInteractor.changeScene(toScene = SceneKey.Gone, loggingReason = "reason")
+ runCurrent()
+ assertThat(kosmos.deviceUnlockedInteractor.isDeviceUnlocked.value).isTrue()
+ assertThat(currentScene).isEqualTo(SceneKey.Gone)
+
+ // Call start to begin hydrating based on the scene framework:
+ underTest.start()
+
+ kosmos.sceneInteractor.changeScene(toScene = SceneKey.Shade, loggingReason = "reason")
+ runCurrent()
+ assertThat(currentScene).isEqualTo(SceneKey.Shade)
+ assertThat(statusBarState).isEqualTo(StatusBarState.SHADE)
+
+ kosmos.sceneInteractor.changeScene(
+ toScene = SceneKey.QuickSettings,
+ loggingReason = "reason"
+ )
+ runCurrent()
+ assertThat(currentScene).isEqualTo(SceneKey.QuickSettings)
+ assertThat(statusBarState).isEqualTo(StatusBarState.SHADE)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
index 3811f04a365d..06410cd3530e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
@@ -431,28 +431,6 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase {
@Test
public void publicMode_settingsDisallow() {
- mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, true);
- // GIVEN an 'unfiltered-keyguard-showing' state
- setupUnfilteredState(mEntry);
-
- // WHEN the notification's user is in public mode and settings are configured to disallow
- // notifications in public mode
- when(mLockscreenUserManager.isLockscreenPublicMode(NOTIF_USER_ID)).thenReturn(true);
- when(mLockscreenUserManager.userAllowsNotificationsInPublic(NOTIF_USER_ID))
- .thenReturn(false);
-
- mEntry.setRanking(new RankingBuilder()
- .setChannel(new NotificationChannel("1", "1", 4))
- .setVisibilityOverride(VISIBILITY_NO_OVERRIDE)
- .setKey(mEntry.getKey()).build());
-
- // THEN filter out the entry
- assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry));
- }
-
- @Test
- public void publicMode_settingsDisallow_mainThread() {
- mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, false);
// GIVEN an 'unfiltered-keyguard-showing' state
setupUnfilteredState(mEntry);
@@ -473,7 +451,6 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase {
@Test
public void publicMode_nullChannel_allowed() {
- mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, true);
// GIVEN an 'unfiltered-keyguard-showing' state
setupUnfilteredState(mEntry);
@@ -490,7 +467,6 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase {
@Test
public void publicMode_notifDisallowed() {
- mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, true);
NotificationChannel channel = new NotificationChannel("1", "1", IMPORTANCE_HIGH);
channel.setLockscreenVisibility(VISIBILITY_SECRET);
// GIVEN an 'unfiltered-keyguard-showing' state
@@ -509,23 +485,6 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase {
}
@Test
- public void publicMode_notifDisallowed_mainThread() {
- mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, false);
- // GIVEN an 'unfiltered-keyguard-showing' state
- setupUnfilteredState(mEntry);
-
- // WHEN the notification's user is in public mode and settings are configured to disallow
- // notifications in public mode
- when(mLockscreenUserManager.isLockscreenPublicMode(CURR_USER_ID)).thenReturn(true);
- mEntry.setRanking(new RankingBuilder()
- .setKey(mEntry.getKey())
- .setVisibilityOverride(VISIBILITY_SECRET).build());
-
- // THEN filter out the entry
- assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry));
- }
-
- @Test
public void doesNotExceedThresholdToShow() {
// GIVEN an 'unfiltered-keyguard-showing' state
setupUnfilteredState(mEntry);
@@ -579,7 +538,6 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase {
@Test
public void notificationChannelVisibilityNoOverride() {
- mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, true);
// GIVEN a VISIBILITY_PRIVATE notification
NotificationEntryBuilder entryBuilder = new NotificationEntryBuilder()
.setUser(new UserHandle(NOTIF_USER_ID));
@@ -602,7 +560,6 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase {
@Test
public void notificationChannelVisibilitySecret() {
- mFeatureFlags.set(Flags.NOTIF_LS_BACKGROUND_THREAD, true);
// GIVEN a VISIBILITY_PRIVATE notification
NotificationEntryBuilder entryBuilder = new NotificationEntryBuilder()
.setUser(new UserHandle(NOTIF_USER_ID));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
index 4b145d8b0dd2..5c45b2e53047 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
@@ -30,7 +30,7 @@ import android.view.ViewGroup;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.media.controls.ui.KeyguardMediaController;
+import com.android.systemui.media.controls.ui.controller.KeyguardMediaController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
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 f2ef4e17900d..a4f88fbe1469 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
@@ -63,7 +63,7 @@ import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository;
import com.android.systemui.keyguard.shared.model.KeyguardState;
import com.android.systemui.keyguard.shared.model.TransitionStep;
-import com.android.systemui.media.controls.ui.KeyguardMediaController;
+import com.android.systemui.media.controls.ui.controller.KeyguardMediaController;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.OnMenuEventListener;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt
index 2b3f9d0f3c39..6fec9ad7bd66 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt
@@ -23,7 +23,7 @@ import android.view.View.VISIBLE
import androidx.test.filters.SmallTest
import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
-import com.android.systemui.media.controls.pipeline.MediaDataManager
+import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
import com.android.systemui.statusbar.LockscreenShadeTransitionController
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.SysuiStatusBarStateController
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java
index 41b959e98221..9d53b9c66b33 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java
@@ -119,7 +119,7 @@ public class LegacyNotificationIconAreaControllerImplTest extends SysuiTestCase
@Test
public void testAppearResetsTranslation() {
- mSetFlagsRule.disableFlags(Flags.FLAG_KEYGUARD_SHADE_MIGRATION_NSSL);
+ mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT);
mController.setupAodIcons(mAodIcons);
when(mDozeParameters.shouldControlScreenOff()).thenReturn(false);
mController.appearAodIcons();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
index f53fc46d8ab2..cd0652e53657 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
@@ -20,6 +20,7 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.log.core.FakeLogBuffer
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
@@ -28,7 +29,10 @@ import com.android.systemui.statusbar.pipeline.satellite.domain.interactor.Devic
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
+import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.mockito.MockitoAnnotations
@@ -61,6 +65,7 @@ class DeviceBasedSatelliteViewModelTest : SysuiTestCase() {
interactor,
testScope.backgroundScope,
airplaneModeRepository,
+ FakeLogBuffer.Factory.create(),
)
}
@@ -121,8 +126,9 @@ class DeviceBasedSatelliteViewModelTest : SysuiTestCase() {
assertThat(latest).isNull()
}
+ @OptIn(ExperimentalCoroutinesApi::class)
@Test
- fun icon_satelliteIsOff() =
+ fun icon_satelliteIsOn() =
testScope.runTest {
val latest by collectLastValue(underTest.icon)
@@ -133,7 +139,45 @@ class DeviceBasedSatelliteViewModelTest : SysuiTestCase() {
val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
i1.isInService.value = false
- // THEN icon is null because we have service
+ // GIVEN apm is disabled
+ airplaneModeRepository.setIsAirplaneMode(false)
+
+ // Wait for delay to be completed
+ advanceTimeBy(10.seconds)
+
+ // THEN icon is set because we don't have service
+ assertThat(latest).isInstanceOf(Icon::class.java)
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun icon_hysteresisWhenEnablingIcon() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.icon)
+
+ // GIVEN satellite is allowed
+ repo.isSatelliteAllowedForCurrentLocation.value = true
+
+ // GIVEN all icons are OOS
+ val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
+ i1.isInService.value = false
+
+ // GIVEN apm is disabled
+ airplaneModeRepository.setIsAirplaneMode(false)
+
+ // THEN icon is null because of the hysteresis
+ assertThat(latest).isNull()
+
+ // Wait for delay to be completed
+ advanceTimeBy(10.seconds)
+
+ // THEN icon is set after the delay
assertThat(latest).isInstanceOf(Icon::class.java)
+
+ // GIVEN apm is enabled
+ airplaneModeRepository.setIsAirplaneMode(true)
+
+ // THEN icon is null immediately
+ assertThat(latest).isNull()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java
index f1a2c281595d..ddd29c3f2803 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java
@@ -79,6 +79,7 @@ public class ZenModeControllerImplTest extends SysuiTestCase {
mController = new ZenModeControllerImpl(
mContext,
Handler.createAsync(TestableLooper.get(this).getLooper()),
+ Handler.createAsync(TestableLooper.get(this).getLooper()),
mBroadcastDispatcher,
mDumpManager,
mGlobalSettings,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index d87df0af6ba9..b25ac24093c5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -415,7 +415,6 @@ public class BubblesTest extends SysuiTestCase {
mTestScope.getBackgroundScope(),
mKosmos.getFakeSceneContainerConfig(),
mKosmos.getSceneDataSource()),
- powerInteractor,
mock(SceneLogger.class),
mKosmos.getDeviceUnlockedInteractor());
diff --git a/packages/SystemUI/tests/utils/src/android/graphics/drawable/TestStubDrawable.kt b/packages/SystemUI/tests/utils/src/android/graphics/drawable/TestStubDrawable.kt
index b88f302cdfdd..1a9f4b40c179 100644
--- a/packages/SystemUI/tests/utils/src/android/graphics/drawable/TestStubDrawable.kt
+++ b/packages/SystemUI/tests/utils/src/android/graphics/drawable/TestStubDrawable.kt
@@ -24,12 +24,27 @@ import android.graphics.PixelFormat
* Stub drawable that does nothing. It's to be used in tests as a mock drawable and checked for the
* same instance
*/
-class TestStubDrawable : Drawable() {
+class TestStubDrawable(private val name: String? = null) : Drawable() {
override fun draw(canvas: Canvas) = Unit
override fun setAlpha(alpha: Int) = Unit
override fun setColorFilter(colorFilter: ColorFilter?) = Unit
override fun getOpacity(): Int = PixelFormat.UNKNOWN
- override fun equals(other: Any?): Boolean = this === other
+ override fun toString(): String {
+ return name ?: super.toString()
+ }
+
+ override fun getConstantState(): ConstantState =
+ TestStubConstantState(this, changingConfigurations)
+
+ private class TestStubConstantState(
+ private val drawable: Drawable,
+ private val changingConfigurations: Int,
+ ) : ConstantState() {
+
+ override fun newDrawable(): Drawable = drawable
+
+ override fun getChangingConfigurations(): Int = changingConfigurations
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
index b8c880b3892f..62a1aa93f35a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
@@ -37,7 +37,7 @@ import com.android.systemui.log.LogBuffer
import com.android.systemui.log.dagger.BiometricLog
import com.android.systemui.log.dagger.BroadcastDispatcherLog
import com.android.systemui.log.dagger.SceneFrameworkLog
-import com.android.systemui.media.controls.ui.MediaHierarchyManager
+import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
import com.android.systemui.model.SysUiState
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.DarkIconDispatcher
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/FaceHelpMessageDeferralKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/FaceHelpMessageDeferralKosmos.kt
new file mode 100644
index 000000000000..681412117779
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/FaceHelpMessageDeferralKosmos.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.domain
+
+import com.android.systemui.biometrics.FaceHelpMessageDeferral
+import com.android.systemui.biometrics.FaceHelpMessageDeferralFactory
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+
+val Kosmos.faceHelpMessageDeferral by Kosmos.Fixture { mock<FaceHelpMessageDeferral>() }
+val Kosmos.faceHelpMessageDeferralFactory by
+ Kosmos.Fixture {
+ val mockFactory = mock<FaceHelpMessageDeferralFactory>()
+ whenever(mockFactory.create()).thenReturn(faceHelpMessageDeferral)
+ mockFactory
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/FakeCredentialInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/FakeCredentialInteractor.kt
index fbe291ebaf5d..a4c8a0b2c228 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/FakeCredentialInteractor.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/FakeCredentialInteractor.kt
@@ -14,10 +14,15 @@ class FakeCredentialInteractor : CredentialInteractor {
/** Sets return value for [getCredentialOwnerOrSelfId]. */
var credentialOwnerId: Int? = null
+ /** Sets return value for [getParentProfileIdOrSelfId]. */
+ var userIdForPasswordEntry: Int? = null
+
override fun isStealthModeActive(userId: Int): Boolean = stealthMode
override fun getCredentialOwnerOrSelfId(userId: Int): Int = credentialOwnerId ?: userId
+ override fun getParentProfileIdOrSelfId(userId: Int): Int = userIdForPasswordEntry ?: userId
+
override fun verifyCredential(
request: BiometricPromptRequest.Credential,
credential: LockscreenCredential,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/classifier/domain/interactor/FalsingInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/classifier/domain/interactor/FalsingInteractorKosmos.kt
index 8fee5b2b305c..43dc372f020f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/classifier/domain/interactor/FalsingInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/classifier/domain/interactor/FalsingInteractorKosmos.kt
@@ -17,11 +17,13 @@
package com.android.systemui.classifier.domain.interactor
import com.android.systemui.classifier.falsingCollector
+import com.android.systemui.classifier.falsingManager
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
val Kosmos.falsingInteractor by Fixture {
FalsingInteractor(
collector = falsingCollector,
+ manager = falsingManager,
)
}
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 ae7d87783b7c..9d508d23dca7 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
@@ -38,11 +38,4 @@ class FakeCommunalRepository(
override fun setTransitionState(transitionState: Flow<ObservableCommunalTransitionState>?) {
_transitionState.value = transitionState
}
-
- private val _isCommunalHubShowing: MutableStateFlow<Boolean> = MutableStateFlow(false)
- override val isCommunalHubShowing: Flow<Boolean> = _isCommunalHubShowing
-
- fun setIsCommunalHubShowing(isCommunalHubShowing: Boolean) {
- _isCommunalHubShowing.value = isCommunalHubShowing
- }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
index 566fc2529954..6af08d3df554 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
@@ -29,6 +29,8 @@ import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.log.logcatLogBuffer
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.smartspace.data.repository.smartspaceRepository
import com.android.systemui.user.data.repository.fakeUserRepository
import com.android.systemui.util.mockito.mock
@@ -47,6 +49,8 @@ val Kosmos.communalInteractor by Fixture {
logBuffer = logcatLogBuffer("CommunalInteractor"),
tableLogBuffer = mock(),
communalSettingsInteractor = communalSettingsInteractor,
+ sceneInteractor = sceneInteractor,
+ sceneContainerFlags = fakeSceneContainerFlags,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorKosmos.kt
index 00fdceda01d1..23f63e6e20a7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorKosmos.kt
@@ -16,7 +16,6 @@
package com.android.systemui.communal.domain.interactor
-import com.android.systemui.communal.data.repository.communalRepository
import com.android.systemui.communal.data.repository.communalTutorialRepository
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.kosmos.Kosmos
@@ -29,7 +28,6 @@ val Kosmos.communalTutorialInteractor by
scope = applicationCoroutineScope,
communalTutorialRepository = communalTutorialRepository,
keyguardInteractor = keyguardInteractor,
- communalRepository = communalRepository,
communalInteractor = communalInteractor,
communalSettingsInteractor = communalSettingsInteractor,
tableLogBuffer = mock(),
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/BiometricMessageInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/BiometricMessageInteractorKosmos.kt
index 77f48db60f41..3ea46872ebcf 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/BiometricMessageInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/BiometricMessageInteractorKosmos.kt
@@ -30,5 +30,6 @@ val Kosmos.biometricMessageInteractor by
fingerprintPropertyInteractor = fingerprintPropertyInteractor,
faceAuthInteractor = deviceEntryFaceAuthInteractor,
biometricSettingsInteractor = deviceEntryBiometricSettingsInteractor,
+ faceHelpMessageDeferralInteractor = faceHelpMessageDeferralInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/FaceHelpMessageDeferralInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/FaceHelpMessageDeferralInteractorKosmos.kt
new file mode 100644
index 000000000000..724e943c9f55
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/FaceHelpMessageDeferralInteractorKosmos.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.deviceentry.domain.interactor
+
+import com.android.systemui.biometrics.domain.faceHelpMessageDeferralFactory
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+@ExperimentalCoroutinesApi
+val Kosmos.faceHelpMessageDeferralInteractor by
+ Kosmos.Fixture {
+ FaceHelpMessageDeferralInteractor(
+ scope = applicationCoroutineScope,
+ faceAuthInteractor = deviceEntryFaceAuthInteractor,
+ biometricSettingsInteractor = deviceEntryBiometricSettingsInteractor,
+ faceHelpMessageDeferralFactory = faceHelpMessageDeferralFactory,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt
index 43897c985c2c..cceb3ffab282 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt
@@ -17,9 +17,10 @@
package com.android.systemui.flags
import android.platform.test.annotations.EnableFlags
+import com.android.systemui.Flags.FLAG_COMPOSE_LOCKSCREEN
import com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR
-import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT
import com.android.systemui.Flags.FLAG_MEDIA_IN_SCENE_CONTAINER
+import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT
import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
/**
@@ -30,6 +31,7 @@ import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
FLAG_SCENE_CONTAINER,
FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR,
FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT,
+ FLAG_COMPOSE_LOCKSCREEN,
FLAG_MEDIA_IN_SCENE_CONTAINER,
)
@Retention(AnnotationRetention.RUNTIME)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/globalactions/data/repository/GlobalActionsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/globalactions/data/repository/GlobalActionsRepositoryKosmos.kt
new file mode 100644
index 000000000000..0e439b04f484
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/globalactions/data/repository/GlobalActionsRepositoryKosmos.kt
@@ -0,0 +1,21 @@
+/*
+ * 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.globalactions.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.globalActionsRepository by Kosmos.Fixture { GlobalActionsRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/globalactions/domain/interactor/GlobalActionsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/globalactions/domain/interactor/GlobalActionsInteractorKosmos.kt
new file mode 100644
index 000000000000..72143095a114
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/globalactions/domain/interactor/GlobalActionsInteractorKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * 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.globalactions.domain.interactor
+
+import com.android.systemui.globalactions.data.repository.globalActionsRepository
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.globalActionsInteractor by
+ Kosmos.Fixture { GlobalActionsInteractor(globalActionsRepository) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
index e20a0ab4190e..a9a2d91c0815 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
@@ -43,7 +43,7 @@ import kotlinx.coroutines.test.runCurrent
class FakeKeyguardTransitionRepository @Inject constructor() : KeyguardTransitionRepository {
private val _transitions =
- MutableSharedFlow<TransitionStep>(replay = 2, onBufferOverflow = BufferOverflow.DROP_OLDEST)
+ MutableSharedFlow<TransitionStep>(replay = 3, onBufferOverflow = BufferOverflow.DROP_OLDEST)
override val transitions: SharedFlow<TransitionStep> = _transitions
init {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModelKosmos.kt
index 9fb32841d201..f1784a8bc9f2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModelKosmos.kt
@@ -18,6 +18,7 @@
package com.android.systemui.keyguard.ui.viewmodel
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
@@ -28,5 +29,6 @@ val Kosmos.aodAlphaViewModel by Fixture {
keyguardTransitionInteractor = keyguardTransitionInteractor,
goneToAodTransitionViewModel = goneToAodTransitionViewModel,
goneToDozingTransitionViewModel = goneToDozingTransitionViewModel,
+ keyguardInteractor = keyguardInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelKosmos.kt
index 733340c67e55..460913f75eb4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelKosmos.kt
@@ -22,11 +22,13 @@ import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsIntera
import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.shade.domain.interactor.shadeInteractor
import kotlinx.coroutines.ExperimentalCoroutinesApi
val Kosmos.aodToLockscreenTransitionViewModel by Fixture {
AodToLockscreenTransitionViewModel(
deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor,
+ shadeInteractor = shadeInteractor,
animationFlow = keyguardTransitionAnimationFlow,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt
new file mode 100644
index 000000000000..b37085957d7e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt
@@ -0,0 +1,27 @@
+/*
+ * 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 com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.dreamingToGlanceableHubTransitionViewModel by
+ Kosmos.Fixture {
+ DreamingToGlanceableHubTransitionViewModel(
+ animationFlow = keyguardTransitionAnimationFlow,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModelKosmos.kt
index 28fce77b75b7..b1c21b8fa6cf 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModelKosmos.kt
@@ -18,6 +18,7 @@
package com.android.systemui.keyguard.ui.viewmodel
+import com.android.systemui.common.ui.domain.interactor.configurationInteractor
import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
@@ -26,6 +27,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
@OptIn(ExperimentalCoroutinesApi::class)
val Kosmos.glanceableHubToLockscreenTransitionViewModel by Fixture {
GlanceableHubToLockscreenTransitionViewModel(
+ configurationInteractor = configurationInteractor,
animationFlow = keyguardTransitionAnimationFlow,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
index 4939237bbafe..ecf66a297f0d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
@@ -24,6 +24,7 @@ import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationsKeyguardInteractor
import com.android.systemui.statusbar.phone.dozeParameters
import com.android.systemui.statusbar.phone.screenOffAnimationController
@@ -56,5 +57,6 @@ val Kosmos.keyguardRootViewModel by Fixture {
screenOffAnimationController = screenOffAnimationController,
aodBurnInViewModel = aodBurnInViewModel,
aodAlphaViewModel = aodAlphaViewModel,
+ shadeInteractor = shadeInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModelKosmos.kt
index 9fe4ea347f63..471381f7a13f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModelKosmos.kt
@@ -18,13 +18,16 @@
package com.android.systemui.keyguard.ui.viewmodel
+import com.android.systemui.common.ui.domain.interactor.configurationInteractor
import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import kotlinx.coroutines.ExperimentalCoroutinesApi
+@ExperimentalCoroutinesApi
val Kosmos.lockscreenToGlanceableHubTransitionViewModel by Fixture {
LockscreenToGlanceableHubTransitionViewModel(
+ configurationInteractor = configurationInteractor,
animationFlow = keyguardTransitionAnimationFlow,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
index f6b32800263a..3fc5af1a50ab 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
@@ -30,6 +30,7 @@ import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.globalactions.domain.interactor.globalActionsInteractor
import com.android.systemui.jank.interactionJankMonitor
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
@@ -91,6 +92,7 @@ class KosmosJavaAdapter(
val fromPrimaryBouncerTransitionInteractor by lazy {
kosmos.fromPrimaryBouncerTransitionInteractor
}
+ val globalActionsInteractor by lazy { kosmos.globalActionsInteractor }
val sceneDataSource by lazy { kosmos.sceneDataSource }
init {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/MediaHierarchyManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerKosmos.kt
index db2cdfa58f8d..7c24b4cc3d60 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/MediaHierarchyManagerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerKosmos.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.media.controls.ui
+package com.android.systemui.media.controls.ui.controller
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt
index be559efc7946..7264f7a2bc95 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt
@@ -17,8 +17,10 @@
package com.android.systemui.plugins.statusbar
import com.android.internal.logging.uiEventLogger
+import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
import com.android.systemui.jank.interactionJankMonitor
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.statusbar.StatusBarStateControllerImpl
import com.android.systemui.util.mockito.mock
@@ -29,7 +31,8 @@ var Kosmos.statusBarStateController by
uiEventLogger,
interactionJankMonitor,
mock(),
- ) {
- shadeInteractor
- }
+ { shadeInteractor },
+ { deviceUnlockedInteractor },
+ { sceneInteractor },
+ )
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/privacy/PrivacyDialogControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/privacy/PrivacyDialogControllerKosmos.kt
new file mode 100644
index 000000000000..960a06940a94
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/privacy/PrivacyDialogControllerKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * 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.privacy
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+var Kosmos.privacyDialogController: PrivacyDialogController by
+ Kosmos.Fixture { mock<PrivacyDialogController>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/privacy/PrivacyDialogControllerV2Kosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/privacy/PrivacyDialogControllerV2Kosmos.kt
new file mode 100644
index 000000000000..7628c0e64e6a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/privacy/PrivacyDialogControllerV2Kosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * 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.privacy
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+var Kosmos.privacyDialogControllerV2: PrivacyDialogControllerV2 by
+ Kosmos.Fixture { mock<PrivacyDialogControllerV2>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/FakeIQSTileService.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/FakeIQSTileService.kt
new file mode 100644
index 000000000000..cff59807e00f
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/FakeIQSTileService.kt
@@ -0,0 +1,61 @@
+/*
+ * 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.external
+
+import android.os.Binder
+import android.os.IBinder
+import android.service.quicksettings.IQSTileService
+
+class FakeIQSTileService : IQSTileService {
+
+ var isTileAdded: Boolean = false
+ private set
+ var isTileListening: Boolean = false
+ private set
+ var isUnlockComplete: Boolean = false
+ val clicks: List<IBinder?>
+ get() = mutableClicks
+
+ private val mutableClicks: MutableList<IBinder?> = mutableListOf()
+ private val binder = Binder()
+
+ override fun asBinder(): IBinder = binder
+
+ override fun onTileAdded() {
+ isTileAdded = true
+ }
+
+ override fun onTileRemoved() {
+ isTileAdded = false
+ }
+
+ override fun onStartListening() {
+ isTileListening = true
+ }
+
+ override fun onStopListening() {
+ isTileListening = false
+ }
+
+ override fun onClick(wtoken: IBinder?) {
+ mutableClicks.add(wtoken)
+ }
+
+ override fun onUnlockComplete() {
+ isUnlockComplete = true
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/FakeTileServiceManagerFacade.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/FakeTileServiceManagerFacade.kt
new file mode 100644
index 000000000000..101335f38531
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/FakeTileServiceManagerFacade.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.external
+
+import android.service.quicksettings.IQSTileService
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+
+// TODO(b/299909989) Make a fake instead
+class FakeTileServiceManagerFacade(
+ private val iQSTileService: IQSTileService,
+ val tileServiceManager: TileServiceManager = mock {},
+) {
+
+ private var hasPendingBind: Boolean = false
+
+ var isBound: Boolean = false
+ private set
+
+ init {
+ with(tileServiceManager) {
+ whenever(tileService).thenReturn(iQSTileService)
+ whenever(setBindRequested(any())).then {
+ val isRequested: Boolean = it.getArgument(0)
+ hasPendingBind = isRequested
+ if (!isRequested) {
+ isBound = false
+ }
+ Unit
+ }
+ whenever(clearPendingBind()).then {
+ hasPendingBind = false
+ Unit
+ }
+ whenever(hasPendingBind()).then { hasPendingBind }
+ }
+ }
+
+ fun processPendingBind() {
+ if (hasPendingBind) {
+ isBound = true
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/FakeTileServicesFacade.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/FakeTileServicesFacade.kt
new file mode 100644
index 000000000000..0975e55295e4
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/FakeTileServicesFacade.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.external
+
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+
+class FakeTileServicesFacade(
+ private val TileServiceManager: TileServiceManager,
+ val tileServices: TileServices = mock {}
+) {
+
+ var customTileInterface: CustomTileInterface? = null
+ private set
+
+ init {
+ with(tileServices) {
+ whenever(getTileWrapper(any())).then {
+ customTileInterface = it.getArgument(0)
+ TileServiceManager
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TilesExternalKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TilesExternalKosmos.kt
new file mode 100644
index 000000000000..36c2c2b6eb23
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TilesExternalKosmos.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.external
+
+import android.content.ComponentName
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+var Kosmos.componentName: ComponentName by Kosmos.Fixture()
+
+/** Returns mocks */
+var Kosmos.tileLifecycleManagerFactory: TileLifecycleManager.Factory by Kosmos.Fixture { mock {} }
+
+val Kosmos.iQSTileService: FakeIQSTileService by Kosmos.Fixture { FakeIQSTileService() }
+val Kosmos.tileServiceManagerFacade: FakeTileServiceManagerFacade by
+ Kosmos.Fixture { FakeTileServiceManagerFacade(iQSTileService) }
+
+val Kosmos.tileServiceManager: TileServiceManager by
+ Kosmos.Fixture { tileServiceManagerFacade.tileServiceManager }
+
+val Kosmos.tileServicesFacade: FakeTileServicesFacade by
+ Kosmos.Fixture { (FakeTileServicesFacade(tileServiceManager)) }
+val Kosmos.tileServices: TileServices by Kosmos.Fixture { tileServicesFacade.tileServices }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeDefaultTilesRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeDefaultTilesRepository.kt
new file mode 100644
index 000000000000..ced29cc9eb47
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeDefaultTilesRepository.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.data.repository
+
+import com.android.systemui.qs.pipeline.shared.TileSpec
+
+class FakeDefaultTilesRepository(override val defaultTiles: List<TileSpec> = emptyList()) :
+ DefaultTilesRepository
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeTileSpecRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeTileSpecRepository.kt
index ae4cf3afe671..a9cce6912f15 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeTileSpecRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeTileSpecRepository.kt
@@ -23,7 +23,9 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
-class FakeTileSpecRepository : TileSpecRepository {
+class FakeTileSpecRepository(
+ private val defaultTilesRepository: DefaultTilesRepository = FakeDefaultTilesRepository()
+) : TileSpecRepository {
private val tilesPerUser = mutableMapOf<Int, MutableStateFlow<List<TileSpec>>>()
@@ -67,4 +69,8 @@ class FakeTileSpecRepository : TileSpecRepository {
value = UserTileSpecRepository.reconcileTiles(value, currentAutoAdded, restoreData)
}
}
+
+ override suspend fun prependDefault(userId: Int) {
+ with(getFlow(userId)) { value = defaultTilesRepository.defaultTiles + value }
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/QSPipelineRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/QSPipelineRepositoryKosmos.kt
index 009148266143..604c16fd9e74 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/QSPipelineRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/QSPipelineRepositoryKosmos.kt
@@ -18,7 +18,17 @@ package com.android.systemui.qs.pipeline.data.repository
import com.android.systemui.kosmos.Kosmos
-val Kosmos.fakeTileSpecRepository by Kosmos.Fixture { FakeTileSpecRepository() }
+/** This fake uses 0 as the minimum number of tiles. That means that no tiles is a valid state. */
+var Kosmos.fakeMinimumTilesRepository by Kosmos.Fixture { MinimumTilesFixedRepository(0) }
+val Kosmos.minimumTilesRepository: MinimumTilesRepository by
+ Kosmos.Fixture { fakeMinimumTilesRepository }
+
+var Kosmos.fakeDefaultTilesRepository by Kosmos.Fixture { FakeDefaultTilesRepository() }
+val Kosmos.defaultTilesRepository: DefaultTilesRepository by
+ Kosmos.Fixture { fakeDefaultTilesRepository }
+
+val Kosmos.fakeTileSpecRepository by
+ Kosmos.Fixture { FakeTileSpecRepository(defaultTilesRepository) }
var Kosmos.tileSpecRepository: TileSpecRepository by Kosmos.Fixture { fakeTileSpecRepository }
val Kosmos.fakeAutoAddRepository by Kosmos.Fixture { FakeAutoAddRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorKosmos.kt
index 67df563ec5b0..9ef44c4b9085 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorKosmos.kt
@@ -24,6 +24,7 @@ import com.android.systemui.qs.external.tileLifecycleManagerFactory
import com.android.systemui.qs.newQSTileFactory
import com.android.systemui.qs.pipeline.data.repository.customTileAddedRepository
import com.android.systemui.qs.pipeline.data.repository.installedTilesRepository
+import com.android.systemui.qs.pipeline.data.repository.minimumTilesRepository
import com.android.systemui.qs.pipeline.data.repository.tileSpecRepository
import com.android.systemui.qs.pipeline.shared.logging.qsLogger
import com.android.systemui.qs.pipeline.shared.pipelineFlagsRepository
@@ -37,6 +38,7 @@ val Kosmos.currentTilesInteractor: CurrentTilesInteractor by
tileSpecRepository,
installedTilesRepository,
userRepository,
+ minimumTilesRepository,
customTileStatePersister,
{ newQSTileFactory },
qsTileFactory,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/CustomTileKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/CustomTileKosmos.kt
index 14f28fedc5e4..561e2540d465 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/CustomTileKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/CustomTileKosmos.kt
@@ -17,19 +17,47 @@
package com.android.systemui.qs.tiles.impl.custom
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testCase
+import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.activityStarter
import com.android.systemui.qs.external.FakeCustomTileStatePersister
+import com.android.systemui.qs.external.tileServices
import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.logging.QSTileLogger
import com.android.systemui.qs.tiles.impl.custom.data.repository.FakeCustomTileDefaultsRepository
import com.android.systemui.qs.tiles.impl.custom.data.repository.FakeCustomTilePackageUpdatesRepository
import com.android.systemui.qs.tiles.impl.custom.data.repository.FakeCustomTileRepository
import com.android.systemui.qs.tiles.impl.custom.data.repository.FakePackageManagerAdapterFacade
+import com.android.systemui.qs.tiles.impl.custom.domain.interactor.CustomTileInteractor
+import com.android.systemui.qs.tiles.impl.custom.domain.interactor.CustomTileServiceInteractor
+import com.android.systemui.qs.tiles.impl.custom.domain.interactor.CustomTileUserActionInteractor
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfigTestBuilder
+import com.android.systemui.user.data.repository.userRepository
+import com.android.systemui.util.mockito.mock
var Kosmos.tileSpec: TileSpec.CustomTileSpec by Kosmos.Fixture()
+var Kosmos.customTileQsTileConfig: QSTileConfig by
+ Kosmos.Fixture { QSTileConfigTestBuilder.build { tileSpec = this@Fixture.tileSpec } }
+val Kosmos.qsTileLogger: QSTileLogger by Kosmos.Fixture { mock {} }
+
val Kosmos.customTileStatePersister: FakeCustomTileStatePersister by
Kosmos.Fixture { FakeCustomTileStatePersister() }
+val Kosmos.customTileInteractor: CustomTileInteractor by
+ Kosmos.Fixture {
+ CustomTileInteractor(
+ tileSpec,
+ customTileDefaultsRepository,
+ customTileRepository,
+ testScope.backgroundScope,
+ testScope.testScheduler,
+ )
+ }
+
val Kosmos.customTileRepository: FakeCustomTileRepository by
Kosmos.Fixture {
FakeCustomTileRepository(
@@ -48,3 +76,31 @@ val Kosmos.customTilePackagesUpdatesRepository: FakeCustomTilePackageUpdatesRepo
val Kosmos.packageManagerAdapterFacade: FakePackageManagerAdapterFacade by
Kosmos.Fixture { FakePackageManagerAdapterFacade(tileSpec.componentName) }
+
+val Kosmos.customTileServiceInteractor: CustomTileServiceInteractor by
+ Kosmos.Fixture {
+ CustomTileServiceInteractor(
+ tileSpec,
+ activityStarter,
+ { customTileUserActionInteractor },
+ customTileInteractor,
+ userRepository,
+ qsTileLogger,
+ tileServices,
+ testScope.backgroundScope,
+ )
+ }
+
+val Kosmos.customTileUserActionInteractor: CustomTileUserActionInteractor by
+ Kosmos.Fixture {
+ CustomTileUserActionInteractor(
+ testCase.context,
+ tileSpec,
+ qsTileLogger,
+ mock {},
+ mock {},
+ FakeQSTileIntentUserInputHandler(),
+ testDispatcher,
+ customTileServiceInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt
index 9d0faca94fb4..4f5c9b48690d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt
@@ -70,7 +70,7 @@ private constructor(failureMetadata: FailureMetadata, subject: QSTileState?) :
}
/** Shortcut for `Truth.assertAbout(states()).that(state)`. */
- fun assertThat(state: QSTileState?): QSTileStateSubject =
- Truth.assertAbout(states()).that(state)
+ fun assertThat(actual: QSTileState?): QSTileStateSubject =
+ Truth.assertAbout(states()).that(actual)
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneInteractorKosmos.kt
index fc023758fdf6..ef7aa6308491 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneInteractorKosmos.kt
@@ -19,7 +19,6 @@ package com.android.systemui.scene.domain.interactor
import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
-import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.scene.data.repository.sceneContainerRepository
import com.android.systemui.scene.shared.logger.sceneLogger
@@ -28,7 +27,6 @@ val Kosmos.sceneInteractor by
SceneInteractor(
applicationScope = applicationCoroutineScope,
repository = sceneContainerRepository,
- powerInteractor = powerInteractor,
logger = sceneLogger,
deviceUnlockedInteractor = deviceUnlockedInteractor,
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakePrivacyChipRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakePrivacyChipRepository.kt
new file mode 100644
index 000000000000..5bc61e23cd6e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakePrivacyChipRepository.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.shade.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.privacy.PrivacyItem
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+
+/** Fake implementation of [PrivacyChipRepository] */
+@SysUISingleton
+class FakePrivacyChipRepository @Inject constructor() : PrivacyChipRepository {
+ private val _isSafetyCenterEnabled = MutableStateFlow(false)
+ override val isSafetyCenterEnabled = _isSafetyCenterEnabled
+
+ private val _privacyItems: MutableStateFlow<List<PrivacyItem>> = MutableStateFlow(emptyList())
+ override val privacyItems = _privacyItems
+
+ private val _isMicCameraIndicationEnabled = MutableStateFlow(false)
+ override val isMicCameraIndicationEnabled = _isMicCameraIndicationEnabled
+
+ private val _isLocationIndicationEnabled = MutableStateFlow(false)
+ override val isLocationIndicationEnabled = _isLocationIndicationEnabled
+
+ fun setIsSafetyCenterEnabled(value: Boolean) {
+ _isSafetyCenterEnabled.value = value
+ }
+
+ fun setPrivacyItems(value: List<PrivacyItem>) {
+ _privacyItems.value = value
+ }
+
+ fun setIsMicCameraIndicationEnabled(value: Boolean) {
+ _isMicCameraIndicationEnabled.value = value
+ }
+
+ fun setIsLocationIndicationEnabled(value: Boolean) {
+ _isLocationIndicationEnabled.value = value
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileLifecycleManagerFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/PrivacyChipRepositoryKosmos.kt
index f8ce707b0bb2..2428c6156720 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileLifecycleManagerFactoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/PrivacyChipRepositoryKosmos.kt
@@ -14,11 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.qs.external
+package com.android.systemui.shade.data.repository
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.util.mockito.mock
-/** Returns mocks */
-var Kosmos.tileLifecycleManagerFactory: TileLifecycleManager.Factory by
- Kosmos.Fixture { TileLifecycleManager.Factory { _, _ -> mock<TileLifecycleManager>() } }
+var Kosmos.privacyChipRepository: PrivacyChipRepository by
+ Kosmos.Fixture { fakePrivacyChipRepository }
+val Kosmos.fakePrivacyChipRepository: FakePrivacyChipRepository by
+ Kosmos.Fixture { FakePrivacyChipRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/PrivacyChipInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/PrivacyChipInteractorKosmos.kt
new file mode 100644
index 000000000000..7334286f00c4
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/PrivacyChipInteractorKosmos.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.privacy.privacyDialogController
+import com.android.systemui.privacy.privacyDialogControllerV2
+import com.android.systemui.shade.data.repository.fakePrivacyChipRepository
+import com.android.systemui.statusbar.policy.deviceProvisionedController
+
+var Kosmos.privacyChipInteractor: PrivacyChipInteractor by
+ Kosmos.Fixture {
+ PrivacyChipInteractor(
+ applicationScope = applicationCoroutineScope,
+ repository = fakePrivacyChipRepository,
+ privacyDialogController = privacyDialogController,
+ privacyDialogControllerV2 = privacyDialogControllerV2,
+ deviceProvisionedController = deviceProvisionedController,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerKosmos.kt
index 81888c48488a..e5072f1c9f1c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerKosmos.kt
@@ -24,7 +24,7 @@ import com.android.systemui.keyguard.domain.interactor.naturalScrollingSettingOb
import com.android.systemui.keyguard.wakefulnessLifecycle
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.media.controls.ui.mediaHierarchyManager
+import com.android.systemui.media.controls.ui.controller.mediaHierarchyManager
import com.android.systemui.plugins.activityStarter
import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.shade.domain.interactor.shadeInteractor
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/HeadsUpNotificationRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/HeadsUpNotificationRepositoryKosmos.kt
new file mode 100644
index 000000000000..25864aee2136
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/HeadsUpNotificationRepositoryKosmos.kt
@@ -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.
+ */
+
+package com.android.systemui.statusbar.notification.stack.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.statusbar.notification.data.repository.HeadsUpNotificationRepository
+import kotlinx.coroutines.flow.MutableStateFlow
+
+val Kosmos.headsUpNotificationRepository by Fixture { FakeHeadsUpNotificationRepository() }
+
+class FakeHeadsUpNotificationRepository : HeadsUpNotificationRepository {
+ override val hasPinnedHeadsUp = MutableStateFlow(false)
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HeadsUpNotificationInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HeadsUpNotificationInteractorKosmos.kt
new file mode 100644
index 000000000000..d3451075c51b
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HeadsUpNotificationInteractorKosmos.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.stack.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor
+import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository
+
+val Kosmos.headsUpNotificationInteractor by Fixture {
+ HeadsUpNotificationInteractor(headsUpNotificationRepository)
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/anc/FakeSliceFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/anc/FakeSliceFactory.kt
new file mode 100644
index 000000000000..fc406eaae0f1
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/anc/FakeSliceFactory.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.anc
+
+import androidx.slice.Slice
+import androidx.slice.SliceItem
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+
+object FakeSliceFactory {
+
+ fun createSlice(hasError: Boolean, hasSliceItem: Boolean): Slice {
+ return mock {
+ val sliceItem: SliceItem = mock {
+ whenever(format).thenReturn(android.app.slice.SliceItem.FORMAT_SLICE)
+ }
+
+ whenever(items)
+ .thenReturn(
+ buildList {
+ if (hasSliceItem) {
+ add(sliceItem)
+ }
+ }
+ )
+
+ whenever(hints)
+ .thenReturn(
+ buildList {
+ if (hasError) {
+ add(android.app.slice.Slice.HINT_ERROR)
+ }
+ }
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/anc/VolumePanelAncKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/anc/VolumePanelAncKosmos.kt
new file mode 100644
index 000000000000..f9b7e69eea7d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/anc/VolumePanelAncKosmos.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.anc
+
+import androidx.slice.SliceViewManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.volume.panel.component.anc.data.repository.FakeAncSliceRepository
+import com.android.systemui.volume.panel.component.anc.domain.interactor.AncSliceInteractor
+
+var Kosmos.sliceViewManager: SliceViewManager by Kosmos.Fixture { mock {} }
+val Kosmos.ancSliceRepository by Kosmos.Fixture { FakeAncSliceRepository() }
+val Kosmos.ancSliceInteractor by
+ Kosmos.Fixture { AncSliceInteractor(ancSliceRepository, testScope.backgroundScope) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/anc/data/repository/FakeAncSliceRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/anc/data/repository/FakeAncSliceRepository.kt
new file mode 100644
index 000000000000..b66d7f974eca
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/anc/data/repository/FakeAncSliceRepository.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.anc.data.repository
+
+import androidx.slice.Slice
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class FakeAncSliceRepository : AncSliceRepository {
+
+ private val sliceByWidth = mutableMapOf<Int, MutableStateFlow<Slice?>>()
+
+ override fun ancSlice(width: Int): Flow<Slice?> =
+ sliceByWidth.getOrPut(width) { MutableStateFlow(null) }
+
+ fun putSlice(width: Int, slice: Slice?) {
+ sliceByWidth.getOrPut(width) { MutableStateFlow(null) }.value = slice
+ }
+}
diff --git a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
index 053ed779a27a..3ecdf3f101a5 100644
--- a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
+++ b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
@@ -36,9 +36,11 @@ import static com.android.window.flags.Flags.multiCrop;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
@@ -57,6 +59,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.graphics.Rect;
import android.os.FileUtils;
import android.os.ParcelFileDescriptor;
import android.os.UserHandle;
@@ -78,6 +81,7 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -87,6 +91,7 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
import java.util.Optional;
@RunWith(AndroidJUnit4.class)
@@ -815,6 +820,48 @@ public class WallpaperBackupAgentTest {
WallpaperEventLogger.ERROR_LIVE_PACKAGE_NOT_INSTALLED);
}
+ @Test
+ public void testOnRestore_noCropHints() throws Exception {
+ testParseCropHints(Map.of());
+ }
+
+ @Test
+ public void testOnRestore_singleCropHint() throws Exception {
+ Map<Integer, Rect> testMap = Map.of(WallpaperManager.PORTRAIT, new Rect(1, 2, 3, 4));
+ testParseCropHints(testMap);
+ }
+
+ @Test
+ public void testOnRestore_multipleCropHints() throws Exception {
+ Map<Integer, Rect> testMap = Map.of(
+ WallpaperManager.PORTRAIT, new Rect(1, 2, 3, 4),
+ WallpaperManager.SQUARE_PORTRAIT, new Rect(5, 6, 7, 8),
+ WallpaperManager.SQUARE_LANDSCAPE, new Rect(9, 10, 11, 12));
+ testParseCropHints(testMap);
+ }
+
+ private void testParseCropHints(Map<Integer, Rect> testMap) throws Exception {
+ assumeTrue(multiCrop());
+ mockRestoredStaticWallpaperFile(testMap);
+ mockStagedWallpaperFile(SYSTEM_WALLPAPER_STAGE);
+ mWallpaperBackupAgent.onCreate(USER_HANDLE, BackupAnnotations.BackupDestination.CLOUD,
+ BackupAnnotations.OperationType.RESTORE);
+
+ mWallpaperBackupAgent.onRestoreFinished();
+
+ ArgumentMatcher<SparseArray<Rect>> matcher = array -> {
+ boolean result = testMap.entrySet().stream().allMatch(entry -> {
+ int key = entry.getKey();
+ return (array.contains(key) && array.get(key).equals(testMap.get(key)));
+ });
+ for (int i = 0; i < array.size(); i++) {
+ if (!testMap.containsKey(array.keyAt(i))) result = false;
+ }
+ return result;
+ };
+ verify(mWallpaperManager).setStreamWithCrops(any(), argThat(matcher), eq(true), anyInt());
+ }
+
private void mockCurrentWallpaperIds(int systemWallpaperId, int lockWallpaperId) {
when(mWallpaperManager.getWallpaperId(eq(FLAG_SYSTEM))).thenReturn(systemWallpaperId);
when(mWallpaperManager.getWallpaperId(eq(FLAG_LOCK))).thenReturn(lockWallpaperId);
@@ -880,6 +927,34 @@ public class WallpaperBackupAgentTest {
fstream.close();
}
+ private void mockRestoredStaticWallpaperFile(Map<Integer, Rect> crops) throws Exception {
+ File wallpaperFile = new File(mContext.getFilesDir(), WALLPAPER_INFO_STAGE);
+ wallpaperFile.createNewFile();
+ FileOutputStream fstream = new FileOutputStream(wallpaperFile, false);
+ TypedXmlSerializer out = Xml.resolveSerializer(fstream);
+ out.startDocument(null, true);
+ out.startTag(null, "wp");
+ for (Map.Entry<Integer, Rect> entry: crops.entrySet()) {
+ String orientation = switch (entry.getKey()) {
+ case WallpaperManager.PORTRAIT -> "Portrait";
+ case WallpaperManager.LANDSCAPE -> "Landscape";
+ case WallpaperManager.SQUARE_PORTRAIT -> "SquarePortrait";
+ case WallpaperManager.SQUARE_LANDSCAPE -> "SquareLandscape";
+ default -> throw new IllegalArgumentException("Invalid orientation");
+ };
+ Rect rect = entry.getValue();
+ out.attributeInt(null, "cropLeft" + orientation, rect.left);
+ out.attributeInt(null, "cropTop" + orientation, rect.top);
+ out.attributeInt(null, "cropRight" + orientation, rect.right);
+ out.attributeInt(null, "cropBottom" + orientation, rect.bottom);
+ }
+ out.endTag(null, "wp");
+ out.endDocument();
+ fstream.flush();
+ FileUtils.sync(fstream);
+ fstream.close();
+ }
+
private WallpaperInfo getFakeWallpaperInfo() throws Exception {
Context context = InstrumentationRegistry.getTargetContext();
Intent intent = new Intent(WallpaperService.SERVICE_INTERFACE);
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp
index 132804f4f91d..53897e14ecd6 100644
--- a/ravenwood/Android.bp
+++ b/ravenwood/Android.bp
@@ -74,11 +74,14 @@ java_library {
"androidx.test.monitor-for-device",
],
libs: [
+ "android.test.mock",
"framework-minus-apex.ravenwood",
+ "services.core.ravenwood",
"junit",
],
sdk_version: "core_current",
visibility: ["//frameworks/base"],
+ jarjar_rules: ":ravenwood-services-jarjar-rules",
}
// Carefully compiles against only test_current to support tests that
@@ -111,3 +114,9 @@ java_device_for_host {
"androidx.test.monitor",
],
}
+
+filegroup {
+ name: "ravenwood-services-jarjar-rules",
+ srcs: ["ravenwood-services-jarjar-rules.txt"],
+ visibility: ["//frameworks/base"],
+}
diff --git a/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodReplace.java b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodReplace.java
index a920f63152fb..83a7b6e54389 100644
--- a/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodReplace.java
+++ b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodReplace.java
@@ -32,4 +32,14 @@ import java.lang.annotation.Target;
@Target({METHOD})
@Retention(RetentionPolicy.CLASS)
public @interface RavenwoodReplace {
+ /**
+ * One or more classes that aren't yet supported by Ravenwood, which is why this method is
+ * being replaced.
+ */
+ Class<?>[] blockedBy() default {};
+
+ /**
+ * General free-form description of why this method is being replaced.
+ */
+ String reason() default "";
}
diff --git a/ravenwood/framework-minus-apex-ravenwood-policies.txt b/ravenwood/framework-minus-apex-ravenwood-policies.txt
index 49cef07033c1..6b6736476210 100644
--- a/ravenwood/framework-minus-apex-ravenwood-policies.txt
+++ b/ravenwood/framework-minus-apex-ravenwood-policies.txt
@@ -52,5 +52,6 @@ class android.content.BroadcastReceiver stub
method <init> ()V stub
class android.content.Context stub
method <init> ()V stub
+ method getSystemService (Ljava/lang/Class;)Ljava/lang/Object; stub
class android.content.pm.PackageManager stub
method <init> ()V stub
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java
new file mode 100644
index 000000000000..3668b03e58d3
--- /dev/null
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java
@@ -0,0 +1,90 @@
+/*
+ * 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 android.content.Context;
+import android.hardware.ISerialManager;
+import android.hardware.SerialManager;
+import android.os.PermissionEnforcer;
+import android.os.ServiceManager;
+import android.test.mock.MockContext;
+import android.util.ArrayMap;
+import android.util.Singleton;
+
+import java.util.function.Supplier;
+
+public class RavenwoodContext extends MockContext {
+ private final RavenwoodPermissionEnforcer mEnforcer = new RavenwoodPermissionEnforcer();
+
+ private final ArrayMap<Class<?>, String> mClassToName = new ArrayMap<>();
+ private final ArrayMap<String, Supplier<?>> mNameToFactory = new ArrayMap<>();
+
+ private void registerService(Class<?> serviceClass, String serviceName,
+ Supplier<?> serviceSupplier) {
+ mClassToName.put(serviceClass, serviceName);
+ mNameToFactory.put(serviceName, serviceSupplier);
+ }
+
+ public RavenwoodContext() {
+ registerService(PermissionEnforcer.class,
+ Context.PERMISSION_ENFORCER_SERVICE, () -> mEnforcer);
+ registerService(SerialManager.class,
+ Context.SERIAL_SERVICE, asSingleton(() ->
+ new SerialManager(this, ISerialManager.Stub.asInterface(
+ ServiceManager.getService(Context.SERIAL_SERVICE)))
+ ));
+ }
+
+ @Override
+ public Object getSystemService(String serviceName) {
+ // TODO: pivot to using SystemServiceRegistry
+ final Supplier<?> serviceSupplier = mNameToFactory.get(serviceName);
+ if (serviceSupplier != null) {
+ return serviceSupplier.get();
+ } else {
+ throw new UnsupportedOperationException(
+ "Service " + serviceName + " not yet supported under Ravenwood");
+ }
+ }
+
+ @Override
+ public String getSystemServiceName(Class<?> serviceClass) {
+ // TODO: pivot to using SystemServiceRegistry
+ final String serviceName = mClassToName.get(serviceClass);
+ if (serviceName != null) {
+ return serviceName;
+ } else {
+ throw new UnsupportedOperationException(
+ "Service " + serviceClass + " not yet supported under Ravenwood");
+ }
+ }
+
+ /**
+ * Wrap the given {@link Supplier} to become a memoized singleton.
+ */
+ private static <T> Supplier<T> asSingleton(Supplier<T> supplier) {
+ final Singleton<T> singleton = new Singleton<>() {
+ @Override
+ protected T create() {
+ return supplier.get();
+ }
+ };
+ return () -> {
+ return singleton.get();
+ };
+ }
+}
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodPermissionEnforcer.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodPermissionEnforcer.java
new file mode 100644
index 000000000000..42441352536e
--- /dev/null
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodPermissionEnforcer.java
@@ -0,0 +1,38 @@
+/*
+ * 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 static android.permission.PermissionManager.PERMISSION_GRANTED;
+
+import android.content.AttributionSource;
+import android.os.PermissionEnforcer;
+
+public class RavenwoodPermissionEnforcer extends PermissionEnforcer {
+ @Override
+ protected int checkPermission(String permission, AttributionSource source) {
+ // For the moment, since Ravenwood doesn't offer cross-process capabilities, assume all
+ // permissions are granted during tests
+ return PERMISSION_GRANTED;
+ }
+
+ @Override
+ protected int checkPermission(String permission, int pid, int uid) {
+ // For the moment, since Ravenwood doesn't offer cross-process capabilities, assume all
+ // permissions are granted during tests
+ return PERMISSION_GRANTED;
+ }
+}
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 1d5c79cf10a6..231cce95f353 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
@@ -24,11 +24,13 @@ import android.os.Build;
import android.os.Bundle;
import android.os.HandlerThread;
import android.os.Looper;
+import android.os.ServiceManager;
import android.util.Log;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.internal.os.RuntimeInit;
+import com.android.server.LocalServices;
import org.junit.After;
import org.junit.Assert;
@@ -103,9 +105,10 @@ public class RavenwoodRuleImpl {
rule.mSystemProperties.getKeyReadablePredicate(),
rule.mSystemProperties.getKeyWritablePredicate());
- ActivityManager.init$ravenwood(rule.mCurrentUser);
+ ServiceManager.init$ravenwood();
+ LocalServices.removeAllServicesForTest();
- com.android.server.LocalServices.removeAllServicesForTest();
+ ActivityManager.init$ravenwood(rule.mCurrentUser);
if (rule.mProvideMainThread) {
final HandlerThread main = new HandlerThread(MAIN_THREAD_NAME);
@@ -113,7 +116,12 @@ public class RavenwoodRuleImpl {
Looper.setMainLooperForTest(main.getLooper());
}
- InstrumentationRegistry.registerInstance(new Instrumentation(), Bundle.EMPTY);
+ rule.mContext = new RavenwoodContext();
+ rule.mInstrumentation = new Instrumentation();
+ rule.mInstrumentation.basicInit(rule.mContext);
+ InstrumentationRegistry.registerInstance(rule.mInstrumentation, Bundle.EMPTY);
+
+ RavenwoodSystemServer.init(rule);
if (ENABLE_TIMEOUT_STACKS) {
sPendingTimeout = sTimeoutExecutor.schedule(RavenwoodRuleImpl::dumpStacks,
@@ -121,7 +129,7 @@ public class RavenwoodRuleImpl {
}
// Touch some references early to ensure they're <clinit>'ed
- Objects.requireNonNull(Build.IS_USERDEBUG);
+ Objects.requireNonNull(Build.TYPE);
Objects.requireNonNull(Build.VERSION.SDK);
}
@@ -130,17 +138,22 @@ public class RavenwoodRuleImpl {
sPendingTimeout.cancel(false);
}
+ RavenwoodSystemServer.reset(rule);
+
InstrumentationRegistry.registerInstance(null, Bundle.EMPTY);
+ rule.mInstrumentation = null;
+ rule.mContext = null;
if (rule.mProvideMainThread) {
Looper.getMainLooper().quit();
Looper.clearMainLooperForTest();
}
- com.android.server.LocalServices.removeAllServicesForTest();
-
ActivityManager.reset$ravenwood();
+ LocalServices.removeAllServicesForTest();
+ ServiceManager.reset$ravenwood();
+
android.os.SystemProperties.reset$ravenwood();
android.os.Binder.reset$ravenwood();
android.os.Process.reset$ravenwood();
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java
new file mode 100644
index 000000000000..bb280f47ccd9
--- /dev/null
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java
@@ -0,0 +1,85 @@
+/*
+ * 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 android.hardware.SerialManager;
+import android.os.SystemClock;
+import android.util.ArrayMap;
+
+import com.android.server.LocalServices;
+import com.android.server.SystemService;
+import com.android.server.SystemServiceManager;
+import com.android.server.utils.TimingsTraceAndSlog;
+
+public class RavenwoodSystemServer {
+ /**
+ * Set of services that we know how to provide under Ravenwood. We keep this set distinct
+ * from {@code com.android.server.SystemServer} to give us the ability to choose either
+ * "real" or "fake" implementations based on the commitments of the service owner.
+ *
+ * Map from {@code FooManager.class} to the {@code com.android.server.SystemService}
+ * lifecycle class name used to instantiate and drive that service.
+ */
+ private static final ArrayMap<Class<?>, String> sKnownServices = new ArrayMap<>();
+
+ // TODO: expand SystemService API to support dependency expression, so we don't need test
+ // authors to exhaustively declare all transitive services
+
+ static {
+ sKnownServices.put(SerialManager.class, "com.android.server.SerialService$Lifecycle");
+ }
+
+ private static TimingsTraceAndSlog sTimings;
+ private static SystemServiceManager sServiceManager;
+
+ public static void init(RavenwoodRule rule) {
+ // Avoid overhead if no services required
+ if (rule.mServicesRequired.isEmpty()) return;
+
+ sTimings = new TimingsTraceAndSlog();
+ sServiceManager = new SystemServiceManager(rule.mContext);
+ sServiceManager.setStartInfo(false,
+ SystemClock.elapsedRealtime(),
+ SystemClock.uptimeMillis());
+ LocalServices.addService(SystemServiceManager.class, sServiceManager);
+
+ for (Class<?> service : rule.mServicesRequired) {
+ final String target = sKnownServices.get(service);
+ if (target == null) {
+ throw new RuntimeException("The requested service " + service
+ + " is not yet supported under the Ravenwood deviceless testing "
+ + "environment; consider requesting support from the API owner or "
+ + "consider using Mockito; more details at go/ravenwood-docs");
+ } else {
+ sServiceManager.startService(target);
+ }
+ }
+ sServiceManager.sealStartedServices();
+
+ // TODO: expand to include additional boot phases when relevant
+ sServiceManager.startBootPhase(sTimings, SystemService.PHASE_SYSTEM_SERVICES_READY);
+ sServiceManager.startBootPhase(sTimings, SystemService.PHASE_BOOT_COMPLETED);
+ }
+
+ public static void reset(RavenwoodRule rule) {
+ // TODO: consider introducing shutdown boot phases
+
+ LocalServices.removeServiceForTest(SystemServiceManager.class);
+ sServiceManager = null;
+ sTimings = null;
+ }
+}
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
index b90f112c1655..a8c24fcbd7e0 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
@@ -22,10 +22,13 @@ import static android.os.UserHandle.USER_SYSTEM;
import static org.junit.Assert.fail;
+import android.app.Instrumentation;
+import android.content.Context;
import android.platform.test.annotations.DisabledOnNonRavenwood;
import android.platform.test.annotations.DisabledOnRavenwood;
import android.platform.test.annotations.EnabledOnRavenwood;
import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.util.ArraySet;
import org.junit.Assume;
import org.junit.rules.TestRule;
@@ -122,6 +125,11 @@ public class RavenwoodRule implements TestRule {
final RavenwoodSystemProperties mSystemProperties = new RavenwoodSystemProperties();
+ final ArraySet<Class<?>> mServicesRequired = new ArraySet<>();
+
+ volatile Context mContext;
+ volatile Instrumentation mInstrumentation;
+
public RavenwoodRule() {
}
@@ -192,6 +200,23 @@ public class RavenwoodRule implements TestRule {
return this;
}
+ /**
+ * Configure the set of system services that are required for this test to operate.
+ *
+ * For example, passing {@code android.hardware.SerialManager.class} as an argument will
+ * ensure that the underlying service is created, initialized, and ready to use for the
+ * duration of the test. The {@code SerialManager} instance can be obtained via
+ * {@code RavenwoodRule.getContext()} and {@code Context.getSystemService()}, and
+ * {@code SerialManagerInternal} can be obtained via {@code LocalServices.getService()}.
+ */
+ public Builder setServicesRequired(Class<?>... services) {
+ mRule.mServicesRequired.clear();
+ for (Class<?> service : services) {
+ mRule.mServicesRequired.add(service);
+ }
+ return this;
+ }
+
public RavenwoodRule build() {
return mRule;
}
@@ -212,6 +237,28 @@ public class RavenwoodRule implements TestRule {
return IS_ON_RAVENWOOD;
}
+ /**
+ * Return a {@code Context} available for usage during the currently running test case.
+ *
+ * Each test should obtain needed information or references via this method;
+ * references must not be stored beyond the scope of a test case.
+ */
+ public Context getContext() {
+ return Objects.requireNonNull(mContext,
+ "Context is only available during @Test execution");
+ }
+
+ /**
+ * Return a {@code Instrumentation} available for usage during the currently running test case.
+ *
+ * Each test should obtain needed information or references via this method;
+ * references must not be stored beyond the scope of a test case.
+ */
+ public Instrumentation getInstrumentation() {
+ return Objects.requireNonNull(mInstrumentation,
+ "Instrumentation is only available during @Test execution");
+ }
+
static boolean shouldEnableOnDevice(Description description) {
if (description.isTest()) {
if (description.getAnnotation(DisabledOnNonRavenwood.class) != null) {
diff --git a/ravenwood/ravenwood-annotation-allowed-classes.txt b/ravenwood/ravenwood-annotation-allowed-classes.txt
index b5baef666b52..4a4c29030f3c 100644
--- a/ravenwood/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/ravenwood-annotation-allowed-classes.txt
@@ -99,6 +99,7 @@ android.util.SparseSetArray
android.util.StringBuilderPrinter
android.util.TeeWriter
android.util.TimeUtils
+android.util.TimingsTraceLog
android.util.UtilConfig
android.util.Xml
@@ -152,6 +153,7 @@ android.os.ParcelFormatException
android.os.ParcelUuid
android.os.Parcelable
android.os.PatternMatcher
+android.os.PermissionEnforcer
android.os.PersistableBundle
android.os.PowerComponents
android.os.Process
@@ -159,6 +161,8 @@ android.os.RemoteCallback
android.os.RemoteCallbackList
android.os.RemoteException
android.os.ResultReceiver
+android.os.ServiceManager
+android.os.ServiceManager$ServiceNotFoundException
android.os.ServiceSpecificException
android.os.StrictMode
android.os.SystemClock
@@ -252,6 +256,9 @@ android.view.Display$HdrCapabilities
android.view.Display$Mode
android.view.DisplayInfo
+android.hardware.SerialManager
+android.hardware.SerialManagerInternal
+
android.telephony.ActivityStatsTechSpecificInfo
android.telephony.CellSignalStrength
android.telephony.ModemActivityInfo
@@ -310,3 +317,9 @@ com.android.internal.os.StoragedUidIoStatsReader
com.google.android.collect.Lists
com.google.android.collect.Maps
com.google.android.collect.Sets
+
+com.android.server.SerialService
+com.android.server.SystemService
+com.android.server.SystemServiceManager
+
+com.android.server.utils.TimingsTraceAndSlog
diff --git a/ravenwood/ravenwood-services-jarjar-rules.txt b/ravenwood/ravenwood-services-jarjar-rules.txt
new file mode 100644
index 000000000000..8fdd3408f74d
--- /dev/null
+++ b/ravenwood/ravenwood-services-jarjar-rules.txt
@@ -0,0 +1,11 @@
+# Ignore one-off class defined out in core/java/
+rule com.android.server.LocalServices @0
+rule com.android.server.pm.pkg.AndroidPackage @0
+rule com.android.server.pm.pkg.AndroidPackageSplit @0
+
+# Rename all other service internals so that tests can continue to statically
+# link services code when owners aren't ready to support on Ravenwood
+rule com.android.server.** repackaged.@0
+
+# TODO: support AIDL generated Parcelables via hoststubgen
+rule android.hardware.power.stats.** repackaged.@0
diff --git a/ravenwood/run-ravenwood-tests.sh b/ravenwood/run-ravenwood-tests.sh
index 259aa702452d..a303626bb445 100755
--- a/ravenwood/run-ravenwood-tests.sh
+++ b/ravenwood/run-ravenwood-tests.sh
@@ -20,5 +20,9 @@ all_tests="hoststubgentest tiny-framework-dump-test hoststubgen-invoke-test"
# "echo" is to remove the newlines
all_tests="$all_tests $(echo $(${0%/*}/list-ravenwood-tests.sh) )"
-echo "Running tests: $all_tests"
-atest $all_tests
+run() {
+ echo "Running: $*"
+ "${@}"
+}
+
+run ${ATEST:-atest} $all_tests
diff --git a/ravenwood/services-test/Android.bp b/ravenwood/services-test/Android.bp
new file mode 100644
index 000000000000..39858f05e80d
--- /dev/null
+++ b/ravenwood/services-test/Android.bp
@@ -0,0 +1,21 @@
+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_ravenwood_test {
+ name: "RavenwoodServicesTest",
+ static_libs: [
+ "androidx.annotation_annotation",
+ "androidx.test.ext.junit",
+ "androidx.test.rules",
+ ],
+ srcs: [
+ "test/**/*.java",
+ ],
+ auto_gen_config: true,
+}
diff --git a/ravenwood/services-test/test/com/android/ravenwood/RavenwoodServicesTest.java b/ravenwood/services-test/test/com/android/ravenwood/RavenwoodServicesTest.java
new file mode 100644
index 000000000000..c1dee5d2f55b
--- /dev/null
+++ b/ravenwood/services-test/test/com/android/ravenwood/RavenwoodServicesTest.java
@@ -0,0 +1,83 @@
+/*
+ * 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.ravenwood;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.content.Context;
+import android.hardware.SerialManager;
+import android.hardware.SerialManagerInternal;
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.server.LocalServices;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class RavenwoodServicesTest {
+ private static final String TEST_VIRTUAL_PORT = "virtual:example";
+
+ @Rule
+ public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+ .setProcessSystem()
+ .setServicesRequired(SerialManager.class)
+ .build();
+
+ @Test
+ public void testDefined() {
+ final SerialManager fromName = (SerialManager)
+ mRavenwood.getContext().getSystemService(Context.SERIAL_SERVICE);
+ final SerialManager fromClass =
+ mRavenwood.getContext().getSystemService(SerialManager.class);
+ assertNotNull(fromName);
+ assertNotNull(fromClass);
+ assertEquals(fromName, fromClass);
+
+ assertNotNull(LocalServices.getService(SerialManagerInternal.class));
+ }
+
+ @Test
+ public void testSimple() {
+ // Verify that we can obtain a manager, and talk to the backend service, and that no
+ // serial ports are configured by default
+ final SerialManager service = (SerialManager)
+ mRavenwood.getContext().getSystemService(Context.SERIAL_SERVICE);
+ final String[] ports = service.getSerialPorts();
+ assertEquals(0, ports.length);
+ }
+
+ @Test
+ public void testDriven() {
+ final SerialManager service = (SerialManager)
+ mRavenwood.getContext().getSystemService(Context.SERIAL_SERVICE);
+ final SerialManagerInternal internal = LocalServices.getService(
+ SerialManagerInternal.class);
+
+ internal.addVirtualSerialPortForTest(TEST_VIRTUAL_PORT, () -> {
+ throw new UnsupportedOperationException(
+ "Needs socketpair() to offer accurate emulation");
+ });
+ final String[] ports = service.getSerialPorts();
+ assertEquals(1, ports.length);
+ assertEquals(TEST_VIRTUAL_PORT, ports[0]);
+ }
+}
diff --git a/ravenwood/services.core-ravenwood-policies.txt b/ravenwood/services.core-ravenwood-policies.txt
new file mode 100644
index 000000000000..d8d563e05435
--- /dev/null
+++ b/ravenwood/services.core-ravenwood-policies.txt
@@ -0,0 +1,7 @@
+# Ravenwood "policy" file for services.core.
+
+# Keep all AIDL interfaces
+class :aidl stubclass
+
+# Keep all feature flag implementations
+class :feature_flags stubclass
diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig
index 015c35eed326..d4f049e09ee2 100644
--- a/services/accessibility/accessibility.aconfig
+++ b/services/accessibility/accessibility.aconfig
@@ -24,6 +24,13 @@ flag {
}
flag {
+ name: "compute_window_changes_on_a11y"
+ namespace: "accessibility"
+ description: "Computes accessibility window changes in accessibility instead of wm package."
+ bug: "322444245"
+}
+
+flag {
name: "deprecate_package_list_observer"
namespace: "accessibility"
description: "Stops using the deprecated PackageListObserver."
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 3442a6ac56b3..46db624cf3c1 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -64,6 +64,7 @@ import android.app.AppOpsManager;
import android.app.PendingIntent;
import android.app.RemoteAction;
import android.app.admin.DevicePolicyManager;
+import android.app.ecm.EnhancedConfirmationManager;
import android.appwidget.AppWidgetManagerInternal;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
@@ -112,6 +113,7 @@ import android.text.TextUtils;
import android.text.TextUtils.SimpleStringSplitter;
import android.util.ArraySet;
import android.util.IntArray;
+import android.util.Log;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
@@ -4394,13 +4396,29 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
// permittedServices null means all accessibility services are allowed.
boolean allowed = permittedServices == null || permittedServices.contains(packageName);
if (allowed) {
- final AppOpsManager appOps = mContext.getSystemService(AppOpsManager.class);
- final int mode = appOps.noteOpNoThrow(
- AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS,
- uid, packageName, /* attributionTag= */ null, /* message= */ null);
- final boolean ecmEnabled = mContext.getResources().getBoolean(
- R.bool.config_enhancedConfirmationModeEnabled);
- return !ecmEnabled || mode == AppOpsManager.MODE_ALLOWED;
+ if (android.permission.flags.Flags.enhancedConfirmationModeApisEnabled()
+ && android.security.Flags.extendEcmToAllSettings()) {
+ try {
+ return !mContext.getSystemService(EnhancedConfirmationManager.class)
+ .isRestricted(packageName,
+ AppOpsManager.OPSTR_BIND_ACCESSIBILITY_SERVICE);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(LOG_TAG, "Exception when retrieving package:" + packageName, e);
+ return false;
+ }
+ } else {
+ try {
+ final int mode = mContext.getSystemService(AppOpsManager.class)
+ .noteOpNoThrow(AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS,
+ uid, packageName);
+ final boolean ecmEnabled = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_enhancedConfirmationModeEnabled);
+ return !ecmEnabled || mode == AppOpsManager.MODE_ALLOWED;
+ } catch (Exception e) {
+ // Fallback in case if app ops is not available in testing.
+ return false;
+ }
+ }
}
return false;
} finally {
@@ -4423,8 +4441,21 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
return true;
}
- RestrictedLockUtils.sendShowRestrictedSettingDialogIntent(mContext,
- packageName, uid);
+ if (android.permission.flags.Flags.enhancedConfirmationModeApisEnabled()
+ && android.security.Flags.extendEcmToAllSettings()) {
+ try {
+ Intent settingDialogIntent = mContext
+ .getSystemService(EnhancedConfirmationManager.class)
+ .createRestrictedSettingDialogIntent(packageName,
+ AppOpsManager.OPSTR_BIND_ACCESSIBILITY_SERVICE);
+ mContext.startActivity(settingDialogIntent);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(LOG_TAG, "Exception when retrieving package:" + packageName, e);
+ }
+ } else {
+ RestrictedLockUtils.sendShowRestrictedSettingDialogIntent(mContext,
+ packageName, uid);
+ }
return true;
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
index 26c1bc904a1a..b8181505b9c4 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
@@ -19,6 +19,8 @@ package com.android.server.accessibility;
import static android.accessibilityservice.AccessibilityTrace.FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION;
import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MANAGER_INTERNAL;
import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
+import static android.view.WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY;
import static android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED;
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
@@ -27,6 +29,7 @@ import static com.android.server.accessibility.AbstractAccessibilityServiceConne
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.graphics.Point;
import android.graphics.Region;
import android.os.Binder;
import android.os.Handler;
@@ -37,6 +40,7 @@ import android.os.SystemClock;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.Slog;
import android.util.SparseArray;
import android.view.Display;
@@ -52,6 +56,7 @@ import android.view.accessibility.IAccessibilityInteractionConnection;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.accessibility.AccessibilitySecurityPolicy.AccessibilityUserManager;
import com.android.server.utils.Slogf;
+import com.android.server.wm.AccessibilityWindowsPopulator.AccessibilityWindow;
import com.android.server.wm.WindowManagerInternal;
import java.io.FileDescriptor;
@@ -60,6 +65,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.Set;
import java.util.stream.Collectors;
/**
@@ -440,7 +446,7 @@ public class AccessibilityWindowManager {
updateWindowsByWindowAttributesLocked(windows);
if (DEBUG) {
Slogf.i(LOG_TAG, "mDisplayId=%d, topFocusedDisplayId=%d, currentUserId=%d, "
- + "visibleBgUsers=%s", mDisplayId, topFocusedDisplayId,
+ + "visibleBgUsers=%s", mDisplayId, topFocusedDisplayId,
mAccessibilityUserManager.getCurrentUserIdLocked(),
mAccessibilityUserManager.getVisibleUserIdsLocked());
if (VERBOSE) {
@@ -460,7 +466,7 @@ public class AccessibilityWindowManager {
mTopFocusedWindowToken = topFocusedWindowToken;
if (DEBUG) {
Slogf.d(LOG_TAG, "onWindowsForAccessibilityChanged(): updating windows for "
- + "display %d and token %s",
+ + "display %d and token %s",
topFocusedDisplayId, topFocusedWindowToken);
}
cacheWindows(windows);
@@ -472,12 +478,168 @@ public class AccessibilityWindowManager {
}
else if (DEBUG) {
Slogf.d(LOG_TAG, "onWindowsForAccessibilityChanged(): NOT updating windows for "
- + "display %d and token %s",
+ + "display %d and token %s",
topFocusedDisplayId, topFocusedWindowToken);
}
}
}
+ /**
+ * Called when the windows for accessibility changed. This is called if
+ * {@link com.android.server.accessibility.Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y} is
+ * true.
+ *
+ * @param forceSend Send the windows for accessibility even if they haven't
+ * changed.
+ * @param topFocusedDisplayId The display Id which has the top focused window.
+ * @param topFocusedWindowToken The window token of top focused window.
+ * @param screenSize The size of the display that the change happened.
+ * @param windows The windows for accessibility.
+ */
+ @Override
+ public void onAccessibilityWindowsChanged(boolean forceSend, int topFocusedDisplayId,
+ @NonNull IBinder topFocusedWindowToken, @NonNull Point screenSize,
+ @NonNull List<AccessibilityWindow> windows) {
+ // TODO(b/322444245): Get a screenSize from DisplayManager#getDisplay(int)
+ // .getRealSize().
+ final List<WindowInfo> windowInfoList = createWindowInfoList(screenSize, windows);
+ onWindowsForAccessibilityChanged(forceSend, topFocusedDisplayId,
+ topFocusedWindowToken, windowInfoList);
+ }
+
+ private static List<WindowInfo> createWindowInfoList(@NonNull Point screenSize,
+ @NonNull List<AccessibilityWindow> visibleWindows) {
+ final Set<IBinder> addedWindows = new ArraySet<>();
+ final List<WindowInfo> windows = new ArrayList<>();
+
+ // Avoid allocating Region for each window.
+ final Region regionInWindow = new Region();
+ final Region touchableRegionInScreen = new Region();
+
+ // Iterate until we figure out what is touchable for the entire screen.
+ boolean focusedWindowAdded = false;
+ final Region unaccountedSpace = new Region(0, 0, screenSize.x, screenSize.y);
+ for (final AccessibilityWindow a11yWindow : visibleWindows) {
+ a11yWindow.getTouchableRegionInWindow(regionInWindow);
+ if (windowMattersToAccessibility(a11yWindow, regionInWindow, unaccountedSpace)) {
+ final WindowInfo window = a11yWindow.getWindowInfo();
+ if (window.token != null) {
+ // Even if token is null, the window will be used in calculating visible
+ // windows, but is excluded from the accessibility window list.
+ // TODO(b/322444245): We can call #updateWindowWithWindowAttributes() here.
+ window.regionInScreen.set(regionInWindow);
+ window.layer = addedWindows.size();
+ windows.add(window);
+ addedWindows.add(window.token);
+ }
+
+ if (windowMattersToUnaccountedSpaceComputation(a11yWindow)) {
+ // Account for the space this window takes.
+ a11yWindow.getTouchableRegionInScreen(touchableRegionInScreen);
+ unaccountedSpace.op(touchableRegionInScreen, unaccountedSpace,
+ Region.Op.REVERSE_DIFFERENCE);
+ }
+
+ focusedWindowAdded |= a11yWindow.isFocused();
+ } else if (a11yWindow.isUntouchableNavigationBar()
+ && a11yWindow.getSystemBarInsetsFrame() != null) {
+ // If this widow is navigation bar without touchable region, accounting the
+ // region of navigation bar inset because all touch events from this region
+ // would be received by launcher, i.e. this region is a un-touchable one
+ // for the application.
+ unaccountedSpace.op(
+ a11yWindow.getSystemBarInsetsFrame(),
+ unaccountedSpace,
+ Region.Op.REVERSE_DIFFERENCE);
+ }
+
+ if (unaccountedSpace.isEmpty() && focusedWindowAdded) {
+ break;
+ }
+ }
+
+ // Remove child/parent references to windows that were not added.
+ for (final WindowInfo window : windows) {
+ if (!addedWindows.contains(window.parentToken)) {
+ window.parentToken = null;
+ }
+ if (window.childTokens != null) {
+ final int childTokenCount = window.childTokens.size();
+ for (int j = childTokenCount - 1; j >= 0; j--) {
+ if (!addedWindows.contains(window.childTokens.get(j))) {
+ window.childTokens.remove(j);
+ }
+ }
+ // Leave the child token list if empty.
+ }
+ }
+
+ return windows;
+ }
+
+ private static boolean windowMattersToAccessibility(AccessibilityWindow a11yWindow,
+ Region regionInScreen, Region unaccountedSpace) {
+ if (a11yWindow.ignoreRecentsAnimationForAccessibility()) {
+ return false;
+ }
+
+ if (a11yWindow.isFocused()) {
+ return true;
+ }
+
+ // Ignore non-touchable windows, except the split-screen divider, which is
+ // occasionally non-touchable but still useful for identifying split-screen
+ // mode and the PIP menu.
+ if (!a11yWindow.isTouchable()
+ && a11yWindow.getType() != TYPE_DOCK_DIVIDER && !a11yWindow.isPIPMenu()) {
+ return false;
+ }
+
+ // If the window is completely covered by other windows - ignore.
+ if (unaccountedSpace.quickReject(regionInScreen)) {
+ return false;
+ }
+
+ // Add windows of certain types not covered by modal windows.
+ if (isReportedWindowType(a11yWindow.getType())) {
+ return true;
+ }
+
+ return false;
+ }
+
+ private static boolean isReportedWindowType(int windowType) {
+ return (windowType != WindowManager.LayoutParams.TYPE_WALLPAPER
+ && windowType != WindowManager.LayoutParams.TYPE_BOOT_PROGRESS
+ && windowType != WindowManager.LayoutParams.TYPE_DISPLAY_OVERLAY
+ && windowType != WindowManager.LayoutParams.TYPE_DRAG
+ && windowType != WindowManager.LayoutParams.TYPE_INPUT_CONSUMER
+ && windowType != WindowManager.LayoutParams.TYPE_POINTER
+ && windowType != TYPE_MAGNIFICATION_OVERLAY
+ && windowType != WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY
+ && windowType != WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY
+ && windowType != WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION);
+ }
+
+ // Some windows should be excluded from unaccounted space computation, though they still
+ // should be reported
+ private static boolean windowMattersToUnaccountedSpaceComputation(
+ AccessibilityWindow a11yWindow) {
+ // Do not account space of trusted non-touchable windows, except the split-screen
+ // divider.
+ // If it's not trusted, touch events are not sent to the windows behind it.
+ if (!a11yWindow.isTouchable()
+ && (a11yWindow.getType() != TYPE_DOCK_DIVIDER)
+ && a11yWindow.isTrustedOverlay()) {
+ return false;
+ }
+
+ if (a11yWindow.getType() == WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY) {
+ return false;
+ }
+ return true;
+ }
+
private void updateWindowsByWindowAttributesLocked(List<WindowInfo> windows) {
for (int i = windows.size() - 1; i >= 0; i--) {
final WindowInfo windowInfo = windows.get(i);
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
index 351760bd8865..a5bbc7efcb23 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
@@ -599,7 +599,7 @@ public class FullScreenMagnificationController implements
callback.onFullScreenMagnificationActivationState(
mDisplayId, mMagnificationActivated);
});
- mControllerCtx.getWindowManager().setForceShowMagnifiableBounds(
+ mControllerCtx.getWindowManager().setFullscreenMagnificationActivated(
mDisplayId, activated);
}
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 18e11bab3c54..6f45f60f6dfa 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -2413,6 +2413,16 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
}
AppWidgetProviderInfo info = createPartialProviderInfo(providerId, ri, existing);
+
+ if (android.os.Flags.allowPrivateProfile()
+ && android.multiuser.Flags.disablePrivateSpaceItemsOnHome()) {
+ // Do not add widget providers for profiles with items restricted on home screen.
+ if (info != null && mUserManager
+ .getUserProperties(info.getProfile()).areItemsRestrictedOnHomeScreen()) {
+ return false;
+ }
+ }
+
if (info != null) {
if (existing != null) {
if (existing.zombie && !mSafeMode) {
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index d47245eb2272..c4341b9367ec 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -127,7 +127,6 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.IBinder.DeathRecipient;
-import android.os.OutcomeReceiver;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.Process;
@@ -2770,6 +2769,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
+ id + " destroyed");
return;
}
+ if (sDebug) {
+ Slog.d(TAG, "setAuthenticationResultLocked(): id= " + authenticationId
+ + ", data=" + data);
+ }
final int requestId = AutofillManager.getRequestIdFromAuthenticationId(authenticationId);
if (requestId == AUGMENTED_AUTOFILL_REQUEST_ID) {
setAuthenticationResultForAugmentedAutofillLocked(data, authenticationId);
@@ -2823,26 +2826,34 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
+ ", clientState=" + newClientState + ", authenticationId=" + authenticationId);
}
if (result instanceof FillResponse) {
+ if (sDebug) {
+ Slog.d(TAG, "setAuthenticationResultLocked(): received FillResponse from"
+ + " authentication flow");
+ }
logAuthenticationStatusLocked(requestId, MetricsEvent.AUTOFILL_AUTHENTICATED);
mPresentationStatsEventLogger.maybeSetAuthenticationResult(
AUTHENTICATION_RESULT_SUCCESS);
replaceResponseLocked(authenticatedResponse, (FillResponse) result, newClientState);
} else if (result instanceof GetCredentialResponse) {
- Slog.d(TAG, "Received GetCredentialResponse from authentication flow");
- boolean isCredmanCallbackInvoked = false;
- if (Flags.autofillCredmanIntegration()) {
- GetCredentialResponse response = (GetCredentialResponse) result;
- isCredmanCallbackInvoked = invokeCredentialManagerCallback(response);
+ if (sDebug) {
+ Slog.d(TAG, "Received GetCredentialResponse from authentication flow");
}
-
- if (!isCredmanCallbackInvoked) {
+ if (Flags.autofillCredmanDevIntegration()) {
+ GetCredentialResponse response = (GetCredentialResponse) result;
+ sendCredentialManagerResponseToApp(response,
+ /*exception=*/ null, response.getAutofillId());
+ } else if (Flags.autofillCredmanIntegration()) {
Dataset dataset = getDatasetFromCredentialResponse(
- (GetCredentialResponse) result);
+ (GetCredentialResponse) result);
if (dataset != null) {
autoFill(requestId, datasetIdx, dataset, false, UI_TYPE_UNKNOWN);
}
}
} else if (result instanceof Dataset) {
+ if (sDebug) {
+ Slog.d(TAG, "setAuthenticationResultLocked(): received Dataset from"
+ + " authentication flow");
+ }
if (datasetIdx != AutofillManager.AUTHENTICATION_ID_DATASET_ID_UNDEFINED) {
logAuthenticationStatusLocked(requestId,
MetricsEvent.AUTOFILL_DATASET_AUTHENTICATED);
@@ -2878,49 +2889,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
}
- private boolean invokeCredentialManagerCallback(GetCredentialResponse response) {
- synchronized (mLock) {
- return invokeCredentialManagerCallbackLocked(response);
- }
- }
-
- @GuardedBy("mLock")
- private boolean invokeCredentialManagerCallbackLocked(GetCredentialResponse response) {
- AutofillId autofillId = response.getAutofillId();
- if (autofillId != null) {
- OutcomeReceiver<GetCredentialResponse,
- GetCredentialException> callback =
- getCredmanCallbackFromContextsLocked(autofillId);
- if (callback != null) {
- Slog.w(TAG, "Propagating response to Credential Manager callback");
- callback.onResult(response);
- return true;
- } else {
- Slog.w(TAG, "Received Credential Manager response but no callback found");
- }
- } else {
- Slog.w(TAG, "Received Credential Manager response but no autofillId found");
- }
- return false;
- }
-
- @GuardedBy("mLock")
- @Nullable
- private OutcomeReceiver<GetCredentialResponse,
- GetCredentialException> getCredmanCallbackFromContextsLocked(
- @NonNull AutofillId autofillId) {
- final int numContexts = mContexts.size();
- for (int i = numContexts - 1; i >= 0; i--) {
- final FillContext context = mContexts.get(i);
- final ViewNode node = Helper.findViewNodeByAutofillId(context.getStructure(),
- autofillId);
- if (node != null) {
- return node.getCredentialManagerCallback();
- }
- }
- return null;
- }
-
private Dataset getDatasetFromCredentialResponse(GetCredentialResponse result) {
if (result == null) {
return null;
@@ -5095,10 +5063,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
CredentialProviderService.EXTRA_GET_CREDENTIAL_RESPONSE,
GetCredentialResponse.class);
- isCredmanCallbackInvoked =
- invokeCredentialManagerCallback(getCredentialResponse);
-
- if (!isCredmanCallbackInvoked) {
+ if (Flags.autofillCredmanDevIntegration()) {
+ sendCredentialManagerResponseToApp(getCredentialResponse,
+ /*exception=*/ null, getCredentialResponse.getAutofillId());
+ } else {
Dataset datasetFromCredential = getDatasetFromCredentialResponse(
getCredentialResponse);
if (datasetFromCredential != null) {
@@ -5114,6 +5082,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
Slog.w(TAG, "Credman bottom sheet from pinned "
+ "entry failed with: + " + exception[0] + " , "
+ exception[1]);
+ // TODO(b/326313420): Propagate exception
}
} else {
Slog.d(TAG, "Unknown resultCode from credential "
@@ -6393,6 +6362,37 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
}
+ void sendCredentialManagerResponseToApp(@Nullable GetCredentialResponse response,
+ @Nullable GetCredentialException exception, @NonNull AutofillId viewId) {
+ synchronized (mLock) {
+ if (mDestroyed) {
+ Slog.w(TAG, "Call to Session#sendCredentialManagerResponseToApp() rejected "
+ + "- session: " + id + " destroyed");
+ return;
+ }
+ try {
+ final ViewState viewState = mViewStates.get(viewId);
+ if (mService.getMaster().getIsFillFieldsFromCurrentSessionOnly()
+ && viewState != null && viewState.id.getSessionId() != id) {
+ if (sVerbose) {
+ Slog.v(TAG, "Skipping sending credential response to view: "
+ + viewId + " as it isn't part of the current session: " + id);
+ }
+ }
+ if (exception != null) {
+ // TODO(b/326313420): Add Exception support
+ } else if (response != null) {
+ mClient.onGetCredentialResponse(id, viewId, response);
+ } else {
+ Slog.w(TAG, "sendCredentialManagerResponseToApp called with null response"
+ + "and exception");
+ }
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Error sending credential response to activity: " + e);
+ }
+ }
+ }
+
void autoFillApp(Dataset dataset) {
synchronized (mLock) {
if (mDestroyed) {
diff --git a/services/autofill/java/com/android/server/autofill/ui/FillUi.java b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
index d580f3a7d7d8..78edb8ead7a0 100644
--- a/services/autofill/java/com/android/server/autofill/ui/FillUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
@@ -15,7 +15,7 @@
*/
package com.android.server.autofill.ui;
-import static android.service.autofill.FillRequest.FLAG_VIEW_REQUESTS_CREDMAN_SERVICE;
+import static android.service.autofill.FillResponse.FLAG_CREDENTIAL_MANAGER_RESPONSE;
import static com.android.server.autofill.Helper.paramsToString;
import static com.android.server.autofill.Helper.sDebug;
import static com.android.server.autofill.Helper.sFullScreenMode;
@@ -215,7 +215,7 @@ final class FillUi {
Slog.v(TAG, "overriding maximum visible datasets to " + mVisibleDatasetsMaxCount);
}
} else if (Flags.autofillCredmanIntegration() && (
- (response.getFlags() & FLAG_VIEW_REQUESTS_CREDMAN_SERVICE) != 0)) {
+ (response.getFlags() & FLAG_CREDENTIAL_MANAGER_RESPONSE) != 0)) {
mVisibleDatasetsMaxCount = AUTOFILL_CREDMAN_MAX_VISIBLE_DATASETS;
}
else {
diff --git a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
index 31766f2ec4eb..ff076192b6ad 100644
--- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
+++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
@@ -29,6 +29,7 @@ import android.app.compat.CompatChanges;
import android.companion.virtual.VirtualDeviceManager.ActivityListener;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
+import android.content.AttributionSource;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
@@ -44,6 +45,7 @@ import android.window.DisplayWindowPolicyController;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.BlockedAppStreamingActivity;
+import com.android.modules.expresslog.Counter;
import java.util.Set;
@@ -104,6 +106,8 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController
@EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
public static final long ALLOW_SECURE_ACTIVITY_DISPLAY_ON_REMOTE_DEVICE = 201712607L;
@NonNull
+ private final AttributionSource mAttributionSource;
+ @NonNull
private final ArraySet<UserHandle> mAllowedUsers;
@GuardedBy("mGenericWindowPolicyControllerLock")
private boolean mActivityLaunchAllowedByDefault;
@@ -144,6 +148,7 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController
*
* @param windowFlags The window flags that this controller is interested in.
* @param systemWindowFlags The system window flags that this controller is interested in.
+ * @param attributionSource The AttributionSource of the VirtualDevice owner application.
* @param allowedUsers The set of users that are allowed to stream in this display.
* @param activityLaunchAllowedByDefault Whether activities are default allowed to be launched
* or blocked.
@@ -169,6 +174,7 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController
public GenericWindowPolicyController(
int windowFlags,
int systemWindowFlags,
+ AttributionSource attributionSource,
@NonNull ArraySet<UserHandle> allowedUsers,
boolean activityLaunchAllowedByDefault,
@NonNull Set<ComponentName> activityPolicyExemptions,
@@ -184,6 +190,7 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController
boolean showTasksInHostDeviceRecents,
@Nullable ComponentName customHomeComponent) {
super();
+ mAttributionSource = attributionSource;
mAllowedUsers = allowedUsers;
mActivityLaunchAllowedByDefault = activityLaunchAllowedByDefault;
mActivityPolicyExemptions = activityPolicyExemptions;
@@ -436,6 +443,12 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController
if (!mIsMirrorDisplay && mActivityBlockedCallback != null) {
mActivityBlockedCallback.onActivityBlocked(mDisplayId, activityInfo);
}
+ if (android.companion.virtualdevice.flags.Flags.metricsCollection()) {
+ Counter.logIncrementWithUid(
+ "virtual_devices.value_activity_blocked_count",
+ mAttributionSource.getUid());
+ }
+
}
private static boolean isAllowedByPolicy(boolean allowedByDefault,
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 1b49f18e60cf..9b72288955a7 100644
--- a/services/companion/java/com/android/server/companion/virtual/InputController.java
+++ b/services/companion/java/com/android/server/companion/virtual/InputController.java
@@ -21,6 +21,7 @@ import static android.text.TextUtils.formatSimple;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.StringDef;
+import android.content.AttributionSource;
import android.graphics.PointF;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.input.InputDeviceIdentifier;
@@ -38,6 +39,7 @@ import android.os.IBinder;
import android.os.IInputConstants;
import android.os.RemoteException;
import android.util.ArrayMap;
+import android.util.Log;
import android.util.Slog;
import android.view.Display;
import android.view.InputDevice;
@@ -45,6 +47,7 @@ import android.view.WindowManager;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.expresslog.Counter;
import com.android.server.LocalServices;
import com.android.server.input.InputManagerInternal;
@@ -98,11 +101,12 @@ class InputController {
private final DisplayManagerInternal mDisplayManagerInternal;
private final InputManagerInternal mInputManagerInternal;
private final WindowManager mWindowManager;
+ private final AttributionSource mAttributionSource;
private final DeviceCreationThreadVerifier mThreadVerifier;
InputController(@NonNull Handler handler,
- @NonNull WindowManager windowManager) {
- this(new NativeWrapper(), handler, windowManager,
+ @NonNull WindowManager windowManager, AttributionSource attributionSource) {
+ this(new NativeWrapper(), handler, windowManager, attributionSource,
// Verify that virtual devices are not created on the handler thread.
() -> !handler.getLooper().isCurrentThread());
}
@@ -110,12 +114,14 @@ class InputController {
@VisibleForTesting
InputController(@NonNull NativeWrapper nativeWrapper,
@NonNull Handler handler, @NonNull WindowManager windowManager,
+ AttributionSource attributionSource,
@NonNull DeviceCreationThreadVerifier threadVerifier) {
mHandler = handler;
mNativeWrapper = nativeWrapper;
mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
mWindowManager = windowManager;
+ mAttributionSource = attributionSource;
mThreadVerifier = threadVerifier;
}
@@ -838,6 +844,33 @@ class InputController {
new InputDeviceDescriptor(ptr, binderDeathRecipient, type, displayId, phys,
deviceName, inputDeviceId));
}
+
+ if (android.companion.virtualdevice.flags.Flags.metricsCollection()) {
+ String metricId = getMetricIdForInputType(type);
+ if (metricId != null) {
+ Counter.logIncrementWithUid(metricId, mAttributionSource.getUid());
+ }
+ }
+ }
+
+ private static String getMetricIdForInputType(@InputDeviceDescriptor.Type int type) {
+ switch (type) {
+ case InputDeviceDescriptor.TYPE_KEYBOARD:
+ return "virtual_devices.value_virtual_keyboard_created_count";
+ case InputDeviceDescriptor.TYPE_MOUSE:
+ return "virtual_devices.value_virtual_mouse_created_count";
+ case InputDeviceDescriptor.TYPE_TOUCHSCREEN:
+ return "virtual_devices.value_virtual_touchscreen_created_count";
+ case InputDeviceDescriptor.TYPE_DPAD:
+ return "virtual_devices.value_virtual_dpad_created_count";
+ case InputDeviceDescriptor.TYPE_NAVIGATION_TOUCHPAD:
+ return "virtual_devices.value_virtual_navigationtouchpad_created_count";
+ case InputDeviceDescriptor.TYPE_STYLUS:
+ return "virtual_devices.value_virtual_stylus_created_count";
+ default:
+ Log.e(TAG, "No metric known for input type: " + type);
+ return null;
+ }
}
@VisibleForTesting
diff --git a/services/companion/java/com/android/server/companion/virtual/SensorController.java b/services/companion/java/com/android/server/companion/virtual/SensorController.java
index e9241dddcde9..cf4818060aa8 100644
--- a/services/companion/java/com/android/server/companion/virtual/SensorController.java
+++ b/services/companion/java/com/android/server/companion/virtual/SensorController.java
@@ -23,6 +23,7 @@ import android.companion.virtual.sensor.IVirtualSensorCallback;
import android.companion.virtual.sensor.VirtualSensor;
import android.companion.virtual.sensor.VirtualSensorConfig;
import android.companion.virtual.sensor.VirtualSensorEvent;
+import android.content.AttributionSource;
import android.hardware.SensorDirectChannel;
import android.os.Binder;
import android.os.IBinder;
@@ -35,6 +36,7 @@ import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.expresslog.Counter;
import com.android.server.LocalServices;
import com.android.server.sensors.SensorManagerInternal;
@@ -71,14 +73,18 @@ public class SensorController {
private List<VirtualSensor> mVirtualSensorList = null;
@NonNull
+ private final AttributionSource mAttributionSource;
+ @NonNull
private final SensorManagerInternal.RuntimeSensorCallback mRuntimeSensorCallback;
private final SensorManagerInternal mSensorManagerInternal;
private final VirtualDeviceManagerInternal mVdmInternal;
public SensorController(@NonNull IVirtualDevice virtualDevice, int virtualDeviceId,
+ @NonNull AttributionSource attributionSource,
@Nullable IVirtualSensorCallback virtualSensorCallback,
@NonNull List<VirtualSensorConfig> sensors) {
mVirtualDeviceId = virtualDeviceId;
+ mAttributionSource = attributionSource;
mRuntimeSensorCallback = new RuntimeSensorCallbackWrapper(virtualSensorCallback);
mSensorManagerInternal = LocalServices.getService(SensorManagerInternal.class);
mVdmInternal = LocalServices.getService(VirtualDeviceManagerInternal.class);
@@ -139,6 +145,11 @@ public class SensorController {
mSensorDescriptors.put(sensorToken, sensorDescriptor);
mVirtualSensors.put(handle, sensor);
}
+ if (android.companion.virtualdevice.flags.Flags.metricsCollection()) {
+ Counter.logIncrementWithUid(
+ "virtual_devices.value_virtual_sensors_created_count",
+ mAttributionSource.getUid());
+ }
}
boolean sendSensorEvent(@NonNull IBinder token, @NonNull VirtualSensorEvent event) {
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index f13f49a5d378..6d731b21ac8a 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -104,6 +104,7 @@ import android.widget.Toast;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.BlockedAppStreamingActivity;
+import com.android.modules.expresslog.Counter;
import com.android.server.LocalServices;
import com.android.server.companion.virtual.GenericWindowPolicyController.RunningAppsChangedListener;
import com.android.server.companion.virtual.audio.VirtualAudioController;
@@ -152,6 +153,8 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
private final int mOwnerUid;
private final VirtualDeviceLog mVirtualDeviceLog;
private final String mOwnerPackageName;
+ @NonNull
+ private final AttributionSource mAttributionSource;
private final int mDeviceId;
@Nullable
private final String mPersistentDeviceId;
@@ -288,6 +291,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
super(PermissionEnforcer.fromContext(context));
mVirtualDeviceLog = virtualDeviceLog;
mOwnerPackageName = attributionSource.getPackageName();
+ mAttributionSource = attributionSource;
UserHandle ownerUserHandle = UserHandle.getUserHandleForUid(attributionSource.getUid());
mContext = context.createContextAsUser(ownerUserHandle, 0);
mAssociationInfo = associationInfo;
@@ -307,11 +311,11 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
if (inputController == null) {
mInputController = new InputController(
context.getMainThreadHandler(),
- context.getSystemService(WindowManager.class));
+ context.getSystemService(WindowManager.class), mAttributionSource);
} else {
mInputController = inputController;
}
- mSensorController = new SensorController(this, mDeviceId,
+ mSensorController = new SensorController(this, mDeviceId, mAttributionSource,
mParams.getVirtualSensorCallback(), mParams.getVirtualSensorConfigs());
mCameraAccessController = cameraAccessController;
if (mCameraAccessController != null) {
@@ -620,7 +624,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
}
if (mVirtualAudioController == null) {
- mVirtualAudioController = new VirtualAudioController(mContext);
+ mVirtualAudioController = new VirtualAudioController(mContext, mAttributionSource);
GenericWindowPolicyController gwpc = mVirtualDisplays.get(
displayId).getWindowPolicyController();
mVirtualAudioController.startListening(gwpc, routingCallback,
@@ -1028,7 +1032,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
if (mVirtualCameraController == null) {
throw new UnsupportedOperationException("Virtual camera controller is not available");
}
- mVirtualCameraController.registerCamera(cameraConfig);
+ mVirtualCameraController.registerCamera(cameraConfig, mAttributionSource);
}
@Override // Binder call
@@ -1110,6 +1114,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
final GenericWindowPolicyController gwpc = new GenericWindowPolicyController(
FLAG_SECURE,
SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
+ mAttributionSource,
getAllowedUserHandles(),
activityLaunchAllowedByDefault,
mActivityPolicyExemptions,
@@ -1179,6 +1184,11 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
Binder.restoreCallingIdentity(token);
}
+ if (android.companion.virtualdevice.flags.Flags.metricsCollection()) {
+ Counter.logIncrementWithUid(
+ "virtual_devices.value_virtual_display_created_count",
+ mAttributionSource.getUid());
+ }
return displayId;
}
@@ -1220,6 +1230,12 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
if ((display.getFlags() & FLAG_SECURE) == 0) {
showToastWhereUidIsRunning(uid, com.android.internal.R.string.vdm_secure_window,
Toast.LENGTH_LONG, mContext.getMainLooper());
+
+ if (android.companion.virtualdevice.flags.Flags.metricsCollection()) {
+ Counter.logIncrementWithUid(
+ "virtual_devices.value_secure_window_blocked_count",
+ mAttributionSource.getUid());
+ }
}
}
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 2168cb2043ed..9ad73cae08cd 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -482,6 +482,11 @@ public class VirtualDeviceManagerService extends SystemService {
}
});
}
+ if (android.companion.virtualdevice.flags.Flags.metricsCollection()) {
+ Counter.logIncrementWithUid(
+ "virtual_devices.value_virtual_devices_created_with_uid_count",
+ attributionSource.getUid());
+ }
return virtualDevice;
}
diff --git a/services/companion/java/com/android/server/companion/virtual/audio/VirtualAudioController.java b/services/companion/java/com/android/server/companion/virtual/audio/VirtualAudioController.java
index c91877aad47e..4bffb76e1701 100644
--- a/services/companion/java/com/android/server/companion/virtual/audio/VirtualAudioController.java
+++ b/services/companion/java/com/android/server/companion/virtual/audio/VirtualAudioController.java
@@ -23,6 +23,7 @@ import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.companion.virtual.audio.IAudioConfigChangedCallback;
import android.companion.virtual.audio.IAudioRoutingCallback;
+import android.content.AttributionSource;
import android.content.Context;
import android.media.AudioManager;
import android.media.AudioPlaybackConfiguration;
@@ -35,6 +36,7 @@ import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.expresslog.Counter;
import com.android.server.companion.virtual.GenericWindowPolicyController;
import com.android.server.companion.virtual.GenericWindowPolicyController.RunningAppsChangedListener;
import com.android.server.companion.virtual.audio.AudioPlaybackDetector.AudioPlaybackCallback;
@@ -70,10 +72,16 @@ public final class VirtualAudioController implements AudioPlaybackCallback,
@GuardedBy("mCallbackLock")
private IAudioConfigChangedCallback mConfigChangedCallback;
- public VirtualAudioController(Context context) {
+ public VirtualAudioController(Context context, AttributionSource attributionSource) {
mContext = context;
mAudioPlaybackDetector = new AudioPlaybackDetector(context);
mAudioRecordingDetector = new AudioRecordingDetector(context);
+
+ if (android.companion.virtualdevice.flags.Flags.metricsCollection()) {
+ Counter.logIncrementWithUid(
+ "virtual_devices.value_virtual_audio_created_count",
+ attributionSource.getUid());
+ }
}
/**
diff --git a/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java b/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java
index 2d82b5e7dd66..3bb1e33f83bb 100644
--- a/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java
+++ b/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java
@@ -26,6 +26,7 @@ import android.companion.virtual.VirtualDeviceParams.DevicePolicy;
import android.companion.virtual.camera.VirtualCameraConfig;
import android.companion.virtualcamera.IVirtualCameraService;
import android.companion.virtualcamera.VirtualCameraConfiguration;
+import android.content.AttributionSource;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
@@ -35,6 +36,7 @@ import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.expresslog.Counter;
import java.io.PrintWriter;
import java.util.Map;
@@ -76,7 +78,8 @@ public final class VirtualCameraController implements IBinder.DeathRecipient {
*
* @param cameraConfig The {@link VirtualCameraConfig} sent by the client.
*/
- public void registerCamera(@NonNull VirtualCameraConfig cameraConfig) {
+ public void registerCamera(@NonNull VirtualCameraConfig cameraConfig,
+ AttributionSource attributionSource) {
checkConfigByPolicy(cameraConfig);
connectVirtualCameraServiceIfNeeded();
@@ -97,6 +100,11 @@ public final class VirtualCameraController implements IBinder.DeathRecipient {
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
+ if (android.companion.virtualdevice.flags.Flags.metricsCollection()) {
+ Counter.logIncrementWithUid(
+ "virtual_devices.value_virtual_camera_created_count",
+ attributionSource.getUid());
+ }
}
/**
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 469209976f5f..7940ca64b330 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -207,6 +207,7 @@ java_library_static {
"notification_flags_lib",
"biometrics_flags_lib",
"am_flags_lib",
+ "com_android_server_accessibility_flags_lib",
"com_android_systemui_shared_flags_lib",
"com_android_wm_shell_flags_lib",
"com.android.server.utils_aconfig-java",
diff --git a/services/core/java/com/android/server/IntentResolver.java b/services/core/java/com/android/server/IntentResolver.java
index 81c9ee790d72..4aa5ce19b9b0 100644
--- a/services/core/java/com/android/server/IntentResolver.java
+++ b/services/core/java/com/android/server/IntentResolver.java
@@ -16,6 +16,8 @@
package com.android.server;
+import static android.permission.flags.Flags.ignoreProcessText;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
@@ -353,6 +355,13 @@ public abstract class IntentResolver<F, R extends Object> {
public List<R> queryIntentFromList(@NonNull Computer computer, Intent intent,
String resolvedType, boolean defaultOnly, ArrayList<F[]> listCut, int userId,
long customFlags) {
+ if (Intent.ACTION_PROCESS_TEXT.equals(intent.getAction()) && ignoreProcessText()) {
+ // This is for an experiment about deprecating PROCESS_TEXT
+ // Note: SettingsProvider isn't ready early in boot and ACTION_PROCESS_TEXT isn't
+ // queried during boot so we are checking the action before the flag.
+ return Collections.emptyList();
+ }
+
ArrayList<R> resultList = new ArrayList<R>();
final boolean debug = localLOGV ||
@@ -377,6 +386,13 @@ public abstract class IntentResolver<F, R extends Object> {
protected final List<R> queryIntent(@NonNull PackageDataSnapshot snapshot, Intent intent,
String resolvedType, boolean defaultOnly, @UserIdInt int userId, long customFlags) {
+ if (Intent.ACTION_PROCESS_TEXT.equals(intent.getAction()) && ignoreProcessText()) {
+ // This is for an experiment about deprecating PROCESS_TEXT
+ // Note: SettingsProvider isn't ready early in boot and ACTION_PROCESS_TEXT isn't
+ // queried during boot so we are checking the action before the flag.
+ return Collections.emptyList();
+ }
+
String scheme = intent.getScheme();
ArrayList<R> finalList = new ArrayList<R>();
diff --git a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
index fff283dd41bc..0904c47b61f2 100644
--- a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
+++ b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
@@ -18,14 +18,17 @@ package com.android.server;
import static android.permission.flags.Flags.sensitiveNotificationAppProtection;
import static android.provider.Settings.Global.DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS;
-import static com.android.internal.util.Preconditions.checkNotNull;
+import static android.view.flags.Flags.sensitiveContentAppProtection;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
+import android.content.pm.PackageManagerInternal;
import android.media.projection.MediaProjectionInfo;
import android.media.projection.MediaProjectionManager;
+import android.os.Binder;
+import android.os.IBinder;
import android.os.RemoteException;
import android.os.Trace;
import android.os.UserHandle;
@@ -35,29 +38,30 @@ import android.service.notification.NotificationListenerService.RankingMap;
import android.service.notification.StatusBarNotification;
import android.util.ArraySet;
import android.util.Log;
+import android.view.ISensitiveContentProtectionManager;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.wm.SensitiveContentPackages.PackageInfo;
import com.android.server.wm.WindowManagerInternal;
+import java.util.Objects;
import java.util.Set;
/**
- * Service that monitors for notifications with sensitive content and protects content from screen
- * sharing
+ * This service protects sensitive content from screen sharing. The service monitors notifications
+ * for sensitive content and protects from screen share. The service also protects sensitive
+ * content rendered on screen during screen share.
*/
public final class SensitiveContentProtectionManagerService extends SystemService {
private static final String TAG = "SensitiveContentProtect";
private static final boolean DEBUG = false;
- private static final boolean sNotificationProtectionEnabled =
- sensitiveNotificationAppProtection();
@VisibleForTesting
@Nullable
NotificationListener mNotificationListener;
- private @Nullable MediaProjectionManager mProjectionManager;
- private @Nullable WindowManagerInternal mWindowManager;
+ @Nullable private MediaProjectionManager mProjectionManager;
+ @Nullable private WindowManagerInternal mWindowManager;
final Object mSensitiveContentProtectionLock = new Object();
@@ -92,7 +96,7 @@ public final class SensitiveContentProtectionManagerService extends SystemServic
public SensitiveContentProtectionManagerService(@NonNull Context context) {
super(context);
- if (sNotificationProtectionEnabled) {
+ if (sensitiveNotificationAppProtection()) {
mNotificationListener = new NotificationListener();
}
}
@@ -110,14 +114,18 @@ public final class SensitiveContentProtectionManagerService extends SystemServic
init(getContext().getSystemService(MediaProjectionManager.class),
LocalServices.getService(WindowManagerInternal.class));
+ if (sensitiveContentAppProtection()) {
+ publishBinderService(Context.SENSITIVE_CONTENT_PROTECTION_SERVICE,
+ new SensitiveContentProtectionManagerServiceBinder());
+ }
}
@VisibleForTesting
void init(MediaProjectionManager projectionManager, WindowManagerInternal windowManager) {
if (DEBUG) Log.d(TAG, "init");
- checkNotNull(projectionManager, "Failed to get valid MediaProjectionManager");
- checkNotNull(windowManager, "Failed to get valid WindowManagerInternal");
+ Objects.requireNonNull(projectionManager);
+ Objects.requireNonNull(windowManager);
mProjectionManager = projectionManager;
mWindowManager = windowManager;
@@ -126,7 +134,7 @@ public final class SensitiveContentProtectionManagerService extends SystemServic
// handler, delegate, and binder death recipient
mProjectionManager.addCallback(mProjectionCallback, getContext().getMainThreadHandler());
- if (sNotificationProtectionEnabled) {
+ if (sensitiveNotificationAppProtection()) {
try {
mNotificationListener.registerAsSystemService(
getContext(),
@@ -144,7 +152,7 @@ public final class SensitiveContentProtectionManagerService extends SystemServic
if (mProjectionManager != null) {
mProjectionManager.removeCallback(mProjectionCallback);
}
- if (sNotificationProtectionEnabled) {
+ if (sensitiveNotificationAppProtection()) {
try {
mNotificationListener.unregisterAsSystemService();
} catch (RemoteException e) {
@@ -169,7 +177,7 @@ public final class SensitiveContentProtectionManagerService extends SystemServic
synchronized (mSensitiveContentProtectionLock) {
mProjectionActive = true;
- if (sNotificationProtectionEnabled) {
+ if (sensitiveNotificationAppProtection()) {
updateAppsThatShouldBlockScreenCapture();
}
}
@@ -305,4 +313,74 @@ public final class SensitiveContentProtectionManagerService extends SystemServic
}
}
}
+
+ /**
+ * Block projection for a package window when the window is showing sensitive content on
+ * the screen, the projection is unblocked when window no more shows sensitive content.
+ *
+ * @param windowToken window where the content is shown.
+ * @param packageName package name.
+ * @param uid uid of the package.
+ * @param isShowingSensitiveContent whether the window is showing sensitive content.
+ */
+ @VisibleForTesting
+ void setSensitiveContentProtection(IBinder windowToken, String packageName, int uid,
+ boolean isShowingSensitiveContent) {
+ synchronized (mSensitiveContentProtectionLock) {
+ if (!mProjectionActive) {
+ return;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "setSensitiveContentProtection - windowToken=" + windowToken
+ + ", package=" + packageName + ", uid=" + uid
+ + ", isShowingSensitiveContent=" + isShowingSensitiveContent);
+ }
+
+ // The window token distinguish this package from packages added for notifications.
+ PackageInfo packageInfo = new PackageInfo(packageName, uid, windowToken);
+ ArraySet<PackageInfo> packageInfos = new ArraySet<>();
+ packageInfos.add(packageInfo);
+ if (isShowingSensitiveContent) {
+ mWindowManager.addBlockScreenCaptureForApps(packageInfos);
+ } else {
+ mWindowManager.removeBlockScreenCaptureForApps(packageInfos);
+ }
+ }
+ }
+
+ private final class SensitiveContentProtectionManagerServiceBinder
+ extends ISensitiveContentProtectionManager.Stub {
+ private final PackageManagerInternal mPackageManagerInternal;
+
+ SensitiveContentProtectionManagerServiceBinder() {
+ mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
+ }
+
+ public void setSensitiveContentProtection(IBinder windowToken, String packageName,
+ boolean isShowingSensitiveContent) {
+ Trace.beginSection(
+ "SensitiveContentProtectionManagerService.setSensitiveContentProtection");
+ try {
+ int callingUid = Binder.getCallingUid();
+ verifyCallingPackage(callingUid, packageName);
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ SensitiveContentProtectionManagerService.this.setSensitiveContentProtection(
+ windowToken, packageName, callingUid, isShowingSensitiveContent);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ } finally {
+ Trace.endSection();
+ }
+ }
+
+ private void verifyCallingPackage(int callingUid, String callingPackage) {
+ if (mPackageManagerInternal.getPackageUid(
+ callingPackage, 0, UserHandle.getUserId(callingUid)) != callingUid) {
+ throw new SecurityException("Specified calling package [" + callingPackage
+ + "] does not match the calling uid " + callingUid);
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/SerialService.java b/services/core/java/com/android/server/SerialService.java
index ff903a0b4c24..82c2038d8011 100644
--- a/services/core/java/com/android/server/SerialService.java
+++ b/services/core/java/com/android/server/SerialService.java
@@ -17,51 +17,123 @@
package com.android.server;
import android.annotation.EnforcePermission;
+import android.annotation.NonNull;
import android.content.Context;
import android.hardware.ISerialManager;
+import android.hardware.SerialManagerInternal;
import android.os.ParcelFileDescriptor;
+import android.os.PermissionEnforcer;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.Preconditions;
import java.io.File;
import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.function.Supplier;
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public class SerialService extends ISerialManager.Stub {
-
private final Context mContext;
- private final String[] mSerialPorts;
+
+ @GuardedBy("mSerialPorts")
+ private final LinkedHashMap<String, Supplier<ParcelFileDescriptor>> mSerialPorts =
+ new LinkedHashMap<>();
+
+ private static final String PREFIX_VIRTUAL = "virtual:";
public SerialService(Context context) {
+ super(PermissionEnforcer.fromContext(context));
mContext = context;
- mSerialPorts = context.getResources().getStringArray(
+
+ synchronized (mSerialPorts) {
+ final String[] serialPorts = getSerialPorts(context);
+ for (String serialPort : serialPorts) {
+ mSerialPorts.put(serialPort, () -> {
+ return native_open(serialPort);
+ });
+ }
+ }
+ }
+
+ @android.ravenwood.annotation.RavenwoodReplace
+ private static String[] getSerialPorts(Context context) {
+ return context.getResources().getStringArray(
com.android.internal.R.array.config_serialPorts);
}
+ private static String[] getSerialPorts$ravenwood(Context context) {
+ return new String[0];
+ }
+
+ public static class Lifecycle extends SystemService {
+ private SerialService mService;
+
+ public Lifecycle(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onStart() {
+ mService = new SerialService(getContext());
+ publishBinderService(Context.SERIAL_SERVICE, mService);
+ publishLocalService(SerialManagerInternal.class, mService.mInternal);
+ }
+ }
+
@EnforcePermission(android.Manifest.permission.SERIAL_PORT)
public String[] getSerialPorts() {
super.getSerialPorts_enforcePermission();
- ArrayList<String> ports = new ArrayList<String>();
- for (int i = 0; i < mSerialPorts.length; i++) {
- String path = mSerialPorts[i];
- if (new File(path).exists()) {
- ports.add(path);
+ synchronized (mSerialPorts) {
+ final ArrayList<String> ports = new ArrayList<>();
+ for (String path : mSerialPorts.keySet()) {
+ if (path.startsWith(PREFIX_VIRTUAL) || new File(path).exists()) {
+ ports.add(path);
+ }
}
+ return ports.toArray(new String[ports.size()]);
}
- String[] result = new String[ports.size()];
- ports.toArray(result);
- return result;
}
@EnforcePermission(android.Manifest.permission.SERIAL_PORT)
public ParcelFileDescriptor openSerialPort(String path) {
super.openSerialPort_enforcePermission();
- for (int i = 0; i < mSerialPorts.length; i++) {
- if (mSerialPorts[i].equals(path)) {
- return native_open(path);
+ synchronized (mSerialPorts) {
+ final Supplier<ParcelFileDescriptor> supplier = mSerialPorts.get(path);
+ if (supplier != null) {
+ return supplier.get();
+ } else {
+ throw new IllegalArgumentException("Invalid serial port " + path);
}
}
- throw new IllegalArgumentException("Invalid serial port " + path);
}
+ private final SerialManagerInternal mInternal = new SerialManagerInternal() {
+ @Override
+ public void addVirtualSerialPortForTest(@NonNull String name,
+ @NonNull Supplier<ParcelFileDescriptor> supplier) {
+ synchronized (mSerialPorts) {
+ Preconditions.checkState(!mSerialPorts.containsKey(name),
+ "Port " + name + " already defined");
+ Preconditions.checkArgument(name.startsWith(PREFIX_VIRTUAL),
+ "Port " + name + " must be under " + PREFIX_VIRTUAL);
+ mSerialPorts.put(name, supplier);
+ }
+ }
+
+ @Override
+ public void removeVirtualSerialPortForTest(@NonNull String name) {
+ synchronized (mSerialPorts) {
+ Preconditions.checkState(mSerialPorts.containsKey(name),
+ "Port " + name + " not yet defined");
+ Preconditions.checkArgument(name.startsWith(PREFIX_VIRTUAL),
+ "Port " + name + " must be under " + PREFIX_VIRTUAL);
+ mSerialPorts.remove(name);
+ }
+ }
+ };
+
private native ParcelFileDescriptor native_open(String path);
}
diff --git a/services/core/java/com/android/server/SystemConfig.java b/services/core/java/com/android/server/SystemConfig.java
index 2e14abbd4d40..9189ea763577 100644
--- a/services/core/java/com/android/server/SystemConfig.java
+++ b/services/core/java/com/android/server/SystemConfig.java
@@ -332,7 +332,6 @@ public class SystemConfig {
private ArrayMap<String, Set<String>> mPackageToUserTypeBlacklist = new ArrayMap<>();
private final ArraySet<String> mRollbackWhitelistedPackages = new ArraySet<>();
- private final ArraySet<String> mAutomaticRollbackDenylistedPackages = new ArraySet<>();
private final ArraySet<String> mWhitelistedStagedInstallers = new ArraySet<>();
// A map from package name of vendor APEXes that can be updated to an installer package name
// allowed to install updates for it.
@@ -499,10 +498,6 @@ public class SystemConfig {
return mRollbackWhitelistedPackages;
}
- public Set<String> getAutomaticRollbackDenylistedPackages() {
- return mAutomaticRollbackDenylistedPackages;
- }
-
public Set<String> getWhitelistedStagedInstallers() {
return mWhitelistedStagedInstallers;
}
@@ -1481,16 +1476,6 @@ public class SystemConfig {
}
XmlUtils.skipCurrentTag(parser);
} break;
- case "automatic-rollback-denylisted-app": {
- String pkgname = parser.getAttributeValue(null, "package");
- if (pkgname == null) {
- Slog.w(TAG, "<" + name + "> without package in " + permFile
- + " at " + parser.getPositionDescription());
- } else {
- mAutomaticRollbackDenylistedPackages.add(pkgname);
- }
- XmlUtils.skipCurrentTag(parser);
- } break;
case "whitelisted-staged-installer": {
if (allowAppConfigs) {
String pkgname = parser.getAttributeValue(null, "package");
diff --git a/services/core/java/com/android/server/SystemService.java b/services/core/java/com/android/server/SystemService.java
index 933d2596aed8..7dc9f10e646c 100644
--- a/services/core/java/com/android/server/SystemService.java
+++ b/services/core/java/com/android/server/SystemService.java
@@ -66,6 +66,7 @@ import java.util.List;
* {@hide}
*/
@SystemApi(client = Client.SYSTEM_SERVER)
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public abstract class SystemService {
/** @hide */
diff --git a/services/core/java/com/android/server/SystemServiceManager.java b/services/core/java/com/android/server/SystemServiceManager.java
index a05b84baf667..20816a1b22c8 100644
--- a/services/core/java/com/android/server/SystemServiceManager.java
+++ b/services/core/java/com/android/server/SystemServiceManager.java
@@ -67,6 +67,8 @@ import java.util.concurrent.TimeUnit;
*
* {@hide}
*/
+@android.ravenwood.annotation.RavenwoodKeepPartialClass
+@android.ravenwood.annotation.RavenwoodKeepStaticInitializer
public final class SystemServiceManager implements Dumpable {
private static final String TAG = SystemServiceManager.class.getSimpleName();
private static final boolean DEBUG = false;
@@ -123,7 +125,8 @@ public final class SystemServiceManager implements Dumpable {
@GuardedBy("mTargetUsers")
@Nullable private TargetUser mCurrentUser;
- SystemServiceManager(Context context) {
+ @android.ravenwood.annotation.RavenwoodKeep
+ public SystemServiceManager(Context context) {
mContext = context;
mServices = new ArrayList<>();
mServiceClassnames = new ArraySet<>();
@@ -136,6 +139,7 @@ public final class SystemServiceManager implements Dumpable {
*
* @return The service instance.
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public SystemService startService(String className) {
final Class<SystemService> serviceClass = loadClassFromLoader(className,
this.getClass().getClassLoader());
@@ -178,6 +182,7 @@ public final class SystemServiceManager implements Dumpable {
* Loads and initializes a class from the given classLoader. Returns the class.
*/
@SuppressWarnings("unchecked")
+ @android.ravenwood.annotation.RavenwoodKeep
private static Class<SystemService> loadClassFromLoader(String className,
ClassLoader classLoader) {
try {
@@ -201,6 +206,7 @@ public final class SystemServiceManager implements Dumpable {
* @return The service instance, never null.
* @throws RuntimeException if the service fails to start.
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public <T extends SystemService> T startService(Class<T> serviceClass) {
try {
final String name = serviceClass.getName();
@@ -237,6 +243,7 @@ public final class SystemServiceManager implements Dumpable {
}
}
+ @android.ravenwood.annotation.RavenwoodKeep
public void startService(@NonNull final SystemService service) {
// Check if already started
String className = service.getClass().getName();
@@ -261,7 +268,8 @@ public final class SystemServiceManager implements Dumpable {
}
/** Disallow starting new services after this call. */
- void sealStartedServices() {
+ @android.ravenwood.annotation.RavenwoodKeep
+ public void sealStartedServices() {
mServiceClassnames = Collections.emptySet();
mServices = Collections.unmodifiableList(mServices);
}
@@ -273,6 +281,7 @@ public final class SystemServiceManager implements Dumpable {
* @param t trace logger
* @param phase The boot phase to start.
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public void startBootPhase(@NonNull TimingsTraceAndSlog t, int phase) {
if (phase <= mCurrentPhase) {
throw new IllegalArgumentException("Next phase must be larger than previous");
@@ -305,13 +314,23 @@ public final class SystemServiceManager implements Dumpable {
if (phase == SystemService.PHASE_BOOT_COMPLETED) {
final long totalBootTime = SystemClock.uptimeMillis() - mRuntimeStartUptime;
t.logDuration("TotalBootTime", totalBootTime);
- SystemServerInitThreadPool.shutdown();
+ shutdownInitThreadPool();
}
}
+ @android.ravenwood.annotation.RavenwoodReplace
+ private void shutdownInitThreadPool() {
+ SystemServerInitThreadPool.shutdown();
+ }
+
+ private void shutdownInitThreadPool$ravenwood() {
+ // Thread pool not configured yet on Ravenwood; ignored
+ }
+
/**
* @return true if system has completed the boot; false otherwise.
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public boolean isBootCompleted() {
return mCurrentPhase >= SystemService.PHASE_BOOT_COMPLETED;
}
@@ -646,12 +665,14 @@ public final class SystemServiceManager implements Dumpable {
}
/** Logs the failure. That's all. Tests may rely on parsing it, so only modify carefully. */
+ @android.ravenwood.annotation.RavenwoodKeep
private void logFailure(String onWhat, TargetUser curUser, String serviceName, Exception ex) {
Slog.wtf(TAG, "SystemService failure: Failure reporting " + onWhat + " of user "
+ curUser + " to service " + serviceName, ex);
}
/** Sets the safe mode flag for services to query. */
+ @android.ravenwood.annotation.RavenwoodKeep
void setSafeMode(boolean safeMode) {
mSafeMode = safeMode;
}
@@ -661,6 +682,7 @@ public final class SystemServiceManager implements Dumpable {
*
* @return safe mode flag
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public boolean isSafeMode() {
return mSafeMode;
}
@@ -668,6 +690,7 @@ public final class SystemServiceManager implements Dumpable {
/**
* @return true if runtime was restarted, false if it's normal boot
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public boolean isRuntimeRestarted() {
return mRuntimeRestarted;
}
@@ -675,6 +698,7 @@ public final class SystemServiceManager implements Dumpable {
/**
* @return Time when SystemServer was started, in elapsed realtime.
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public long getRuntimeStartElapsedTime() {
return mRuntimeStartElapsedTime;
}
@@ -682,17 +706,20 @@ public final class SystemServiceManager implements Dumpable {
/**
* @return Time when SystemServer was started, in uptime.
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public long getRuntimeStartUptime() {
return mRuntimeStartUptime;
}
- void setStartInfo(boolean runtimeRestarted,
+ @android.ravenwood.annotation.RavenwoodKeep
+ public void setStartInfo(boolean runtimeRestarted,
long runtimeStartElapsedTime, long runtimeStartUptime) {
mRuntimeRestarted = runtimeRestarted;
mRuntimeStartElapsedTime = runtimeStartElapsedTime;
mRuntimeStartUptime = runtimeStartUptime;
}
+ @android.ravenwood.annotation.RavenwoodKeep
private void warnIfTooLong(long duration, SystemService service, String operation) {
if (duration > SERVICE_CALL_WARN_TIME_MS) {
Slog.w(TAG, "Service " + service.getClass().getName() + " took " + duration + " ms in "
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index 05010f88efaf..f1776f4f9148 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -17,7 +17,7 @@
package com.android.server;
import static android.app.Flags.modesApi;
-import static android.app.Flags.enableNightModeCache;
+import static android.app.Flags.enableNightModeBinderCache;
import static android.app.UiModeManager.ContrastUtils.CONTRAST_DEFAULT_VALUE;
import static android.app.UiModeManager.DEFAULT_PRIORITY;
import static android.app.UiModeManager.MODE_ATTENTION_THEME_OVERLAY_OFF;
@@ -148,7 +148,7 @@ final class UiModeManagerService extends SystemService {
@Override
public void set(int mode) {
mNightModeValue = mode;
- if (enableNightModeCache()) {
+ if (enableNightModeBinderCache()) {
UiModeManager.invalidateNightModeCache();
}
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index bbd3ad0bf5ce..2dd2f8fde797 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -5478,6 +5478,7 @@ public class ActivityManagerService extends IActivityManager.Stub
}
}
intents[i] = new Intent(intent);
+ intents[i].removeExtendedFlags(Intent.EXTENDED_FLAG_FILTER_MISMATCH);
}
}
if (resolvedTypes != null && resolvedTypes.length != intents.length) {
@@ -13666,9 +13667,13 @@ public class ActivityManagerService extends IActivityManager.Stub
throws TransactionTooLargeException {
enforceNotIsolatedCaller("startService");
enforceAllowedToStartOrBindServiceIfSdkSandbox(service);
- // Refuse possible leaked file descriptors
- if (service != null && service.hasFileDescriptors() == true) {
- throw new IllegalArgumentException("File descriptors passed in Intent");
+ if (service != null) {
+ // Refuse possible leaked file descriptors
+ if (service.hasFileDescriptors()) {
+ throw new IllegalArgumentException("File descriptors passed in Intent");
+ }
+ // Remove existing mismatch flag so it can be properly updated later
+ service.removeExtendedFlags(Intent.EXTENDED_FLAG_FILTER_MISMATCH);
}
if (callingPackage == null) {
@@ -13897,9 +13902,13 @@ public class ActivityManagerService extends IActivityManager.Stub
enforceNotIsolatedCaller("bindService");
enforceAllowedToStartOrBindServiceIfSdkSandbox(service);
- // Refuse possible leaked file descriptors
- if (service != null && service.hasFileDescriptors() == true) {
- throw new IllegalArgumentException("File descriptors passed in Intent");
+ if (service != null) {
+ // Refuse possible leaked file descriptors
+ if (service.hasFileDescriptors()) {
+ throw new IllegalArgumentException("File descriptors passed in Intent");
+ }
+ // Remove existing mismatch flag so it can be properly updated later
+ service.removeExtendedFlags(Intent.EXTENDED_FLAG_FILTER_MISMATCH);
}
if (callingPackage == null) {
@@ -15800,9 +15809,13 @@ public class ActivityManagerService extends IActivityManager.Stub
}
final Intent verifyBroadcastLocked(Intent intent) {
- // Refuse possible leaked file descriptors
- if (intent != null && intent.hasFileDescriptors() == true) {
- throw new IllegalArgumentException("File descriptors passed in Intent");
+ if (intent != null) {
+ // Refuse possible leaked file descriptors
+ if (intent.hasFileDescriptors()) {
+ throw new IllegalArgumentException("File descriptors passed in Intent");
+ }
+ // Remove existing mismatch flag so it can be properly updated later
+ intent.removeExtendedFlags(Intent.EXTENDED_FLAG_FILTER_MISMATCH);
}
int flags = intent.getFlags();
diff --git a/services/core/java/com/android/server/am/LmkdStatsReporter.java b/services/core/java/com/android/server/am/LmkdStatsReporter.java
index 507fd9efaffd..595d16d32e7b 100644
--- a/services/core/java/com/android/server/am/LmkdStatsReporter.java
+++ b/services/core/java/com/android/server/am/LmkdStatsReporter.java
@@ -34,7 +34,6 @@ public final class LmkdStatsReporter {
static final String TAG = TAG_WITH_CLASS_NAME ? "LmkdStatsReporter" : TAG_AM;
public static final int KILL_OCCURRED_MSG_SIZE = 80;
- public static final int STATE_CHANGED_MSG_SIZE = 8;
private static final int PRESSURE_AFTER_KILL = 0;
private static final int NOT_RESPONDING = 1;
@@ -79,16 +78,6 @@ public final class LmkdStatsReporter {
}
}
- /**
- * Processes the LMK_STATE_CHANGED packet
- * Logs the change in LMKD state which is used as start/stop boundaries for logging
- * LMK_KILL_OCCURRED event.
- * Code: LMK_STATE_CHANGED = 54
- */
- public static void logStateChanged(int state) {
- FrameworkStatsLog.write(FrameworkStatsLog.LMK_STATE_CHANGED, state);
- }
-
private static int mapKillReason(int reason) {
switch (reason) {
case PRESSURE_AFTER_KILL:
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index df46e5dafce8..9568116288b5 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -1628,6 +1628,7 @@ public class OomAdjuster {
int appUid;
int logUid;
int processStateCurTop;
+ String mAdjType;
ProcessStateRecord mState;
void initialize(ProcessRecord app, int adj, boolean foregroundActivities,
@@ -1642,6 +1643,7 @@ public class OomAdjuster {
this.appUid = appUid;
this.logUid = logUid;
this.processStateCurTop = processStateCurTop;
+ mAdjType = app.mState.getAdjType();
this.mState = app.mState;
}
@@ -1650,14 +1652,14 @@ public class OomAdjuster {
// App has a visible activity; only upgrade adjustment.
if (adj > VISIBLE_APP_ADJ) {
adj = VISIBLE_APP_ADJ;
- mState.setAdjType("vis-activity");
+ mAdjType = "vis-activity";
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to vis-activity: " + app);
}
}
if (procState > processStateCurTop) {
procState = processStateCurTop;
- mState.setAdjType("vis-activity");
+ mAdjType = "vis-activity";
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
reportOomAdjMessageLocked(TAG_OOM_ADJ,
"Raise procstate to vis-activity (top): " + app);
@@ -1666,8 +1668,6 @@ public class OomAdjuster {
if (schedGroup < SCHED_GROUP_DEFAULT) {
schedGroup = SCHED_GROUP_DEFAULT;
}
- mState.setCached(false);
- mState.setEmpty(false);
foregroundActivities = true;
mHasVisibleActivities = true;
}
@@ -1676,14 +1676,14 @@ public class OomAdjuster {
public void onPausedActivity() {
if (adj > PERCEPTIBLE_APP_ADJ) {
adj = PERCEPTIBLE_APP_ADJ;
- mState.setAdjType("pause-activity");
+ mAdjType = "pause-activity";
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to pause-activity: " + app);
}
}
if (procState > processStateCurTop) {
procState = processStateCurTop;
- mState.setAdjType("pause-activity");
+ mAdjType = "pause-activity";
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
reportOomAdjMessageLocked(TAG_OOM_ADJ,
"Raise procstate to pause-activity (top): " + app);
@@ -1692,8 +1692,6 @@ public class OomAdjuster {
if (schedGroup < SCHED_GROUP_DEFAULT) {
schedGroup = SCHED_GROUP_DEFAULT;
}
- mState.setCached(false);
- mState.setEmpty(false);
foregroundActivities = true;
mHasVisibleActivities = false;
}
@@ -1702,7 +1700,7 @@ public class OomAdjuster {
public void onStoppingActivity(boolean finishing) {
if (adj > PERCEPTIBLE_APP_ADJ) {
adj = PERCEPTIBLE_APP_ADJ;
- mState.setAdjType("stop-activity");
+ mAdjType = "stop-activity";
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
reportOomAdjMessageLocked(TAG_OOM_ADJ,
"Raise adj to stop-activity: " + app);
@@ -1718,15 +1716,13 @@ public class OomAdjuster {
if (!finishing) {
if (procState > PROCESS_STATE_LAST_ACTIVITY) {
procState = PROCESS_STATE_LAST_ACTIVITY;
- mState.setAdjType("stop-activity");
+ mAdjType = "stop-activity";
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
reportOomAdjMessageLocked(TAG_OOM_ADJ,
"Raise procstate to stop-activity: " + app);
}
}
}
- mState.setCached(false);
- mState.setEmpty(false);
foregroundActivities = true;
mHasVisibleActivities = false;
}
@@ -1735,7 +1731,7 @@ public class OomAdjuster {
public void onOtherActivity() {
if (procState > PROCESS_STATE_CACHED_ACTIVITY) {
procState = PROCESS_STATE_CACHED_ACTIVITY;
- mState.setAdjType("cch-act");
+ mAdjType = "cch-act";
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
reportOomAdjMessageLocked(TAG_OOM_ADJ,
"Raise procstate to cached activity: " + app);
@@ -1791,8 +1787,6 @@ public class OomAdjuster {
state.setAdjTypeCode(ActivityManager.RunningAppProcessInfo.REASON_UNKNOWN);
state.setAdjSource(null);
state.setAdjTarget(null);
- state.setEmpty(false);
- state.setCached(false);
if (!couldRecurse || !cycleReEval) {
// Don't reset this flag when doing cycles re-evaluation.
state.setNoKillOnBgRestrictedAndIdle(false);
@@ -1944,8 +1938,6 @@ public class OomAdjuster {
adj = cachedAdj;
procState = PROCESS_STATE_CACHED_EMPTY;
if (!couldRecurse || !state.containsCycle()) {
- state.setCached(true);
- state.setEmpty(true);
state.setAdjType("cch-empty");
}
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
@@ -1964,6 +1956,7 @@ public class OomAdjuster {
hasVisibleActivities = state.getCachedHasVisibleActivities();
procState = state.getCachedProcState();
schedGroup = state.getCachedSchedGroup();
+ state.setAdjType(state.getCachedAdjType());
}
if (procState > PROCESS_STATE_CACHED_RECENT && state.getCachedHasRecentTasks()) {
@@ -2021,7 +2014,6 @@ public class OomAdjuster {
adj = newAdj;
procState = newProcState;
state.setAdjType(adjType);
- state.setCached(false);
schedGroup = SCHED_GROUP_DEFAULT;
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
@@ -2079,7 +2071,6 @@ public class OomAdjuster {
// thus out of background check), so we yes the best background level we can.
adj = PERCEPTIBLE_APP_ADJ;
procState = PROCESS_STATE_TRANSIENT_BACKGROUND;
- state.setCached(false);
state.setAdjType("force-imp");
state.setAdjSource(state.getForcingToImportant());
schedGroup = SCHED_GROUP_DEFAULT;
@@ -2094,7 +2085,6 @@ public class OomAdjuster {
// We don't want to kill the current heavy-weight process.
adj = HEAVY_WEIGHT_APP_ADJ;
schedGroup = SCHED_GROUP_BACKGROUND;
- state.setCached(false);
state.setAdjType("heavy");
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to heavy: " + app);
@@ -2115,7 +2105,6 @@ public class OomAdjuster {
// home app, so we don't want to let it go into the background.
adj = HOME_APP_ADJ;
schedGroup = SCHED_GROUP_BACKGROUND;
- state.setCached(false);
state.setAdjType("home");
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to home: " + app);
@@ -2149,7 +2138,6 @@ public class OomAdjuster {
if (adj > PREVIOUS_APP_ADJ) {
adj = PREVIOUS_APP_ADJ;
schedGroup = SCHED_GROUP_BACKGROUND;
- state.setCached(false);
state.setAdjType("previous");
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to prev: " + app);
@@ -2197,7 +2185,6 @@ public class OomAdjuster {
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to backup: " + app);
}
- state.setCached(false);
}
if (procState > ActivityManager.PROCESS_STATE_BACKUP) {
procState = ActivityManager.PROCESS_STATE_BACKUP;
@@ -2251,7 +2238,6 @@ public class OomAdjuster {
reportOomAdjMessageLocked(TAG_OOM_ADJ,
"Raise adj to started service: " + app);
}
- state.setCached(false);
}
}
// If we have let the service slide into the background
@@ -2376,7 +2362,6 @@ public class OomAdjuster {
adj = FOREGROUND_APP_ADJ;
state.setCurRawAdj(adj);
schedGroup = SCHED_GROUP_DEFAULT;
- state.setCached(false);
state.setAdjType("ext-provider");
state.setAdjTarget(cpr.name);
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
@@ -2399,7 +2384,6 @@ public class OomAdjuster {
if (adj > PREVIOUS_APP_ADJ) {
adj = PREVIOUS_APP_ADJ;
schedGroup = SCHED_GROUP_BACKGROUND;
- state.setCached(false);
state.setAdjType("recent-provider");
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
reportOomAdjMessageLocked(TAG_OOM_ADJ,
@@ -2707,10 +2691,12 @@ public class OomAdjuster {
if (adj > clientAdj) {
adjType = "cch-bound-ui-services";
}
- if (state.setCached(false, dryRun)) {
+
+ if (state.isCached() && dryRun) {
// Bail out early, as we only care about the return value for a dryrun.
return true;
}
+
clientAdj = adj;
clientProcState = procState;
} else {
@@ -2795,12 +2781,14 @@ public class OomAdjuster {
newAdj = adj;
}
}
+
if (!cstate.isCached()) {
- if (state.setCached(false, dryRun)) {
+ if (state.isCached() && dryRun) {
// Bail out early, as we only care about the return value for a dryrun.
return true;
}
}
+
if (adj > newAdj) {
adj = newAdj;
if (state.setCurRawAdj(adj, dryRun)) {
@@ -2958,8 +2946,8 @@ public class OomAdjuster {
schedGroup = SCHED_GROUP_DEFAULT;
}
}
+
if (!dryRun) {
- state.setCached(false);
state.setAdjType("service");
state.setAdjTypeCode(ActivityManager.RunningAppProcessInfo
.REASON_SERVICE_IN_USE);
@@ -3000,7 +2988,6 @@ public class OomAdjuster {
}
state.setCurCapability(capability);
- state.setEmpty(false);
return updated;
}
@@ -3090,7 +3077,8 @@ public class OomAdjuster {
}
adjType = "provider";
}
- if (state.setCached(state.isCached() & cstate.isCached(), dryRun)) {
+
+ if (state.isCached() && !cstate.isCached() && dryRun) {
// Bail out early, as we only care about the return value for a dryrun.
return true;
}
@@ -3157,7 +3145,6 @@ public class OomAdjuster {
}
state.setCurCapability(capability);
- state.setEmpty(false);
return false;
}
@@ -3614,7 +3601,6 @@ public class OomAdjuster {
state.setCurProcState(initialProcState);
state.setCurRawProcState(initialProcState);
state.setCurCapability(initialCapability);
- state.setCached(initialCached);
state.setCurAdj(ProcessList.FOREGROUND_APP_ADJ);
state.setCurRawAdj(ProcessList.FOREGROUND_APP_ADJ);
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 3adea7a929cd..89c89944e92e 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -348,7 +348,7 @@ public final class ProcessList {
// LMK_PROCKILL
// LMK_UPDATE_PROPS
// LMK_KILL_OCCURRED
- // LMK_STATE_CHANGED
+ // LMK_START_MONITORING
static final byte LMK_TARGET = 0;
static final byte LMK_PROCPRIO = 1;
static final byte LMK_PROCREMOVE = 2;
@@ -358,7 +358,6 @@ public final class ProcessList {
static final byte LMK_PROCKILL = 6; // Note: this is an unsolicited command
static final byte LMK_UPDATE_PROPS = 7;
static final byte LMK_KILL_OCCURRED = 8; // Msg to subscribed clients on kill occurred event
- static final byte LMK_STATE_CHANGED = 9; // Msg to subscribed clients on state changed
static final byte LMK_START_MONITORING = 9; // Start monitoring if delayed earlier
// Low Memory Killer Daemon command codes.
@@ -965,14 +964,6 @@ public final class ProcessList {
foregroundServices.first,
foregroundServices.second);
return true;
- case LMK_STATE_CHANGED:
- if (receivedLen
- != LmkdStatsReporter.STATE_CHANGED_MSG_SIZE) {
- return false;
- }
- final int state = inputData.readInt();
- LmkdStatsReporter.logStateChanged(state);
- return true;
default:
return false;
}
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 9883f091deef..7009bd0b4695 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -1122,11 +1122,6 @@ class ProcessRecord implements WindowProcessListener {
mInFullBackup = inFullBackup;
}
- @GuardedBy("mService")
- public void setCached(boolean cached) {
- mState.setCached(cached);
- }
-
@Override
@GuardedBy("mService")
public boolean isCached() {
diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java
index 8362eaf76d55..8de748e37b5e 100644
--- a/services/core/java/com/android/server/am/ProcessStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessStateRecord.java
@@ -17,6 +17,7 @@
package com.android.server.am;
import static android.app.ActivityManager.PROCESS_CAPABILITY_NONE;
+import static android.app.ActivityManager.PROCESS_STATE_CACHED_EMPTY;
import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UI_VISIBILITY;
import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_ACTIVITY;
@@ -24,6 +25,7 @@ import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_BROADCAST_RE
import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_STARTED_SERVICE;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ;
+import static com.android.server.am.ProcessList.CACHED_APP_MIN_ADJ;
import static com.android.server.am.ProcessRecord.TAG;
import android.annotation.ElapsedRealtimeLong;
@@ -283,18 +285,6 @@ final class ProcessStateRecord {
private long mLastTopTime = Long.MIN_VALUE;
/**
- * Is this an empty background process?
- */
- @GuardedBy("mService")
- private boolean mEmpty;
-
- /**
- * Is this a cached process?
- */
- @GuardedBy("mService")
- private boolean mCached;
-
- /**
* This is a system process, but not currently showing UI.
*/
@GuardedBy("mService")
@@ -395,7 +385,7 @@ final class ProcessStateRecord {
private boolean mNoKillOnBgRestrictedAndIdle;
/**
- * Last set value of {@link #mCached}.
+ * Last set value of {@link #isCached()}.
*/
@GuardedBy("mService")
private boolean mSetCached;
@@ -408,7 +398,7 @@ final class ProcessStateRecord {
/**
* The last time when the {@link #mNoKillOnBgRestrictedAndIdle} is false and the
- * {@link #mCached} is true, and either the former state is flipping from true to false
+ * {@link #isCached()} is true, and either the former state is flipping from true to false
* when latter state is true, or the latter state is flipping from false to true when the
* former state is false.
*/
@@ -446,6 +436,8 @@ final class ProcessStateRecord {
};
@GuardedBy("mService")
+ private String mCachedAdjType = null;
+ @GuardedBy("mService")
private int mCachedAdj = ProcessList.INVALID_ADJ;
@GuardedBy("mService")
private boolean mCachedForegroundActivities = false;
@@ -535,7 +527,7 @@ final class ProcessStateRecord {
@GuardedBy(anyOf = {"mService", "mProcLock"})
int getSetAdjWithServices() {
- if (mSetAdj >= ProcessList.CACHED_APP_MIN_ADJ) {
+ if (mSetAdj >= CACHED_APP_MIN_ADJ) {
if (mHasStartedServices) {
return ProcessList.SERVICE_B_ADJ;
}
@@ -915,36 +907,13 @@ final class ProcessStateRecord {
}
@GuardedBy("mService")
- void setEmpty(boolean empty) {
- mEmpty = empty;
- }
-
- @GuardedBy("mService")
boolean isEmpty() {
- return mEmpty;
- }
-
- @GuardedBy("mService")
- void setCached(boolean cached) {
- setCached(cached, false);
- }
-
- /**
- * @return {@code true} if it's a dry run and it's going to uncache the process
- * if it was a real run.
- */
- @GuardedBy("mService")
- boolean setCached(boolean cached, boolean dryRun) {
- if (dryRun) {
- return mCached && !cached;
- }
- mCached = cached;
- return false;
+ return mCurProcState >= PROCESS_STATE_CACHED_EMPTY;
}
@GuardedBy("mService")
boolean isCached() {
- return mCached;
+ return mCurAdj >= CACHED_APP_MIN_ADJ;
}
@GuardedBy("mService")
@@ -1041,6 +1010,7 @@ final class ProcessStateRecord {
mCachedForegroundActivities = false;
mCachedProcState = ActivityManager.PROCESS_STATE_CACHED_EMPTY;
mCachedSchedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
+ mCachedAdjType = null;
}
@GuardedBy("mService")
@@ -1152,6 +1122,7 @@ final class ProcessStateRecord {
mCachedHasVisibleActivities = callback.mHasVisibleActivities ? VALUE_TRUE : VALUE_FALSE;
mCachedProcState = callback.procState;
mCachedSchedGroup = callback.schedGroup;
+ mCachedAdjType = callback.mAdjType;
if (mCachedAdj == ProcessList.VISIBLE_APP_ADJ) {
mCachedAdj += minLayer;
@@ -1179,6 +1150,11 @@ final class ProcessStateRecord {
}
@GuardedBy("mService")
+ String getCachedAdjType() {
+ return mCachedAdjType;
+ }
+
+ @GuardedBy("mService")
boolean shouldScheduleLikeTopApp() {
return mScheduleLikeTopApp;
}
@@ -1381,8 +1357,8 @@ final class ProcessStateRecord {
pw.print(prefix); pw.print("hasShownUi="); pw.print(mHasShownUi);
pw.print(" pendingUiClean="); pw.println(mApp.mProfile.hasPendingUiClean());
}
- pw.print(prefix); pw.print("cached="); pw.print(mCached);
- pw.print(" empty="); pw.println(mEmpty);
+ pw.print(prefix); pw.print("cached="); pw.print(isCached());
+ pw.print(" empty="); pw.println(isEmpty());
if (mServiceB) {
pw.print(prefix); pw.print("serviceb="); pw.print(mServiceB);
pw.print(" serviceHighRam="); pw.println(mServiceHighRam);
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index df5a82485bfc..767f54d6a8c7 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -161,6 +161,7 @@ public class SettingsToPropertiesMapper {
"media_solutions",
"nfc",
"pdf_viewer",
+ "perfetto",
"pixel_audio_android",
"pixel_biometrics_face",
"pixel_bluetooth",
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 102a960d35f6..e0c24256f5b1 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -167,8 +167,7 @@ public class AudioDeviceInventory {
ads = findBtDeviceStateForAddress(peerAddress, deviceType);
}
if (ads != null) {
- if (ads.getAudioDeviceCategory() != category
- && category != AUDIO_DEVICE_CATEGORY_UNKNOWN) {
+ if (ads.getAudioDeviceCategory() != category) {
ads.setAudioDeviceCategory(category);
mDeviceBroker.postUpdatedAdiDeviceState(ads);
mDeviceBroker.postPersistAudioDeviceSettings();
@@ -892,7 +891,7 @@ public class AudioDeviceInventory {
if (mDeviceBroker.hasScheduledA2dpConnection(btDevice)) {
AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
"A2dp config change ignored (scheduled connection change)")
- .printLog(TAG));
+ .printSlog(EventLogger.Event.ALOGI, TAG));
mmi.set(MediaMetrics.Property.EARLY_RETURN, "A2dp config change ignored")
.record();
return;
@@ -929,7 +928,7 @@ public class AudioDeviceInventory {
"APM handleDeviceConfigChange failed for A2DP device addr="
+ address + " codec="
+ AudioSystem.audioFormatToString(codec))
- .printLog(TAG));
+ .printSlog(EventLogger.Event.ALOGE, TAG));
// force A2DP device disconnection in case of error so that AudioService
// state is consistent with audio policy manager state
@@ -940,7 +939,7 @@ public class AudioDeviceInventory {
"APM handleDeviceConfigChange success for A2DP device addr="
+ address
+ " codec=" + AudioSystem.audioFormatToString(codec))
- .printLog(TAG));
+ .printSlog(EventLogger.Event.ALOGI, TAG));
}
}
}
@@ -1707,10 +1706,13 @@ public class AudioDeviceInventory {
if (res != AudioSystem.AUDIO_STATUS_OK) {
final String reason = "not connecting device 0x" + Integer.toHexString(device)
+ " due to command error " + res;
- Slog.e(TAG, reason);
mmi.set(MediaMetrics.Property.EARLY_RETURN, reason)
.set(MediaMetrics.Property.STATE, MediaMetrics.Value.DISCONNECTED)
.record();
+ AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
+ "APM failed to make available device 0x" + Integer.toHexString(device)
+ + "addr=" + address + " error=" + res)
+ .printSlog(EventLogger.Event.ALOGE, TAG));
return false;
}
mConnectedDevices.put(deviceKey, new DeviceInfo(device, deviceName, address));
@@ -1736,7 +1738,8 @@ public class AudioDeviceInventory {
AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
"SCO " + (AudioSystem.isInputDevice(device) ? "source" : "sink")
+ " device addr=" + address
- + (connect ? " now available" : " made unavailable")).printLog(TAG));
+ + (connect ? " now available" : " made unavailable"))
+ .printSlog(EventLogger.Event.ALOGI, TAG));
}
mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.CONNECTED).record();
} else {
@@ -1992,14 +1995,15 @@ public class AudioDeviceInventory {
// double connection is made.
if (res != AudioSystem.AUDIO_STATUS_OK) {
AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
- "APM failed to make available A2DP device addr=" + address
- + " error=" + res).printLog(TAG));
+ "APM failed to make available A2DP device addr="
+ + Utils.anonymizeBluetoothAddress(address)
+ + " error=" + res).printSlog(EventLogger.Event.ALOGE, TAG));
// TODO: connection failed, stop here
// TODO: return;
} else {
AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
"A2DP sink device addr=" + Utils.anonymizeBluetoothAddress(address)
- + " now available").printLog(TAG));
+ + " now available").printSlog(EventLogger.Event.ALOGI, TAG));
}
// Reset A2DP suspend state each time a new sink is connected
@@ -2240,7 +2244,8 @@ public class AudioDeviceInventory {
// removing A2DP device not currently used by AudioPolicy, log but don't act on it
AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
"A2DP device " + Utils.anonymizeBluetoothAddress(address)
- + " made unavailable, was not used")).printLog(TAG));
+ + " made unavailable, was not used"))
+ .printSlog(EventLogger.Event.ALOGI, TAG));
mmi.set(MediaMetrics.Property.EARLY_RETURN,
"A2DP device made unavailable, was not used")
.record();
@@ -2258,13 +2263,13 @@ public class AudioDeviceInventory {
AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
"APM failed to make unavailable A2DP device addr="
+ Utils.anonymizeBluetoothAddress(address)
- + " error=" + res).printLog(TAG));
+ + " error=" + res).printSlog(EventLogger.Event.ALOGE, TAG));
// TODO: failed to disconnect, stop here
// TODO: return;
} else {
AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
"A2DP device addr=" + Utils.anonymizeBluetoothAddress(address)
- + " made unavailable")).printLog(TAG));
+ + " made unavailable")).printSlog(EventLogger.Event.ALOGI, TAG));
}
mApmConnectedDevices.remove(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP);
@@ -2297,10 +2302,22 @@ public class AudioDeviceInventory {
@GuardedBy("mDevicesLock")
private void makeA2dpSrcAvailable(String address) {
- mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(
+ final int res = mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(
AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address),
AudioSystem.DEVICE_STATE_AVAILABLE,
AudioSystem.AUDIO_FORMAT_DEFAULT);
+ if (res != AudioSystem.AUDIO_STATUS_OK) {
+ AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
+ "APM failed to make available A2DP source device addr="
+ + Utils.anonymizeBluetoothAddress(address)
+ + " error=" + res).printSlog(EventLogger.Event.ALOGE, TAG));
+ // TODO: connection failed, stop here
+ // TODO: return
+ } else {
+ AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
+ "A2DP source device addr=" + Utils.anonymizeBluetoothAddress(address)
+ + " now available").printSlog(EventLogger.Event.ALOGI, TAG));
+ }
mConnectedDevices.put(
DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address),
new DeviceInfo(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, "", address));
@@ -2453,14 +2470,14 @@ public class AudioDeviceInventory {
if (res != AudioSystem.AUDIO_STATUS_OK) {
AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
"APM failed to make available LE Audio device addr=" + address
- + " error=" + res).printLog(TAG));
+ + " error=" + res).printSlog(EventLogger.Event.ALOGE, TAG));
// TODO: connection failed, stop here
// TODO: return;
} else {
AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
"LE Audio " + (AudioSystem.isInputDevice(device) ? "source" : "sink")
+ " device addr=" + Utils.anonymizeBluetoothAddress(address)
- + " now available").printLog(TAG));
+ + " now available").printSlog(EventLogger.Event.ALOGI, TAG));
}
// Reset LEA suspend state each time a new sink is connected
mDeviceBroker.clearLeAudioSuspended(true /* internalOnly */);
@@ -2502,13 +2519,13 @@ public class AudioDeviceInventory {
if (res != AudioSystem.AUDIO_STATUS_OK) {
AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
"APM failed to make unavailable LE Audio device addr=" + address
- + " error=" + res).printLog(TAG));
+ + " error=" + res).printSlog(EventLogger.Event.ALOGE, TAG));
// TODO: failed to disconnect, stop here
// TODO: return;
} else {
AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
"LE Audio device addr=" + Utils.anonymizeBluetoothAddress(address)
- + " made unavailable").printLog(TAG));
+ + " made unavailable").printSlog(EventLogger.Event.ALOGI, TAG));
}
mConnectedDevices.remove(DeviceInfo.makeDeviceListKey(device, address));
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index ef934004ab2f..cb6d26f61314 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -5673,7 +5673,7 @@ public class AudioService extends IAudioService.Stub
final boolean isMuted = isStreamMutedByRingerOrZenMode(streamType);
final boolean muteAllowedBySco =
!(shouldRingSco && streamType == AudioSystem.STREAM_RING);
- final boolean shouldZenMute = shouldZenMuteStream(streamType);
+ final boolean shouldZenMute = isStreamAffectedByCurrentZen(streamType);
final boolean shouldMute = shouldZenMute || (ringerModeMute
&& isStreamAffectedByRingerMode(streamType) && muteAllowedBySco);
if (isMuted == shouldMute) continue;
@@ -6937,24 +6937,8 @@ public class AudioService extends IAudioService.Stub
return (mRingerModeAffectedStreams & (1 << streamType)) != 0;
}
- private boolean shouldZenMuteStream(int streamType) {
- if (mNm.getZenMode() != Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) {
- return false;
- }
-
- NotificationManager.Policy zenPolicy = mNm.getConsolidatedNotificationPolicy();
- final boolean muteAlarms = (zenPolicy.priorityCategories
- & NotificationManager.Policy.PRIORITY_CATEGORY_ALARMS) == 0;
- final boolean muteMedia = (zenPolicy.priorityCategories
- & NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA) == 0;
- final boolean muteSystem = (zenPolicy.priorityCategories
- & NotificationManager.Policy.PRIORITY_CATEGORY_SYSTEM) == 0;
- final boolean muteNotificationAndRing = ZenModeConfig
- .areAllPriorityOnlyRingerSoundsMuted(zenPolicy);
- return muteAlarms && isAlarm(streamType)
- || muteMedia && isMedia(streamType)
- || muteSystem && isSystem(streamType)
- || muteNotificationAndRing && isNotificationOrRinger(streamType);
+ public boolean isStreamAffectedByCurrentZen(int streamType) {
+ return (mZenModeAffectedStreams & (1 << streamType)) != 0;
}
private boolean isStreamMutedByRingerOrZenMode(int streamType) {
@@ -6962,11 +6946,9 @@ public class AudioService extends IAudioService.Stub
}
/**
- * Notifications, ringer and system sounds are controlled by the ringer:
- * {@link ZenModeHelper.RingerModeDelegate#getRingerModeAffectedStreams(int)} but can
- * also be muted by DND based on the DND mode:
- * DND total silence: media and alarms streams can be muted by DND
- * DND alarms only: no streams additionally controlled by DND
+ * Volume streams can be muted based on the current DND state:
+ * DND total silence: ringer, notification, system, media and alarms streams muted by DND
+ * DND alarms only: ringer, notification, system streams muted by DND
* DND priority only: alarms, media, system, ringer and notification streams can be muted by
* DND. The current applied zenPolicy determines which streams will be muted by DND.
* @return true if changed, else false
@@ -6976,12 +6958,20 @@ public class AudioService extends IAudioService.Stub
return false;
}
+ // If DND is off, no streams are muted by DND
int zenModeAffectedStreams = 0;
final int zenMode = mNm.getZenMode();
if (zenMode == Settings.Global.ZEN_MODE_NO_INTERRUPTIONS) {
+ zenModeAffectedStreams |= 1 << AudioManager.STREAM_SYSTEM;
+ zenModeAffectedStreams |= 1 << AudioManager.STREAM_NOTIFICATION;
+ zenModeAffectedStreams |= 1 << AudioManager.STREAM_RING;
zenModeAffectedStreams |= 1 << AudioManager.STREAM_ALARM;
zenModeAffectedStreams |= 1 << AudioManager.STREAM_MUSIC;
+ } else if (zenMode == Settings.Global.ZEN_MODE_ALARMS) {
+ zenModeAffectedStreams |= 1 << AudioManager.STREAM_SYSTEM;
+ zenModeAffectedStreams |= 1 << AudioManager.STREAM_NOTIFICATION;
+ zenModeAffectedStreams |= 1 << AudioManager.STREAM_RING;
} else if (zenMode == Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) {
NotificationManager.Policy zenPolicy = mNm.getConsolidatedNotificationPolicy();
if ((zenPolicy.priorityCategories
@@ -7023,7 +7013,6 @@ public class AudioService extends IAudioService.Stub
((1 << AudioSystem.STREAM_RING)|(1 << AudioSystem.STREAM_NOTIFICATION)|
(1 << AudioSystem.STREAM_SYSTEM)|(1 << AudioSystem.STREAM_SYSTEM_ENFORCED)),
UserHandle.USER_CURRENT);
-
if (mIsSingleVolume) {
ringerModeAffectedStreams = 0;
} else if (mRingerModeDelegate != null) {
diff --git a/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java b/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java
index 5d609bca334c..526264d67318 100644
--- a/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java
+++ b/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java
@@ -153,7 +153,6 @@ public class AuthenticationStatsCollector {
if (authenticationStats.getTotalAttempts() < MINIMUM_ATTEMPTS) {
return;
}
-
// Don't send notification if FRR below the threshold.
if (authenticationStats.getEnrollmentNotifications() >= MAXIMUM_ENROLLMENT_NOTIFICATIONS
|| authenticationStats.getFrr() < mThreshold) {
@@ -161,6 +160,7 @@ public class AuthenticationStatsCollector {
return;
}
+
authenticationStats.resetData();
final boolean hasEnrolledFace = hasEnrolledFace(userId);
@@ -186,6 +186,28 @@ public class AuthenticationStatsCollector {
}
}
+ /**
+ * This is meant for debug purposes only, this will bypass many checks.
+ * The origination of this call should be from an adb shell command sent from
+ * FaceService.
+ *
+ * adb shell cmd face notification
+ */
+ public void sendFaceReEnrollNotification() {
+ mBiometricNotification.sendFaceEnrollNotification(mContext);
+ }
+
+ /**
+ * This is meant for debug purposes only, this will bypass many checks.
+ * The origination of this call should be from an adb shell command sent from
+ * FingerprintService.
+ *
+ * adb shell cmd fingerprint notification
+ */
+ public void sendFingerprintReEnrollNotification() {
+ mBiometricNotification.sendFpEnrollNotification(mContext);
+ }
+
private void onUserRemoved(final int userId) {
mUserAuthenticationStatsMap.remove(userId);
mAuthenticationStatsPersister.removeFrrStats(userId);
diff --git a/services/core/java/com/android/server/biometrics/BiometricNotificationLogger.java b/services/core/java/com/android/server/biometrics/BiometricNotificationLogger.java
new file mode 100644
index 000000000000..51a2b1ac2dfc
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/BiometricNotificationLogger.java
@@ -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.server.biometrics;
+
+import android.hardware.biometrics.BiometricsProtoEnums;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.StatusBarNotification;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.biometrics.sensors.BiometricNotificationUtils;
+import com.android.server.biometrics.log.BiometricFrameworkStatsLogger;
+
+/**
+ * A class that logs metric info related to the posting/dismissal of Biometric FRR notifications.
+ */
+public class BiometricNotificationLogger extends NotificationListenerService {
+ private static final String TAG = "FRRNotificationListener";
+ private BiometricFrameworkStatsLogger mLogger;
+
+ BiometricNotificationLogger() {
+ this(BiometricFrameworkStatsLogger.getInstance());
+ }
+
+ @VisibleForTesting
+ BiometricNotificationLogger(BiometricFrameworkStatsLogger logger) {
+ mLogger = logger;
+ }
+
+ @Override
+ public void onNotificationPosted(StatusBarNotification sbn, RankingMap map) {
+ if (sbn == null || sbn.getTag() == null) {
+ return;
+ }
+ switch (sbn.getTag()) {
+ case BiometricNotificationUtils.FACE_ENROLL_NOTIFICATION_TAG:
+ case BiometricNotificationUtils.FINGERPRINT_ENROLL_NOTIFICATION_TAG:
+ final int modality =
+ sbn.getTag() == BiometricNotificationUtils.FACE_ENROLL_NOTIFICATION_TAG
+ ? BiometricsProtoEnums.MODALITY_FACE
+ : BiometricsProtoEnums.MODALITY_FINGERPRINT;
+ Slog.d(TAG, "onNotificationPosted, tag=(" + sbn.getTag() + ")");
+ mLogger.logFrameworkNotification(
+ BiometricsProtoEnums.FRR_NOTIFICATION_ACTION_SHOWN,
+ modality
+ );
+ break;
+ default:
+ break;
+ }
+ }
+
+ @Override
+ public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap,
+ int reason) {
+ if (sbn == null || sbn.getTag() == null) {
+ return;
+ }
+ switch (sbn.getTag()) {
+ case BiometricNotificationUtils.FACE_ENROLL_NOTIFICATION_TAG:
+ case BiometricNotificationUtils.FINGERPRINT_ENROLL_NOTIFICATION_TAG:
+ Slog.d(TAG, "onNotificationRemoved, tag=("
+ + sbn.getTag() + "), reason=(" + reason + ")");
+ final int modality =
+ sbn.getTag() == BiometricNotificationUtils.FACE_ENROLL_NOTIFICATION_TAG
+ ? BiometricsProtoEnums.MODALITY_FACE
+ : BiometricsProtoEnums.MODALITY_FINGERPRINT;
+ switch (reason) {
+ // REASON_CLICK = 1
+ case NotificationListenerService.REASON_CLICK:
+ mLogger.logFrameworkNotification(
+ BiometricsProtoEnums.FRR_NOTIFICATION_ACTION_CLICKED,
+ modality);
+ break;
+ // REASON_CANCEL = 2
+ case NotificationListenerService.REASON_CANCEL:
+ mLogger.logFrameworkNotification(
+ BiometricsProtoEnums.FRR_NOTIFICATION_ACTION_DISMISSED,
+ modality);
+ break;
+ default:
+ Slog.d(TAG, "unhandled reason, ignoring logging");
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index fc948da260e6..894b4d5d0b36 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -32,6 +32,7 @@ import android.app.UserSwitchObserver;
import android.app.admin.DevicePolicyManager;
import android.app.trust.ITrustManager;
import android.content.ContentResolver;
+import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
@@ -143,6 +144,8 @@ public class BiometricService extends SystemService {
private final BiometricCameraManager mBiometricCameraManager;
+ private final BiometricNotificationLogger mBiometricNotificationLogger;
+
/**
* Tracks authenticatorId invalidation. For more details, see
* {@link com.android.server.biometrics.sensors.InvalidationRequesterClient}.
@@ -1100,6 +1103,10 @@ public class BiometricService extends SystemService {
return new BiometricCameraManagerImpl(context.getSystemService(CameraManager.class),
context.getSystemService(SensorPrivacyManager.class));
}
+
+ public BiometricNotificationLogger getNotificationLogger() {
+ return new BiometricNotificationLogger();
+ }
}
/**
@@ -1133,6 +1140,7 @@ public class BiometricService extends SystemService {
mBiometricCameraManager = injector.getBiometricCameraManager(context);
mKeystoreAuthorization = injector.getKeystoreAuthorizationService();
mGateKeeper = injector.getGateKeeperService();
+ mBiometricNotificationLogger = injector.getNotificationLogger();
try {
injector.getActivityManagerService().registerUserSwitchObserver(
@@ -1157,6 +1165,20 @@ public class BiometricService extends SystemService {
mInjector.publishBinderService(this, mImpl);
mBiometricStrengthController = mInjector.getBiometricStrengthController(this);
mBiometricStrengthController.startListening();
+
+ mHandler.post(new Runnable(){
+ @Override
+ public void run() {
+ try {
+ mBiometricNotificationLogger.registerAsSystemService(getContext(),
+ new ComponentName(getContext(), BiometricNotificationLogger.class),
+ UserHandle.USER_ALL);
+ } catch (RemoteException e) {
+ // Intra-process call, should never happen.
+ }
+ }
+
+ });
}
private boolean isStrongBiometric(int id) {
@@ -1508,4 +1530,5 @@ public class BiometricService extends SystemService {
pw.println("CurrentSession: " + mAuthSession);
pw.println();
}
+
}
diff --git a/services/core/java/com/android/server/biometrics/TEST_MAPPING b/services/core/java/com/android/server/biometrics/TEST_MAPPING
deleted file mode 100644
index 9e60ba8b5d87..000000000000
--- a/services/core/java/com/android/server/biometrics/TEST_MAPPING
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "presubmit": [
- {
- "name": "CtsBiometricsTestCases"
- },
- {
- "name": "CtsBiometricsHostTestCases"
- }
- ]
-}
diff --git a/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java b/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java
index 6bd48802baf5..f31b2e11b021 100644
--- a/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java
+++ b/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java
@@ -113,14 +113,16 @@ public class BiometricFrameworkStatsLogger {
/** {@see FrameworkStatsLog.BIOMETRIC_ENROLLED}. */
public void enroll(int statsModality, int statsAction, int statsClient,
- int targetUserId, long latency, boolean enrollSuccessful, float ambientLightLux) {
+ int targetUserId, long latency, boolean enrollSuccessful, float ambientLightLux,
+ int source) {
FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_ENROLLED,
statsModality,
targetUserId,
sanitizeLatency(latency),
enrollSuccessful,
-1, /* sensorId */
- ambientLightLux);
+ ambientLightLux,
+ source);
}
/** {@see FrameworkStatsLog.BIOMETRIC_ERROR_OCCURRED}. */
@@ -239,6 +241,12 @@ public class BiometricFrameworkStatsLogger {
-1 /* sensorId */);
}
+ /** {@see FrameworkStatsLog.BIOMETRIC_FRR_NOTIFICATION}. */
+ public void logFrameworkNotification(int action, int modality) {
+ FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_FRR_NOTIFICATION,
+ action, modality);
+ }
+
private long sanitizeLatency(long latency) {
if (latency < 0) {
Slog.w(TAG, "found a negative latency : " + latency);
diff --git a/services/core/java/com/android/server/biometrics/log/BiometricLogger.java b/services/core/java/com/android/server/biometrics/log/BiometricLogger.java
index dbef7178efd0..cd5d0c8dc3f2 100644
--- a/services/core/java/com/android/server/biometrics/log/BiometricLogger.java
+++ b/services/core/java/com/android/server/biometrics/log/BiometricLogger.java
@@ -252,7 +252,8 @@ public class BiometricLogger {
}
/** Log enrollment outcome. */
- public void logOnEnrolled(int targetUserId, long latency, boolean enrollSuccessful) {
+ public void logOnEnrolled(int targetUserId, long latency, boolean enrollSuccessful,
+ int source) {
if (!mShouldLogMetrics) {
return;
}
@@ -273,7 +274,7 @@ public class BiometricLogger {
}
mSink.enroll(mStatsModality, mStatsAction, mStatsClient,
- targetUserId, latency, enrollSuccessful, mALSProbe.getMostRecentLux());
+ targetUserId, latency, enrollSuccessful, mALSProbe.getMostRecentLux(), source);
}
/** Report unexpected enrollment reported by the HAL. */
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java b/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java
index 2aec9aefa8d1..0e22f7511af9 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java
@@ -23,6 +23,9 @@ import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
+import android.hardware.biometrics.BiometricManager;
+import android.hardware.face.FaceEnrollOptions;
+import android.hardware.fingerprint.FingerprintEnrollOptions;
import android.os.SystemClock;
import android.os.UserHandle;
import android.util.Slog;
@@ -36,25 +39,28 @@ public class BiometricNotificationUtils {
private static final String TAG = "BiometricNotificationUtils";
private static final String FACE_RE_ENROLL_NOTIFICATION_TAG = "FaceReEnroll";
- private static final String FACE_ENROLL_NOTIFICATION_TAG = "FaceEnroll";
- private static final String FINGERPRINT_ENROLL_NOTIFICATION_TAG = "FingerprintEnroll";
private static final String BAD_CALIBRATION_NOTIFICATION_TAG = "FingerprintBadCalibration";
private static final String KEY_RE_ENROLL_FACE = "re_enroll_face_unlock";
private static final String FACE_SETTINGS_ACTION = "android.settings.FACE_SETTINGS";
- private static final String FINGERPRINT_SETTINGS_ACTION =
- "android.settings.FINGERPRINT_SETTINGS";
private static final String FACE_ENROLL_ACTION = "android.settings.FACE_ENROLL";
private static final String FINGERPRINT_ENROLL_ACTION = "android.settings.FINGERPRINT_ENROLL";
+ private static final String FINGERPRINT_SETTINGS_ACTION =
+ "android.settings.FINGERPRINT_SETTINGS";
private static final String SETTINGS_PACKAGE = "com.android.settings";
private static final String FACE_ENROLL_CHANNEL = "FaceEnrollNotificationChannel";
private static final String FACE_RE_ENROLL_CHANNEL = "FaceReEnrollNotificationChannel";
private static final String FINGERPRINT_ENROLL_CHANNEL = "FingerprintEnrollNotificationChannel";
private static final String FINGERPRINT_BAD_CALIBRATION_CHANNEL =
"FingerprintBadCalibrationNotificationChannel";
- private static final int NOTIFICATION_ID = 1;
private static final long NOTIFICATION_INTERVAL_MS = 24 * 60 * 60 * 1000;
private static long sLastAlertTime = 0;
+ private static final String ACTION_BIOMETRIC_FRR_DISMISS = "action_biometric_frr_dismiss";
+ // Dismissal action for FRR notification.
+ private static final Intent DISMISS_FRR_INTENT = new Intent(ACTION_BIOMETRIC_FRR_DISMISS);
+ public static final int NOTIFICATION_ID = 1;
+ public static final String FACE_ENROLL_NOTIFICATION_TAG = "FaceEnroll";
+ public static final String FINGERPRINT_ENROLL_NOTIFICATION_TAG = "FingerprintEnroll";
/**
* Shows a face re-enrollment notification.
*/
@@ -71,7 +77,6 @@ public class BiometricNotificationUtils {
final Intent intent = new Intent(FACE_SETTINGS_ACTION);
intent.setPackage(SETTINGS_PACKAGE);
- intent.putExtra(KEY_RE_ENROLL_FACE, true);
final PendingIntent pendingIntent = PendingIntent.getActivityAsUser(context,
0 /* requestCode */, intent, PendingIntent.FLAG_IMMUTABLE /* flags */,
@@ -79,13 +84,14 @@ public class BiometricNotificationUtils {
showNotificationHelper(context, name, title, content, pendingIntent, FACE_RE_ENROLL_CHANNEL,
Notification.CATEGORY_SYSTEM, FACE_RE_ENROLL_NOTIFICATION_TAG,
- Notification.VISIBILITY_SECRET);
+ Notification.VISIBILITY_SECRET, false);
}
/**
* Shows a face enrollment notification.
*/
public static void showFaceEnrollNotification(@NonNull Context context) {
+ Slog.d(TAG, "Showing Face Enroll Notification");
final String name =
context.getString(R.string.device_unlock_notification_name);
@@ -96,6 +102,8 @@ public class BiometricNotificationUtils {
final Intent intent = new Intent(FACE_ENROLL_ACTION);
intent.setPackage(SETTINGS_PACKAGE);
+ intent.putExtra(BiometricManager.EXTRA_ENROLL_REASON,
+ FaceEnrollOptions.ENROLL_REASON_RE_ENROLL_NOTIFICATION);
final PendingIntent pendingIntent = PendingIntent.getActivityAsUser(context,
0 /* requestCode */, intent, PendingIntent.FLAG_IMMUTABLE /* flags */,
@@ -103,14 +111,15 @@ public class BiometricNotificationUtils {
showNotificationHelper(context, name, title, content, pendingIntent, FACE_ENROLL_CHANNEL,
Notification.CATEGORY_RECOMMENDATION, FACE_ENROLL_NOTIFICATION_TAG,
- Notification.VISIBILITY_PUBLIC);
+ Notification.VISIBILITY_PUBLIC, true);
+
}
/**
* Shows a fingerprint enrollment notification.
*/
public static void showFingerprintEnrollNotification(@NonNull Context context) {
-
+ Slog.d(TAG, "Showing Fingerprint Enroll Notification");
final String name =
context.getString(R.string.device_unlock_notification_name);
final String title =
@@ -120,6 +129,8 @@ public class BiometricNotificationUtils {
final Intent intent = new Intent(FINGERPRINT_ENROLL_ACTION);
intent.setPackage(SETTINGS_PACKAGE);
+ intent.putExtra(BiometricManager.EXTRA_ENROLL_REASON,
+ FingerprintEnrollOptions.ENROLL_REASON_RE_ENROLL_NOTIFICATION);
final PendingIntent pendingIntent = PendingIntent.getActivityAsUser(context,
0 /* requestCode */, intent, PendingIntent.FLAG_IMMUTABLE /* flags */,
@@ -127,7 +138,8 @@ public class BiometricNotificationUtils {
showNotificationHelper(context, name, title, content, pendingIntent,
Notification.CATEGORY_RECOMMENDATION, FINGERPRINT_ENROLL_CHANNEL,
- FINGERPRINT_ENROLL_NOTIFICATION_TAG, Notification.VISIBILITY_PUBLIC);
+ FINGERPRINT_ENROLL_NOTIFICATION_TAG, Notification.VISIBILITY_PUBLIC, true);
+
}
/**
@@ -162,17 +174,22 @@ public class BiometricNotificationUtils {
showNotificationHelper(context, name, title, content, pendingIntent,
Notification.CATEGORY_SYSTEM, FINGERPRINT_BAD_CALIBRATION_CHANNEL,
- BAD_CALIBRATION_NOTIFICATION_TAG, Notification.VISIBILITY_SECRET);
+ BAD_CALIBRATION_NOTIFICATION_TAG, Notification.VISIBILITY_SECRET, false);
}
private static void showNotificationHelper(Context context, String name, String title,
String content, PendingIntent pendingIntent, String category,
- String channelName, String notificationTag, int visibility) {
+ String channelName, String notificationTag, int visibility,
+ boolean listenToDismissEvent) {
+ Slog.v(TAG," listenToDismissEvent = " + listenToDismissEvent);
+ final PendingIntent dismissIntent = PendingIntent.getActivityAsUser(context,
+ 0 /* requestCode */, DISMISS_FRR_INTENT, PendingIntent.FLAG_IMMUTABLE /* flags */,
+ null /* options */, UserHandle.CURRENT);
final NotificationManager notificationManager =
context.getSystemService(NotificationManager.class);
final NotificationChannel channel = new NotificationChannel(channelName, name,
NotificationManager.IMPORTANCE_HIGH);
- final Notification notification = new Notification.Builder(context, channelName)
+ final Notification.Builder builder = new Notification.Builder(context, channelName)
.setSmallIcon(R.drawable.ic_lock)
.setContentTitle(title)
.setContentText(content)
@@ -183,12 +200,17 @@ public class BiometricNotificationUtils {
.setAutoCancel(true)
.setCategory(category)
.setContentIntent(pendingIntent)
- .setVisibility(visibility)
- .build();
+ .setVisibility(visibility);
+
+ if (listenToDismissEvent) {
+ builder.setDeleteIntent(dismissIntent);
+ }
+ final Notification notification = builder.build();
notificationManager.createNotificationChannel(channel);
notificationManager.notifyAsUser(notificationTag, NOTIFICATION_ID, notification,
UserHandle.CURRENT);
+
}
/**
diff --git a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
index 8e7004d8fde5..af6de5c7316a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
@@ -45,6 +45,7 @@ public abstract class EnrollClient<T> extends AcquisitionClient<T> implements En
private long mEnrollmentStartTimeMs;
private final boolean mHasEnrollmentsBeforeStarting;
+ private final int mEnrollReason;
/**
* @return true if the user has already enrolled the maximum number of templates.
@@ -55,13 +56,15 @@ public abstract class EnrollClient<T> extends AcquisitionClient<T> implements En
@NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
@NonNull byte[] hardwareAuthToken, @NonNull String owner, @NonNull BiometricUtils utils,
int timeoutSec, int sensorId, boolean shouldVibrate,
- @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+ int enrollReason) {
super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
shouldVibrate, logger, biometricContext);
mBiometricUtils = utils;
mHardwareAuthToken = Arrays.copyOf(hardwareAuthToken, hardwareAuthToken.length);
mTimeoutSec = timeoutSec;
mHasEnrollmentsBeforeStarting = hasEnrollments();
+ mEnrollReason = enrollReason;
}
@Override
@@ -91,7 +94,7 @@ public abstract class EnrollClient<T> extends AcquisitionClient<T> implements En
mBiometricUtils.addBiometricForUser(getContext(), getTargetUserId(), identifier);
getLogger().logOnEnrolled(getTargetUserId(),
System.currentTimeMillis() - mEnrollmentStartTimeMs,
- true /* enrollSuccessful */);
+ true /* enrollSuccessful */, mEnrollReason);
mCallback.onClientFinished(this, true /* success */);
}
notifyUserActivity();
@@ -119,7 +122,7 @@ public abstract class EnrollClient<T> extends AcquisitionClient<T> implements En
public void onError(int error, int vendorCode) {
getLogger().logOnEnrolled(getTargetUserId(),
System.currentTimeMillis() - mEnrollmentStartTimeMs,
- false /* enrollSuccessful */);
+ false /* enrollSuccessful */, mEnrollReason);
super.onError(error, vendorCode);
}
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 321e951ec09b..68b4e3fb51ba 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
@@ -37,6 +37,7 @@ import android.hardware.biometrics.face.IFace;
import android.hardware.biometrics.face.SensorProps;
import android.hardware.face.Face;
import android.hardware.face.FaceAuthenticateOptions;
+import android.hardware.face.FaceEnrollOptions;
import android.hardware.face.FaceSensorConfigurations;
import android.hardware.face.FaceSensorPropertiesInternal;
import android.hardware.face.FaceServiceReceiver;
@@ -44,6 +45,7 @@ import android.hardware.face.IFaceAuthenticatorsRegisteredCallback;
import android.hardware.face.IFaceService;
import android.hardware.face.IFaceServiceReceiver;
import android.os.Binder;
+import android.os.Build;
import android.os.IBinder;
import android.os.NativeHandle;
import android.os.RemoteException;
@@ -210,7 +212,8 @@ public class FaceService extends SystemService {
@Override // Binder call
public long enroll(int userId, final IBinder token, final byte[] hardwareAuthToken,
final IFaceServiceReceiver receiver, final String opPackageName,
- final int[] disabledFeatures, Surface previewSurface, boolean debugConsent) {
+ final int[] disabledFeatures, Surface previewSurface, boolean debugConsent,
+ FaceEnrollOptions options) {
super.enroll_enforcePermission();
final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
@@ -220,7 +223,8 @@ public class FaceService extends SystemService {
}
return provider.second.scheduleEnroll(provider.first, token, hardwareAuthToken, userId,
- receiver, opPackageName, disabledFeatures, previewSurface, debugConsent);
+ receiver, opPackageName, disabledFeatures, previewSurface, debugConsent,
+ options);
}
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@@ -958,4 +962,25 @@ public class FaceService extends SystemService {
}
}
}
+
+ /**
+ * This should only be called from FaceShellCommand class.
+ */
+ void sendFaceReEnrollNotification() {
+ Utils.checkPermissionOrShell(getContext(), MANAGE_FACE);
+ if (Build.IS_DEBUGGABLE) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
+ if (provider != null) {
+ FaceProvider faceProvider = (FaceProvider) provider.second;
+ faceProvider.sendFaceReEnrollNotification();
+ } else {
+ Slog.w(TAG, "Null provider for notification");
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceShellCommand.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceShellCommand.java
index 187575dcd664..e167bbafc02e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceShellCommand.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceShellCommand.java
@@ -42,6 +42,8 @@ public class FaceShellCommand extends ShellCommand {
return doHelp();
case "sync":
return doSync();
+ case "notification":
+ return doNotify();
default:
getOutPrintWriter().println("Unrecognized command: " + cmd);
}
@@ -59,6 +61,8 @@ public class FaceShellCommand extends ShellCommand {
pw.println(" Print this help text.");
pw.println(" sync");
pw.println(" Sync enrollments now (virtualized sensors only).");
+ pw.println(" notification");
+ pw.println(" Sends a Face re-enrollment notification");
}
private int doHelp() {
@@ -70,4 +74,9 @@ public class FaceShellCommand extends ShellCommand {
mService.syncEnrollmentsNow();
return 0;
}
+
+ private int doNotify() {
+ mService.sendFaceReEnrollNotification();
+ return 0;
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
index 2cf64b72d01f..6f76cdae4539 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
@@ -23,6 +23,7 @@ import android.hardware.biometrics.ITestSession;
import android.hardware.biometrics.ITestSessionCallback;
import android.hardware.face.Face;
import android.hardware.face.FaceAuthenticateOptions;
+import android.hardware.face.FaceEnrollOptions;
import android.hardware.face.FaceManager;
import android.hardware.face.FaceSensorPropertiesInternal;
import android.hardware.face.IFaceServiceReceiver;
@@ -79,7 +80,7 @@ public interface ServiceProvider extends BiometricServiceProvider<FaceSensorProp
long scheduleEnroll(int sensorId, @NonNull IBinder token, @NonNull byte[] hardwareAuthToken,
int userId, @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName,
@NonNull int[] disabledFeatures, @Nullable Surface previewSurface,
- boolean debugConsent);
+ boolean debugConsent, FaceEnrollOptions options);
void cancelEnrollment(int sensorId, @NonNull IBinder token, long requestId);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
index d11f0991a5d4..0fdd57d64d8d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
@@ -26,6 +26,7 @@ import android.hardware.biometrics.face.EnrollmentFrame;
import android.hardware.face.Face;
import android.hardware.face.FaceAuthenticationFrame;
import android.hardware.face.FaceEnrollFrame;
+import android.hardware.face.FaceEnrollOptions;
import android.hardware.face.IFaceServiceReceiver;
import android.os.Binder;
import android.os.RemoteException;
@@ -155,7 +156,8 @@ public class BiometricTestSessionImpl extends ITestSession.Stub {
mProvider.scheduleEnroll(mSensorId, new Binder(), new byte[69], userId, mReceiver,
mContext.getOpPackageName(), new int[0] /* disabledFeatures */,
- null /* previewSurface */, false /* debugConsent */);
+ null /* previewSurface */, false /* debugConsent */,
+ (new FaceEnrollOptions.Builder()).build());
}
@android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
index 5f370f23134c..781e3f491ec8 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
@@ -93,9 +93,11 @@ public class FaceEnrollClient extends EnrollClient<AidlSession> {
@NonNull BiometricUtils<Face> utils, @NonNull int[] disabledFeatures, int timeoutSec,
@Nullable Surface previewSurface, int sensorId,
@NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
- int maxTemplatesPerUser, boolean debugConsent) {
+ int maxTemplatesPerUser, boolean debugConsent,
+ android.hardware.face.FaceEnrollOptions options) {
super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, opPackageName, utils,
- timeoutSec, sensorId, false /* shouldVibrate */, logger, biometricContext);
+ timeoutSec, sensorId, false /* shouldVibrate */, logger, biometricContext,
+ BiometricFaceConstants.reasonToMetric(options.getEnrollReason()));
setRequestId(requestId);
mEnrollIgnoreList = getContext().getResources()
.getIntArray(R.array.config_face_acquire_enroll_ignorelist);
@@ -105,6 +107,9 @@ public class FaceEnrollClient extends EnrollClient<AidlSession> {
mDebugConsent = debugConsent;
mDisabledFeatures = disabledFeatures;
mPreviewSurface = previewSurface;
+ Slog.w(TAG, "EnrollOptions "
+ + android.hardware.face.FaceEnrollOptions.enrollReasonToString(
+ options.getEnrollReason()));
}
@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 f469f6224e4c..007b7462f637 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
@@ -35,6 +35,7 @@ import android.hardware.biometrics.face.IFace;
import android.hardware.biometrics.face.SensorProps;
import android.hardware.face.Face;
import android.hardware.face.FaceAuthenticateOptions;
+import android.hardware.face.FaceEnrollOptions;
import android.hardware.face.FaceSensorPropertiesInternal;
import android.hardware.face.IFaceServiceReceiver;
import android.os.Binder;
@@ -519,7 +520,7 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider {
public long scheduleEnroll(int sensorId, @NonNull IBinder token,
@NonNull byte[] hardwareAuthToken, int userId, @NonNull IFaceServiceReceiver receiver,
@NonNull String opPackageName, @NonNull int[] disabledFeatures,
- @Nullable Surface previewSurface, boolean debugConsent) {
+ @Nullable Surface previewSurface, boolean debugConsent, FaceEnrollOptions options) {
final long id = mRequestCounter.incrementAndGet();
mHandler.post(() -> {
mFaceSensors.get(sensorId).scheduleFaceUpdateActiveUserClient(userId);
@@ -533,7 +534,7 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider {
createLogger(BiometricsProtoEnums.ACTION_ENROLL,
BiometricsProtoEnums.CLIENT_UNKNOWN,
mAuthenticationStatsCollector),
- mBiometricContext, maxTemplatesPerUser, debugConsent);
+ mBiometricContext, maxTemplatesPerUser, debugConsent, options);
if (Flags.deHidl()) {
scheduleForSensor(sensorId, client, mBiometricStateCallback);
} else {
@@ -903,4 +904,11 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider {
public boolean getTestHalEnabled() {
return mTestHalEnabled;
}
+
+ /**
+ * Sends a face re enroll notification.
+ */
+ public void sendFaceReEnrollNotification() {
+ mAuthenticationStatsCollector.sendFaceReEnrollNotification();
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java
index 151ffaa1cb28..0e2367a599a5 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java
@@ -23,6 +23,7 @@ import android.hardware.biometrics.ITestSessionCallback;
import android.hardware.face.Face;
import android.hardware.face.FaceAuthenticationFrame;
import android.hardware.face.FaceEnrollFrame;
+import android.hardware.face.FaceEnrollOptions;
import android.hardware.face.IFaceServiceReceiver;
import android.os.Binder;
import android.os.RemoteException;
@@ -143,7 +144,8 @@ public class BiometricTestSessionImpl extends ITestSession.Stub {
mFace10.scheduleEnroll(mSensorId, new Binder(), new byte[69], userId, mReceiver,
mContext.getOpPackageName(), new int[0] /* disabledFeatures */,
- null /* previewSurface */, false /* debugConsent */);
+ null /* previewSurface */, false /* debugConsent */,
+ (new FaceEnrollOptions.Builder()).build());
}
@android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
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 48a676ce4937..306ddfa1e083 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
@@ -32,6 +32,7 @@ import android.hardware.biometrics.face.V1_0.IBiometricsFace;
import android.hardware.biometrics.face.V1_0.IBiometricsFaceClientCallback;
import android.hardware.face.Face;
import android.hardware.face.FaceAuthenticateOptions;
+import android.hardware.face.FaceEnrollOptions;
import android.hardware.face.FaceSensorPropertiesInternal;
import android.hardware.face.IFaceServiceReceiver;
import android.os.Binder;
@@ -711,16 +712,17 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {
public long scheduleEnroll(int sensorId, @NonNull IBinder token,
@NonNull byte[] hardwareAuthToken, int userId, @NonNull IFaceServiceReceiver receiver,
@NonNull String opPackageName, @NonNull int[] disabledFeatures,
- @Nullable Surface previewSurface, boolean debugConsent) {
+ @Nullable Surface previewSurface, boolean debugConsent,
+ @NonNull FaceEnrollOptions options) {
final long id = mRequestCounter.incrementAndGet();
mHandler.post(() -> {
scheduleUpdateActiveUserWithoutHandler(userId);
if (Flags.deHidl()) {
scheduleEnrollAidl(token, hardwareAuthToken, userId, receiver,
- opPackageName, disabledFeatures, previewSurface, id);
+ opPackageName, disabledFeatures, previewSurface, id, options);
} else {
scheduleEnrollHidl(token, hardwareAuthToken, userId, receiver,
- opPackageName, disabledFeatures, previewSurface, id);
+ opPackageName, disabledFeatures, previewSurface, id, options);
}
});
return id;
@@ -729,7 +731,8 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {
private void scheduleEnrollAidl(@NonNull IBinder token,
@NonNull byte[] hardwareAuthToken, int userId, @NonNull IFaceServiceReceiver receiver,
@NonNull String opPackageName, @NonNull int[] disabledFeatures,
- @Nullable Surface previewSurface, long id) {
+ @Nullable Surface previewSurface, long id,
+ @NonNull FaceEnrollOptions options) {
final com.android.server.biometrics.sensors.face.aidl.FaceEnrollClient client =
new com.android.server.biometrics.sensors.face.aidl.FaceEnrollClient(
mContext, this::getSession, token,
@@ -742,7 +745,7 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {
mAuthenticationStatsCollector), mBiometricContext,
mContext.getResources().getInteger(
com.android.internal.R.integer.config_faceMaxTemplatesPerUser),
- false);
+ false, options);
mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
@Override
@@ -770,14 +773,14 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {
private void scheduleEnrollHidl(@NonNull IBinder token,
@NonNull byte[] hardwareAuthToken, int userId, @NonNull IFaceServiceReceiver receiver,
@NonNull String opPackageName, @NonNull int[] disabledFeatures,
- @Nullable Surface previewSurface, long id) {
+ @Nullable Surface previewSurface, long id, FaceEnrollOptions options) {
final FaceEnrollClient client = new FaceEnrollClient(mContext, mLazyDaemon, token,
new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
opPackageName, id, FaceUtils.getLegacyInstance(mSensorId), disabledFeatures,
ENROLL_TIMEOUT_SEC, previewSurface, mSensorId,
createLogger(BiometricsProtoEnums.ACTION_ENROLL,
BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
- mBiometricContext);
+ mBiometricContext, options);
mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
@Override
public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java
index 27b9c79516af..815cf9180130 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java
@@ -23,6 +23,7 @@ import android.hardware.biometrics.BiometricFaceConstants;
import android.hardware.biometrics.face.V1_0.IBiometricsFace;
import android.hardware.biometrics.face.V1_0.Status;
import android.hardware.face.Face;
+import android.hardware.face.FaceEnrollOptions;
import android.hardware.face.FaceManager;
import android.os.IBinder;
import android.os.RemoteException;
@@ -61,15 +62,20 @@ public class FaceEnrollClient extends EnrollClient<IBiometricsFace> {
@NonNull byte[] hardwareAuthToken, @NonNull String owner, long requestId,
@NonNull BiometricUtils<Face> utils, @NonNull int[] disabledFeatures, int timeoutSec,
@Nullable Surface previewSurface, int sensorId,
- @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext) {
+ @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
+ @NonNull FaceEnrollOptions options) {
super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
- timeoutSec, sensorId, false /* shouldVibrate */, logger, biometricContext);
+ timeoutSec, sensorId, false /* shouldVibrate */, logger, biometricContext,
+ BiometricFaceConstants.reasonToMetric(options.getEnrollReason()));
setRequestId(requestId);
mDisabledFeatures = Arrays.copyOf(disabledFeatures, disabledFeatures.length);
mEnrollIgnoreList = getContext().getResources()
.getIntArray(R.array.config_face_acquire_enroll_ignorelist);
mEnrollIgnoreListVendor = getContext().getResources()
.getIntArray(R.array.config_face_acquire_vendor_enroll_ignorelist);
+
+ Slog.w(TAG, "EnrollOptions "
+ + FaceEnrollOptions.enrollReasonToString(options.getEnrollReason()));
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index e01d672d7e34..1ba12134ab29 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -48,6 +48,7 @@ import android.hardware.biometrics.fingerprint.PointerContext;
import android.hardware.biometrics.fingerprint.SensorProps;
import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.FingerprintAuthenticateOptions;
+import android.hardware.fingerprint.FingerprintEnrollOptions;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorConfigurations;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
@@ -239,7 +240,8 @@ public class FingerprintService extends SystemService {
@Override // Binder call
public long enroll(final IBinder token, @NonNull final byte[] hardwareAuthToken,
final int userId, final IFingerprintServiceReceiver receiver,
- final String opPackageName, @FingerprintManager.EnrollReason int enrollReason) {
+ final String opPackageName, @FingerprintManager.EnrollReason int enrollReason,
+ FingerprintEnrollOptions options) {
super.enroll_enforcePermission();
final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
@@ -249,7 +251,7 @@ public class FingerprintService extends SystemService {
}
return provider.second.scheduleEnroll(provider.first, token, hardwareAuthToken, userId,
- receiver, opPackageName, enrollReason);
+ receiver, opPackageName, enrollReason, options);
}
@android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_FINGERPRINT)
@@ -1322,4 +1324,25 @@ public class FingerprintService extends SystemService {
}
}
}
+
+ /**
+ * This should only be called from FingerprintShellCommand
+ */
+ void sendFingerprintReEnrollNotification() {
+ Utils.checkPermissionOrShell(getContext(), MANAGE_FINGERPRINT);
+ if (Build.IS_DEBUGGABLE) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
+ if (provider != null) {
+ FingerprintProvider fingerprintProvider = (FingerprintProvider) provider.second;
+ fingerprintProvider.sendFingerprintReEnrollNotification();
+ } else {
+ Slog.w(TAG, "Null provider for notification");
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintShellCommand.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintShellCommand.java
index dc6a63f82bb1..766b3a1f1fbf 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintShellCommand.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintShellCommand.java
@@ -47,6 +47,8 @@ public class FingerprintShellCommand extends ShellCommand {
return doSync();
case "fingerdown":
return doSimulateVhalFingerDown();
+ case "notification":
+ return doNotify();
default:
getOutPrintWriter().println("Unrecognized command: " + cmd);
}
@@ -66,6 +68,8 @@ public class FingerprintShellCommand extends ShellCommand {
pw.println(" Sync enrollments now (virtualized sensors only).");
pw.println(" fingerdown");
pw.println(" Simulate finger down event (virtualized sensors only).");
+ pw.println(" notification");
+ pw.println(" Sends a Fingerprint re-enrollment notification");
}
private int doHelp() {
@@ -82,4 +86,9 @@ public class FingerprintShellCommand extends ShellCommand {
mService.simulateVhalFingerDown();
return 0;
}
+
+ private int doNotify() {
+ mService.sendFingerprintReEnrollNotification();
+ return 0;
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
index fc37d7020a21..c2d11699b91f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
@@ -24,6 +24,7 @@ import android.hardware.biometrics.ITestSessionCallback;
import android.hardware.biometrics.fingerprint.PointerContext;
import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.FingerprintAuthenticateOptions;
+import android.hardware.fingerprint.FingerprintEnrollOptions;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.IFingerprintServiceReceiver;
@@ -74,7 +75,8 @@ public interface ServiceProvider extends
*/
long scheduleEnroll(int sensorId, @NonNull IBinder token, @NonNull byte[] hardwareAuthToken,
int userId, @NonNull IFingerprintServiceReceiver receiver,
- @NonNull String opPackageName, @FingerprintManager.EnrollReason int enrollReason);
+ @NonNull String opPackageName, @FingerprintManager.EnrollReason int enrollReason,
+ @NonNull FingerprintEnrollOptions options);
void cancelEnrollment(int sensorId, @NonNull IBinder token, long requestId);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
index ec1eeb138505..d64b6c29c840 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
@@ -16,13 +16,12 @@
package com.android.server.biometrics.sensors.fingerprint.aidl;
-import static android.Manifest.permission.TEST_BIOMETRIC;
-
import android.annotation.NonNull;
import android.content.Context;
import android.hardware.biometrics.ITestSession;
import android.hardware.biometrics.ITestSessionCallback;
import android.hardware.fingerprint.Fingerprint;
+import android.hardware.fingerprint.FingerprintEnrollOptions;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.IFingerprintServiceReceiver;
import android.os.Binder;
@@ -30,7 +29,6 @@ import android.os.RemoteException;
import android.util.Slog;
import com.android.server.biometrics.HardwareAuthTokenUtils;
-import com.android.server.biometrics.Utils;
import com.android.server.biometrics.sensors.BaseClientMonitor;
import com.android.server.biometrics.sensors.BiometricStateCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
@@ -153,7 +151,8 @@ class BiometricTestSessionImpl extends ITestSession.Stub {
super.startEnroll_enforcePermission();
mProvider.scheduleEnroll(mSensorId, new Binder(), new byte[69], userId, mReceiver,
- mContext.getOpPackageName(), FingerprintManager.ENROLL_ENROLL);
+ mContext.getOpPackageName(), FingerprintManager.ENROLL_ENROLL,
+ (new FingerprintEnrollOptions.Builder()).build());
}
@android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
index 79975e515c70..a24ab1d022c6 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
@@ -29,6 +29,7 @@ import android.hardware.biometrics.common.ICancellationSignal;
import android.hardware.biometrics.common.OperationState;
import android.hardware.biometrics.fingerprint.PointerContext;
import android.hardware.fingerprint.Fingerprint;
+import android.hardware.fingerprint.FingerprintEnrollOptions;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.ISidefpsController;
@@ -98,11 +99,13 @@ public class FingerprintEnrollClient extends EnrollClient<AidlSession> implement
// TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
@Nullable ISidefpsController sidefpsController,
@NonNull AuthenticationStateListeners authenticationStateListeners,
- int maxTemplatesPerUser, @FingerprintManager.EnrollReason int enrollReason) {
+ int maxTemplatesPerUser, @FingerprintManager.EnrollReason int enrollReason,
+ @NonNull FingerprintEnrollOptions options) {
// UDFPS haptics occur when an image is acquired (instead of when the result is known)
super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
0 /* timeoutSec */, sensorId, shouldVibrateFor(context, sensorProps),
- logger, biometricContext);
+ logger, biometricContext,
+ BiometricFingerprintConstants.reasonToMetric(options.getEnrollReason()));
setRequestId(requestId);
mSensorProps = sensorProps;
if (sidefpsControllerRefactor()) {
@@ -120,6 +123,8 @@ public class FingerprintEnrollClient extends EnrollClient<AidlSession> implement
if (enrollReason == FingerprintManager.ENROLL_FIND_SENSOR) {
getLogger().disableMetrics();
}
+ Slog.w(TAG, "EnrollOptions "
+ + FingerprintEnrollOptions.enrollReasonToString(options.getEnrollReason()));
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index fd938ed9c389..a104cf4e1726 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -39,6 +39,7 @@ import android.hardware.biometrics.fingerprint.PointerContext;
import android.hardware.biometrics.fingerprint.SensorProps;
import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.FingerprintAuthenticateOptions;
+import android.hardware.fingerprint.FingerprintEnrollOptions;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.IFingerprintServiceReceiver;
@@ -515,7 +516,8 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
public long scheduleEnroll(int sensorId, @NonNull IBinder token,
@NonNull byte[] hardwareAuthToken, int userId,
@NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName,
- @FingerprintManager.EnrollReason int enrollReason) {
+ @FingerprintManager.EnrollReason int enrollReason,
+ @NonNull FingerprintEnrollOptions options) {
final long id = mRequestCounter.incrementAndGet();
mHandler.post(() -> {
final int maxTemplatesPerUser = mFingerprintSensors.get(sensorId).getSensorProperties()
@@ -529,7 +531,7 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
mBiometricContext,
mFingerprintSensors.get(sensorId).getSensorProperties(),
mUdfpsOverlayController, mSidefpsController,
- mAuthenticationStateListeners, maxTemplatesPerUser, enrollReason);
+ mAuthenticationStateListeners, maxTemplatesPerUser, enrollReason, options);
if (Flags.deHidl()) {
scheduleForSensor(sensorId, client, mBiometricStateCallback);
} else {
@@ -1021,4 +1023,11 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
Slog.e(getTag(), "failed hal operation ", e);
}
}
+
+ /**
+ * Sends a fingerprint enroll notification.
+ */
+ public void sendFingerprintReEnrollNotification() {
+ mAuthenticationStatsCollector.sendFingerprintReEnrollNotification();
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java
index c20a9eb958c4..fc037aeb7e17 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java
@@ -16,20 +16,18 @@
package com.android.server.biometrics.sensors.fingerprint.hidl;
-import static android.Manifest.permission.TEST_BIOMETRIC;
-
import android.annotation.NonNull;
import android.content.Context;
import android.hardware.biometrics.ITestSession;
import android.hardware.biometrics.ITestSessionCallback;
import android.hardware.fingerprint.Fingerprint;
+import android.hardware.fingerprint.FingerprintEnrollOptions;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.IFingerprintServiceReceiver;
import android.os.Binder;
import android.os.RemoteException;
import android.util.Slog;
-import com.android.server.biometrics.Utils;
import com.android.server.biometrics.sensors.BaseClientMonitor;
import com.android.server.biometrics.sensors.BiometricStateCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
@@ -153,7 +151,8 @@ public class BiometricTestSessionImpl extends ITestSession.Stub {
super.startEnroll_enforcePermission();
mFingerprint21.scheduleEnroll(mSensorId, new Binder(), new byte[69], userId, mReceiver,
- mContext.getOpPackageName(), FingerprintManager.ENROLL_ENROLL);
+ mContext.getOpPackageName(), FingerprintManager.ENROLL_ENROLL,
+ (new FingerprintEnrollOptions.Builder()).build());
}
@android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
index 4accf8f7ff30..33e448bbe612 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
@@ -35,6 +35,7 @@ import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
import android.hardware.biometrics.fingerprint.V2_2.IBiometricsFingerprintClientCallback;
import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.FingerprintAuthenticateOptions;
+import android.hardware.fingerprint.FingerprintEnrollOptions;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorProperties;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
@@ -700,17 +701,18 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider
public long scheduleEnroll(int sensorId, @NonNull IBinder token,
@NonNull byte[] hardwareAuthToken, int userId,
@NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName,
- @FingerprintManager.EnrollReason int enrollReason) {
+ @FingerprintManager.EnrollReason int enrollReason,
+ @NonNull FingerprintEnrollOptions options) {
final long id = mRequestCounter.incrementAndGet();
mHandler.post(() -> {
scheduleUpdateActiveUserWithoutHandler(userId);
if (Flags.deHidl()) {
scheduleEnrollAidl(token, hardwareAuthToken, userId, receiver,
- opPackageName, enrollReason, id);
+ opPackageName, enrollReason, id, options);
} else {
scheduleEnrollHidl(token, hardwareAuthToken, userId, receiver,
- opPackageName, enrollReason, id);
+ opPackageName, enrollReason, id, options);
}
});
return id;
@@ -719,7 +721,8 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider
private void scheduleEnrollHidl(@NonNull IBinder token,
@NonNull byte[] hardwareAuthToken, int userId,
@NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName,
- @FingerprintManager.EnrollReason int enrollReason, long id) {
+ @FingerprintManager.EnrollReason int enrollReason, long id,
+ @NonNull FingerprintEnrollOptions options) {
final FingerprintEnrollClient client = new FingerprintEnrollClient(mContext,
mLazyDaemon, token, id, new ClientMonitorCallbackConverter(receiver),
userId, hardwareAuthToken, opPackageName,
@@ -730,7 +733,7 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider
mBiometricContext, mUdfpsOverlayController,
// TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
mSidefpsController,
- mAuthenticationStateListeners, enrollReason);
+ mAuthenticationStateListeners, enrollReason, options);
mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
@Override
public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
@@ -758,7 +761,8 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider
private void scheduleEnrollAidl(@NonNull IBinder token,
@NonNull byte[] hardwareAuthToken, int userId,
@NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName,
- @FingerprintManager.EnrollReason int enrollReason, long id) {
+ @FingerprintManager.EnrollReason int enrollReason, long id,
+ @NonNull FingerprintEnrollOptions options) {
final com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintEnrollClient
client =
new com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintEnrollClient(
@@ -778,8 +782,7 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider
mAuthenticationStateListeners,
mContext.getResources().getInteger(
com.android.internal.R.integer.config_fingerprintMaxTemplatesPerUser),
- enrollReason);
-
+ enrollReason, options);
mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
@Override
public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
index 26332ff6893c..8f937fc65cbb 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
@@ -27,6 +27,7 @@ import android.hardware.biometrics.BiometricStateListener;
import android.hardware.biometrics.fingerprint.PointerContext;
import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
import android.hardware.fingerprint.Fingerprint;
+import android.hardware.fingerprint.FingerprintEnrollOptions;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.ISidefpsController;
import android.hardware.fingerprint.IUdfpsOverlayController;
@@ -75,10 +76,12 @@ public class FingerprintEnrollClient extends EnrollClient<IBiometricsFingerprint
// TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
@Nullable ISidefpsController sidefpsController,
@NonNull AuthenticationStateListeners authenticationStateListeners,
- @FingerprintManager.EnrollReason int enrollReason) {
+ @FingerprintManager.EnrollReason int enrollReason,
+ @NonNull FingerprintEnrollOptions options) {
super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
timeoutSec, sensorId, true /* shouldVibrate */, biometricLogger,
- biometricContext);
+ biometricContext,
+ BiometricFingerprintConstants.reasonToMetric(options.getEnrollReason()));
setRequestId(requestId);
if (sidefpsControllerRefactor()) {
mSensorOverlays = new SensorOverlays(udfpsOverlayController);
@@ -91,6 +94,8 @@ public class FingerprintEnrollClient extends EnrollClient<IBiometricsFingerprint
if (enrollReason == FingerprintManager.ENROLL_FIND_SENSOR) {
getLogger().disableMetrics();
}
+ Slog.w(TAG, "EnrollOptions "
+ + FingerprintEnrollOptions.enrollReasonToString(options.getEnrollReason()));
}
@Override
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 4c4cf6080965..d1374a5ab9dc 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -2161,7 +2161,7 @@ public class DisplayDeviceConfig {
final List<SdrHdrRatioPoint> points = sdrHdrRatioMap.getPoint();
final int size = points.size();
- if (size <= 0) {
+ if (size == 0) {
return null;
}
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 3a6333099b77..88c24e0a7eff 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -842,7 +842,8 @@ final class LocalDisplayAdapter extends DisplayAdapter {
// We must tell sidekick/displayoffload to stop controlling the display
// before we can change its power mode, so do that first.
if (isDisplayOffloadEnabled) {
- if (displayOffloadSession != null) {
+ if (displayOffloadSession != null
+ && !DisplayOffloadSession.isSupportedOffloadState(state)) {
displayOffloadSession.stopOffload();
}
} else {
@@ -874,8 +875,8 @@ final class LocalDisplayAdapter extends DisplayAdapter {
// have a sidekick/displayoffload available, tell it now that it can take
// control.
if (isDisplayOffloadEnabled) {
- if (DisplayOffloadSession.isSupportedOffloadState(state)
- && displayOffloadSession != null) {
+ if (displayOffloadSession != null
+ && DisplayOffloadSession.isSupportedOffloadState(state)) {
displayOffloadSession.startOffload();
}
} else {
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
index fab769e8bc4f..40e9198ea921 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
@@ -75,6 +75,6 @@ abstract class BrightnessClamper<T> {
protected enum Type {
THERMAL,
POWER,
- BEDTIME_MODE,
+ WEAR_BEDTIME_MODE,
}
}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
index 2c02fc610a51..bc5fcb449c95 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
@@ -156,6 +156,8 @@ public class BrightnessClamperController {
return BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL;
} else if (mClamperType == Type.POWER) {
return BrightnessInfo.BRIGHTNESS_MAX_REASON_POWER_IC;
+ } else if (mClamperType == Type.WEAR_BEDTIME_MODE) {
+ return BrightnessInfo.BRIGHTNESS_MAX_REASON_WEAR_BEDTIME_MODE;
} else {
Slog.wtf(TAG, "BrightnessMaxReason not mapped for type=" + mClamperType);
return BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeClamper.java
index 7e853bfc4ad6..1902e35ed397 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeClamper.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeClamper.java
@@ -64,7 +64,7 @@ public class BrightnessWearBedtimeModeClamper extends
@NonNull
@Override
Type getType() {
- return Type.BEDTIME_MODE;
+ return Type.WEAR_BEDTIME_MODE;
}
@Override
diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionManagerInternal.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionManagerInternal.java
index 6a6e6ab23687..c2c82edee33d 100644
--- a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionManagerInternal.java
+++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionManagerInternal.java
@@ -16,6 +16,7 @@
package com.android.server.grammaticalinflection;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.res.Configuration;
@@ -55,11 +56,11 @@ public abstract class GrammaticalInflectionManagerInternal {
*
*/
public abstract @Configuration.GrammaticalGender int retrieveSystemGrammaticalGender(
- Configuration configuration);
+ @NonNull Configuration configuration);
/**
* Whether the package can get the system grammatical gender or not.
*/
- public abstract boolean canGetSystemGrammaticalGender(int uid, String packageName);
+ public abstract boolean canGetSystemGrammaticalGender(int uid, @Nullable String packageName);
}
diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
index d01f54f09679..252ea4b08464 100644
--- a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
+++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
@@ -222,7 +222,7 @@ public class GrammaticalInflectionService extends SystemService {
}
final int uid = mPackageManagerInternal.getPackageUid(appPackageName, 0, userId);
- FrameworkStatsLog.write(FrameworkStatsLog.GRAMMATICAL_INFLECTION_CHANGED,
+ FrameworkStatsLog.write(FrameworkStatsLog.APPLICATION_GRAMMATICAL_INFLECTION_CHANGED,
FrameworkStatsLog.APPLICATION_GRAMMATICAL_INFLECTION_CHANGED__SOURCE_ID__OTHERS,
uid,
gender != GRAMMATICAL_GENDER_NOT_SPECIFIED,
@@ -266,8 +266,14 @@ public class GrammaticalInflectionService extends SystemService {
try {
Configuration config = new Configuration();
+ int preValue = config.getGrammaticalGender();
config.setGrammaticalGender(grammaticalGender);
ActivityTaskManager.getService().updateConfiguration(config);
+ FrameworkStatsLog.write(FrameworkStatsLog.SYSTEM_GRAMMATICAL_INFLECTION_CHANGED,
+ FrameworkStatsLog.SYSTEM_GRAMMATICAL_INFLECTION_CHANGED__SOURCE_ID__SYSTEM,
+ userId,
+ grammaticalGender != GRAMMATICAL_GENDER_NOT_SPECIFIED,
+ preValue != GRAMMATICAL_GENDER_NOT_SPECIFIED);
} catch (RemoteException e) {
Log.w(TAG, "Can not update configuration", e);
}
@@ -329,8 +335,9 @@ public class GrammaticalInflectionService extends SystemService {
private void checkCallerIsSystem() {
int callingUid = Binder.getCallingUid();
- if (callingUid != Process.SYSTEM_UID && callingUid != Process.SHELL_UID) {
- throw new SecurityException("Caller is not system and shell.");
+ if (callingUid != Process.SYSTEM_UID && callingUid != Process.SHELL_UID
+ && callingUid != Process.ROOT_UID) {
+ throw new SecurityException("Caller is not system, shell and root.");
}
}
@@ -354,12 +361,11 @@ public class GrammaticalInflectionService extends SystemService {
final File file = getGrammaticalGenderFile(userId);
synchronized (mLock) {
if (!file.exists()) {
- Log.d(TAG, "User " + userId + "doesn't have the grammatical gender file.");
+ Log.d(TAG, "User " + userId + " doesn't have the grammatical gender file.");
return;
}
if (mGrammaticalGenderCache.indexOfKey(userId) < 0) {
- try {
- InputStream in = new FileInputStream(file);
+ try (FileInputStream in = new FileInputStream(file)) {
final TypedXmlPullParser parser = Xml.resolvePullParser(in);
mGrammaticalGenderCache.put(userId, getGrammaticalGenderFromXml(parser));
} catch (IOException | XmlPullParserException e) {
diff --git a/services/core/java/com/android/server/input/InputManagerInternal.java b/services/core/java/com/android/server/input/InputManagerInternal.java
index b963a4b614e8..7e190ddbb24f 100644
--- a/services/core/java/com/android/server/input/InputManagerInternal.java
+++ b/services/core/java/com/android/server/input/InputManagerInternal.java
@@ -61,20 +61,26 @@ public abstract class InputManagerInternal {
public abstract void setPulseGestureEnabled(boolean enabled);
/**
- * Atomically transfers touch focus from one window to another as identified by
- * their input channels. It is possible for multiple windows to have
- * touch focus if they support split touch dispatch
- * {@link android.view.WindowManager.LayoutParams#FLAG_SPLIT_TOUCH} but this
- * method only transfers touch focus of the specified window without affecting
- * other windows that may also have touch focus at the same time.
+ * Atomically transfers an active touch gesture from one window to another, as identified by
+ * their input channels.
*
- * @param fromChannelToken The channel token of a window that currently has touch focus.
- * @param toChannelToken The channel token of the window that should receive touch focus in
- * place of the first.
- * @return {@code true} if the transfer was successful. {@code false} if the window with the
- * specified channel did not actually have touch focus at the time of the request.
+ * <p>Only the touch gesture that is currently being dispatched to a window associated with
+ * {@code fromChannelToken} will be effected. That window will no longer receive
+ * the touch gesture (i.e. it will receive {@link android.view.MotionEvent#ACTION_CANCEL}).
+ * A window associated with the {@code toChannelToken} will receive the rest of the gesture
+ * (i.e. beginning with {@link android.view.MotionEvent#ACTION_DOWN} or
+ * {@link android.view.MotionEvent#ACTION_POINTER_DOWN}).
+ *
+ * <p>Transferring touch gestures will have no impact on focused windows. If the {@code
+ * toChannelToken} window is focusable, this will not bring focus to that window.
+ *
+ * @param fromChannelToken The channel token of a window that has an active touch gesture.
+ * @param toChannelToken The channel token of the window that should receive the gesture in
+ * place of the first.
+ * @return True if the transfer was successful. False if the specified windows don't exist, or
+ * if the source window is not actively receiving a touch gesture at the time of the request.
*/
- public abstract boolean transferTouchFocus(@NonNull IBinder fromChannelToken,
+ public abstract boolean transferTouchGesture(@NonNull IBinder fromChannelToken,
@NonNull IBinder toChannelToken);
/**
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 574be34e56e5..7b18fb6a7c35 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -737,7 +737,9 @@ public class InputManagerService extends IInputManager.Stub
* @param destChannelToken The token of the window or input channel that should receive the
* gesture
* @return True if the transfer succeeded, false if there was no active touch gesture happening
+ * @deprecated Use {@link #transferTouchGesture(IBinder, IBinder)}
*/
+ @Deprecated
public boolean transferTouch(IBinder destChannelToken, int displayId) {
// TODO(b/162194035): Replace this with a SPY window
Objects.requireNonNull(destChannelToken, "destChannelToken must not be null");
@@ -1343,43 +1345,44 @@ public class InputManagerService extends IInputManager.Stub
}
/**
- * Atomically transfers touch focus from one window to another as identified by
- * their input channels. It is possible for multiple windows to have
- * touch focus if they support split touch dispatch
- * {@link android.view.WindowManager.LayoutParams#FLAG_SPLIT_TOUCH} but this
- * method only transfers touch focus of the specified window without affecting
- * other windows that may also have touch focus at the same time.
- * @param fromChannel The channel of a window that currently has touch focus.
- * @param toChannel The channel of the window that should receive touch focus in
- * place of the first.
- * @param isDragDrop True if transfer touch focus for drag and drop.
- * @return True if the transfer was successful. False if the window with the
- * specified channel did not actually have touch focus at the time of the request.
+ * Start drag and drop.
+ *
+ * @param fromChannel The input channel that is currently receiving a touch gesture that should
+ * be turned into the drag pointer.
+ * @param dragAndDropChannel The input channel associated with the system drag window.
+ * @return true if drag and drop was successfully started, false otherwise.
*/
- public boolean transferTouchFocus(@NonNull InputChannel fromChannel,
- @NonNull InputChannel toChannel, boolean isDragDrop) {
- return mNative.transferTouchFocus(fromChannel.getToken(), toChannel.getToken(),
- isDragDrop);
+ public boolean startDragAndDrop(@NonNull InputChannel fromChannel,
+ @NonNull InputChannel dragAndDropChannel) {
+ return mNative.transferTouchGesture(fromChannel.getToken(), dragAndDropChannel.getToken(),
+ true /* isDragDrop */);
}
/**
- * Atomically transfers touch focus from one window to another as identified by
- * their input channels. It is possible for multiple windows to have
- * touch focus if they support split touch dispatch
- * {@link android.view.WindowManager.LayoutParams#FLAG_SPLIT_TOUCH} but this
- * method only transfers touch focus of the specified window without affecting
- * other windows that may also have touch focus at the same time.
- * @param fromChannelToken The channel token of a window that currently has touch focus.
- * @param toChannelToken The channel token of the window that should receive touch focus in
- * place of the first.
- * @return True if the transfer was successful. False if the window with the
- * specified channel did not actually have touch focus at the time of the request.
+ * Atomically transfers an active touch gesture from one window to another, as identified by
+ * their input channels.
+ *
+ * <p>Only the touch gesture that is currently being dispatched to a window associated with
+ * {@code fromChannelToken} will be effected. That window will no longer receive
+ * the touch gesture (i.e. it will receive {@link android.view.MotionEvent#ACTION_CANCEL}).
+ * A window associated with the {@code toChannelToken} will receive the rest of the gesture
+ * (i.e. beginning with {@link android.view.MotionEvent#ACTION_DOWN} or
+ * {@link android.view.MotionEvent#ACTION_POINTER_DOWN}).
+ *
+ * <p>Transferring touch gestures will have no impact on focused windows. If the {@code
+ * toChannelToken} window is focusable, this will not bring focus to that window.
+ *
+ * @param fromChannelToken The channel token of a window that has an active touch gesture.
+ * @param toChannelToken The channel token of the window that should receive the gesture in
+ * place of the first.
+ * @return True if the transfer was successful. False if the specified windows don't exist, or
+ * if the source window is not actively receiving a touch gesture at the time of the request.
*/
- public boolean transferTouchFocus(@NonNull IBinder fromChannelToken,
+ public boolean transferTouchGesture(@NonNull IBinder fromChannelToken,
@NonNull IBinder toChannelToken) {
Objects.requireNonNull(fromChannelToken);
Objects.requireNonNull(toChannelToken);
- return mNative.transferTouchFocus(fromChannelToken, toChannelToken,
+ return mNative.transferTouchGesture(fromChannelToken, toChannelToken,
false /* isDragDrop */);
}
@@ -3312,9 +3315,9 @@ public class InputManagerService extends IInputManager.Stub
}
@Override
- public boolean transferTouchFocus(@NonNull IBinder fromChannelToken,
+ public boolean transferTouchGesture(@NonNull IBinder fromChannelToken,
@NonNull IBinder toChannelToken) {
- return InputManagerService.this.transferTouchFocus(fromChannelToken, toChannelToken);
+ return InputManagerService.this.transferTouchGesture(fromChannelToken, toChannelToken);
}
@Override
diff --git a/services/core/java/com/android/server/input/KeyboardMetricsCollector.java b/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
index ebc784d763ef..4b9f2cf9d0c0 100644
--- a/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
+++ b/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
@@ -227,7 +227,12 @@ public final class KeyboardMetricsCollector {
"LAUNCH_DEFAULT_FITNESS"),
LAUNCH_APPLICATION_BY_PACKAGE_NAME(
FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_APPLICATION_BY_PACKAGE_NAME,
- "LAUNCH_APPLICATION_BY_PACKAGE_NAME");
+ "LAUNCH_APPLICATION_BY_PACKAGE_NAME"),
+ DESKTOP_MODE(
+ FrameworkStatsLog
+ .KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__DESKTOP_MODE,
+ "DESKTOP_MODE");
+
private final int mValue;
private final String mName;
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index b16df0f8a28a..972a9e34f496 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -110,13 +110,15 @@ interface NativeInputManagerService {
void setMinTimeBetweenUserActivityPokes(long millis);
- boolean transferTouchFocus(IBinder fromChannelToken, IBinder toChannelToken,
+ boolean transferTouchGesture(IBinder fromChannelToken, IBinder toChannelToken,
boolean isDragDrop);
/**
* Transfer the current touch gesture to the window identified by 'destChannelToken' positioned
* on display with id 'displayId'.
+ * @deprecated Use {@link #transferTouchGesture(IBinder, IBinder, boolean)}
*/
+ @Deprecated
boolean transferTouch(IBinder destChannelToken, int displayId);
int getMousePointerSpeed();
@@ -359,10 +361,11 @@ interface NativeInputManagerService {
public native void setMinTimeBetweenUserActivityPokes(long millis);
@Override
- public native boolean transferTouchFocus(IBinder fromChannelToken, IBinder toChannelToken,
+ public native boolean transferTouchGesture(IBinder fromChannelToken, IBinder toChannelToken,
boolean isDragDrop);
@Override
+ @Deprecated
public native boolean transferTouch(IBinder destChannelToken, int displayId);
@Override
diff --git a/services/core/java/com/android/server/inputmethod/IInputMethodClientInvoker.java b/services/core/java/com/android/server/inputmethod/IInputMethodClientInvoker.java
index 977dbff0b02e..84a59b4d28e4 100644
--- a/services/core/java/com/android/server/inputmethod/IInputMethodClientInvoker.java
+++ b/services/core/java/com/android/server/inputmethod/IInputMethodClientInvoker.java
@@ -117,6 +117,30 @@ final class IInputMethodClientInvoker {
}
@AnyThread
+ void onStartInputResult(@NonNull InputBindResult res, int startInputSeq) {
+ if (mIsProxy) {
+ onStartInputResultInternal(res, startInputSeq);
+ } else {
+ mHandler.post(() -> onStartInputResultInternal(res, startInputSeq));
+ }
+ }
+
+ @AnyThread
+ private void onStartInputResultInternal(@NonNull InputBindResult res, int startInputSeq) {
+ try {
+ mTarget.onStartInputResult(res, startInputSeq);
+ } catch (RemoteException e) {
+ logRemoteException(e);
+ } finally {
+ // Dispose the channel if the input method is not local to this process
+ // because the remote proxy will get its own copy when unparceled.
+ if (res.channel != null && mIsProxy) {
+ res.channel.dispose();
+ }
+ }
+ }
+
+ @AnyThread
void onBindAccessibilityService(@NonNull InputBindResult res, int id) {
if (mIsProxy) {
onBindAccessibilityServiceInternal(res, id);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index bc169ca40117..76956c88695d 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -148,6 +148,7 @@ import com.android.internal.content.PackageMonitor;
import com.android.internal.infra.AndroidFuture;
import com.android.internal.inputmethod.DirectBootAwareness;
import com.android.internal.inputmethod.IAccessibilityInputMethodSession;
+import com.android.internal.inputmethod.IBooleanListener;
import com.android.internal.inputmethod.IConnectionlessHandwritingCallback;
import com.android.internal.inputmethod.IImeTracker;
import com.android.internal.inputmethod.IInlineSuggestionsRequestCallback;
@@ -1539,7 +1540,13 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
@Override
public void onStart() {
mService.publishLocalService();
- publishBinderService(Context.INPUT_METHOD_SERVICE, mService, false /*allowIsolated*/,
+ IInputMethodManager.Stub service;
+ if (Flags.useZeroJankProxy()) {
+ service = new ZeroJankProxy(mService.mHandler::post, mService);
+ } else {
+ service = mService;
+ }
+ publishBinderService(Context.INPUT_METHOD_SERVICE, service, false /*allowIsolated*/,
DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_NORMAL | DUMP_FLAG_PROTO);
}
@@ -2216,6 +2223,14 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
}
}
+ @Nullable
+ ClientState getClientState(IInputMethodClient client) {
+ synchronized (ImfLock.class) {
+ return mClientController.getClient(client.asBinder());
+ }
+ }
+
+ // TODO(b/314150112): Move this to ClientController.
@GuardedBy("ImfLock.class")
void unbindCurrentClientLocked(@UnbindReason int unbindClientReason) {
if (mCurClient != null) {
@@ -2421,20 +2436,17 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
return InputBindResult.NOT_IME_TARGET_WINDOW;
}
final int csDisplayId = cs.mSelfReportedDisplayId;
- final int oldDisplayIdToShowIme = mDisplayIdToShowIme;
mDisplayIdToShowIme = mVisibilityStateComputer.computeImeDisplayId(winState, csDisplayId);
// 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) {
- mVisibilityStateComputer.getImePolicy().setImeHiddenByDisplayPolicy(true);
- } else if (!Objects.equals(deviceMethodId, selectedMethodId)) {
- setInputMethodLocked(deviceMethodId, NOT_A_SUBTYPE_ID, mDeviceIdToShowIme);
- selectedMethodId = deviceMethodId;
- }
+ final String deviceMethodId = computeCurrentDeviceMethodIdLocked(selectedMethodId);
+ if (deviceMethodId == null) {
+ mVisibilityStateComputer.getImePolicy().setImeHiddenByDisplayPolicy(true);
+ } else if (!Objects.equals(deviceMethodId, selectedMethodId)) {
+ setInputMethodLocked(deviceMethodId, NOT_A_SUBTYPE_ID, mDeviceIdToShowIme);
+ selectedMethodId = deviceMethodId;
}
if (mVisibilityStateComputer.getImePolicy().isImeHiddenByDisplayPolicy()) {
@@ -2534,10 +2546,10 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
final int oldDeviceId = mDeviceIdToShowIme;
mDeviceIdToShowIme = mVdmInternal.getDeviceIdForDisplayId(mDisplayIdToShowIme);
- if (mDeviceIdToShowIme == oldDeviceId) {
- return currentMethodId;
- }
if (mDeviceIdToShowIme == DEVICE_ID_DEFAULT) {
+ if (oldDeviceId == DEVICE_ID_DEFAULT) {
+ return currentMethodId;
+ }
final String defaultDeviceMethodId = mSettings.getSelectedDefaultDeviceInputMethod();
if (DEBUG) {
Slog.v(TAG, "Restoring default device input method: " + defaultDeviceMethodId);
@@ -3530,6 +3542,19 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
}
@Override
+ public void acceptStylusHandwritingDelegationAsync(
+ @NonNull IInputMethodClient client,
+ @UserIdInt int userId,
+ @NonNull String delegatePackageName,
+ @NonNull String delegatorPackageName,
+ @InputMethodManager.HandwritingDelegateFlags int flags, IBooleanListener callback)
+ throws RemoteException {
+ boolean result = acceptStylusHandwritingDelegation(
+ client, userId, delegatePackageName, delegatorPackageName, flags);
+ callback.onResult(result);
+ }
+
+ @Override
public boolean acceptStylusHandwritingDelegation(
@NonNull IInputMethodClient client,
@UserIdInt int userId,
@@ -3741,6 +3766,20 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
return imeClientFocus == WindowManagerInternal.ImeClientFocusResult.HAS_IME_FOCUS;
}
+ //TODO(b/293640003): merge with startInputOrWindowGainedFocus once Flags.useZeroJankProxy()
+ // is enabled.
+ @Override
+ public void startInputOrWindowGainedFocusAsync(
+ @StartInputReason int startInputReason, IInputMethodClient client, IBinder windowToken,
+ @StartInputFlags int startInputFlags, @SoftInputModeFlags int softInputMode,
+ int windowFlags, @Nullable EditorInfo editorInfo,
+ IRemoteInputConnection inputConnection,
+ IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
+ int unverifiedTargetSdkVersion, @UserIdInt int userId,
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher, int startInputSeq) {
+ // implemented by ZeroJankProxy
+ }
+
@NonNull
@Override
public InputBindResult startInputOrWindowGainedFocus(
@@ -5808,7 +5847,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
}
curHostInputToken = mCurHostInputToken;
}
- return mInputManagerInternal.transferTouchFocus(sourceInputToken, curHostInputToken);
+ return mInputManagerInternal.transferTouchGesture(sourceInputToken, curHostInputToken);
}
@Override
diff --git a/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java b/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
new file mode 100644
index 000000000000..62d44557ae4c
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
@@ -0,0 +1,420 @@
+/*
+ * 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.
+ */
+
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import static com.android.server.inputmethod.InputMethodManagerService.TAG;
+
+import android.Manifest;
+import android.annotation.BinderThread;
+import android.annotation.EnforcePermission;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.UserIdInt;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
+import android.util.ExceptionUtils;
+import android.util.Slog;
+import android.view.WindowManager;
+import android.view.inputmethod.CursorAnchorInfo;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.ImeTracker;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.InputMethodSubtype;
+import android.window.ImeOnBackInvokedDispatcher;
+
+import com.android.internal.inputmethod.DirectBootAwareness;
+import com.android.internal.inputmethod.IBooleanListener;
+import com.android.internal.inputmethod.IConnectionlessHandwritingCallback;
+import com.android.internal.inputmethod.IImeTracker;
+import com.android.internal.inputmethod.IInputMethodClient;
+import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
+import com.android.internal.inputmethod.IRemoteInputConnection;
+import com.android.internal.inputmethod.InputBindResult;
+import com.android.internal.inputmethod.SoftInputShowHideReason;
+import com.android.internal.inputmethod.StartInputFlags;
+import com.android.internal.inputmethod.StartInputReason;
+import com.android.internal.util.FunctionalUtils.ThrowingRunnable;
+import com.android.internal.view.IInputMethodManager;
+
+import java.io.FileDescriptor;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+
+/**
+ * A proxy that processes all {@link IInputMethodManager} calls asynchronously.
+ * @hide
+ */
+public class ZeroJankProxy extends IInputMethodManager.Stub {
+
+ private final IInputMethodManager mInner;
+ private final Executor mExecutor;
+
+ ZeroJankProxy(Executor executor, IInputMethodManager inner) {
+ mInner = inner;
+ mExecutor = executor;
+ }
+
+ private void offload(ThrowingRunnable r) {
+ offloadInner(r);
+ }
+
+ private void offload(Runnable r) {
+ offloadInner(r);
+ }
+
+ private void offloadInner(Runnable r) {
+ boolean useThrowingRunnable = r instanceof ThrowingRunnable;
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> {
+ final long inner = Binder.clearCallingIdentity();
+ // Restoring calling identity, so we can still do permission checks on caller.
+ Binder.restoreCallingIdentity(identity);
+ try {
+ try {
+ if (useThrowingRunnable) {
+ ((ThrowingRunnable) r).runOrThrow();
+ } else {
+ r.run();
+ }
+ } catch (Exception e) {
+ Slog.e(TAG, "Error in async call", e);
+ throw ExceptionUtils.propagate(e);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(inner);
+ }
+ });
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void addClient(IInputMethodClient client, IRemoteInputConnection inputConnection,
+ int selfReportedDisplayId) throws RemoteException {
+ offload(() -> mInner.addClient(client, inputConnection, selfReportedDisplayId));
+ }
+
+ @Override
+ public InputMethodInfo getCurrentInputMethodInfoAsUser(int userId) throws RemoteException {
+ return mInner.getCurrentInputMethodInfoAsUser(userId);
+ }
+
+ @Override
+ public List<InputMethodInfo> getInputMethodList(
+ int userId, @DirectBootAwareness int directBootAwareness) throws RemoteException {
+ return mInner.getInputMethodList(userId, directBootAwareness);
+ }
+
+ @Override
+ public List<InputMethodInfo> getEnabledInputMethodList(int userId) throws RemoteException {
+ return mInner.getEnabledInputMethodList(userId);
+ }
+
+ @Override
+ public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(String imiId,
+ boolean allowsImplicitlyEnabledSubtypes, int userId)
+ throws RemoteException {
+ return mInner.getEnabledInputMethodSubtypeList(imiId, allowsImplicitlyEnabledSubtypes,
+ userId);
+ }
+
+ @Override
+ public InputMethodSubtype getLastInputMethodSubtype(int userId) throws RemoteException {
+ return mInner.getLastInputMethodSubtype(userId);
+ }
+
+ @Override
+ public boolean showSoftInput(IInputMethodClient client, IBinder windowToken,
+ @Nullable ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags,
+ int lastClickTooType, ResultReceiver resultReceiver,
+ @SoftInputShowHideReason int reason)
+ throws RemoteException {
+ offload(() -> mInner.showSoftInput(client, windowToken, statsToken, flags, lastClickTooType,
+ resultReceiver, reason));
+ return true;
+ }
+
+ @Override
+ public boolean hideSoftInput(IInputMethodClient client, IBinder windowToken,
+ @Nullable ImeTracker.Token statsToken, @InputMethodManager.HideFlags int flags,
+ ResultReceiver resultReceiver, @SoftInputShowHideReason int reason)
+ throws RemoteException {
+ offload(() -> mInner.hideSoftInput(client, windowToken, statsToken, flags, resultReceiver,
+ reason));
+ return true;
+ }
+
+ @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+ @Override
+ public void startInputOrWindowGainedFocusAsync(
+ @StartInputReason int startInputReason,
+ IInputMethodClient client, IBinder windowToken,
+ @StartInputFlags int startInputFlags,
+ @WindowManager.LayoutParams.SoftInputModeFlags int softInputMode,
+ int windowFlags, @Nullable EditorInfo editorInfo,
+ IRemoteInputConnection inputConnection,
+ IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
+ int unverifiedTargetSdkVersion, @UserIdInt int userId,
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher, int startInputSeq)
+ throws RemoteException {
+ offload(() -> {
+ InputBindResult result = mInner.startInputOrWindowGainedFocus(startInputReason, client,
+ windowToken, startInputFlags, softInputMode, windowFlags,
+ editorInfo,
+ inputConnection, remoteAccessibilityInputConnection,
+ unverifiedTargetSdkVersion,
+ userId, imeDispatcher);
+ sendOnStartInputResult(client, result, startInputSeq);
+ });
+ }
+
+ @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+ @Override
+ public InputBindResult startInputOrWindowGainedFocus(
+ @StartInputReason int startInputReason,
+ IInputMethodClient client, IBinder windowToken,
+ @StartInputFlags int startInputFlags,
+ @WindowManager.LayoutParams.SoftInputModeFlags int softInputMode,
+ int windowFlags, @Nullable EditorInfo editorInfo,
+ IRemoteInputConnection inputConnection,
+ IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
+ int unverifiedTargetSdkVersion, @UserIdInt int userId,
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher)
+ throws RemoteException {
+ // Should never be called when flag is enabled i.e. when this proxy is used.
+ return null;
+ }
+
+ @Override
+ public void showInputMethodPickerFromClient(IInputMethodClient client,
+ int auxiliarySubtypeMode)
+ throws RemoteException {
+ offload(() -> mInner.showInputMethodPickerFromClient(client, auxiliarySubtypeMode));
+ }
+
+ @EnforcePermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+ @Override
+ public void showInputMethodPickerFromSystem(int auxiliarySubtypeMode, int displayId)
+ throws RemoteException {
+ mInner.showInputMethodPickerFromSystem(auxiliarySubtypeMode, displayId);
+ }
+
+ @EnforcePermission(Manifest.permission.TEST_INPUT_METHOD)
+ @Override
+ public boolean isInputMethodPickerShownForTest() throws RemoteException {
+ super.isInputMethodPickerShownForTest_enforcePermission();
+ return mInner.isInputMethodPickerShownForTest();
+ }
+
+ @Override
+ public InputMethodSubtype getCurrentInputMethodSubtype(int userId) throws RemoteException {
+ return mInner.getCurrentInputMethodSubtype(userId);
+ }
+
+ @Override
+ public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes,
+ @UserIdInt int userId) throws RemoteException {
+ mInner.setAdditionalInputMethodSubtypes(imiId, subtypes, userId);
+ }
+
+ @Override
+ public void setExplicitlyEnabledInputMethodSubtypes(String imeId,
+ @NonNull int[] subtypeHashCodes, @UserIdInt int userId) throws RemoteException {
+ mInner.setExplicitlyEnabledInputMethodSubtypes(imeId, subtypeHashCodes, userId);
+ }
+
+ @Override
+ public int getInputMethodWindowVisibleHeight(IInputMethodClient client)
+ throws RemoteException {
+ return mInner.getInputMethodWindowVisibleHeight(client);
+ }
+
+ @Override
+ public void reportPerceptibleAsync(IBinder windowToken, boolean perceptible)
+ throws RemoteException {
+ // Already async TODO(b/293640003): ordering issues?
+ mInner.reportPerceptibleAsync(windowToken, perceptible);
+ }
+
+ @EnforcePermission(Manifest.permission.INTERNAL_SYSTEM_WINDOW)
+ @Override
+ public void removeImeSurface() throws RemoteException {
+ mInner.removeImeSurface();
+ }
+
+ @Override
+ public void removeImeSurfaceFromWindowAsync(IBinder windowToken) throws RemoteException {
+ mInner.removeImeSurfaceFromWindowAsync(windowToken);
+ }
+
+ @Override
+ public void startProtoDump(byte[] bytes, int i, String s) throws RemoteException {
+ mInner.startProtoDump(bytes, i, s);
+ }
+
+ @Override
+ public boolean isImeTraceEnabled() throws RemoteException {
+ return mInner.isImeTraceEnabled();
+ }
+
+ @EnforcePermission(Manifest.permission.CONTROL_UI_TRACING)
+ @Override
+ public void startImeTrace() throws RemoteException {
+ mInner.startImeTrace();
+ }
+
+ @EnforcePermission(Manifest.permission.CONTROL_UI_TRACING)
+ @Override
+ public void stopImeTrace() throws RemoteException {
+ mInner.stopImeTrace();
+ }
+
+ @Override
+ public void startStylusHandwriting(IInputMethodClient client)
+ throws RemoteException {
+ offload(() -> mInner.startStylusHandwriting(client));
+ }
+
+ @Override
+ public void startConnectionlessStylusHandwriting(IInputMethodClient client, int userId,
+ @Nullable CursorAnchorInfo cursorAnchorInfo, @Nullable String delegatePackageName,
+ @Nullable String delegatorPackageName,
+ @NonNull IConnectionlessHandwritingCallback callback) throws RemoteException {
+ offload(() -> mInner.startConnectionlessStylusHandwriting(
+ client, userId, cursorAnchorInfo, delegatePackageName, delegatorPackageName,
+ callback));
+ }
+
+ @Override
+ public boolean acceptStylusHandwritingDelegation(
+ @NonNull IInputMethodClient client,
+ @UserIdInt int userId,
+ @NonNull String delegatePackageName,
+ @NonNull String delegatorPackageName,
+ @InputMethodManager.HandwritingDelegateFlags int flags) {
+ try {
+ return CompletableFuture.supplyAsync(() -> {
+ try {
+ return mInner.acceptStylusHandwritingDelegation(
+ client, userId, delegatePackageName, delegatorPackageName, flags);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }, this::offload).get();
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ } catch (ExecutionException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void acceptStylusHandwritingDelegationAsync(
+ @NonNull IInputMethodClient client,
+ @UserIdInt int userId,
+ @NonNull String delegatePackageName,
+ @NonNull String delegatorPackageName,
+ @InputMethodManager.HandwritingDelegateFlags int flags, IBooleanListener callback)
+ throws RemoteException {
+ offload(() -> mInner.acceptStylusHandwritingDelegationAsync(
+ client, userId, delegatePackageName, delegatorPackageName, flags, callback));
+ }
+
+ @Override
+ public void prepareStylusHandwritingDelegation(
+ @NonNull IInputMethodClient client,
+ @UserIdInt int userId,
+ @NonNull String delegatePackageName,
+ @NonNull String delegatorPackageName) {
+ offload(() -> mInner.prepareStylusHandwritingDelegation(
+ client, userId, delegatePackageName, delegatorPackageName));
+ }
+
+ @Override
+ public boolean isStylusHandwritingAvailableAsUser(int userId, boolean connectionless)
+ throws RemoteException {
+ return mInner.isStylusHandwritingAvailableAsUser(userId, connectionless);
+ }
+
+ @EnforcePermission("android.permission.TEST_INPUT_METHOD")
+ @Override
+ public void addVirtualStylusIdForTestSession(IInputMethodClient client)
+ throws RemoteException {
+ mInner.addVirtualStylusIdForTestSession(client);
+ }
+
+ @EnforcePermission("android.permission.TEST_INPUT_METHOD")
+ @Override
+ public void setStylusWindowIdleTimeoutForTest(IInputMethodClient client, long timeout)
+ throws RemoteException {
+ mInner.setStylusWindowIdleTimeoutForTest(client, timeout);
+ }
+
+ @Override
+ public IImeTracker getImeTrackerService() throws RemoteException {
+ return mInner.getImeTrackerService();
+ }
+
+ @BinderThread
+ @Override
+ public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
+ @Nullable FileDescriptor err,
+ @NonNull String[] args, @Nullable ShellCallback callback,
+ @NonNull ResultReceiver resultReceiver) throws RemoteException {
+ ((InputMethodManagerService) mInner).onShellCommand(
+ in, out, err, args, callback, resultReceiver);
+ }
+
+ private void sendOnStartInputResult(
+ IInputMethodClient client, InputBindResult res, int startInputSeq) {
+ InputMethodManagerService service = (InputMethodManagerService) mInner;
+ final ClientState cs = service.getClientState(client);
+ if (cs != null && cs.mClient != null) {
+ cs.mClient.onStartInputResult(res, startInputSeq);
+ } else {
+ // client is unbound.
+ Slog.i(TAG, "Client that requested startInputOrWindowGainedFocus is no longer"
+ + " bound. InputBindResult: " + res + " for startInputSeq: " + startInputSeq);
+ }
+ }
+}
+
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index c5f38553ed81..75be0684c8f4 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -1346,7 +1346,9 @@ public class LocationManagerService extends ILocationManager.Stub implements
"setAutomotiveGnssSuspended only allowed on automotive devices");
}
- mGnssManagerService.setAutomotiveGnssSuspended(suspended);
+ if (mGnssManagerService != null) {
+ mGnssManagerService.setAutomotiveGnssSuspended(suspended);
+ }
}
@android.annotation.EnforcePermission(android.Manifest.permission.CONTROL_AUTOMOTIVE_GNSS)
@@ -1361,7 +1363,10 @@ public class LocationManagerService extends ILocationManager.Stub implements
"isAutomotiveGnssSuspended only allowed on automotive devices");
}
- return mGnssManagerService.isAutomotiveGnssSuspended();
+ if (mGnssManagerService != null) {
+ return mGnssManagerService.isAutomotiveGnssSuspended();
+ }
+ return false;
}
@Override
diff --git a/services/core/java/com/android/server/media/MediaSession2Record.java b/services/core/java/com/android/server/media/MediaSession2Record.java
index 34bb219a9162..db70ce281eb5 100644
--- a/services/core/java/com/android/server/media/MediaSession2Record.java
+++ b/services/core/java/com/android/server/media/MediaSession2Record.java
@@ -144,7 +144,7 @@ public class MediaSession2Record implements MediaSessionRecordImpl {
@Override
public boolean checkPlaybackActiveState(boolean expected) {
synchronized (mLock) {
- return mIsConnected && mController.isPlaybackActive() == expected;
+ return (mIsConnected && mController.isPlaybackActive()) == expected;
}
}
@@ -224,10 +224,6 @@ public class MediaSession2Record implements MediaSessionRecordImpl {
mIsConnected = true;
service = mService;
}
-
- // TODO (b/318745416): Add support for FGS in MediaSession2. Passing a
- // null playback state means the owning process will not be allowed to
- // run in the foreground.
service.onSessionActiveStateChanged(MediaSession2Record.this,
/* playbackState= */ null);
}
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 3e8af27d2584..757b26c45ab1 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -312,18 +312,27 @@ public class MediaSessionService extends SystemService implements Monitor {
}
user.mPriorityStack.onSessionActiveStateChanged(record);
}
- boolean allowRunningInForeground = record.isActive()
- && (playbackState == null || playbackState.isActive());
+ boolean isUserEngaged = isUserEngaged(record, playbackState);
Log.d(TAG, "onSessionActiveStateChanged: "
+ "record=" + record
+ "playbackState=" + playbackState
- + "allowRunningInForeground=" + allowRunningInForeground);
- setForegroundServiceAllowance(record, allowRunningInForeground);
+ + "allowRunningInForeground=" + isUserEngaged);
+ setForegroundServiceAllowance(record, /* allowRunningInForeground= */ isUserEngaged);
+ reportMediaInteractionEvent(record, isUserEngaged);
mHandler.postSessionsChanged(record);
}
}
+ private boolean isUserEngaged(MediaSessionRecordImpl record,
+ @Nullable PlaybackState playbackState) {
+ if (playbackState == null) {
+ // MediaSession2 case
+ return record.checkPlaybackActiveState(/* expected= */ true);
+ }
+ return playbackState.isActive() && record.isActive();
+ }
+
// Currently only media1 can become global priority session.
void setGlobalPrioritySession(MediaSessionRecord record) {
synchronized (mLock) {
@@ -416,14 +425,13 @@ public class MediaSessionService extends SystemService implements Monitor {
return;
}
user.mPriorityStack.onPlaybackStateChanged(record, shouldUpdatePriority);
- if (playbackState != null) {
- boolean allowRunningInForeground = playbackState.isActive() && record.isActive();
- Log.d(TAG, "onSessionPlaybackStateChanged: "
- + "record=" + record
- + "playbackState=" + playbackState
- + "allowRunningInForeground=" + allowRunningInForeground);
- setForegroundServiceAllowance(record, allowRunningInForeground);
- }
+ boolean isUserEngaged = isUserEngaged(record, playbackState);
+ Log.d(TAG, "onSessionPlaybackStateChanged: "
+ + "record=" + record
+ + "playbackState=" + playbackState
+ + "allowRunningInForeground=" + isUserEngaged);
+ setForegroundServiceAllowance(record, /* allowRunningInForeground= */ isUserEngaged);
+ reportMediaInteractionEvent(record, isUserEngaged);
}
}
@@ -590,6 +598,7 @@ public class MediaSessionService extends SystemService implements Monitor {
Log.d(TAG, "destroySessionLocked: record=" + session);
setForegroundServiceAllowance(session, /* allowRunningInForeground= */ false);
+ reportMediaInteractionEvent(session, /* userEngaged= */ false);
mHandler.postSessionsChanged(session);
}
@@ -608,11 +617,9 @@ public class MediaSessionService extends SystemService implements Monitor {
if (allowRunningInForeground) {
mActivityManagerInternal.startForegroundServiceDelegate(
foregroundServiceDelegationOptions, /* connection= */ null);
- reportMediaInteractionEvent(record, /* userEngaged= */ true);
} else {
mActivityManagerInternal.stopForegroundServiceDelegate(
foregroundServiceDelegationOptions);
- reportMediaInteractionEvent(record, /* userEngaged= */ false);
}
}
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 796d8d73fc51..b5c51af47009 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -340,8 +340,6 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
static final String TAG = NetworkPolicyLogger.TAG;
private static final boolean LOGD = NetworkPolicyLogger.LOGD;
private static final boolean LOGV = NetworkPolicyLogger.LOGV;
- // TODO: b/304347838 - Remove once the feature is in staging.
- private static final boolean ALWAYS_RESTRICT_BACKGROUND_NETWORK = false;
/**
* No opportunistic quota could be calculated from user data plan or data settings.
@@ -1063,8 +1061,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
}
// The flag is boot-stable.
- mBackgroundNetworkRestricted = ALWAYS_RESTRICT_BACKGROUND_NETWORK
- && Flags.networkBlockedForTopSleepingAndAbove();
+ mBackgroundNetworkRestricted = Flags.networkBlockedForTopSleepingAndAbove();
if (mBackgroundNetworkRestricted) {
// Firewall rules and UidBlockedState will get updated in
// updateRulesForGlobalChangeAL below.
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 53ae60b0cc76..7455fe0043f8 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -5720,6 +5720,14 @@ public class NotificationManagerService extends SystemService {
}
@Override
+ @Condition.State
+ public int getAutomaticZenRuleState(@NonNull String id) {
+ Objects.requireNonNull(id, "id is null");
+ enforcePolicyAccess(Binder.getCallingUid(), "getAutomaticZenRuleState");
+ return mZenModeHelper.getAutomaticZenRuleState(id);
+ }
+
+ @Override
public void setAutomaticZenRuleState(String id, Condition condition) {
Objects.requireNonNull(id, "id is null");
Objects.requireNonNull(condition, "Condition is null");
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 54de1976edb6..eed0eecb0a22 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -123,6 +123,7 @@ import java.time.Clock;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -593,20 +594,20 @@ public class ZenModeHelper {
if (mConfig == null) {
return;
}
+ ZenModeConfig newConfig = mConfig.copy();
+ ZenRule rule = newConfig.automaticRules.get(implicitRuleId(callingPkg));
if (zenMode == Global.ZEN_MODE_OFF) {
// Deactivate implicit rule if it exists and is active; otherwise ignore.
- ZenRule rule = mConfig.automaticRules.get(implicitRuleId(callingPkg));
if (rule != null) {
Condition deactivated = new Condition(rule.conditionId,
mContext.getString(R.string.zen_mode_implicit_deactivated),
Condition.STATE_FALSE);
- setAutomaticZenRuleState(rule.id, deactivated, UPDATE_ORIGIN_APP, callingUid);
+ setAutomaticZenRuleStateLocked(newConfig, Collections.singletonList(rule),
+ deactivated, UPDATE_ORIGIN_APP, callingUid);
}
} else {
// Either create a new rule with a default ZenPolicy, or update an existing rule's
// filter value. In both cases, also activate (and unsnooze) it.
- ZenModeConfig newConfig = mConfig.copy();
- ZenRule rule = newConfig.automaticRules.get(implicitRuleId(callingPkg));
if (rule == null) {
rule = newImplicitZenRule(callingPkg);
@@ -856,6 +857,20 @@ public class ZenModeHelper {
}
}
+ @Condition.State
+ int getAutomaticZenRuleState(String id) {
+ synchronized (mConfigLock) {
+ if (mConfig == null) {
+ return Condition.STATE_UNKNOWN;
+ }
+ ZenRule rule = mConfig.automaticRules.get(id);
+ if (rule == null || !canManageAutomaticZenRule(rule)) {
+ return Condition.STATE_UNKNOWN;
+ }
+ return rule.condition != null ? rule.condition.state : Condition.STATE_FALSE;
+ }
+ }
+
void setAutomaticZenRuleState(String id, Condition condition, @ConfigChangeOrigin int origin,
int callingUid) {
requirePublicOrigin("setAutomaticZenRuleState", origin);
@@ -864,9 +879,17 @@ public class ZenModeHelper {
if (mConfig == null) return;
newConfig = mConfig.copy();
- ArrayList<ZenRule> rules = new ArrayList<>();
- rules.add(newConfig.automaticRules.get(id));
- setAutomaticZenRuleStateLocked(newConfig, rules, condition, origin, callingUid);
+ ZenRule rule = newConfig.automaticRules.get(id);
+ if (Flags.modesApi()) {
+ if (rule != null && canManageAutomaticZenRule(rule)) {
+ setAutomaticZenRuleStateLocked(newConfig, Collections.singletonList(rule),
+ condition, origin, callingUid);
+ }
+ } else {
+ ArrayList<ZenRule> rules = new ArrayList<>();
+ rules.add(rule); // rule may be null and throw NPE in the next method.
+ setAutomaticZenRuleStateLocked(newConfig, rules, condition, origin, callingUid);
+ }
}
}
@@ -878,9 +901,15 @@ public class ZenModeHelper {
if (mConfig == null) return;
newConfig = mConfig.copy();
- setAutomaticZenRuleStateLocked(newConfig,
- findMatchingRules(newConfig, ruleDefinition, condition),
- condition, origin, callingUid);
+ List<ZenRule> matchingRules = findMatchingRules(newConfig, ruleDefinition, condition);
+ if (Flags.modesApi()) {
+ for (int i = matchingRules.size() - 1; i >= 0; i--) {
+ if (!canManageAutomaticZenRule(matchingRules.get(i))) {
+ matchingRules.remove(i);
+ }
+ }
+ }
+ setAutomaticZenRuleStateLocked(newConfig, matchingRules, condition, origin, callingUid);
}
}
@@ -900,8 +929,9 @@ public class ZenModeHelper {
}
}
- private List<ZenRule> findMatchingRules(ZenModeConfig config, Uri id, Condition condition) {
- List<ZenRule> matchingRules= new ArrayList<>();
+ private static List<ZenRule> findMatchingRules(ZenModeConfig config, Uri id,
+ Condition condition) {
+ List<ZenRule> matchingRules = new ArrayList<>();
if (ruleMatches(id, condition, config.manualRule)) {
matchingRules.add(config.manualRule);
} else {
@@ -914,7 +944,7 @@ public class ZenModeHelper {
return matchingRules;
}
- private boolean ruleMatches(Uri id, Condition condition, ZenRule rule) {
+ private static boolean ruleMatches(Uri id, Condition condition, ZenRule rule) {
if (id == null || rule == null || rule.conditionId == null) return false;
if (!rule.conditionId.equals(id)) return false;
if (Objects.equals(condition, rule.condition)) return false;
@@ -1866,12 +1896,14 @@ public class ZenModeHelper {
if (rule.zenMode == Global.ZEN_MODE_NO_INTERRUPTIONS) {
policy.apply(new ZenPolicy.Builder()
.disallowAllSounds()
+ .allowPriorityChannels(false)
.build());
} else if (rule.zenMode == Global.ZEN_MODE_ALARMS) {
policy.apply(new ZenPolicy.Builder()
.disallowAllSounds()
.allowAlarms(true)
.allowMedia(true)
+ .allowPriorityChannels(false)
.build());
} else if (rule.zenPolicy != null) {
policy.apply(rule.zenPolicy);
diff --git a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
new file mode 100644
index 000000000000..a4c4347b7cef
--- /dev/null
+++ b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
@@ -0,0 +1,419 @@
+/*
+ * 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.ondeviceintelligence;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.app.AppGlobals;
+import android.app.ondeviceintelligence.Content;
+import android.app.ondeviceintelligence.DownloadCallback;
+import android.app.ondeviceintelligence.Feature;
+import android.app.ondeviceintelligence.IDownloadCallback;
+import android.app.ondeviceintelligence.IFeatureCallback;
+import android.app.ondeviceintelligence.IFeatureDetailsCallback;
+import android.app.ondeviceintelligence.IListFeaturesCallback;
+import android.app.ondeviceintelligence.IOnDeviceIntelligenceManager;
+import android.app.ondeviceintelligence.IProcessingSignal;
+import android.app.ondeviceintelligence.IResponseCallback;
+import android.app.ondeviceintelligence.IStreamingResponseCallback;
+import android.app.ondeviceintelligence.ITokenCountCallback;
+import android.app.ondeviceintelligence.OnDeviceIntelligenceManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.os.ICancellationSignal;
+import android.os.ParcelFileDescriptor;
+import android.os.PersistableBundle;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.provider.DeviceConfig;
+import android.service.ondeviceintelligence.IOnDeviceTrustedInferenceService;
+import android.service.ondeviceintelligence.IRemoteStorageService;
+import android.text.TextUtils;
+import android.util.Slog;
+
+import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.infra.AndroidFuture;
+import com.android.internal.infra.ServiceConnector;
+import com.android.internal.os.BackgroundThread;
+import com.android.server.SystemService;
+
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * This is the system service for handling calls on the {@link OnDeviceIntelligenceManager}. This
+ * service holds connection references to the underlying remote services i.e. the isolated service
+ * {@link android.service.ondeviceintelligence.OnDeviceTrustedInferenceService} and a regular
+ * service counter part {@link android.service.ondeviceintelligence.OnDeviceIntelligenceService}.
+ *
+ * Note: Both the remote services run under the SYSTEM user, as we cannot have separate instance of
+ * the Inference service for each user, due to possible high memory footprint.
+ *
+ * @hide
+ */
+public class OnDeviceIntelligenceManagerService extends SystemService {
+
+ private static final String TAG = OnDeviceIntelligenceManagerService.class.getSimpleName();
+ private static final String KEY_SERVICE_ENABLED = "service_enabled";
+
+ /** Default value in absence of {@link DeviceConfig} override. */
+ private static final boolean DEFAULT_SERVICE_ENABLED = true;
+ private static final String NAMESPACE_ON_DEVICE_INTELLIGENCE = "ondeviceintelligence";
+
+ private final Context mContext;
+ protected final Object mLock = new Object();
+
+
+ private RemoteOnDeviceTrustedInferenceService mRemoteInferenceService;
+ private RemoteOnDeviceIntelligenceService mRemoteOnDeviceIntelligenceService;
+ volatile boolean mIsServiceEnabled;
+
+ public OnDeviceIntelligenceManagerService(Context context) {
+ super(context);
+ mContext = context;
+ }
+
+ @Override
+ public void onStart() {
+ publishBinderService(
+ Context.ON_DEVICE_INTELLIGENCE_SERVICE, new OnDeviceIntelligenceManagerInternal(),
+ /* allowIsolated = */true);
+ }
+
+
+ @Override
+ public void onBootPhase(int phase) {
+ if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
+ DeviceConfig.addOnPropertiesChangedListener(
+ NAMESPACE_ON_DEVICE_INTELLIGENCE,
+ BackgroundThread.getExecutor(),
+ (properties) -> onDeviceConfigChange(properties.getKeyset()));
+
+ mIsServiceEnabled = isServiceEnabled();
+ }
+ }
+
+ private void onDeviceConfigChange(@NonNull Set<String> keys) {
+ if (keys.contains(KEY_SERVICE_ENABLED)) {
+ mIsServiceEnabled = isServiceEnabled();
+ }
+ }
+
+ private boolean isServiceEnabled() {
+ return DeviceConfig.getBoolean(
+ NAMESPACE_ON_DEVICE_INTELLIGENCE,
+ KEY_SERVICE_ENABLED, DEFAULT_SERVICE_ENABLED);
+ }
+
+ private final class OnDeviceIntelligenceManagerInternal extends
+ IOnDeviceIntelligenceManager.Stub {
+ @Override
+ public void getVersion(RemoteCallback remoteCallback) throws RemoteException {
+ Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getVersion");
+ Objects.requireNonNull(remoteCallback);
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
+ if (!mIsServiceEnabled) {
+ Slog.w(TAG, "Service not available");
+ remoteCallback.sendResult(null);
+ return;
+ }
+ ensureRemoteIntelligenceServiceInitialized();
+ mRemoteOnDeviceIntelligenceService.post(
+ service -> service.getVersion(remoteCallback));
+ }
+
+ @Override
+ public void getFeature(int id, IFeatureCallback featureCallback) throws RemoteException {
+ Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getFeatures");
+ Objects.requireNonNull(featureCallback);
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
+ if (!mIsServiceEnabled) {
+ Slog.w(TAG, "Service not available");
+ featureCallback.onFailure(
+ OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException.ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE,
+ "OnDeviceIntelligenceManagerService is unavailable",
+ new PersistableBundle());
+ return;
+ }
+ ensureRemoteIntelligenceServiceInitialized();
+ mRemoteOnDeviceIntelligenceService.post(
+ service -> service.getFeature(id, featureCallback));
+ }
+
+ @Override
+ public void listFeatures(IListFeaturesCallback listFeaturesCallback)
+ throws RemoteException {
+ Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getFeatures");
+ Objects.requireNonNull(listFeaturesCallback);
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
+ if (!mIsServiceEnabled) {
+ Slog.w(TAG, "Service not available");
+ listFeaturesCallback.onFailure(
+ OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException.ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE,
+ "OnDeviceIntelligenceManagerService is unavailable",
+ new PersistableBundle());
+ return;
+ }
+ ensureRemoteIntelligenceServiceInitialized();
+ mRemoteOnDeviceIntelligenceService.post(
+ service -> service.listFeatures(listFeaturesCallback));
+ }
+
+ @Override
+ public void getFeatureDetails(Feature feature,
+ IFeatureDetailsCallback featureDetailsCallback)
+ throws RemoteException {
+ Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getFeatureStatus");
+ Objects.requireNonNull(feature);
+ Objects.requireNonNull(featureDetailsCallback);
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
+ if (!mIsServiceEnabled) {
+ Slog.w(TAG, "Service not available");
+ featureDetailsCallback.onFailure(
+ OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException.ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE,
+ "OnDeviceIntelligenceManagerService is unavailable",
+ new PersistableBundle());
+ return;
+ }
+ ensureRemoteIntelligenceServiceInitialized();
+ mRemoteOnDeviceIntelligenceService.post(
+ service -> service.getFeatureDetails(feature, featureDetailsCallback));
+ }
+
+ @Override
+ public void requestFeatureDownload(Feature feature, ICancellationSignal cancellationSignal,
+ IDownloadCallback downloadCallback) throws RemoteException {
+ Slog.i(TAG, "OnDeviceIntelligenceManagerInternal requestFeatureDownload");
+ Objects.requireNonNull(feature);
+ Objects.requireNonNull(downloadCallback);
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
+ if (!mIsServiceEnabled) {
+ Slog.w(TAG, "Service not available");
+ downloadCallback.onDownloadFailed(
+ DownloadCallback.DOWNLOAD_FAILURE_STATUS_UNAVAILABLE,
+ "OnDeviceIntelligenceManagerService is unavailable",
+ new PersistableBundle());
+ }
+ ensureRemoteIntelligenceServiceInitialized();
+ mRemoteOnDeviceIntelligenceService.post(
+ service -> service.requestFeatureDownload(feature, cancellationSignal,
+ downloadCallback));
+ }
+
+
+ @Override
+ public void requestTokenCount(Feature feature,
+ Content request, ICancellationSignal cancellationSignal,
+ ITokenCountCallback tokenCountcallback) throws RemoteException {
+ Slog.i(TAG, "OnDeviceIntelligenceManagerInternal prepareFeatureProcessing");
+ Objects.requireNonNull(feature);
+ Objects.requireNonNull(request);
+ Objects.requireNonNull(tokenCountcallback);
+
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
+ if (!mIsServiceEnabled) {
+ Slog.w(TAG, "Service not available");
+ tokenCountcallback.onFailure(
+ OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException.ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE,
+ "OnDeviceIntelligenceManagerService is unavailable",
+ new PersistableBundle());
+ }
+ ensureRemoteTrustedInferenceServiceInitialized();
+ mRemoteInferenceService.post(
+ service -> service.requestTokenCount(feature, request, cancellationSignal,
+ tokenCountcallback));
+ }
+
+ @Override
+ public void processRequest(Feature feature,
+ Content request,
+ int requestType,
+ ICancellationSignal cancellationSignal,
+ IProcessingSignal processingSignal,
+ IResponseCallback responseCallback)
+ throws RemoteException {
+ Slog.i(TAG, "OnDeviceIntelligenceManagerInternal processRequest");
+ Objects.requireNonNull(feature);
+ Objects.requireNonNull(responseCallback);
+ Objects.requireNonNull(request);
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
+ if (!mIsServiceEnabled) {
+ Slog.w(TAG, "Service not available");
+ responseCallback.onFailure(
+ OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException.PROCESSING_ERROR_SERVICE_UNAVAILABLE,
+ "OnDeviceIntelligenceManagerService is unavailable",
+ new PersistableBundle());
+ }
+ ensureRemoteTrustedInferenceServiceInitialized();
+ mRemoteInferenceService.post(
+ service -> service.processRequest(feature, request, requestType,
+ cancellationSignal, processingSignal,
+ responseCallback));
+ }
+
+ @Override
+ public void processRequestStreaming(Feature feature,
+ Content request,
+ int requestType,
+ ICancellationSignal cancellationSignal,
+ IProcessingSignal processingSignal,
+ IStreamingResponseCallback streamingCallback) throws RemoteException {
+ Slog.i(TAG, "OnDeviceIntelligenceManagerInternal processRequestStreaming");
+ Objects.requireNonNull(feature);
+ Objects.requireNonNull(request);
+ Objects.requireNonNull(streamingCallback);
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
+ if (!mIsServiceEnabled) {
+ Slog.w(TAG, "Service not available");
+ streamingCallback.onFailure(
+ OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException.PROCESSING_ERROR_SERVICE_UNAVAILABLE,
+ "OnDeviceIntelligenceManagerService is unavailable",
+ new PersistableBundle());
+ }
+ ensureRemoteTrustedInferenceServiceInitialized();
+ mRemoteInferenceService.post(
+ service -> service.processRequestStreaming(feature, request, requestType,
+ cancellationSignal, processingSignal,
+ streamingCallback));
+ }
+ }
+
+ private void ensureRemoteIntelligenceServiceInitialized() throws RemoteException {
+ synchronized (mLock) {
+ if (mRemoteOnDeviceIntelligenceService == null) {
+ String serviceName = mContext.getResources().getString(
+ R.string.config_defaultOnDeviceIntelligenceService);
+ validateService(serviceName, false);
+ mRemoteOnDeviceIntelligenceService = new RemoteOnDeviceIntelligenceService(mContext,
+ ComponentName.unflattenFromString(serviceName),
+ UserHandle.SYSTEM.getIdentifier());
+ }
+ }
+ }
+
+ private void ensureRemoteTrustedInferenceServiceInitialized() throws RemoteException {
+ synchronized (mLock) {
+ if (mRemoteInferenceService == null) {
+ String serviceName = mContext.getResources().getString(
+ R.string.config_defaultOnDeviceTrustedInferenceService);
+ validateService(serviceName, true);
+ mRemoteInferenceService = new RemoteOnDeviceTrustedInferenceService(mContext,
+ ComponentName.unflattenFromString(serviceName),
+ UserHandle.SYSTEM.getIdentifier());
+ mRemoteInferenceService.setServiceLifecycleCallbacks(
+ new ServiceConnector.ServiceLifecycleCallbacks<>() {
+ @Override
+ public void onConnected(
+ @NonNull IOnDeviceTrustedInferenceService service) {
+ try {
+ service.registerRemoteStorageService(
+ getIRemoteStorageService());
+ } catch (RemoteException ex) {
+ Slog.w(TAG, "Failed to send connected event", ex);
+ }
+ }
+ });
+ }
+ }
+ }
+
+ @NonNull
+ private IRemoteStorageService.Stub getIRemoteStorageService() {
+ return new IRemoteStorageService.Stub() {
+ @Override
+ public void getReadOnlyFileDescriptor(
+ String filePath,
+ AndroidFuture<ParcelFileDescriptor> future) {
+ mRemoteOnDeviceIntelligenceService.post(
+ service -> service.getReadOnlyFileDescriptor(
+ filePath, future));
+ }
+
+ @Override
+ public void getReadOnlyFeatureFileDescriptorMap(
+ Feature feature,
+ RemoteCallback remoteCallback)
+ throws RemoteException {
+ mRemoteOnDeviceIntelligenceService.post(
+ service -> service.getReadOnlyFeatureFileDescriptorMap(
+ feature, remoteCallback));
+ }
+ };
+ }
+
+ @GuardedBy("mLock")
+ private void validateService(String serviceName, boolean checkIsolated)
+ throws RemoteException {
+ if (TextUtils.isEmpty(serviceName)) {
+ throw new RuntimeException("");
+ }
+ ComponentName serviceComponent = ComponentName.unflattenFromString(
+ serviceName);
+ ServiceInfo serviceInfo = AppGlobals.getPackageManager().getServiceInfo(
+ serviceComponent,
+ PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, 0);
+ if (serviceInfo != null) {
+ if (!checkIsolated) {
+ checkServiceRequiresPermission(serviceInfo,
+ Manifest.permission.BIND_ON_DEVICE_INTELLIGENCE_SERVICE);
+ return;
+ }
+
+ checkServiceRequiresPermission(serviceInfo,
+ Manifest.permission.BIND_ON_DEVICE_TRUSTED_SERVICE);
+ if (!isIsolatedService(serviceInfo)) {
+ throw new SecurityException(
+ "Call required an isolated service, but the configured service: "
+ + serviceName + ", is not isolated");
+ }
+ } else {
+ throw new RuntimeException(
+ "Could not find service info for serviceName: " + serviceName);
+ }
+ }
+
+ private static void checkServiceRequiresPermission(ServiceInfo serviceInfo,
+ String requiredPermission) {
+ final String permission = serviceInfo.permission;
+ if (!requiredPermission.equals(permission)) {
+ throw new SecurityException(String.format(
+ "Service %s requires %s permission. Found %s permission",
+ serviceInfo.getComponentName(),
+ requiredPermission,
+ serviceInfo.permission));
+ }
+ }
+
+ @GuardedBy("mLock")
+ private boolean isIsolatedService(@NonNull ServiceInfo serviceInfo) {
+ return (serviceInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) != 0
+ && (serviceInfo.flags & ServiceInfo.FLAG_EXTERNAL_SERVICE) == 0;
+ }
+}
diff --git a/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceIntelligenceService.java b/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceIntelligenceService.java
new file mode 100644
index 000000000000..48258d7bea72
--- /dev/null
+++ b/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceIntelligenceService.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.ondeviceintelligence;
+
+import static android.content.Context.BIND_FOREGROUND_SERVICE;
+import static android.content.Context.BIND_INCLUDE_CAPABILITIES;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.service.ondeviceintelligence.IOnDeviceIntelligenceService;
+import android.service.ondeviceintelligence.OnDeviceIntelligenceService;
+
+import com.android.internal.infra.ServiceConnector;
+
+/**
+ * Manages the connection to the remote on-device intelligence service. Also, handles unbinding
+ * logic set by the service implementation via a Secure Settings flag.
+ */
+public class RemoteOnDeviceIntelligenceService extends
+ ServiceConnector.Impl<IOnDeviceIntelligenceService> {
+ private static final String TAG =
+ RemoteOnDeviceIntelligenceService.class.getSimpleName();
+
+ RemoteOnDeviceIntelligenceService(Context context, ComponentName serviceName,
+ int userId) {
+ super(context, new Intent(
+ OnDeviceIntelligenceService.SERVICE_INTERFACE).setComponent(serviceName),
+ BIND_FOREGROUND_SERVICE | BIND_INCLUDE_CAPABILITIES, userId,
+ IOnDeviceIntelligenceService.Stub::asInterface);
+
+ // Bind right away
+ connect();
+ }
+
+ @Override
+ protected long getAutoDisconnectTimeoutMs() {
+ // Disable automatic unbinding.
+ // TODO: add logic to fetch this flag via SecureSettings.
+ return -1;
+ }
+}
diff --git a/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceTrustedInferenceService.java b/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceTrustedInferenceService.java
new file mode 100644
index 000000000000..cc8e78804bb6
--- /dev/null
+++ b/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceTrustedInferenceService.java
@@ -0,0 +1,64 @@
+/*
+ * 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.ondeviceintelligence;
+
+import static android.content.Context.BIND_FOREGROUND_SERVICE;
+import static android.content.Context.BIND_INCLUDE_CAPABILITIES;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.service.ondeviceintelligence.IOnDeviceTrustedInferenceService;
+import android.service.ondeviceintelligence.OnDeviceTrustedInferenceService;
+
+import com.android.internal.infra.ServiceConnector;
+
+
+/**
+ * Manages the connection to the remote on-device trusted inference service. Also, handles unbinding
+ * logic set by the service implementation via a SecureSettings flag.
+ */
+public class RemoteOnDeviceTrustedInferenceService extends
+ ServiceConnector.Impl<IOnDeviceTrustedInferenceService> {
+ /**
+ * Creates an instance of {@link ServiceConnector}
+ *
+ * See {@code protected} methods for optional parameters you can override.
+ *
+ * @param context to be used for {@link Context#bindServiceAsUser binding} and
+ * {@link Context#unbindService unbinding}
+ * @param userId to be used for {@link Context#bindServiceAsUser binding}
+ */
+ RemoteOnDeviceTrustedInferenceService(Context context, ComponentName serviceName,
+ int userId) {
+ super(context, new Intent(
+ OnDeviceTrustedInferenceService.SERVICE_INTERFACE).setComponent(serviceName),
+ BIND_FOREGROUND_SERVICE | BIND_INCLUDE_CAPABILITIES, userId,
+ IOnDeviceTrustedInferenceService.Stub::asInterface);
+
+ // Bind right away
+ connect();
+ }
+
+
+ @Override
+ protected long getAutoDisconnectTimeoutMs() {
+ // Disable automatic unbinding.
+ // TODO: add logic to fetch this flag via SecureSettings.
+ return -1;
+ }
+}
diff --git a/services/core/java/com/android/server/pdb/PersistentDataBlockService.java b/services/core/java/com/android/server/pdb/PersistentDataBlockService.java
index 59d3d1746754..5ad550722c93 100644
--- a/services/core/java/com/android/server/pdb/PersistentDataBlockService.java
+++ b/services/core/java/com/android/server/pdb/PersistentDataBlockService.java
@@ -656,8 +656,10 @@ public class PersistentDataBlockService extends SystemService {
@VisibleForTesting
boolean isFrpActive() {
- waitForInitDoneSignal();
synchronized (mLock) {
+ // mFrpActive is initialized and automatic deactivation done (if possible) before the
+ // service is published, so there's no chance that callers could ask for the state
+ // before it has settled.
return mFrpActive;
}
}
@@ -1253,6 +1255,7 @@ public class PersistentDataBlockService extends SystemService {
private void enforceFactoryResetProtectionInactive() {
if (mFrpEnforced && isFrpActive()) {
+ Slog.w(TAG, "Attempt to update PDB was blocked because FRP is active.");
throw new SecurityException("FRP is active");
}
}
diff --git a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
index 524bad58ce07..b6daed121057 100644
--- a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
+++ b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
@@ -30,6 +30,7 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.IBackgroundInstallControlService;
import android.content.pm.InstallSourceInfo;
import android.content.pm.PackageInfo;
+import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ParceledListSlice;
@@ -46,6 +47,7 @@ import android.os.UserHandle;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.AtomicFile;
+import android.util.Log;
import android.util.Slog;
import android.util.SparseArrayMap;
import android.util.SparseSetArray;
@@ -63,8 +65,10 @@ import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Comparator;
import java.util.List;
import java.util.ListIterator;
+import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
@@ -103,6 +107,24 @@ public class BackgroundInstallControlService extends SystemService {
private final SparseArrayMap<String, TreeSet<ForegroundTimeFrame>>
mInstallerForegroundTimeFrames = new SparseArrayMap<>();
+ @VisibleForTesting
+ protected final PackageManagerInternal.PackageListObserver mPackageObserver =
+ new PackageManagerInternal.PackageListObserver() {
+ @Override
+ public void onPackageAdded(String packageName, int uid) {
+ final int userId = UserHandle.getUserId(uid);
+ mHandler.obtainMessage(MSG_PACKAGE_ADDED, userId, 0, packageName)
+ .sendToTarget();
+ }
+
+ @Override
+ public void onPackageRemoved(String packageName, int uid) {
+ final int userId = UserHandle.getUserId(uid);
+ mHandler.obtainMessage(MSG_PACKAGE_REMOVED, userId, 0, packageName)
+ .sendToTarget();
+ }
+ };
+
public BackgroundInstallControlService(@NonNull Context context) {
this(new InjectorImpl(context));
}
@@ -258,6 +280,7 @@ public class BackgroundInstallControlService extends SystemService {
String installerPackageName;
String initiatingPackageName;
+
try {
final InstallSourceInfo installInfo = mPackageManager.getInstallSourceInfo(packageName);
installerPackageName = installInfo.getInstallingPackageName();
@@ -280,7 +303,8 @@ public class BackgroundInstallControlService extends SystemService {
// convert up-time to current time.
final long installTimestamp =
- System.currentTimeMillis() - (SystemClock.uptimeMillis() - appInfo.createTimestamp);
+ System.currentTimeMillis() - (SystemClock.uptimeMillis()
+ - retrieveInstallStartTimestamp(packageName, userId, appInfo));
if (installedByAdb(initiatingPackageName)
|| wasForegroundInstallation(installerPackageName, userId, installTimestamp)) {
@@ -293,6 +317,35 @@ public class BackgroundInstallControlService extends SystemService {
writeBackgroundInstalledPackagesToDisk();
}
+ private long retrieveInstallStartTimestamp(String packageName,
+ int userId, ApplicationInfo appInfo) {
+ long installStartTimestamp = appInfo.createTimestamp;
+
+ try {
+ Optional<PackageInstaller.SessionInfo> latestInstallSession =
+ getLatestInstallSession(packageName, userId);
+ if (latestInstallSession.isEmpty()) {
+ Slog.w(TAG, "Package's historical install session not found, falling back "
+ + "to appInfo.createTimestamp: " + packageName);
+ } else {
+ installStartTimestamp = latestInstallSession.get().getCreatedMillis();
+ }
+ } catch (Exception e) {
+ Slog.w(TAG, "Retrieval of install time from historical session failed, falling "
+ + "back to appInfo.createTimestamp");
+ Slog.w(TAG, Log.getStackTraceString(e));
+ }
+ return installStartTimestamp;
+ }
+
+ private Optional<PackageInstaller.SessionInfo> getLatestInstallSession(
+ String packageName, int userId) {
+ List<PackageInstaller.SessionInfo> historicalSessions =
+ mPackageManagerInternal.getHistoricalSessions(userId).getList();
+ return historicalSessions.stream().filter(s -> packageName.equals(s.getAppPackageName()))
+ .max(Comparator.comparingLong(PackageInstaller.SessionInfo::getCreatedMillis));
+ }
+
// ADB sets installerPackageName to null, this creates a loophole to bypass BIC which will be
// addressed with b/265203007
private boolean installedByAdb(String initiatingPackageName) {
@@ -496,22 +549,7 @@ public class BackgroundInstallControlService extends SystemService {
publishBinderService(Context.BACKGROUND_INSTALL_CONTROL_SERVICE, mBinderService);
}
- mPackageManagerInternal.getPackageList(
- new PackageManagerInternal.PackageListObserver() {
- @Override
- public void onPackageAdded(String packageName, int uid) {
- final int userId = UserHandle.getUserId(uid);
- mHandler.obtainMessage(MSG_PACKAGE_ADDED, userId, 0, packageName)
- .sendToTarget();
- }
-
- @Override
- public void onPackageRemoved(String packageName, int uid) {
- final int userId = UserHandle.getUserId(uid);
- mHandler.obtainMessage(MSG_PACKAGE_REMOVED, userId, 0, packageName)
- .sendToTarget();
- }
- });
+ mPackageManagerInternal.getPackageList(mPackageObserver);
}
// The foreground time frame (ForegroundTimeFrame) represents the period
diff --git a/services/core/java/com/android/server/pm/BroadcastHelper.java b/services/core/java/com/android/server/pm/BroadcastHelper.java
index e984e9c255da..23d48e871d62 100644
--- a/services/core/java/com/android/server/pm/BroadcastHelper.java
+++ b/services/core/java/com/android/server/pm/BroadcastHelper.java
@@ -45,6 +45,8 @@ import android.content.Intent;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
+import android.content.pm.UserProperties;
+import android.multiuser.Flags;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
@@ -361,14 +363,13 @@ public final class BroadcastHelper {
final UserInfo parent = ums.getProfileParent(userId);
final int launcherUserId = (parent != null) ? parent.id : userId;
final ComponentName launcherComponent = snapshot.getDefaultHomeActivity(launcherUserId);
- if (launcherComponent != null) {
+ if (launcherComponent != null && canLauncherAccessProfile(launcherComponent, userId)) {
Intent launcherIntent = new Intent(PackageInstaller.ACTION_SESSION_COMMITTED)
.putExtra(PackageInstaller.EXTRA_SESSION, sessionInfo)
.putExtra(Intent.EXTRA_USER, UserHandle.of(userId))
.setPackage(launcherComponent.getPackageName());
mContext.sendBroadcastAsUser(launcherIntent, UserHandle.of(launcherUserId));
}
- // TODO(b/122900055) Change/Remove this and replace with new permission role.
if (appPredictionServicePackage != null) {
Intent predictorIntent = new Intent(PackageInstaller.ACTION_SESSION_COMMITTED)
.putExtra(PackageInstaller.EXTRA_SESSION, sessionInfo)
@@ -378,6 +379,36 @@ public final class BroadcastHelper {
}
}
+ /**
+ * A Profile is accessible to launcher in question if:
+ * - It's not hidden for API visibility.
+ * - Hidden, but launcher application has either
+ * {@link Manifest.permission.ACCESS_HIDDEN_PROFILES_FULL} or
+ * {@link Manifest.permission.ACCESS_HIDDEN_PROFILES}
+ * granted.
+ */
+ boolean canLauncherAccessProfile(ComponentName launcherComponent, int userId) {
+ if (android.os.Flags.allowPrivateProfile()
+ && Flags.enablePermissionToAccessHiddenProfiles()) {
+ if (mUmInternal.getUserProperties(userId).getProfileApiVisibility()
+ != UserProperties.PROFILE_API_VISIBILITY_HIDDEN) {
+ return true;
+ }
+ if (mContext.getPackageManager().checkPermission(
+ Manifest.permission.ACCESS_HIDDEN_PROFILES_FULL,
+ launcherComponent.getPackageName())
+ == PackageManager.PERMISSION_GRANTED) {
+ return true;
+ }
+ // TODO(b/122900055) Change/Remove this and replace with new permission role.
+ return mContext.getPackageManager().checkPermission(
+ Manifest.permission.ACCESS_HIDDEN_PROFILES,
+ launcherComponent.getPackageName())
+ == PackageManager.PERMISSION_GRANTED;
+ }
+ return true;
+ }
+
void sendPreferredActivityChangedBroadcast(int userId) {
mHandler.post(() -> {
final IActivityManager am = ActivityManager.getService();
diff --git a/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java b/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java
index 5b3f7a58e653..017cf55541fc 100644
--- a/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java
+++ b/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java
@@ -20,6 +20,9 @@ import static android.content.pm.PackageManager.ONLY_IF_NO_MATCH_FOUND;
import static android.content.pm.PackageManager.SKIP_CURRENT_PROFILE;
import static android.speech.RecognizerIntent.ACTION_RECOGNIZE_SPEECH;
+import static com.android.server.pm.CrossProfileIntentFilter.FLAG_ALLOW_CHAINED_RESOLUTION;
+import static com.android.server.pm.CrossProfileIntentFilter.FLAG_IS_PACKAGE_FOR_FILTER;
+
import android.content.Intent;
import android.hardware.usb.UsbManager;
import android.provider.AlarmClock;
@@ -613,6 +616,27 @@ public class DefaultCrossProfileIntentFiltersUtils {
.addDataScheme("mmsto")
.build();
+ private static final DefaultCrossProfileIntentFilter CLONE_TO_PARENT_ACTION_PICK_IMAGES =
+ new DefaultCrossProfileIntentFilter.Builder(
+ DefaultCrossProfileIntentFilter.Direction.TO_PARENT,
+ /* flags= */ FLAG_IS_PACKAGE_FOR_FILTER | FLAG_ALLOW_CHAINED_RESOLUTION,
+ /* letsPersonalDataIntoProfile= */ false)
+ .addAction(MediaStore.ACTION_PICK_IMAGES)
+ .addCategory(Intent.CATEGORY_DEFAULT)
+ .build();
+
+ private static final DefaultCrossProfileIntentFilter
+ CLONE_TO_PARENT_ACTION_PICK_IMAGES_WITH_DATA_TYPES =
+ new DefaultCrossProfileIntentFilter.Builder(
+ DefaultCrossProfileIntentFilter.Direction.TO_PARENT,
+ /* flags= */ FLAG_IS_PACKAGE_FOR_FILTER | FLAG_ALLOW_CHAINED_RESOLUTION,
+ /* letsPersonalDataIntoProfile= */ false)
+ .addAction(MediaStore.ACTION_PICK_IMAGES)
+ .addCategory(Intent.CATEGORY_DEFAULT)
+ .addDataType("image/*")
+ .addDataType("video/*")
+ .build();
+
public static List<DefaultCrossProfileIntentFilter> getDefaultCloneProfileFilters() {
return Arrays.asList(
PARENT_TO_CLONE_SEND_ACTION,
@@ -626,7 +650,80 @@ public class DefaultCrossProfileIntentFiltersUtils {
CLONE_TO_PARENT_PICK_INSERT_ACTION,
CLONE_TO_PARENT_DIAL_DATA,
CLONE_TO_PARENT_SMS_MMS,
- CLONE_TO_PARENT_PHOTOPICKER_SELECTION
+ CLONE_TO_PARENT_PHOTOPICKER_SELECTION,
+ CLONE_TO_PARENT_ACTION_PICK_IMAGES,
+ CLONE_TO_PARENT_ACTION_PICK_IMAGES_WITH_DATA_TYPES
+ );
+ }
+
+ /** Dial intent with mime type can be handled by either private profile or its parent user. */
+ private static final DefaultCrossProfileIntentFilter DIAL_MIME_PRIVATE_PROFILE =
+ new DefaultCrossProfileIntentFilter.Builder(
+ DefaultCrossProfileIntentFilter.Direction.TO_PARENT,
+ ONLY_IF_NO_MATCH_FOUND,
+ /* letsPersonalDataIntoProfile= */ false)
+ .addAction(Intent.ACTION_DIAL)
+ .addAction(Intent.ACTION_VIEW)
+ .addCategory(Intent.CATEGORY_DEFAULT)
+ .addCategory(Intent.CATEGORY_BROWSABLE)
+ .addDataType("vnd.android.cursor.item/phone")
+ .addDataType("vnd.android.cursor.item/phone_v2")
+ .addDataType("vnd.android.cursor.item/person")
+ .addDataType("vnd.android.cursor.dir/calls")
+ .addDataType("vnd.android.cursor.item/calls")
+ .build();
+
+ /** Dial intent with data scheme can be handled by either private profile or its parent user. */
+ private static final DefaultCrossProfileIntentFilter DIAL_DATA_PRIVATE_PROFILE =
+ new DefaultCrossProfileIntentFilter.Builder(
+ DefaultCrossProfileIntentFilter.Direction.TO_PARENT,
+ ONLY_IF_NO_MATCH_FOUND,
+ /* letsPersonalDataIntoProfile= */ false)
+ .addAction(Intent.ACTION_DIAL)
+ .addAction(Intent.ACTION_VIEW)
+ .addCategory(Intent.CATEGORY_DEFAULT)
+ .addCategory(Intent.CATEGORY_BROWSABLE)
+ .addDataScheme("tel")
+ .addDataScheme("sip")
+ .addDataScheme("voicemail")
+ .build();
+
+ /**
+ * Dial intent with no data scheme or type can be handled by either private profile or its
+ * parent user.
+ */
+ private static final DefaultCrossProfileIntentFilter DIAL_RAW_PRIVATE_PROFILE =
+ new DefaultCrossProfileIntentFilter.Builder(
+ DefaultCrossProfileIntentFilter.Direction.TO_PARENT,
+ ONLY_IF_NO_MATCH_FOUND,
+ /* letsPersonalDataIntoProfile= */ false)
+ .addAction(Intent.ACTION_DIAL)
+ .addCategory(Intent.CATEGORY_DEFAULT)
+ .addCategory(Intent.CATEGORY_BROWSABLE)
+ .build();
+
+ /** SMS and MMS can be handled by the private profile or by the parent user. */
+ private static final DefaultCrossProfileIntentFilter SMS_MMS_PRIVATE_PROFILE =
+ new DefaultCrossProfileIntentFilter.Builder(
+ DefaultCrossProfileIntentFilter.Direction.TO_PARENT,
+ ONLY_IF_NO_MATCH_FOUND,
+ /* letsPersonalDataIntoProfile= */ false)
+ .addAction(Intent.ACTION_VIEW)
+ .addAction(Intent.ACTION_SENDTO)
+ .addCategory(Intent.CATEGORY_DEFAULT)
+ .addCategory(Intent.CATEGORY_BROWSABLE)
+ .addDataScheme("sms")
+ .addDataScheme("smsto")
+ .addDataScheme("mms")
+ .addDataScheme("mmsto")
+ .build();
+
+ public static List<DefaultCrossProfileIntentFilter> getDefaultPrivateProfileFilters() {
+ return Arrays.asList(
+ DIAL_MIME_PRIVATE_PROFILE,
+ DIAL_DATA_PRIVATE_PROFILE,
+ DIAL_RAW_PRIVATE_PROFILE,
+ SMS_MMS_PRIVATE_PROFILE
);
}
}
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index f9d81127fd86..d8d8dd2e57a9 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -350,18 +350,50 @@ public class LauncherAppsService extends SystemService {
public void registerPackageInstallerCallback(String callingPackage,
IPackageInstallerCallback callback) {
verifyCallingPackage(callingPackage);
- UserHandle callingIdUserHandle = new UserHandle(getCallingUserId());
- getPackageInstallerService().registerCallback(callback, eventUserId ->
- isEnabledProfileOf(callingIdUserHandle,
- new UserHandle(eventUserId), "shouldReceiveEvent"));
+ BroadcastCookie callerInfo =
+ new BroadcastCookie(
+ new UserHandle(getCallingUserId()),
+ callingPackage,
+ getCallingPid(),
+ getCallingUid());
+ getPackageInstallerService()
+ .registerCallback(
+ callback,
+ eventUserId ->
+ isEnabledProfileOf(
+ callerInfo,
+ new UserHandle(eventUserId),
+ "shouldReceiveEvent"));
+ }
+
+ @Override
+ public List<UserHandle> getUserProfiles() {
+ int[] userIds;
+ if (!canAccessHiddenProfile(getCallingUid(), getCallingPid())) {
+ userIds = mUm.getProfileIdsExcludingHidden(getCallingUserId(), /* enabled= */ true);
+ } else {
+ userIds = mUm.getEnabledProfileIds(getCallingUserId());
+ }
+ final List<UserHandle> result = new ArrayList<>(userIds.length);
+ for (int userId : userIds) {
+ result.add(UserHandle.of(userId));
+ }
+ return result;
}
@Override
public ParceledListSlice<SessionInfo> getAllSessions(String callingPackage) {
verifyCallingPackage(callingPackage);
List<SessionInfo> sessionInfos = new ArrayList<>();
- int[] userIds = mUm.getEnabledProfileIds(getCallingUserId());
final int callingUid = Binder.getCallingUid();
+
+ int[] userIds;
+ if (!canAccessHiddenProfile(callingUid, Binder.getCallingPid())) {
+ userIds = mUm.getProfileIdsExcludingHidden(getCallingUserId(), /* enabled= */ true);
+ } else {
+ userIds = mUm.getEnabledProfileIds(getCallingUserId());
+ }
+
final long token = Binder.clearCallingIdentity();
try {
for (int userId : userIds) {
@@ -389,7 +421,7 @@ public class LauncherAppsService extends SystemService {
mPackageInstallerService = ((PackageInstallerService) ((IPackageManager)
ServiceManager.getService("package")).getPackageInstaller());
} catch (RemoteException e) {
- Slog.wtf(TAG, "Error gettig IPackageInstaller", e);
+ Slog.wtf(TAG, "Error getting IPackageInstaller", e);
}
}
return mPackageInstallerService;
@@ -470,57 +502,86 @@ public class LauncherAppsService extends SystemService {
+ targetUserId + " from " + callingUserId + " not allowed");
return false;
}
-
- if (areHiddenApisChecksEnabled()
- && mUm.getUserProperties(UserHandle.of(targetUserId))
- .getProfileApiVisibility()
- == UserProperties.PROFILE_API_VISIBILITY_HIDDEN
- && !canAccessHiddenProfileInjected(callingUid, callingPid)) {
- return false;
- }
} finally {
injectRestoreCallingIdentity(ident);
}
+ if (isHiddenProfile(UserHandle.of(targetUserId))
+ && !canAccessHiddenProfile(callingUid, callingPid)) {
+ return false;
+ }
+
return mUserManagerInternal.isProfileAccessible(callingUserId, targetUserId,
message, true);
}
- boolean areHiddenApisChecksEnabled() {
- return android.os.Flags.allowPrivateProfile()
- && Flags.enableLauncherAppsHiddenProfileChecks()
- && Flags.enablePermissionToAccessHiddenProfiles();
+ private boolean isHiddenProfile(UserHandle targetUser) {
+ if (!Flags.enableLauncherAppsHiddenProfileChecks()) {
+ return false;
+ }
+
+ long identity = injectClearCallingIdentity();
+ try {
+ UserProperties properties = mUm.getUserProperties(targetUser);
+ if (properties == null) {
+ return false;
+ }
+
+ return properties.getProfileApiVisibility()
+ == UserProperties.PROFILE_API_VISIBILITY_HIDDEN;
+ } catch (IllegalArgumentException e) {
+ return false;
+ } finally {
+ injectRestoreCallingIdentity(identity);
+ }
}
private void verifyCallingPackage(String callingPackage) {
verifyCallingPackage(callingPackage, injectBinderCallingUid());
}
- boolean canAccessHiddenProfileInjected(int callingUid, int callingPid) {
- AndroidPackage callingPackage = mPackageManagerInternal.getPackage(callingUid);
- if (callingPackage == null) {
- return false;
+ private boolean canAccessHiddenProfile(int callingUid, int callingPid) {
+ if (!areHiddenApisChecksEnabled()) {
+ return true;
}
- if (!mRoleManager
- .getRoleHoldersAsUser(
- RoleManager.ROLE_HOME, UserHandle.getUserHandleForUid(callingUid))
- .contains(callingPackage.getPackageName())) {
- return false;
- }
+ long ident = injectClearCallingIdentity();
+ try {
+ AndroidPackage callingPackage = mPackageManagerInternal.getPackage(callingUid);
+ if (callingPackage == null) {
+ return false;
+ }
- if (mContext.checkPermission(
- Manifest.permission.ACCESS_HIDDEN_PROFILES_FULL, callingPid, callingUid)
- == PackageManager.PERMISSION_GRANTED) {
- return true;
+ if (!mRoleManager
+ .getRoleHoldersAsUser(
+ RoleManager.ROLE_HOME, UserHandle.getUserHandleForUid(callingUid))
+ .contains(callingPackage.getPackageName())) {
+ return false;
+ }
+ if (mContext.checkPermission(
+ Manifest.permission.ACCESS_HIDDEN_PROFILES_FULL,
+ callingPid,
+ callingUid)
+ == PackageManager.PERMISSION_GRANTED) {
+ return true;
+ }
+
+ // TODO(b/321988638): add option to disable with a flag
+ return mContext.checkPermission(
+ android.Manifest.permission.ACCESS_HIDDEN_PROFILES,
+ callingPid,
+ callingUid)
+ == PackageManager.PERMISSION_GRANTED;
+ } finally {
+ injectRestoreCallingIdentity(ident);
}
+ }
- // TODO(b/321988638): add option to disable with a flag
- return mContext.checkPermission(
- android.Manifest.permission.ACCESS_HIDDEN_PROFILES,
- callingPid,
- callingUid)
- == PackageManager.PERMISSION_GRANTED;
+ private boolean areHiddenApisChecksEnabled() {
+ return android.os.Flags.allowPrivateProfile()
+ && Flags.enableHidingProfiles()
+ && Flags.enableLauncherAppsHiddenProfileChecks()
+ && Flags.enablePermissionToAccessHiddenProfiles();
}
@VisibleForTesting // We override it in unit tests
@@ -798,6 +859,10 @@ public class LauncherAppsService extends SystemService {
public ParceledListSlice getShortcutConfigActivities(
String callingPackage, String packageName, UserHandle user)
throws RemoteException {
+ // Not supported for user-profiles with items restricted on home screen.
+ if (!mShortcutServiceInternal.areShortcutsSupportedOnHomeScreen(user.getIdentifier())) {
+ return null;
+ }
return queryActivitiesForUser(callingPackage,
new Intent(Intent.ACTION_CREATE_SHORTCUT).setPackage(packageName), user);
}
@@ -1256,6 +1321,14 @@ public class LauncherAppsService extends SystemService {
@Override
public void pinShortcuts(String callingPackage, String packageName, List<String> ids,
UserHandle targetUser) {
+ if (!mShortcutServiceInternal
+ .areShortcutsSupportedOnHomeScreen(targetUser.getIdentifier())) {
+ // Requires strict ACCESS_SHORTCUTS permission for user-profiles with items
+ // restricted on home screen.
+ ensureStrictAccessShortcutsPermission(callingPackage);
+ } else {
+ ensureShortcutPermission(callingPackage);
+ }
ensureShortcutPermission(callingPackage);
if (!canAccessProfile(targetUser.getIdentifier(), "Cannot pin shortcuts")) {
return;
@@ -2056,12 +2129,18 @@ public class LauncherAppsService extends SystemService {
});
}
- /** Checks if user is a profile of or same as listeningUser.
- * and the user is enabled. */
- private boolean isEnabledProfileOf(UserHandle listeningUser, UserHandle user,
- String debugMsg) {
- return mUserManagerInternal.isProfileAccessible(listeningUser.getIdentifier(),
- user.getIdentifier(), debugMsg, false);
+ /**
+ * Checks if user is a profile of or same as listeningUser and the target user is enabled
+ * and accessible for caller.
+ */
+ private boolean isEnabledProfileOf(
+ BroadcastCookie cookie, UserHandle user, String debugMsg) {
+ if (isHiddenProfile(user)
+ && !canAccessHiddenProfile(cookie.callingUid, cookie.callingPid)) {
+ return false;
+ }
+ return mUserManagerInternal.isProfileAccessible(
+ cookie.user.getIdentifier(), user.getIdentifier(), debugMsg, false);
}
/**
@@ -2293,7 +2372,7 @@ public class LauncherAppsService extends SystemService {
mListeners.getBroadcastItem(i);
final BroadcastCookie cookie =
(BroadcastCookie) mListeners.getBroadcastCookie(i);
- if (!isEnabledProfileOf(cookie.user, user, "onPackageRemoved")) {
+ if (!isEnabledProfileOf(cookie, user, "onPackageRemoved")) {
continue;
}
if (!isCallingAppIdAllowed(appIdAllowList, UserHandle.getAppId(
@@ -2332,7 +2411,7 @@ public class LauncherAppsService extends SystemService {
for (int i = 0; i < n; i++) {
IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
BroadcastCookie cookie = (BroadcastCookie) mListeners.getBroadcastCookie(i);
- if (!isEnabledProfileOf(cookie.user, user, "onPackageAdded")) {
+ if (!isEnabledProfileOf(cookie, user, "onPackageAdded")) {
continue;
}
if (!isPackageVisibleToListener(packageName, cookie, user)) {
@@ -2366,7 +2445,7 @@ public class LauncherAppsService extends SystemService {
for (int i = 0; i < n; i++) {
IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
BroadcastCookie cookie = (BroadcastCookie) mListeners.getBroadcastCookie(i);
- if (!isEnabledProfileOf(cookie.user, user, "onPackageModified")) {
+ if (!isEnabledProfileOf(cookie, user, "onPackageModified")) {
continue;
}
if (!isPackageVisibleToListener(packageName, cookie, user)) {
@@ -2391,7 +2470,7 @@ public class LauncherAppsService extends SystemService {
for (int i = 0; i < n; i++) {
IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
BroadcastCookie cookie = (BroadcastCookie) mListeners.getBroadcastCookie(i);
- if (!isEnabledProfileOf(cookie.user, user, "onPackagesAvailable")) {
+ if (!isEnabledProfileOf(cookie, user, "onPackagesAvailable")) {
continue;
}
final String[] filteredPackages =
@@ -2421,7 +2500,7 @@ public class LauncherAppsService extends SystemService {
for (int i = 0; i < n; i++) {
IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
BroadcastCookie cookie = (BroadcastCookie) mListeners.getBroadcastCookie(i);
- if (!isEnabledProfileOf(cookie.user, user, "onPackagesUnavailable")) {
+ if (!isEnabledProfileOf(cookie, user, "onPackagesUnavailable")) {
continue;
}
final String[] filteredPackages =
@@ -2465,7 +2544,7 @@ public class LauncherAppsService extends SystemService {
for (int i = 0; i < n; i++) {
IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
BroadcastCookie cookie = (BroadcastCookie) mListeners.getBroadcastCookie(i);
- if (!isEnabledProfileOf(cookie.user, user, "onPackagesSuspended")) {
+ if (!isEnabledProfileOf(cookie, user, "onPackagesSuspended")) {
continue;
}
final String[] filteredPackagesWithoutExtras =
@@ -2502,7 +2581,7 @@ public class LauncherAppsService extends SystemService {
for (int i = 0; i < n; i++) {
IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
BroadcastCookie cookie = (BroadcastCookie) mListeners.getBroadcastCookie(i);
- if (!isEnabledProfileOf(cookie.user, user, "onPackagesUnsuspended")) {
+ if (!isEnabledProfileOf(cookie, user, "onPackagesUnsuspended")) {
continue;
}
final String[] filteredPackages =
@@ -2539,7 +2618,7 @@ public class LauncherAppsService extends SystemService {
for (int i = 0; i < n; i++) {
IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
BroadcastCookie cookie = (BroadcastCookie) mListeners.getBroadcastCookie(i);
- if (!isEnabledProfileOf(cookie.user, user, "onShortcutChanged")) {
+ if (!isEnabledProfileOf(cookie, user, "onShortcutChanged")) {
continue;
}
if (!isPackageVisibleToListener(packageName, cookie, user)) {
@@ -2613,7 +2692,7 @@ public class LauncherAppsService extends SystemService {
for (int i = 0; i < n; i++) {
IOnAppsChangedListener listener = mListeners.getBroadcastItem(i);
BroadcastCookie cookie = (BroadcastCookie) mListeners.getBroadcastCookie(i);
- if (!isEnabledProfileOf(cookie.user, mUser, "onLoadingProgressChanged")) {
+ if (!isEnabledProfileOf(cookie, mUser, "onLoadingProgressChanged")) {
continue;
}
if (!isPackageVisibleToListener(mPackageName, cookie, mUser)) {
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index 05e8f9aa24ec..cdd52a47e433 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -62,6 +62,7 @@ import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
import android.content.pm.ResolveInfo;
+import android.content.pm.UserInfo;
import android.content.pm.VersionedPackage;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
@@ -85,6 +86,7 @@ import android.os.RemoteException;
import android.os.SELinux;
import android.os.SystemProperties;
import android.os.UserHandle;
+import android.os.UserManager;
import android.text.TextUtils;
import android.util.ExceptionUtils;
import android.util.Pair;
@@ -164,6 +166,9 @@ public class PackageArchiver {
@Nullable
private AppOpsManager mAppOpsManager;
+ @Nullable
+ private UserManager mUserManager;
+
/* IntentSender store that maps key: {userId, appPackageName} to respective existing attached
unarchival intent sender. */
private final Map<Pair<Integer, String>, IntentSender> mLauncherIntentSenders;
@@ -276,12 +281,8 @@ public class PackageArchiver {
Slog.e(TAG, "callerPackageName cannot be null for unarchival!");
return START_CLASS_NOT_FOUND;
}
- if (!isCallingPackageValid(callerPackageName, callingUid, userId)) {
- // Return early as the calling UID does not match caller package's UID.
- return START_CLASS_NOT_FOUND;
- }
- String currentLauncherPackageName = getCurrentLauncherPackageName(userId);
+ String currentLauncherPackageName = getCurrentLauncherPackageName(getParentUserId(userId));
if ((currentLauncherPackageName == null || !callerPackageName.equals(
currentLauncherPackageName)) && callingUid != Process.SHELL_UID) {
// TODO(b/311619990): Remove dependency on SHELL_UID for testing
@@ -316,6 +317,13 @@ public class PackageArchiver {
return START_ABORTED;
}
+ // Profiles share their UI and default apps, so we have to get the profile parent before
+ // fetching the default launcher.
+ private int getParentUserId(int userId) {
+ UserInfo profileParent = getUserManager().getProfileParent(userId);
+ return profileParent == null ? userId : profileParent.id;
+ }
+
/**
* Returns true if the componentName targeted by the intent corresponds to that of an archived
* app.
@@ -354,21 +362,18 @@ public class PackageArchiver {
ps.setArchiveState(/* archiveState= */ null, userId);
}
}
- mPm.mBackgroundHandler.post(
- () -> {
- File iconsDir = getIconsDir(packageName, userId);
- if (!iconsDir.exists()) {
- return;
- }
- // TODO(b/319238030) Move this into installd.
- if (!FileUtils.deleteContentsAndDir(iconsDir)) {
- Slog.e(TAG, "Failed to clean up archive files for " + packageName);
- } else {
- if (DEBUG) {
- Slog.e(TAG, "Deleted icons at " + iconsDir.getAbsolutePath());
- }
- }
- });
+ File iconsDir = getIconsDir(packageName, userId);
+ if (!iconsDir.exists()) {
+ return;
+ }
+ // TODO(b/319238030) Move this into installd.
+ if (!FileUtils.deleteContentsAndDir(iconsDir)) {
+ Slog.e(TAG, "Failed to clean up archive files for " + packageName);
+ } else {
+ if (DEBUG) {
+ Slog.e(TAG, "Deleted icons at " + iconsDir.getAbsolutePath());
+ }
+ }
}
@Nullable
@@ -1131,6 +1136,13 @@ public class PackageArchiver {
return mAppOpsManager;
}
+ private UserManager getUserManager() {
+ if (mUserManager == null) {
+ mUserManager = mContext.getSystemService(UserManager.class);
+ }
+ return mUserManager;
+ }
+
private void storeArchiveState(String packageName, ArchiveState archiveState, int userId)
throws PackageManager.NameNotFoundException {
synchronized (mPm.mLock) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 4f9ed03dc8e7..0a3dfc08686a 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -46,7 +46,7 @@ import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.compat.annotation.ChangeId;
-import android.compat.annotation.EnabledAfter;
+import android.compat.annotation.Disabled;
import android.compat.annotation.Overridable;
import android.content.Context;
import android.content.Intent;
@@ -200,7 +200,7 @@ public class PackageManagerServiceUtils {
*/
@Overridable
@ChangeId
- @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Disabled
private static final long ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS = 161252188;
/**
@@ -1246,6 +1246,9 @@ public class PackageManagerServiceUtils {
ActivityManagerUtils.logUnsafeIntentEvent(
UNSAFE_INTENT_EVENT_REPORTED__EVENT_TYPE__EXPLICIT_INTENT_FILTER_UNMATCH,
filterCallingUid, intent, resolvedType, enforce);
+ if (android.security.Flags.enforceIntentFilterMatch()) {
+ intent.addExtendedFlags(Intent.EXTENDED_FLAG_FILTER_MISMATCH);
+ }
if (enforce) {
Slog.w(TAG, "Intent does not match component's intent filter: " + intent);
Slog.w(TAG, "Access blocked: " + comp.getComponentName());
diff --git a/services/core/java/com/android/server/pm/ShortcutRequestPinProcessor.java b/services/core/java/com/android/server/pm/ShortcutRequestPinProcessor.java
index 1e1f17894605..47a140a97c96 100644
--- a/services/core/java/com/android/server/pm/ShortcutRequestPinProcessor.java
+++ b/services/core/java/com/android/server/pm/ShortcutRequestPinProcessor.java
@@ -416,6 +416,10 @@ class ShortcutRequestPinProcessor {
@VisibleForTesting
Pair<ComponentName, Integer> getRequestPinConfirmationActivity(
int callingUserId, int requestType) {
+ // Pinning is not supported for user-profiles with items restricted on home screen.
+ if (!mService.areShortcutsSupportedOnHomeScreen(callingUserId)) {
+ return null;
+ }
// Find the default launcher.
final int launcherUserId = mService.getParentOrSelfUserId(callingUserId);
final String defaultLauncher = mService.getDefaultLauncher(launcherUserId);
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index c23d2abf0853..a600eeabf62b 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -70,6 +70,7 @@ import android.graphics.Canvas;
import android.graphics.RectF;
import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.Icon;
+import android.multiuser.Flags;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
@@ -2830,6 +2831,26 @@ public class ShortcutService extends IShortcutService.Stub {
}
}
+ @VisibleForTesting
+ boolean areShortcutsSupportedOnHomeScreen(@UserIdInt int userId) {
+ if (!android.os.Flags.allowPrivateProfile() || !Flags.disablePrivateSpaceItemsOnHome()) {
+ return true;
+ }
+ final long start = getStatStartTime();
+ final long token = injectClearCallingIdentity();
+ boolean isSupported;
+ try {
+ synchronized (mLock) {
+ isSupported = !mUserManagerInternal.getUserProperties(userId)
+ .areItemsRestrictedOnHomeScreen();
+ }
+ } finally {
+ injectRestoreCallingIdentity(token);
+ logDurationStat(Stats.GET_DEFAULT_LAUNCHER, start);
+ }
+ return isSupported;
+ }
+
@Nullable
String getDefaultLauncher(@UserIdInt int userId) {
final long start = getStatStartTime();
@@ -3660,6 +3681,10 @@ public class ShortcutService extends IShortcutService.Stub {
callingPid, callingUid);
}
+ public boolean areShortcutsSupportedOnHomeScreen(@UserIdInt int userId) {
+ return ShortcutService.this.areShortcutsSupportedOnHomeScreen(userId);
+ }
+
@Override
public void setShortcutHostPackage(@NonNull String type, @Nullable String packageName,
int userId) {
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index f222fe9add0f..7349755402b1 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -18,6 +18,7 @@ package com.android.server.pm;
import static android.content.Intent.ACTION_SCREEN_OFF;
import static android.content.Intent.ACTION_SCREEN_ON;
+import static android.content.Intent.EXTRA_USER_ID;
import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.os.UserManager.DEV_CREATE_OVERRIDE_PROPERTY;
@@ -25,6 +26,8 @@ import static android.os.UserManager.DISALLOW_USER_SWITCH;
import static android.os.UserManager.SYSTEM_USER_MODE_EMULATION_PROPERTY;
import static android.os.UserManager.USER_OPERATION_ERROR_UNKNOWN;
+import static com.android.internal.app.SetScreenLockDialogActivity.EXTRA_ORIGIN_USER_ID;
+import static com.android.internal.app.SetScreenLockDialogActivity.LAUNCH_REASON_DISABLE_QUIET_MODE;
import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
import static com.android.server.pm.UserJourneyLogger.ERROR_CODE_ABORTED;
import static com.android.server.pm.UserJourneyLogger.ERROR_CODE_UNSPECIFIED;
@@ -137,6 +140,7 @@ import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IAppOpsService;
+import com.android.internal.app.SetScreenLockDialogActivity;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.os.BackgroundThread;
import com.android.internal.os.RoSystemProperties;
@@ -973,7 +977,7 @@ public class UserManagerService extends IUserManager.Stub {
mUsers = users != null ? users : new SparseArray<>();
mHandler = new MainHandler();
mInternalExecutor = new ThreadPoolExecutor(/* corePoolSize */ 0, /* maximumPoolSize */ 1,
- /* keepAliveTime */ 1, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
+ /* keepAliveTime */ 24, TimeUnit.HOURS, new LinkedBlockingQueue<>());
mUserVisibilityMediator = new UserVisibilityMediator(mHandler);
mUserDataPreparer = userDataPreparer;
mUserTypes = UserTypeFactory.getUserTypes();
@@ -1676,6 +1680,10 @@ public class UserManagerService extends IUserManager.Stub {
synchronized (mUsersLock) {
userInfo = getUserInfo(userId);
}
+ if (userInfo == null) {
+ throw new IllegalArgumentException("Invalid user. Can't find user details "
+ + "for userId " + userId);
+ }
if (!userInfo.isManagedProfile()) {
throw new IllegalArgumentException("Invalid flags: " + flags
+ ". Can't skip credential check for the user");
@@ -1692,6 +1700,19 @@ public class UserManagerService extends IUserManager.Stub {
if (onlyIfCredentialNotRequired) {
return false;
}
+
+ if (android.multiuser.Flags.showSetScreenLockDialog()) {
+ // Show the prompt to set a new screen lock if the device does not have one
+ final KeyguardManager km = mContext.getSystemService(KeyguardManager.class);
+ if (km != null && !km.isDeviceSecure()) {
+ Intent setScreenLockPromptIntent =
+ SetScreenLockDialogActivity
+ .createBaseIntent(LAUNCH_REASON_DISABLE_QUIET_MODE);
+ setScreenLockPromptIntent.putExtra(EXTRA_ORIGIN_USER_ID, userId);
+ mContext.startActivity(setScreenLockPromptIntent);
+ return false;
+ }
+ }
showConfirmCredentialToDisableQuietMode(userId, target, callingPackage);
return false;
}
@@ -1915,7 +1936,7 @@ public class UserManagerService extends IUserManager.Stub {
if (target != null) {
callBackIntent.putExtra(Intent.EXTRA_INTENT, target);
}
- callBackIntent.putExtra(Intent.EXTRA_USER_ID, userId);
+ callBackIntent.putExtra(EXTRA_USER_ID, userId);
callBackIntent.setPackage(mContext.getPackageName());
callBackIntent.putExtra(Intent.EXTRA_PACKAGE_NAME, callingPackage);
callBackIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java
index 067a012ed373..114daaac3c18 100644
--- a/services/core/java/com/android/server/pm/UserTypeFactory.java
+++ b/services/core/java/com/android/server/pm/UserTypeFactory.java
@@ -306,6 +306,7 @@ public final class UserTypeFactory {
.setDarkThemeBadgeColors(
R.color.white)
.setDefaultRestrictions(getDefaultProfileRestrictions())
+ .setDefaultCrossProfileIntentFilters(getDefaultPrivateCrossProfileIntentFilter())
.setDefaultUserProperties(new UserProperties.Builder()
.setStartWithParent(true)
.setCredentialShareableWithParent(true)
@@ -446,6 +447,11 @@ public final class UserTypeFactory {
return DefaultCrossProfileIntentFiltersUtils.getDefaultCloneProfileFilters();
}
+ private static List<DefaultCrossProfileIntentFilter> getDefaultPrivateCrossProfileIntentFilter()
+ {
+ return DefaultCrossProfileIntentFiltersUtils.getDefaultPrivateProfileFilters();
+ }
+
/** Gets a default bundle, keyed by Settings.Secure String names, for non-managed profiles. */
private static Bundle getDefaultNonManagedProfileSecureSettings() {
final Bundle settings = new Bundle();
diff --git a/services/core/java/com/android/server/pm/VerifyingSession.java b/services/core/java/com/android/server/pm/VerifyingSession.java
index dd2b409c7100..1a9e012a7c53 100644
--- a/services/core/java/com/android/server/pm/VerifyingSession.java
+++ b/services/core/java/com/android/server/pm/VerifyingSession.java
@@ -47,6 +47,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.DataLoaderType;
+import android.content.pm.Flags;
import android.content.pm.IPackageInstallObserver2;
import android.content.pm.PackageInfoLite;
import android.content.pm.PackageInstaller;
@@ -541,7 +542,12 @@ final class VerifyingSession {
}
final int verificationCodeAtTimeout;
- if (getDefaultVerificationResponse() == PackageManager.VERIFICATION_ALLOW) {
+ // Allows package verification to continue in the event the app being updated is verifying
+ // itself and fails to respond
+ if (Flags.emergencyInstallPermission() && requiredVerifierPackages.contains(
+ pkgLite.packageName)) {
+ verificationCodeAtTimeout = PackageManager.VERIFICATION_ALLOW_WITHOUT_SUFFICIENT;
+ } else if (getDefaultVerificationResponse() == PackageManager.VERIFICATION_ALLOW) {
verificationCodeAtTimeout = PackageManager.VERIFICATION_ALLOW_WITHOUT_SUFFICIENT;
} else {
verificationCodeAtTimeout = PackageManager.VERIFICATION_REJECT;
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index bc260184e487..8781bf19565e 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -236,6 +236,7 @@ import com.android.server.policy.keyguard.KeyguardServiceDelegate.DrawnListener;
import com.android.server.policy.keyguard.KeyguardStateMonitor.StateCallback;
import com.android.server.statusbar.StatusBarManagerInternal;
import com.android.server.vibrator.HapticFeedbackVibrationProvider;
+import com.android.server.vibrator.VibratorFrameworkStatsLogger;
import com.android.server.vr.VrManagerInternal;
import com.android.server.wallpaper.WallpaperManagerInternal;
import com.android.server.wm.ActivityTaskManagerInternal;
@@ -3509,6 +3510,16 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
}
break;
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
+ StatusBarManagerInternal statusbar = getStatusBarManagerInternal();
+ if (statusbar != null) {
+ statusbar.enterDesktop(event.getDisplayId());
+ logKeyboardSystemsEvent(event, KeyboardLogEvent.DESKTOP_MODE);
+ return true;
+ }
+ }
+ break;
case KeyEvent.KEYCODE_DPAD_LEFT:
if (firstDown && event.isMetaPressed()) {
if (event.isCtrlPressed()) {
@@ -6421,6 +6432,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
VibrationAttributes attrs =
mHapticFeedbackVibrationProvider.getVibrationAttributesForHapticFeedback(
effectId, /* bypassVibrationIntensitySetting= */ always);
+ VibratorFrameworkStatsLogger.logPerformHapticsFeedbackIfKeyboard(uid, effectId);
mVibrator.vibrate(uid, packageName, effect, reason, attrs);
return true;
}
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index af4da812d79e..9b347d572459 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -13854,7 +13854,9 @@ public class BatteryStatsImpl extends BatteryStats {
mNumAllUidCpuTimeReads += 2;
}
- updateSystemServerThreadStats();
+ if (!Flags.disableSystemServicePowerAttr()) {
+ updateSystemServerThreadStats();
+ }
if (powerAccumulator != null) {
updateCpuEnergyConsumerStatsLocked(cpuClusterChargeUC, powerAccumulator);
diff --git a/services/core/java/com/android/server/power/stats/MobileRadioPowerCalculator.java b/services/core/java/com/android/server/power/stats/MobileRadioPowerCalculator.java
index 1050e8a371e8..9ea143e5c201 100644
--- a/services/core/java/com/android/server/power/stats/MobileRadioPowerCalculator.java
+++ b/services/core/java/com/android/server/power/stats/MobileRadioPowerCalculator.java
@@ -32,7 +32,6 @@ import com.android.internal.power.ModemPowerProfile;
import java.util.ArrayList;
-@android.ravenwood.annotation.RavenwoodKeepWholeClass
public class MobileRadioPowerCalculator extends PowerCalculator {
private static final String TAG = "MobRadioPowerCalculator";
private static final boolean DEBUG = PowerCalculator.DEBUG;
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index 359678b15213..2a9325544833 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -1212,13 +1212,20 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub implements Rollba
rollback.makeAvailable();
mPackageHealthObserver.notifyRollbackAvailable(rollback.info);
- // TODO(zezeozue): Provide API to explicitly start observing instead
- // of doing this for all rollbacks. If we do this for all rollbacks,
- // should document in PackageInstaller.SessionParams#setEnableRollback
- // After enabling and committing any rollback, observe packages and
- // prepare to rollback if packages crashes too frequently.
- mPackageHealthObserver.startObservingHealth(rollback.getPackageNames(),
- mRollbackLifetimeDurationInMillis);
+ if (Flags.recoverabilityDetection()) {
+ if (rollback.info.getRollbackImpactLevel() == PackageManager.ROLLBACK_USER_IMPACT_LOW) {
+ // TODO(zezeozue): Provide API to explicitly start observing instead
+ // of doing this for all rollbacks. If we do this for all rollbacks,
+ // should document in PackageInstaller.SessionParams#setEnableRollback
+ // After enabling and committing any rollback, observe packages and
+ // prepare to rollback if packages crashes too frequently.
+ mPackageHealthObserver.startObservingHealth(rollback.getPackageNames(),
+ mRollbackLifetimeDurationInMillis);
+ }
+ } else {
+ mPackageHealthObserver.startObservingHealth(rollback.getPackageNames(),
+ mRollbackLifetimeDurationInMillis);
+ }
runExpiration();
}
diff --git a/services/core/java/com/android/server/search/Searchables.java b/services/core/java/com/android/server/search/Searchables.java
index 6e1e979b1bac..7b397755173d 100644
--- a/services/core/java/com/android/server/search/Searchables.java
+++ b/services/core/java/com/android/server/search/Searchables.java
@@ -147,6 +147,9 @@ public class Searchables {
Log.e(LOG_TAG, "Error getting activity info " + re);
return null;
}
+ if (ai == null) {
+ return null;
+ }
String refActivityName = null;
// First look for activity-specific reference
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
index a4c6959f8ad5..3c6baa873eca 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -255,4 +255,9 @@ public interface StatusBarManagerInternal {
* @param tile the ComponentName of the {@link android.service.quicksettings.TileService}
*/
void removeQsTile(ComponentName tile);
+
+ /**
+ * Called when requested to enter desktop from an app.
+ */
+ void enterDesktop(int displayId);
}
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index fd316eaf9b96..14c38bde6621 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -830,6 +830,15 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
}
@Override
+ public void enterDesktop(int displayId) {
+ IStatusBar bar = mBar;
+ if (bar != null) {
+ try {
+ bar.enterDesktop(displayId);
+ } catch (RemoteException ex) { }
+ }
+ }
+ @Override
public void showMediaOutputSwitcher(String packageName) {
IStatusBar bar = mBar;
if (bar != null) {
diff --git a/services/core/java/com/android/server/utils/TimingsTraceAndSlog.java b/services/core/java/com/android/server/utils/TimingsTraceAndSlog.java
index b45c962811ad..d8dfd9f13d18 100644
--- a/services/core/java/com/android/server/utils/TimingsTraceAndSlog.java
+++ b/services/core/java/com/android/server/utils/TimingsTraceAndSlog.java
@@ -23,6 +23,7 @@ import android.util.TimingsTraceLog;
/**
* Helper class for reporting boot and shutdown timing metrics, also logging to {@link Slog}.
*/
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public final class TimingsTraceAndSlog extends TimingsTraceLog {
/**
diff --git a/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java b/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java
index 099c9ae33bfb..17f5e9585c22 100644
--- a/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java
+++ b/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java
@@ -44,6 +44,7 @@ import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting.Visibility;
+import com.android.internal.telephony.flags.Flags;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
@@ -318,8 +319,12 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver {
if (SubscriptionManager.isValidSubscriptionId(subId)) {
// Get only configs as needed to save memory.
- final PersistableBundle carrierConfig = mCarrierConfigManager.getConfigForSubId(subId,
- VcnManager.VCN_RELATED_CARRIER_CONFIG_KEYS);
+ final PersistableBundle carrierConfig =
+ Flags.fixCrashOnGettingConfigWhenPhoneIsGone()
+ ? CarrierConfigManager.getCarrierConfigSubset(mContext, subId,
+ VcnManager.VCN_RELATED_CARRIER_CONFIG_KEYS)
+ : mCarrierConfigManager.getConfigForSubId(subId,
+ VcnManager.VCN_RELATED_CARRIER_CONFIG_KEYS);
if (mDeps.isConfigForIdentifiedCarrier(carrierConfig)) {
mReadySubIdsBySlotId.put(slotId, subId);
diff --git a/services/core/java/com/android/server/vibrator/HalVibration.java b/services/core/java/com/android/server/vibrator/HalVibration.java
index 743d02d100b1..70e2e27a3bae 100644
--- a/services/core/java/com/android/server/vibrator/HalVibration.java
+++ b/services/core/java/com/android/server/vibrator/HalVibration.java
@@ -102,6 +102,23 @@ final class HalVibration extends Vibration {
}
/**
+ * Resolves the default vibration amplitude of {@link #getEffectToPlay()} and each fallback.
+ *
+ * @param defaultAmplitude An integer in [1,255] representing the device default amplitude to
+ * replace the {@link VibrationEffect#DEFAULT_AMPLITUDE}.
+ */
+ public void resolveEffects(int defaultAmplitude) {
+ CombinedVibration newEffect =
+ mEffectToPlay.transform(VibrationEffect::resolve, defaultAmplitude);
+ if (!Objects.equals(mEffectToPlay, newEffect)) {
+ mEffectToPlay = newEffect;
+ }
+ for (int i = 0; i < mFallbacks.size(); i++) {
+ mFallbacks.setValueAt(i, mFallbacks.valueAt(i).resolve(defaultAmplitude));
+ }
+ }
+
+ /**
* Scales the {@link #getEffectToPlay()} and each fallback effect with a scaling transformation.
*
* @param scaler A {@link VibrationEffect.Transformation<Integer>} that takes one of the
diff --git a/services/core/java/com/android/server/vibrator/VibrationScaler.java b/services/core/java/com/android/server/vibrator/VibrationScaler.java
index c9805c706fbc..7163319f281a 100644
--- a/services/core/java/com/android/server/vibrator/VibrationScaler.java
+++ b/services/core/java/com/android/server/vibrator/VibrationScaler.java
@@ -73,6 +73,13 @@ final class VibrationScaler {
}
/**
+ * Returns the default vibration amplitude configured for this device, value in [1,255].
+ */
+ public int getDefaultVibrationAmplitude() {
+ return mDefaultVibrationAmplitude;
+ }
+
+ /**
* Calculates the scale to be applied to external vibration with given usage.
*
* @param usageHint one of VibrationAttributes.USAGE_*
@@ -149,7 +156,7 @@ final class VibrationScaler {
&& mAdaptiveHapticsScales.size() > 0
&& mAdaptiveHapticsScales.contains(usageHint)) {
float adaptiveScale = mAdaptiveHapticsScales.get(usageHint);
- segment = segment.scale(adaptiveScale);
+ segment = segment.scaleLinearly(adaptiveScale);
}
segments.set(i, segment);
diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java
index fab0430cc40d..99ce3e2fb740 100644
--- a/services/core/java/com/android/server/vibrator/VibrationSettings.java
+++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java
@@ -31,6 +31,7 @@ import static android.os.VibrationAttributes.USAGE_UNKNOWN;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
+import android.app.SynchronousUserSwitchObserver;
import android.app.UidObserver;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -145,8 +146,6 @@ final class VibrationSettings {
PowerManager.GO_TO_SLEEP_REASON_INATTENTIVE,
PowerManager.GO_TO_SLEEP_REASON_TIMEOUT));
- private static final IntentFilter USER_SWITCHED_INTENT_FILTER =
- new IntentFilter(Intent.ACTION_USER_SWITCHED);
private static final IntentFilter INTERNAL_RINGER_MODE_CHANGED_INTENT_FILTER =
new IntentFilter(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION);
@@ -162,9 +161,11 @@ final class VibrationSettings {
@VisibleForTesting
final SettingsContentObserver mSettingObserver;
@VisibleForTesting
- final MyUidObserver mUidObserver;
- @VisibleForTesting
final SettingsBroadcastReceiver mSettingChangeReceiver;
+ @VisibleForTesting
+ final VibrationUidObserver mUidObserver;
+ @VisibleForTesting
+ final VibrationUserSwitchObserver mUserSwitchObserver;
@GuardedBy("mLock")
private final List<OnVibratorSettingsChanged> mListeners = new ArrayList<>();
@@ -205,8 +206,9 @@ final class VibrationSettings {
mContext = context;
mVibrationConfig = config;
mSettingObserver = new SettingsContentObserver(handler);
- mUidObserver = new MyUidObserver();
mSettingChangeReceiver = new SettingsBroadcastReceiver();
+ mUidObserver = new VibrationUidObserver();
+ mUserSwitchObserver = new VibrationUserSwitchObserver();
mSystemUiPackage = LocalServices.getService(PackageManagerInternal.class)
.getSystemUiServiceComponent().getPackageName();
@@ -245,7 +247,13 @@ final class VibrationSettings {
try {
ActivityManager.getService().registerUidObserver(mUidObserver,
ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_GONE,
- ActivityManager.PROCESS_STATE_UNKNOWN, null);
+ ActivityManager.PROCESS_STATE_UNKNOWN, /* callingPackage= */ null);
+ } catch (RemoteException e) {
+ // ignored; both services live in system_server
+ }
+
+ try {
+ ActivityManager.getService().registerUserSwitchObserver(mUserSwitchObserver, TAG);
} catch (RemoteException e) {
// ignored; both services live in system_server
}
@@ -270,7 +278,6 @@ final class VibrationSettings {
}
});
- registerSettingsChangeReceiver(USER_SWITCHED_INTENT_FILTER);
registerSettingsChangeReceiver(INTERNAL_RINGER_MODE_CHANGED_INTENT_FILTER);
// Listen to all settings that might affect the result of Vibrator.getVibrationIntensity.
@@ -540,41 +547,44 @@ final class VibrationSettings {
/** Update all cached settings and triggers registered listeners. */
void update() {
- updateSettings();
+ updateSettings(UserHandle.USER_CURRENT);
updateRingerMode();
notifyListeners();
}
- private void updateSettings() {
+ private void updateSettings(int userHandle) {
synchronized (mLock) {
- mVibrateInputDevices = loadSystemSetting(Settings.System.VIBRATE_INPUT_DEVICES, 0) > 0;
- mVibrateOn = loadSystemSetting(Settings.System.VIBRATE_ON, 1) > 0;
+ mVibrateInputDevices =
+ loadSystemSetting(Settings.System.VIBRATE_INPUT_DEVICES, 0, userHandle) > 0;
+ mVibrateOn = loadSystemSetting(Settings.System.VIBRATE_ON, 1, userHandle) > 0;
mKeyboardVibrationOn = loadSystemSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED,
- mVibrationConfig.isDefaultKeyboardVibrationEnabled() ? 1 : 0) > 0;
+ mVibrationConfig.isDefaultKeyboardVibrationEnabled() ? 1 : 0, userHandle) > 0;
int alarmIntensity = toIntensity(
- loadSystemSetting(Settings.System.ALARM_VIBRATION_INTENSITY, -1),
+ loadSystemSetting(Settings.System.ALARM_VIBRATION_INTENSITY, -1, userHandle),
getDefaultIntensity(USAGE_ALARM));
int defaultHapticFeedbackIntensity = getDefaultIntensity(USAGE_TOUCH);
int hapticFeedbackIntensity = toIntensity(
- loadSystemSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, -1),
+ loadSystemSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, -1, userHandle),
defaultHapticFeedbackIntensity);
int positiveHapticFeedbackIntensity = toPositiveIntensity(
hapticFeedbackIntensity, defaultHapticFeedbackIntensity);
int hardwareFeedbackIntensity = toIntensity(
- loadSystemSetting(Settings.System.HARDWARE_HAPTIC_FEEDBACK_INTENSITY, -1),
+ loadSystemSetting(Settings.System.HARDWARE_HAPTIC_FEEDBACK_INTENSITY, -1,
+ userHandle),
positiveHapticFeedbackIntensity);
int mediaIntensity = toIntensity(
- loadSystemSetting(Settings.System.MEDIA_VIBRATION_INTENSITY, -1),
+ loadSystemSetting(Settings.System.MEDIA_VIBRATION_INTENSITY, -1, userHandle),
getDefaultIntensity(USAGE_MEDIA));
int defaultNotificationIntensity = getDefaultIntensity(USAGE_NOTIFICATION);
int notificationIntensity = toIntensity(
- loadSystemSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, -1),
+ loadSystemSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, -1,
+ userHandle),
defaultNotificationIntensity);
int positiveNotificationIntensity = toPositiveIntensity(
notificationIntensity, defaultNotificationIntensity);
int ringIntensity = toIntensity(
- loadSystemSetting(Settings.System.RING_VIBRATION_INTENSITY, -1),
+ loadSystemSetting(Settings.System.RING_VIBRATION_INTENSITY, -1, userHandle),
getDefaultIntensity(USAGE_RINGTONE));
mCurrentVibrationIntensities.clear();
@@ -593,7 +603,7 @@ final class VibrationSettings {
mCurrentVibrationIntensities.put(USAGE_HARDWARE_FEEDBACK, hardwareFeedbackIntensity);
mCurrentVibrationIntensities.put(USAGE_PHYSICAL_EMULATION, hardwareFeedbackIntensity);
- if (!loadBooleanSetting(Settings.System.HAPTIC_FEEDBACK_ENABLED)) {
+ if (!loadBooleanSetting(Settings.System.HAPTIC_FEEDBACK_ENABLED, userHandle)) {
// Make sure deprecated boolean setting still disables touch vibrations.
mCurrentVibrationIntensities.put(USAGE_TOUCH, Vibrator.VIBRATION_INTENSITY_OFF);
} else {
@@ -744,14 +754,13 @@ final class VibrationSettings {
return value;
}
- private boolean loadBooleanSetting(String settingKey) {
- return Settings.System.getIntForUser(mContext.getContentResolver(),
- settingKey, 0, UserHandle.USER_CURRENT) != 0;
+ private boolean loadBooleanSetting(String settingKey, int userHandle) {
+ return loadSystemSetting(settingKey, 0, userHandle) != 0;
}
- private int loadSystemSetting(String settingName, int defaultValue) {
+ private int loadSystemSetting(String settingName, int defaultValue, int userHandle) {
return Settings.System.getIntForUser(mContext.getContentResolver(),
- settingName, defaultValue, UserHandle.USER_CURRENT);
+ settingName, defaultValue, userHandle);
}
private void registerSettingsObserver(Uri settingUri) {
@@ -828,24 +837,18 @@ final class VibrationSettings {
@Override
public void onChange(boolean selfChange) {
- updateSettings();
+ updateSettings(UserHandle.USER_CURRENT);
notifyListeners();
}
}
- /**
- * Implementation of {@link BroadcastReceiver} to update settings on current user or ringer
- * mode change.
- */
+ /** Implementation of {@link BroadcastReceiver} to update on ringer mode change. */
@VisibleForTesting
final class SettingsBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
- if (Intent.ACTION_USER_SWITCHED.equals(action)) {
- // Reload all settings, as they are user-based.
- update();
- } else if (AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION.equals(action)) {
+ if (AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION.equals(action)) {
updateRingerMode();
notifyListeners();
}
@@ -854,7 +857,7 @@ final class VibrationSettings {
/** Implementation of {@link ContentObserver} to be registered to a setting {@link Uri}. */
@VisibleForTesting
- final class MyUidObserver extends UidObserver {
+ final class VibrationUidObserver extends UidObserver {
private final SparseArray<Integer> mProcStatesCache = new SparseArray<>();
public boolean isUidForeground(int uid) {
@@ -878,4 +881,23 @@ final class VibrationSettings {
}
}
}
+
+ /** Implementation of {@link SynchronousUserSwitchObserver} to update on user switch. */
+ @VisibleForTesting
+ final class VibrationUserSwitchObserver extends SynchronousUserSwitchObserver {
+
+ @Override
+ public void onUserSwitching(int newUserId) {
+ // Reload settings early based on new user id.
+ updateSettings(newUserId);
+ notifyListeners();
+ }
+
+ @Override
+ public void onUserSwitchComplete(int newUserId) {
+ // Reload all settings including ones from AudioManager,
+ // as they are based on UserHandle.USER_CURRENT.
+ update();
+ }
+ }
}
diff --git a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
index 9cf942e91439..f6af9ad991ff 100644
--- a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
+++ b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
@@ -160,7 +160,10 @@ final class VibrationStepConductor implements IBinder.DeathRecipient {
if (Flags.adaptiveHapticsEnabled()) {
waitForVibrationParamsIfRequired();
}
+ // Scale resolves the default amplitudes from the effect before scaling them.
mVibration.scaleEffects(mVibrationScaler::scale);
+ } else {
+ mVibration.resolveEffects(mVibrationScaler.getDefaultVibrationAmplitude());
}
mVibration.adaptToDevice(mDeviceAdapter);
diff --git a/services/core/java/com/android/server/vibrator/VibratorFrameworkStatsLogger.java b/services/core/java/com/android/server/vibrator/VibratorFrameworkStatsLogger.java
index f600a2964cbc..7e601b64ad18 100644
--- a/services/core/java/com/android/server/vibrator/VibratorFrameworkStatsLogger.java
+++ b/services/core/java/com/android/server/vibrator/VibratorFrameworkStatsLogger.java
@@ -19,10 +19,12 @@ package com.android.server.vibrator;
import android.os.Handler;
import android.os.SystemClock;
import android.util.Slog;
+import android.view.HapticFeedbackConstants;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.modules.expresslog.Counter;
import java.util.ArrayDeque;
import java.util.Queue;
@@ -137,4 +139,21 @@ public class VibratorFrameworkStatsLogger {
mVibrationReportedLogIntervalMillis);
}
}
+
+ /** Logs only if the haptics feedback effect is one of the KEYBOARD_ constants. */
+ public static void logPerformHapticsFeedbackIfKeyboard(int uid, int hapticsFeedbackEffect) {
+ boolean isKeyboard;
+ switch (hapticsFeedbackEffect) {
+ case HapticFeedbackConstants.KEYBOARD_TAP:
+ case HapticFeedbackConstants.KEYBOARD_RELEASE:
+ isKeyboard = true;
+ break;
+ default:
+ isKeyboard = false;
+ break;
+ }
+ if (isKeyboard) {
+ Counter.logIncrementWithUid("vibrator.value_perform_haptic_feedback_keyboard", uid);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 759450b528b9..be5d15877e32 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -448,6 +448,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
VibrationAttributes attrs =
hapticVibrationProvider.getVibrationAttributesForHapticFeedback(
constant, /* bypassVibrationIntensitySetting= */ always);
+ VibratorFrameworkStatsLogger.logPerformHapticsFeedbackIfKeyboard(uid, constant);
return vibrateWithoutPermissionCheck(uid, deviceId, opPkg, combinedVibration, attrs,
"performHapticFeedback: " + reason, token);
}
@@ -883,8 +884,10 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
private Vibration.EndInfo startVibrationOnInputDevicesLocked(HalVibration vib) {
if (!vib.callerInfo.attrs.isFlagSet(
VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE)) {
- // Scale effect before dispatching it to the input devices.
+ // Scale resolves the default amplitudes from the effect before scaling them.
vib.scaleEffects(mVibrationScaler::scale);
+ } else {
+ vib.resolveEffects(mVibrationScaler.getDefaultVibrationAmplitude());
}
mInputDeviceDelegate.vibrateIfAvailable(vib.callerInfo, vib.getEffectToPlay());
@@ -2220,9 +2223,12 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
// only cancel background vibrations.
IBinder deathBinder = commonOptions.background ? VibratorManagerService.this
: mShellCallbacksToken;
- HalVibration vib = vibrateWithPermissionCheck(Binder.getCallingUid(),
- Context.DEVICE_ID_DEFAULT, SHELL_PACKAGE_NAME, combined, attrs,
- commonOptions.description, deathBinder);
+ int uid = Binder.getCallingUid();
+ // Resolve the package name for the client based on the process UID, to cover cases like
+ // rooted shell clients using ROOT_UID.
+ String resolvedPackageName = AppOpsManager.resolvePackageName(uid, SHELL_PACKAGE_NAME);
+ HalVibration vib = vibrateWithPermissionCheck(uid, Context.DEVICE_ID_DEFAULT,
+ resolvedPackageName, combined, attrs, commonOptions.description, deathBinder);
maybeWaitOnVibration(vib, commonOptions);
}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 118985a729aa..c3efcb14f223 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -52,6 +52,7 @@ import android.app.ApplicationExitInfo;
import android.app.ILocalWallpaperColorConsumer;
import android.app.IWallpaperManager;
import android.app.IWallpaperManagerCallback;
+import android.app.KeyguardManager;
import android.app.PendingIntent;
import android.app.UidObserver;
import android.app.UserSwitchObserver;
@@ -340,8 +341,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
// If this was the system wallpaper, rebind...
wallpaper.mBindSource = BindSource.SET_STATIC;
- bindWallpaperComponentLocked(mImageWallpaper, true, false, wallpaper,
- callback);
+ bindWallpaperComponentLocked(mImageWallpaper, true, false, wallpaper, callback);
}
if (lockWallpaperChanged) {
@@ -368,11 +368,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
if (DEBUG) {
Slog.v(TAG, "Lock screen wallpaper changed to same as home");
}
- final WallpaperData lockedWallpaper = mLockWallpaperMap.get(
- mWallpaper.userId);
- if (lockedWallpaper != null) {
- detachWallpaperLocked(lockedWallpaper);
- }
+ detachWallpaperLocked(mLockWallpaperMap.get(mWallpaper.userId));
clearWallpaperBitmaps(mWallpaper.userId, FLAG_LOCK);
mLockWallpaperMap.remove(wallpaper.userId);
}
@@ -1696,7 +1692,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
sWallpaperType.forEach((type, filename) -> {
final File record = new File(getWallpaperDir(userID), filename);
if (record.exists()) {
- Slog.w(TAG, "User:" + userID + ", wallpaper tyep = " + type
+ Slog.w(TAG, "User:" + userID + ", wallpaper type = " + type
+ ", wallpaper fail detect!! reset to default wallpaper");
clearWallpaperBitmaps(userID, type);
record.delete();
@@ -1792,10 +1788,23 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
systemWallpaper.wallpaperObserver = new WallpaperObserver(systemWallpaper);
systemWallpaper.wallpaperObserver.startWatching();
}
- if (lockWallpaper != systemWallpaper) {
- switchWallpaper(lockWallpaper, null);
+ if (Flags.reorderWallpaperDuringUserSwitch()) {
+ detachWallpaperLocked(mLastLockWallpaper);
+ detachWallpaperLocked(mLastWallpaper);
+ if (lockWallpaper == systemWallpaper) {
+ switchWallpaper(systemWallpaper, reply);
+ } else {
+ KeyguardManager km = mContext.getSystemService(KeyguardManager.class);
+ boolean isDeviceSecure = km != null && km.isDeviceSecure(userId);
+ switchWallpaper(isDeviceSecure ? lockWallpaper : systemWallpaper, reply);
+ switchWallpaper(isDeviceSecure ? systemWallpaper : lockWallpaper, null);
+ }
+ } else {
+ if (lockWallpaper != systemWallpaper) {
+ switchWallpaper(lockWallpaper, null);
+ }
+ switchWallpaper(systemWallpaper, reply);
}
- switchWallpaper(systemWallpaper, reply);
mInitialUserSwitch = false;
}
@@ -2219,8 +2228,9 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
if (wallpaper == null || !wallpaper.mSupportsMultiCrop) return null;
SparseArray<Rect> relativeSuggestedCrops =
mWallpaperCropper.getRelativeCropHints(wallpaper);
- Point croppedBitmapSize =
- new Point(wallpaper.cropHint.width(), wallpaper.cropHint.height());
+ Point croppedBitmapSize = new Point(
+ (int) (0.5f + wallpaper.cropHint.width() / wallpaper.mSampleSize),
+ (int) (0.5f + wallpaper.cropHint.height() / wallpaper.mSampleSize));
SparseArray<Rect> relativeDefaultCrops =
mWallpaperCropper.getDefaultCrops(relativeSuggestedCrops, croppedBitmapSize);
SparseArray<Rect> adjustedRelativeSuggestedCrops = new SparseArray<>();
@@ -3385,10 +3395,10 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
boolean homeUpdated = (newWallpaper.mWhich & FLAG_SYSTEM) != 0;
boolean lockUpdated = (newWallpaper.mWhich & FLAG_LOCK) != 0;
boolean systemWillBecomeLock = newWallpaper.mSystemWasBoth && !lockUpdated;
- if (mLastWallpaper != null && homeUpdated && !systemWillBecomeLock) {
+ if (homeUpdated && !systemWillBecomeLock) {
detachWallpaperLocked(mLastWallpaper);
}
- if (mLastLockWallpaper != null && lockUpdated) {
+ if (lockUpdated) {
detachWallpaperLocked(mLastLockWallpaper);
}
}
@@ -3396,7 +3406,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
// Frees up all rendering resources used by the given wallpaper so that the WallpaperData object
// can be reused: detaches Engine, unbinds WallpaperService, etc.
private void detachWallpaperLocked(WallpaperData wallpaper) {
- if (wallpaper.connection != null) {
+ if (wallpaper != null && wallpaper.connection != null) {
if (DEBUG) {
Slog.v(TAG, "Detaching wallpaper: " + wallpaper);
}
diff --git a/services/core/java/com/android/server/wearable/RemoteWearableSensingService.java b/services/core/java/com/android/server/wearable/RemoteWearableSensingService.java
index 3077fb8aaee9..e230b95fe907 100644
--- a/services/core/java/com/android/server/wearable/RemoteWearableSensingService.java
+++ b/services/core/java/com/android/server/wearable/RemoteWearableSensingService.java
@@ -43,16 +43,16 @@ final class RemoteWearableSensingService extends ServiceConnector.Impl<IWearable
com.android.server.wearable.RemoteWearableSensingService.class.getSimpleName();
private final static boolean DEBUG = false;
- private final Object mSecureWearableConnectionLock = new Object();
+ private final Object mSecureConnectionLock = new Object();
- // mNextSecureWearableConnectionContext will only be non-null when we are waiting for the
+ // mNextSecureConnectionContext will only be non-null when we are waiting for the
// WearableSensingService process to restart. It will be set to null after it is passed into
// WearableSensingService.
- @GuardedBy("mSecureWearableConnectionLock")
- private SecureWearableConnectionContext mNextSecureWearableConnectionContext;
+ @GuardedBy("mSecureConnectionLock")
+ private SecureWearableConnectionContext mNextSecureConnectionContext;
- @GuardedBy("mSecureWearableConnectionLock")
- private boolean mSecureWearableConnectionProvided = false;
+ @GuardedBy("mSecureConnectionLock")
+ private boolean mSecureConnectionProvided = false;
RemoteWearableSensingService(Context context, ComponentName serviceName,
int userId) {
@@ -77,23 +77,23 @@ final class RemoteWearableSensingService extends ServiceConnector.Impl<IWearable
* @param secureWearableConnection The secure connection to the wearable
* @param callback The callback for service status
*/
- public void provideSecureWearableConnection(
+ public void provideSecureConnection(
ParcelFileDescriptor secureWearableConnection, RemoteCallback callback) {
if (DEBUG) {
- Slog.i(TAG, "#provideSecureWearableConnection");
+ Slog.i(TAG, "#provideSecureConnection");
}
if (!Flags.enableRestartWssProcess()) {
Slog.d(
TAG,
"FLAG_ENABLE_RESTART_WSS_PROCESS is disabled. Do not attempt to restart the"
+ " WearableSensingService process");
- provideSecureWearableConnectionInternal(secureWearableConnection, callback);
+ provideSecureConnectionInternal(secureWearableConnection, callback);
return;
}
- synchronized (mSecureWearableConnectionLock) {
- if (mNextSecureWearableConnectionContext != null) {
+ synchronized (mSecureConnectionLock) {
+ if (mNextSecureConnectionContext != null) {
// A process restart is in progress, #binderDied is about to be called. Replace
- // the previous mNextSecureWearableConnectionContext with the current one
+ // the previous mNextSecureConnectionContext with the current one
Slog.i(
TAG,
"A new wearable connection is provided before the process restart triggered"
@@ -101,33 +101,33 @@ final class RemoteWearableSensingService extends ServiceConnector.Impl<IWearable
+ " connection.");
if (Flags.enableProvideWearableConnectionApi()) {
WearableSensingManagerPerUserService.notifyStatusCallback(
- mNextSecureWearableConnectionContext.mStatusCallback,
+ mNextSecureConnectionContext.mStatusCallback,
WearableSensingManager.STATUS_CHANNEL_ERROR);
}
- mNextSecureWearableConnectionContext =
+ mNextSecureConnectionContext =
new SecureWearableConnectionContext(secureWearableConnection, callback);
return;
}
- if (!mSecureWearableConnectionProvided) {
+ if (!mSecureConnectionProvided) {
// no need to kill the process
- provideSecureWearableConnectionInternal(secureWearableConnection, callback);
- mSecureWearableConnectionProvided = true;
+ provideSecureConnectionInternal(secureWearableConnection, callback);
+ mSecureConnectionProvided = true;
return;
}
- mNextSecureWearableConnectionContext =
+ mNextSecureConnectionContext =
new SecureWearableConnectionContext(secureWearableConnection, callback);
// Killing the process causes the binder to die. #binderDied will then be triggered
killWearableSensingServiceProcess();
}
}
- private void provideSecureWearableConnectionInternal(
+ private void provideSecureConnectionInternal(
ParcelFileDescriptor secureWearableConnection, RemoteCallback callback) {
Slog.d(TAG, "Providing secure wearable connection.");
var unused =
post(
service -> {
- service.provideSecureWearableConnection(
+ service.provideSecureConnection(
secureWearableConnection, callback);
try {
// close the local fd after it has been sent to the WSS process
@@ -141,15 +141,15 @@ final class RemoteWearableSensingService extends ServiceConnector.Impl<IWearable
@Override
public void binderDied() {
super.binderDied();
- synchronized (mSecureWearableConnectionLock) {
- if (mNextSecureWearableConnectionContext != null) {
+ synchronized (mSecureConnectionLock) {
+ if (mNextSecureConnectionContext != null) {
// This will call #post, which will recreate the process and bind to it
- provideSecureWearableConnectionInternal(
- mNextSecureWearableConnectionContext.mSecureWearableConnection,
- mNextSecureWearableConnectionContext.mStatusCallback);
- mNextSecureWearableConnectionContext = null;
+ provideSecureConnectionInternal(
+ mNextSecureConnectionContext.mSecureConnection,
+ mNextSecureConnectionContext.mStatusCallback);
+ mNextSecureConnectionContext = null;
} else {
- mSecureWearableConnectionProvided = false;
+ mSecureConnectionProvided = false;
Slog.w(TAG, "Binder died but there is no secure wearable connection to provide.");
}
}
@@ -307,12 +307,12 @@ final class RemoteWearableSensingService extends ServiceConnector.Impl<IWearable
}
private static class SecureWearableConnectionContext {
- final ParcelFileDescriptor mSecureWearableConnection;
+ final ParcelFileDescriptor mSecureConnection;
final RemoteCallback mStatusCallback;
SecureWearableConnectionContext(
ParcelFileDescriptor secureWearableConnection, RemoteCallback statusCallback) {
- this.mSecureWearableConnection = secureWearableConnection;
+ this.mSecureConnection = secureWearableConnection;
this.mStatusCallback = statusCallback;
}
}
diff --git a/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java b/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java
index 2b43203628d9..34b9fe968994 100644
--- a/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java
+++ b/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java
@@ -189,9 +189,9 @@ final class WearableSensingManagerPerUserService extends
* Creates a CompanionDeviceManager secure channel and sends a proxy to the wearable sensing
* service.
*/
- public void onProvideWearableConnection(
+ public void onProvideConnection(
ParcelFileDescriptor wearableConnection, RemoteCallback callback) {
- Slog.i(TAG, "onProvideWearableConnection in per user service.");
+ Slog.i(TAG, "onProvideConnection in per user service.");
synchronized (mLock) {
if (!setUpServiceIfNeeded()) {
Slog.w(TAG, "Detection service is not available at this moment.");
@@ -217,7 +217,7 @@ final class WearableSensingManagerPerUserService extends
Slog.i(TAG, "calling over to remote service.");
synchronized (mLock) {
ensureRemoteServiceInitiated();
- mRemoteService.provideSecureWearableConnection(
+ mRemoteService.provideSecureConnection(
secureTransport, callback);
}
}
diff --git a/services/core/java/com/android/server/wearable/WearableSensingManagerService.java b/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
index 00c3026b1194..5f6ffd988c84 100644
--- a/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
+++ b/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
@@ -55,6 +55,7 @@ import com.android.server.pm.KnownPackages;
import com.android.server.utils.quota.MultiRateLimiter;
import java.io.FileDescriptor;
+import java.time.Duration;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
@@ -106,7 +107,7 @@ public class WearableSensingManagerService extends
private final Context mContext;
private final AtomicInteger mNextDataRequestObserverId = new AtomicInteger(1);
private final Set<DataRequestObserverContext> mDataRequestObserverContexts = new HashSet<>();
- private final MultiRateLimiter mDataRequestRateLimiter;
+ @NonNull private volatile MultiRateLimiter mDataRequestRateLimiter;
volatile boolean mIsServiceEnabled;
public WearableSensingManagerService(Context context) {
@@ -238,6 +239,57 @@ public class WearableSensingManagerService extends
}
}
+ /**
+ * Sets the window size used in data request rate limiting.
+ *
+ * <p>The new value will not be reflected in {@link
+ * WearableSensingDataRequest#getRateLimitWindowSize()}.
+ *
+ * <p>{@code windowSize} will be automatically capped between
+ * com.android.server.utils.quota.QuotaTracker#MIN_WINDOW_SIZE_MS and
+ * com.android.server.utils.quota.QuotaTracker#MAX_WINDOW_SIZE_MS
+ *
+ * <p>The current rate limit will also be reset.
+ *
+ * <p>This method is only used for testing and must not be called in production code because
+ * it effectively bypasses the rate limiting introduced to enhance privacy protection.
+ */
+ @VisibleForTesting
+ void setDataRequestRateLimitWindowSize(@NonNull Duration windowSize) {
+ Slog.w(
+ TAG,
+ TextUtils.formatSimple(
+ "Setting the data request rate limit window size to %s. This also resets"
+ + " the current limit and should only be callable from a test.",
+ windowSize));
+ mDataRequestRateLimiter =
+ new MultiRateLimiter.Builder(mContext)
+ .addRateLimit(WearableSensingDataRequest.getRateLimit(), windowSize)
+ .build();
+ }
+
+ /**
+ * Resets the window size used in data request rate limiting back to the default value.
+ *
+ * <p>The current rate limit will also be reset.
+ *
+ * <p>This method is only used for testing and must not be called in production code because
+ * it effectively bypasses the rate limiting introduced to enhance privacy protection.
+ */
+ @VisibleForTesting
+ void resetDataRequestRateLimitWindowSize() {
+ Slog.w(
+ TAG,
+ "Resetting the data request rate limit window size back to the default value. This"
+ + " also resets the current limit and should only be callable from a test.");
+ mDataRequestRateLimiter =
+ new MultiRateLimiter.Builder(mContext)
+ .addRateLimit(
+ WearableSensingDataRequest.getRateLimit(),
+ WearableSensingDataRequest.getRateLimitWindowSize())
+ .build();
+ }
+
private DataRequestObserverContext getDataRequestObserverContext(
int dataType, int userId, PendingIntent dataRequestPendingIntent) {
synchronized (mDataRequestObserverContexts) {
@@ -347,9 +399,9 @@ public class WearableSensingManagerService extends
private final class WearableSensingManagerInternal extends IWearableSensingManager.Stub {
@Override
- public void provideWearableConnection(
+ public void provideConnection(
ParcelFileDescriptor wearableConnection, RemoteCallback callback) {
- Slog.i(TAG, "WearableSensingManagerInternal provideWearableConnection.");
+ Slog.i(TAG, "WearableSensingManagerInternal provideConnection.");
Objects.requireNonNull(wearableConnection);
Objects.requireNonNull(callback);
mContext.enforceCallingOrSelfPermission(
@@ -361,7 +413,7 @@ public class WearableSensingManagerService extends
return;
}
callPerUserServiceIfExist(
- service -> service.onProvideWearableConnection(wearableConnection, callback),
+ service -> service.onProvideConnection(wearableConnection, callback),
callback);
}
diff --git a/services/core/java/com/android/server/wearable/WearableSensingShellCommand.java b/services/core/java/com/android/server/wearable/WearableSensingShellCommand.java
index 842bccbe0847..0a9cf344e7b2 100644
--- a/services/core/java/com/android/server/wearable/WearableSensingShellCommand.java
+++ b/services/core/java/com/android/server/wearable/WearableSensingShellCommand.java
@@ -29,6 +29,7 @@ import android.util.Slog;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
+import java.time.Duration;
final class WearableSensingShellCommand extends ShellCommand {
private static final String TAG = WearableSensingShellCommand.class.getSimpleName();
@@ -90,6 +91,8 @@ final class WearableSensingShellCommand extends ShellCommand {
return getBoundPackageName();
case "set-temporary-service":
return setTemporaryService();
+ case "set-data-request-rate-limit-window-size":
+ return setDataRequestRateLimitWindowSize();
default:
return handleDefaultCommands(cmd);
}
@@ -114,6 +117,11 @@ final class WearableSensingShellCommand extends ShellCommand {
pw.println(" set-temporary-service USER_ID [PACKAGE_NAME] [COMPONENT_NAME DURATION]");
pw.println(" Temporarily (for DURATION ms) changes the service implementation.");
pw.println(" To reset, call with just the USER_ID argument.");
+ pw.println(" set-data-request-rate-limit-window-size WINDOW_SIZE");
+ pw.println(" Set the window size used in data request rate limiting to WINDOW_SIZE"
+ + " seconds.");
+ pw.println(" positive WINDOW_SIZE smaller than 20 will be automatically set to 20.");
+ pw.println(" To reset, call with 0 or a negative WINDOW_SIZE.");
}
private int createDataStream() {
@@ -209,4 +217,20 @@ final class WearableSensingShellCommand extends ShellCommand {
resultPrinter.println(componentName == null ? "" : componentName.getPackageName());
return 0;
}
+
+ private int setDataRequestRateLimitWindowSize() {
+ Slog.d(TAG, "setDataRequestRateLimitWindowSize");
+ int windowSizeSeconds = Integer.parseInt(getNextArgRequired());
+ if (windowSizeSeconds <= 0) {
+ mService.resetDataRequestRateLimitWindowSize();
+ } else {
+ // 20 is the minimum window size supported by the rate limiter.
+ // It is defined by com.android.server.utils.quota.QuotaTracker#MIN_WINDOW_SIZE_MS
+ if (windowSizeSeconds < 20) {
+ windowSizeSeconds = 20;
+ }
+ mService.setDataRequestRateLimitWindowSize(Duration.ofSeconds(windowSizeSeconds));
+ }
+ return 0;
+ }
}
diff --git a/services/core/java/com/android/server/wm/AbsAppSnapshotController.java b/services/core/java/com/android/server/wm/AbsAppSnapshotController.java
index e5c743cc69e4..fd4b06148c5f 100644
--- a/services/core/java/com/android/server/wm/AbsAppSnapshotController.java
+++ b/services/core/java/com/android/server/wm/AbsAppSnapshotController.java
@@ -147,6 +147,7 @@ abstract class AbsAppSnapshotController<TYPE extends WindowContainer,
@Nullable
protected abstract ActivityRecord findAppTokenForSnapshot(TYPE source);
protected abstract boolean use16BitFormat();
+ protected abstract Rect getLetterboxInsets(ActivityRecord topActivity);
/**
* This is different than {@link #recordSnapshotInner(TYPE)} because it doesn't store
@@ -309,7 +310,7 @@ abstract class AbsAppSnapshotController<TYPE extends WindowContainer,
final WindowState mainWindow = result.second;
final Rect contentInsets = getSystemBarInsets(mainWindow.getFrame(),
mainWindow.getInsetsStateWithVisibilityOverride());
- final Rect letterboxInsets = activity.getLetterboxInsets();
+ final Rect letterboxInsets = getLetterboxInsets(activity);
InsetUtils.addInsets(contentInsets, letterboxInsets);
builder.setIsRealSnapshot(true);
builder.setId(System.currentTimeMillis());
@@ -335,22 +336,27 @@ abstract class AbsAppSnapshotController<TYPE extends WindowContainer,
final Configuration taskConfig = activity.getTask().getConfiguration();
final int displayRotation = taskConfig.windowConfiguration.getDisplayRotation();
final Rect outCrop = new Rect();
+ final Point taskSize = new Point();
final Transition.ChangeInfo changeInfo = mCurrentChangeInfo;
if (changeInfo != null && changeInfo.mRotation != displayRotation) {
// For example, the source is closing and display rotation changes at the same time.
// The snapshot should record the state in previous rotation.
outCrop.set(changeInfo.mAbsoluteBounds);
+ taskSize.set(changeInfo.mAbsoluteBounds.right, changeInfo.mAbsoluteBounds.bottom);
builder.setRotation(changeInfo.mRotation);
builder.setOrientation(changeInfo.mAbsoluteBounds.height()
>= changeInfo.mAbsoluteBounds.width()
? Configuration.ORIENTATION_PORTRAIT : Configuration.ORIENTATION_LANDSCAPE);
} else {
- outCrop.set(taskConfig.windowConfiguration.getBounds());
+ final Configuration srcConfig = source.getConfiguration();
+ outCrop.set(srcConfig.windowConfiguration.getBounds());
+ final Rect taskBounds = taskConfig.windowConfiguration.getBounds();
+ taskSize.set(taskBounds.width(), taskBounds.height());
builder.setRotation(displayRotation);
- builder.setOrientation(taskConfig.orientation);
+ builder.setOrientation(srcConfig.orientation);
}
outCrop.offsetTo(0, 0);
- builder.setTaskSize(new Point(outCrop.right, outCrop.bottom));
+ builder.setTaskSize(taskSize);
return outCrop;
}
@@ -438,7 +444,7 @@ abstract class AbsAppSnapshotController<TYPE extends WindowContainer,
return null;
}
final Rect contentInsets = new Rect(systemBarInsets);
- final Rect letterboxInsets = topActivity.getLetterboxInsets();
+ final Rect letterboxInsets = getLetterboxInsets(topActivity);
InsetUtils.addInsets(contentInsets, letterboxInsets);
// Note, the app theme snapshot is never translucent because we enforce a non-translucent
// color above
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index ee865d3a588a..44a0547a6828 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -523,15 +523,15 @@ final class AccessibilityController {
|| mWindowsForAccessibilityObserver.size() > 0);
}
- void setForceShowMagnifiableBounds(int displayId, boolean show) {
+ void setFullscreenMagnificationActivated(int displayId, boolean activated) {
if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
- mAccessibilityTracing.logTrace(TAG + ".setForceShowMagnifiableBounds",
- FLAGS_MAGNIFICATION_CALLBACK, "displayId=" + displayId + "; show=" + show);
+ mAccessibilityTracing.logTrace(TAG + ".setFullscreenMagnificationActivated",
+ FLAGS_MAGNIFICATION_CALLBACK,
+ "displayId=" + displayId + "; activated=" + activated);
}
final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
if (displayMagnifier != null) {
- displayMagnifier.setForceShowMagnifiableBounds(show);
- displayMagnifier.showMagnificationBoundsIfNeeded();
+ displayMagnifier.setFullscreenMagnificationActivated(activated);
}
}
@@ -624,10 +624,21 @@ final class AccessibilityController {
private final long mLongAnimationDuration;
- private boolean mForceShowMagnifiableBounds = false;
+ private boolean mIsFullscreenMagnificationActivated = false;
+ private final Region mMagnificationRegion = new Region();
+ private final Region mOldMagnificationRegion = new Region();
private final MagnificationSpec mMagnificationSpec = new MagnificationSpec();
+ // Following fields are used for computing magnification region
+ private final Path mCircularPath;
+ private int mTempLayer = 0;
+ private final Point mScreenSize = new Point();
+ private final SparseArray<WindowState> mTempWindowStates =
+ new SparseArray<WindowState>();
+ private final RectF mTempRectF = new RectF();
+ private final Matrix mTempMatrix = new Matrix();
+
DisplayMagnifier(WindowManagerService windowManagerService,
DisplayContent displayContent,
Display display,
@@ -643,6 +654,15 @@ final class AccessibilityController {
AccessibilityController.getAccessibilityControllerInternal(mService);
mLongAnimationDuration = mDisplayContext.getResources().getInteger(
com.android.internal.R.integer.config_longAnimTime);
+ if (mDisplayContext.getResources().getConfiguration().isScreenRound()) {
+ mCircularPath = new Path();
+
+ getDisplaySizeLocked(mScreenSize);
+ final int centerXY = mScreenSize.x / 2;
+ mCircularPath.addCircle(centerXY, centerXY, centerXY, Path.Direction.CW);
+ } else {
+ mCircularPath = null;
+ }
if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
mAccessibilityTracing.logTrace(LOG_TAG + ".DisplayMagnifier.constructor",
FLAGS_MAGNIFICATION_CALLBACK,
@@ -650,6 +670,7 @@ final class AccessibilityController {
+ displayContent + "}; display={" + display + "}; callbacks={"
+ callbacks + "}");
}
+ recomputeBounds();
}
void setMagnificationSpec(MagnificationSpec spec) {
@@ -658,7 +679,7 @@ final class AccessibilityController {
FLAGS_MAGNIFICATION_CALLBACK, "spec={" + spec + "}");
}
updateMagnificationSpec(spec);
- mMagnifedViewport.recomputeBounds();
+ recomputeBounds();
mService.applyMagnificationSpecLocked(mDisplay.getDisplayId(), spec);
mService.scheduleAnimationLocked();
@@ -670,30 +691,26 @@ final class AccessibilityController {
} else {
mMagnificationSpec.clear();
}
- // If this message is pending we are in a rotation animation and do not want
- // to show the border. We will do so when the pending message is handled.
- if (!mHandler.hasMessages(
- MyHandler.MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED)) {
- mMagnifedViewport.setMagnifiedRegionBorderShown(
- isForceShowingMagnifiableBounds(), true);
- }
+
+ mMagnifedViewport.setShowMagnifiedBorderIfNeeded();
}
- void setForceShowMagnifiableBounds(boolean show) {
+ void setFullscreenMagnificationActivated(boolean activated) {
if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
- mAccessibilityTracing.logTrace(LOG_TAG + ".setForceShowMagnifiableBounds",
- FLAGS_MAGNIFICATION_CALLBACK, "show=" + show);
+ mAccessibilityTracing.logTrace(LOG_TAG + ".setFullscreenMagnificationActivated",
+ FLAGS_MAGNIFICATION_CALLBACK, "activated=" + activated);
}
- mForceShowMagnifiableBounds = show;
- mMagnifedViewport.setMagnifiedRegionBorderShown(show, true);
+ mIsFullscreenMagnificationActivated = activated;
+ mMagnifedViewport.setMagnifiedRegionBorderShown(activated, true);
+ mMagnifedViewport.showMagnificationBoundsIfNeeded();
}
- boolean isForceShowingMagnifiableBounds() {
+ boolean isFullscreenMagnificationActivated() {
if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
- mAccessibilityTracing.logTrace(LOG_TAG + ".isForceShowingMagnifiableBounds",
+ mAccessibilityTracing.logTrace(LOG_TAG + ".isFullscreenMagnificationActivated",
FLAGS_MAGNIFICATION_CALLBACK);
}
- return mForceShowMagnifiableBounds;
+ return mIsFullscreenMagnificationActivated;
}
void onWindowLayersChanged() {
@@ -704,7 +721,7 @@ final class AccessibilityController {
if (DEBUG_LAYERS) {
Slog.i(LOG_TAG, "Layers changed.");
}
- mMagnifedViewport.recomputeBounds();
+ recomputeBounds();
mService.scheduleAnimationLocked();
}
@@ -718,6 +735,8 @@ final class AccessibilityController {
Slog.i(LOG_TAG, "Rotation: " + Surface.rotationToString(rotation)
+ " displayId: " + displayContent.getDisplayId());
}
+
+ recomputeBounds();
mMagnifedViewport.onDisplaySizeChanged();
mHandler.sendEmptyMessage(MyHandler.MESSAGE_NOTIFY_DISPLAY_SIZE_CHANGED);
}
@@ -733,7 +752,7 @@ final class AccessibilityController {
+ AppTransition.appTransitionOldToString(transition)
+ " displayId: " + displayId);
}
- final boolean isMagnifierActivated = isForceShowingMagnifiableBounds();
+ final boolean isMagnifierActivated = isFullscreenMagnificationActivated();
if (isMagnifierActivated) {
switch (transition) {
case WindowManager.TRANSIT_OLD_ACTIVITY_OPEN:
@@ -758,7 +777,7 @@ final class AccessibilityController {
Slog.i(LOG_TAG, "Window transition: " + WindowManager.transitTypeToString(type)
+ " displayId: " + displayId);
}
- final boolean isMagnifierActivated = isForceShowingMagnifiableBounds();
+ final boolean isMagnifierActivated = isFullscreenMagnificationActivated();
if (isMagnifierActivated) {
// All opening/closing situations.
switch (type) {
@@ -782,7 +801,7 @@ final class AccessibilityController {
+ AppTransition.appTransitionOldToString(transition)
+ " displayId: " + windowState.getDisplayId());
}
- final boolean isMagnifierActivated = isForceShowingMagnifiableBounds();
+ final boolean isMagnifierActivated = isFullscreenMagnificationActivated();
final int type = windowState.mAttrs.type;
switch (transition) {
case WindowManagerPolicy.TRANSIT_ENTER:
@@ -835,9 +854,7 @@ final class AccessibilityController {
}
void getMagnifiedFrameInContentCoords(Rect rect) {
- Region magnificationRegion = new Region();
- mMagnifedViewport.getMagnificationRegion(magnificationRegion);
- magnificationRegion.getBounds(rect);
+ mMagnificationRegion.getBounds(rect);
rect.offset((int) -mMagnificationSpec.offsetX, (int) -mMagnificationSpec.offsetY);
rect.scale(1.0f / mMagnificationSpec.scale);
}
@@ -872,8 +889,8 @@ final class AccessibilityController {
"outMagnificationRegion={" + outMagnificationRegion + "}");
}
// Make sure we're working with the most current bounds
- mMagnifedViewport.recomputeBounds();
- mMagnifedViewport.getMagnificationRegion(outMagnificationRegion);
+ recomputeBounds();
+ outMagnificationRegion.set(mMagnificationRegion);
}
boolean isMagnifying() {
@@ -887,16 +904,6 @@ final class AccessibilityController {
mMagnifedViewport.destroyWindow();
}
- // Can be called outside of a surface transaction
- void showMagnificationBoundsIfNeeded() {
- if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
- mAccessibilityTracing.logTrace(LOG_TAG + ".showMagnificationBoundsIfNeeded",
- FLAGS_MAGNIFICATION_CALLBACK);
- }
- mHandler.obtainMessage(MyHandler.MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED)
- .sendToTarget();
- }
-
void drawMagnifiedRegionBorderIfNeeded() {
if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
mAccessibilityTracing.logTrace(LOG_TAG + ".drawMagnifiedRegionBorderIfNeeded",
@@ -905,221 +912,236 @@ final class AccessibilityController {
mMagnifedViewport.drawWindowIfNeeded();
}
- void dump(PrintWriter pw, String prefix) {
- mMagnifedViewport.dump(pw, prefix);
- }
+ void recomputeBounds() {
+ getDisplaySizeLocked(mScreenSize);
+ final int screenWidth = mScreenSize.x;
+ final int screenHeight = mScreenSize.y;
- private final class MagnifiedViewport {
-
- private final SparseArray<WindowState> mTempWindowStates =
- new SparseArray<WindowState>();
+ mMagnificationRegion.set(0, 0, 0, 0);
+ final Region availableBounds = mTempRegion1;
+ availableBounds.set(0, 0, screenWidth, screenHeight);
- private final RectF mTempRectF = new RectF();
+ if (mCircularPath != null) {
+ availableBounds.setPath(mCircularPath, availableBounds);
+ }
- private final Point mScreenSize = new Point();
+ Region nonMagnifiedBounds = mTempRegion4;
+ nonMagnifiedBounds.set(0, 0, 0, 0);
- private final Matrix mTempMatrix = new Matrix();
+ SparseArray<WindowState> visibleWindows = mTempWindowStates;
+ visibleWindows.clear();
+ populateWindowsOnScreen(visibleWindows);
- private final Region mMagnificationRegion = new Region();
- private final Region mOldMagnificationRegion = new Region();
+ final int visibleWindowCount = visibleWindows.size();
+ for (int i = visibleWindowCount - 1; i >= 0; i--) {
+ WindowState windowState = visibleWindows.valueAt(i);
+ final int windowType = windowState.mAttrs.type;
+ if (isExcludedWindowType(windowType)
+ || ((windowState.mAttrs.privateFlags
+ & PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION) != 0)
+ || ((windowState.mAttrs.privateFlags
+ & PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY) != 0)) {
+ continue;
+ }
- private final Path mCircularPath;
+ // Consider the touchable portion of the window
+ Matrix matrix = mTempMatrix;
+ populateTransformationMatrix(windowState, matrix);
+ Region touchableRegion = mTempRegion3;
+ windowState.getTouchableRegion(touchableRegion);
+ Rect touchableFrame = mTempRect1;
+ touchableRegion.getBounds(touchableFrame);
+ RectF windowFrame = mTempRectF;
+ windowFrame.set(touchableFrame);
+ windowFrame.offset(-windowState.getFrame().left,
+ -windowState.getFrame().top);
+ matrix.mapRect(windowFrame);
+ Region windowBounds = mTempRegion2;
+ windowBounds.set((int) windowFrame.left, (int) windowFrame.top,
+ (int) windowFrame.right, (int) windowFrame.bottom);
+ // Only update new regions
+ Region portionOfWindowAlreadyAccountedFor = mTempRegion3;
+ portionOfWindowAlreadyAccountedFor.set(mMagnificationRegion);
+ portionOfWindowAlreadyAccountedFor.op(nonMagnifiedBounds, Region.Op.UNION);
+ windowBounds.op(portionOfWindowAlreadyAccountedFor, Region.Op.DIFFERENCE);
+
+ if (windowState.shouldMagnify()) {
+ mMagnificationRegion.op(windowBounds, Region.Op.UNION);
+ mMagnificationRegion.op(availableBounds, Region.Op.INTERSECT);
+ } else {
+ nonMagnifiedBounds.op(windowBounds, Region.Op.UNION);
+ availableBounds.op(windowBounds, Region.Op.DIFFERENCE);
+ }
- private final float mBorderWidth;
- private final int mHalfBorderWidth;
- private final int mDrawBorderInset;
+ // If the navigation bar window doesn't have touchable region, count
+ // navigation bar insets into nonMagnifiedBounds. It happens when
+ // navigation mode is gestural.
+ if (isUntouchableNavigationBar(windowState, mTempRegion3)) {
+ final Rect navBarInsets = getSystemBarInsetsFrame(windowState);
+ nonMagnifiedBounds.op(navBarInsets, Region.Op.UNION);
+ availableBounds.op(navBarInsets, Region.Op.DIFFERENCE);
+ }
- private final ViewportWindow mWindow;
+ // Count letterbox into nonMagnifiedBounds
+ if (windowState.areAppWindowBoundsLetterboxed()) {
+ Region letterboxBounds = getLetterboxBounds(windowState);
+ nonMagnifiedBounds.op(letterboxBounds, Region.Op.UNION);
+ availableBounds.op(letterboxBounds, Region.Op.DIFFERENCE);
+ }
- private boolean mFullRedrawNeeded;
- private int mTempLayer = 0;
+ // Update accounted bounds
+ Region accountedBounds = mTempRegion2;
+ accountedBounds.set(mMagnificationRegion);
+ accountedBounds.op(nonMagnifiedBounds, Region.Op.UNION);
+ accountedBounds.op(0, 0, screenWidth, screenHeight, Region.Op.INTERSECT);
+
+ if (accountedBounds.isRect()) {
+ Rect accountedFrame = mTempRect1;
+ accountedBounds.getBounds(accountedFrame);
+ if (accountedFrame.width() == screenWidth
+ && accountedFrame.height() == screenHeight) {
+ break;
+ }
+ }
+ }
+ visibleWindows.clear();
- MagnifiedViewport() {
- mBorderWidth = mDisplayContext.getResources().getDimension(
- com.android.internal.R.dimen.accessibility_magnification_indicator_width);
- mHalfBorderWidth = (int) Math.ceil(mBorderWidth / 2);
- mDrawBorderInset = (int) mBorderWidth / 2;
- mWindow = new ViewportWindow(mDisplayContext);
+ mMagnifedViewport.intersectWithDrawBorderInset(screenWidth, screenHeight);
- if (mDisplayContext.getResources().getConfiguration().isScreenRound()) {
- mCircularPath = new Path();
- getDisplaySizeLocked(mScreenSize);
- final int centerXY = mScreenSize.x / 2;
- mCircularPath.addCircle(centerXY, centerXY, centerXY, Path.Direction.CW);
- } else {
- mCircularPath = null;
- }
+ final boolean magnifiedChanged =
+ !mOldMagnificationRegion.equals(mMagnificationRegion);
+ if (magnifiedChanged) {
+ mMagnifedViewport.updateBorderDrawingStatus(screenWidth, screenHeight);
- recomputeBounds();
+ mOldMagnificationRegion.set(mMagnificationRegion);
+ final SomeArgs args = SomeArgs.obtain();
+ args.arg1 = Region.obtain(mMagnificationRegion);
+ mHandler.obtainMessage(
+ MyHandler.MESSAGE_NOTIFY_MAGNIFICATION_REGION_CHANGED, args)
+ .sendToTarget();
}
+ }
- void getMagnificationRegion(@NonNull Region outMagnificationRegion) {
- outMagnificationRegion.set(mMagnificationRegion);
+ private Region getLetterboxBounds(WindowState windowState) {
+ final ActivityRecord appToken = windowState.mActivityRecord;
+ if (appToken == null) {
+ return new Region();
}
- void recomputeBounds() {
- getDisplaySizeLocked(mScreenSize);
- final int screenWidth = mScreenSize.x;
- final int screenHeight = mScreenSize.y;
+ final Rect boundsWithoutLetterbox = windowState.getBounds();
+ final Rect letterboxInsets = appToken.getLetterboxInsets();
- mMagnificationRegion.set(0, 0, 0, 0);
- final Region availableBounds = mTempRegion1;
- availableBounds.set(0, 0, screenWidth, screenHeight);
+ final Rect boundsIncludingLetterbox = Rect.copyOrNull(boundsWithoutLetterbox);
+ // Letterbox insets from mActivityRecord are positive, so we negate them to grow the
+ // bounds to include the letterbox.
+ boundsIncludingLetterbox.inset(
+ Insets.subtract(Insets.NONE, Insets.of(letterboxInsets)));
- if (mCircularPath != null) {
- availableBounds.setPath(mCircularPath, availableBounds);
+ final Region letterboxBounds = new Region();
+ letterboxBounds.set(boundsIncludingLetterbox);
+ letterboxBounds.op(boundsWithoutLetterbox, Region.Op.DIFFERENCE);
+ return letterboxBounds;
+ }
+
+ private boolean isExcludedWindowType(int windowType) {
+ return windowType == TYPE_MAGNIFICATION_OVERLAY
+ // Omit the touch region of window magnification to avoid the cut out of the
+ // magnification and the magnified center of window magnification could be
+ // in the bounds
+ || windowType == TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY;
+ }
+
+ private void populateWindowsOnScreen(SparseArray<WindowState> outWindows) {
+ mTempLayer = 0;
+ mDisplayContent.forAllWindows((w) -> {
+ if (w.isOnScreen() && w.isVisible()
+ && (w.mAttrs.alpha != 0)) {
+ mTempLayer++;
+ outWindows.put(mTempLayer, w);
}
+ }, /* traverseTopToBottom= */ false);
+ }
- Region nonMagnifiedBounds = mTempRegion4;
- nonMagnifiedBounds.set(0, 0, 0, 0);
-
- SparseArray<WindowState> visibleWindows = mTempWindowStates;
- visibleWindows.clear();
- populateWindowsOnScreen(visibleWindows);
-
- final int visibleWindowCount = visibleWindows.size();
- for (int i = visibleWindowCount - 1; i >= 0; i--) {
- WindowState windowState = visibleWindows.valueAt(i);
- final int windowType = windowState.mAttrs.type;
- if (isExcludedWindowType(windowType)
- || ((windowState.mAttrs.privateFlags
- & PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION) != 0)
- || ((windowState.mAttrs.privateFlags
- & PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY) != 0)) {
- continue;
- }
+ private void getDisplaySizeLocked(Point outSize) {
+ final Rect bounds =
+ mDisplayContent.getConfiguration().windowConfiguration.getBounds();
+ outSize.set(bounds.width(), bounds.height());
+ }
- // Consider the touchable portion of the window
- Matrix matrix = mTempMatrix;
- populateTransformationMatrix(windowState, matrix);
- Region touchableRegion = mTempRegion3;
- windowState.getTouchableRegion(touchableRegion);
- Rect touchableFrame = mTempRect1;
- touchableRegion.getBounds(touchableFrame);
- RectF windowFrame = mTempRectF;
- windowFrame.set(touchableFrame);
- windowFrame.offset(-windowState.getFrame().left,
- -windowState.getFrame().top);
- matrix.mapRect(windowFrame);
- Region windowBounds = mTempRegion2;
- windowBounds.set((int) windowFrame.left, (int) windowFrame.top,
- (int) windowFrame.right, (int) windowFrame.bottom);
- // Only update new regions
- Region portionOfWindowAlreadyAccountedFor = mTempRegion3;
- portionOfWindowAlreadyAccountedFor.set(mMagnificationRegion);
- portionOfWindowAlreadyAccountedFor.op(nonMagnifiedBounds, Region.Op.UNION);
- windowBounds.op(portionOfWindowAlreadyAccountedFor, Region.Op.DIFFERENCE);
-
- if (windowState.shouldMagnify()) {
- mMagnificationRegion.op(windowBounds, Region.Op.UNION);
- mMagnificationRegion.op(availableBounds, Region.Op.INTERSECT);
- } else {
- nonMagnifiedBounds.op(windowBounds, Region.Op.UNION);
- availableBounds.op(windowBounds, Region.Op.DIFFERENCE);
- }
+ void dump(PrintWriter pw, String prefix) {
+ mMagnifedViewport.dump(pw, prefix);
+ }
- // If the navigation bar window doesn't have touchable region, count
- // navigation bar insets into nonMagnifiedBounds. It happens when
- // navigation mode is gestural.
- if (isUntouchableNavigationBar(windowState, mTempRegion3)) {
- final Rect navBarInsets = getSystemBarInsetsFrame(windowState);
- nonMagnifiedBounds.op(navBarInsets, Region.Op.UNION);
- availableBounds.op(navBarInsets, Region.Op.DIFFERENCE);
- }
+ private final class MagnifiedViewport {
- // Count letterbox into nonMagnifiedBounds
- if (windowState.areAppWindowBoundsLetterboxed()) {
- Region letterboxBounds = getLetterboxBounds(windowState);
- nonMagnifiedBounds.op(letterboxBounds, Region.Op.UNION);
- availableBounds.op(letterboxBounds, Region.Op.DIFFERENCE);
- }
+ private final float mBorderWidth;
+ private final int mHalfBorderWidth;
+ private final int mDrawBorderInset;
- // Update accounted bounds
- Region accountedBounds = mTempRegion2;
- accountedBounds.set(mMagnificationRegion);
- accountedBounds.op(nonMagnifiedBounds, Region.Op.UNION);
- accountedBounds.op(0, 0, screenWidth, screenHeight, Region.Op.INTERSECT);
-
- if (accountedBounds.isRect()) {
- Rect accountedFrame = mTempRect1;
- accountedBounds.getBounds(accountedFrame);
- if (accountedFrame.width() == screenWidth
- && accountedFrame.height() == screenHeight) {
- break;
- }
- }
- }
- visibleWindows.clear();
+ private final ViewportWindow mWindow;
- mMagnificationRegion.op(mDrawBorderInset, mDrawBorderInset,
- screenWidth - mDrawBorderInset, screenHeight - mDrawBorderInset,
- Region.Op.INTERSECT);
+ private boolean mFullRedrawNeeded;
- final boolean magnifiedChanged =
- !mOldMagnificationRegion.equals(mMagnificationRegion);
- if (magnifiedChanged) {
- mWindow.setBounds(mMagnificationRegion);
- final Rect dirtyRect = mTempRect1;
- if (mFullRedrawNeeded) {
- mFullRedrawNeeded = false;
- dirtyRect.set(mDrawBorderInset, mDrawBorderInset,
- screenWidth - mDrawBorderInset,
- screenHeight - mDrawBorderInset);
- mWindow.invalidate(dirtyRect);
- } else {
- final Region dirtyRegion = mTempRegion3;
- dirtyRegion.set(mMagnificationRegion);
- dirtyRegion.op(mOldMagnificationRegion, Region.Op.XOR);
- dirtyRegion.getBounds(dirtyRect);
- mWindow.invalidate(dirtyRect);
- }
+ MagnifiedViewport() {
+ mBorderWidth = mDisplayContext.getResources().getDimension(
+ com.android.internal.R.dimen.accessibility_magnification_indicator_width);
+ mHalfBorderWidth = (int) Math.ceil(mBorderWidth / 2);
+ mDrawBorderInset = (int) mBorderWidth / 2;
+ mWindow = new ViewportWindow(mDisplayContext);
+ }
- mOldMagnificationRegion.set(mMagnificationRegion);
- final SomeArgs args = SomeArgs.obtain();
- args.arg1 = Region.obtain(mMagnificationRegion);
- mHandler.obtainMessage(
- MyHandler.MESSAGE_NOTIFY_MAGNIFICATION_REGION_CHANGED, args)
- .sendToTarget();
+ void updateBorderDrawingStatus(int screenWidth, int screenHeight) {
+ mWindow.setBounds(mMagnificationRegion);
+ final Rect dirtyRect = mTempRect1;
+ if (mFullRedrawNeeded) {
+ mFullRedrawNeeded = false;
+ dirtyRect.set(mDrawBorderInset, mDrawBorderInset,
+ screenWidth - mDrawBorderInset,
+ screenHeight - mDrawBorderInset);
+ mWindow.invalidate(dirtyRect);
+ } else {
+ final Region dirtyRegion = mTempRegion3;
+ dirtyRegion.set(mMagnificationRegion);
+ dirtyRegion.op(mOldMagnificationRegion, Region.Op.XOR);
+ dirtyRegion.getBounds(dirtyRect);
+ mWindow.invalidate(dirtyRect);
}
}
- private Region getLetterboxBounds(WindowState windowState) {
- final ActivityRecord appToken = windowState.mActivityRecord;
- if (appToken == null) {
- return new Region();
+ void setShowMagnifiedBorderIfNeeded() {
+ // If this message is pending, we are in a rotation animation and do not want
+ // to show the border. We will do so when the pending message is handled.
+ if (!mHandler.hasMessages(
+ MyHandler.MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED)) {
+ setMagnifiedRegionBorderShown(
+ isFullscreenMagnificationActivated(), true);
}
+ }
- final Rect boundsWithoutLetterbox = windowState.getBounds();
- final Rect letterboxInsets = appToken.getLetterboxInsets();
-
- final Rect boundsIncludingLetterbox = Rect.copyOrNull(boundsWithoutLetterbox);
- // Letterbox insets from mActivityRecord are positive, so we negate them to grow the
- // bounds to include the letterbox.
- boundsIncludingLetterbox.inset(
- Insets.subtract(Insets.NONE, Insets.of(letterboxInsets)));
-
- final Region letterboxBounds = new Region();
- letterboxBounds.set(boundsIncludingLetterbox);
- letterboxBounds.op(boundsWithoutLetterbox, Region.Op.DIFFERENCE);
- return letterboxBounds;
+ // Can be called outside of a surface transaction
+ void showMagnificationBoundsIfNeeded() {
+ if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
+ mAccessibilityTracing.logTrace(LOG_TAG + ".showMagnificationBoundsIfNeeded",
+ FLAGS_MAGNIFICATION_CALLBACK);
+ }
+ mHandler.obtainMessage(MyHandler.MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED)
+ .sendToTarget();
}
- private boolean isExcludedWindowType(int windowType) {
- return windowType == TYPE_MAGNIFICATION_OVERLAY
- // Omit the touch region of window magnification to avoid the cut out of the
- // magnification and the magnified center of window magnification could be
- // in the bounds
- || windowType == TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY;
+ void intersectWithDrawBorderInset(int screenWidth, int screenHeight) {
+ mMagnificationRegion.op(mDrawBorderInset, mDrawBorderInset,
+ screenWidth - mDrawBorderInset, screenHeight - mDrawBorderInset,
+ Region.Op.INTERSECT);
}
void onDisplaySizeChanged() {
- // If we are showing the magnification border, hide it immediately so
+ // If fullscreen magnification is activated, hide the border immediately so
// the user does not see strange artifacts during display size changed caused by
// rotation or folding/unfolding the device. In the rotation case, the screenshot
// used for rotation already has the border. After the rotation is complete
// we will show the border.
- if (isForceShowingMagnifiableBounds()) {
+ if (isFullscreenMagnificationActivated()) {
setMagnifiedRegionBorderShown(false, false);
final long delay = (long) (mLongAnimationDuration
* mService.getWindowAnimationScaleLocked());
@@ -1127,7 +1149,6 @@ final class AccessibilityController {
MyHandler.MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED);
mHandler.sendMessageDelayed(message, delay);
}
- recomputeBounds();
mWindow.updateSize();
}
@@ -1148,23 +1169,6 @@ final class AccessibilityController {
mWindow.releaseSurface();
}
- private void populateWindowsOnScreen(SparseArray<WindowState> outWindows) {
- mTempLayer = 0;
- mDisplayContent.forAllWindows((w) -> {
- if (w.isOnScreen() && w.isVisible()
- && (w.mAttrs.alpha != 0)) {
- mTempLayer++;
- outWindows.put(mTempLayer, w);
- }
- }, false /* traverseTopToBottom */ );
- }
-
- private void getDisplaySizeLocked(Point outSize) {
- final Rect bounds =
- mDisplayContent.getConfiguration().windowConfiguration.getBounds();
- outSize.set(bounds.width(), bounds.height());
- }
-
void dump(PrintWriter pw, String prefix) {
mWindow.dump(pw, prefix);
}
@@ -1490,7 +1494,7 @@ final class AccessibilityController {
case MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED : {
synchronized (mService.mGlobalLock) {
- if (isForceShowingMagnifiableBounds()) {
+ if (isFullscreenMagnificationActivated()) {
mMagnifedViewport.setMagnifiedRegionBorderShown(true, true);
mService.scheduleAnimationLocked();
}
@@ -1539,8 +1543,6 @@ final class AccessibilityController {
private final Set<IBinder> mTempBinderSet = new ArraySet<>();
- private final Point mTempPoint = new Point();
-
private final Region mTempRegion = new Region();
private final Region mTempRegion2 = new Region();
@@ -1610,8 +1612,9 @@ final class AccessibilityController {
Slog.i(LOG_TAG, "computeChangedWindows()");
}
- final List<WindowInfo> windows;
+ List<WindowInfo> windows = null;
final List<AccessibilityWindow> visibleWindows = new ArrayList<>();
+ final Point screenSize = new Point();
final int topFocusedDisplayId;
IBinder topFocusedWindowToken = null;
@@ -1639,19 +1642,27 @@ final class AccessibilityController {
return;
}
final Display display = dc.getDisplay();
- display.getRealSize(mTempPoint);
+ display.getRealSize(screenSize);
mA11yWindowsPopulator.populateVisibleWindowsOnScreenLocked(
mDisplayId, visibleWindows);
- windows = buildWindowInfoListLocked(visibleWindows, mTempPoint);
+ if (!com.android.server.accessibility.Flags.computeWindowChangesOnA11y()) {
+ windows = buildWindowInfoListLocked(visibleWindows, screenSize);
+ }
// Gets the top focused display Id and window token for supporting multi-display.
topFocusedDisplayId = mService.mRoot.getTopFocusedDisplayContent().getDisplayId();
topFocusedWindowToken = topFocusedWindowState.mClient.asBinder();
}
- mCallback.onWindowsForAccessibilityChanged(forceSend, topFocusedDisplayId,
- topFocusedWindowToken, windows);
+
+ if (com.android.server.accessibility.Flags.computeWindowChangesOnA11y()) {
+ mCallback.onAccessibilityWindowsChanged(forceSend, topFocusedDisplayId,
+ topFocusedWindowToken, screenSize, visibleWindows);
+ } else {
+ mCallback.onWindowsForAccessibilityChanged(forceSend, topFocusedDisplayId,
+ topFocusedWindowToken, windows);
+ }
// Recycle the windows as we do not need them.
for (final AccessibilityWindowsPopulator.AccessibilityWindow window : visibleWindows) {
@@ -1660,6 +1671,9 @@ final class AccessibilityController {
mInitialized = true;
}
+ // Here are old code paths, called when computeWindowChangesOnA11y flag is disabled.
+ // LINT.IfChange
+
/**
* From a list of windows, decides windows to be exposed to accessibility based on touchable
* region in the screen.
@@ -1819,6 +1833,8 @@ final class AccessibilityController {
&& windowType != WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION);
}
+ // LINT.ThenChange(/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java)
+
private WindowState getTopFocusWindow() {
return mService.mRoot.getTopFocusedDisplayContent().mCurrentFocus;
}
diff --git a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
index 3cf19ddbd89d..ac3251c9bb12 100644
--- a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
+++ b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
@@ -22,6 +22,7 @@ import static com.android.internal.util.DumpUtils.dumpSparseArray;
import static com.android.server.wm.utils.RegionUtils.forEachRect;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.RectF;
@@ -655,6 +656,7 @@ public final class AccessibilityWindowsPopulator extends WindowInfosListener {
private final Region mTouchableRegionInScreen = new Region();
private final Region mTouchableRegionInWindow = new Region();
private WindowInfo mWindowInfo;
+ private Rect mSystemBarInsetFrame = null;
/**
@@ -718,6 +720,16 @@ public final class AccessibilityWindowsPopulator extends WindowInfosListener {
Slog.w(TAG, "can't find spec");
}
}
+
+ // Compute system bar insets frame if needed.
+ if (com.android.server.accessibility.Flags.computeWindowChangesOnA11y()
+ && windowState != null && instance.isUntouchableNavigationBar()) {
+ final InsetsSourceProvider provider =
+ windowState.getControllableInsetProvider();
+ if (provider != null) {
+ instance.mSystemBarInsetFrame = provider.getSource().getFrame();
+ }
+ }
return instance;
}
@@ -812,6 +824,15 @@ public final class AccessibilityWindowsPopulator extends WindowInfosListener {
return mIsPIPMenu;
}
+ /**
+ * @return the system inset frame size if the window is untouchable navigation bar.
+ * Returns null otherwise.
+ */
+ @Nullable
+ public Rect getSystemBarInsetsFrame() {
+ return mSystemBarInsetFrame;
+ }
+
private static void getTouchableRegionInWindow(boolean shouldMagnify, Region inRegion,
Region outRegion, Rect frame, Matrix inverseMatrix, Matrix displayMatrix) {
// Some modal windows, like the activity with Theme.dialog, has the full screen
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index c36df8da77ae..56024f75545c 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -303,6 +303,7 @@ import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.ConstrainDisplayApisConfig;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
import android.content.pm.UserProperties;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -1846,20 +1847,20 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
mLetterboxUiController.onMovedToDisplay(mDisplayContent.getDisplayId());
}
- void layoutLetterbox(WindowState winHint) {
- mLetterboxUiController.layoutLetterbox(winHint);
+ void layoutLetterboxIfNeeded(WindowState winHint) {
+ mLetterboxUiController.layoutLetterboxIfNeeded(winHint);
}
boolean hasWallpaperBackgroundForLetterbox() {
return mLetterboxUiController.hasWallpaperBackgroundForLetterbox();
}
- void updateLetterboxSurface(WindowState winHint, Transaction t) {
- mLetterboxUiController.updateLetterboxSurface(winHint, t);
+ void updateLetterboxSurfaceIfNeeded(WindowState winHint, Transaction t) {
+ mLetterboxUiController.updateLetterboxSurfaceIfNeeded(winHint, t);
}
- void updateLetterboxSurface(WindowState winHint) {
- mLetterboxUiController.updateLetterboxSurface(winHint);
+ void updateLetterboxSurfaceIfNeeded(WindowState winHint) {
+ mLetterboxUiController.updateLetterboxSurfaceIfNeeded(winHint);
}
/** Gets the letterbox insets. The insets will be empty if there is no letterbox. */
@@ -4546,7 +4547,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
super.removeChild(child);
checkKeyguardFlagsChanged();
- updateLetterboxSurface(child);
+ updateLetterboxSurfaceIfNeeded(child);
}
void setAppLayoutChanges(int changes, String reason) {
@@ -5009,7 +5010,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
* method will be called at the proper time.
*/
final void deliverNewIntentLocked(int callingUid, Intent intent, NeededUriGrants intentGrants,
- String referrer, boolean isShareIdentityEnabled) {
+ String referrer, boolean isShareIdentityEnabled, int userId, int recipientAppId) {
IBinder callerToken = new Binder();
if (android.security.Flags.contentUriPermissionApis()) {
computeCallerInfo(callerToken, intent, callingUid, referrer, isShareIdentityEnabled);
@@ -5017,6 +5018,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// The activity now gets access to the data associated with this Intent.
mAtmService.mUgmInternal.grantUriPermissionUncheckedFromIntent(intentGrants,
getUriPermissionsLocked());
+ if (isShareIdentityEnabled && android.security.Flags.contentUriPermissionApis()) {
+ final PackageManagerInternal pmInternal = mAtmService.getPackageManagerInternalLocked();
+ pmInternal.grantImplicitAccess(userId, intent, recipientAppId /*recipient*/,
+ callingUid /*visible*/, true /*direct*/);
+ }
final ReferrerIntent rintent = new ReferrerIntent(intent, getFilteredReferrer(referrer),
callerToken);
boolean unsent = true;
@@ -6036,7 +6042,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
if (destroyedSomething) {
final DisplayContent dc = getDisplayContent();
dc.assignWindowLayers(true /*setLayoutNeeded*/);
- updateLetterboxSurface(null);
+ updateLetterboxSurfaceIfNeeded(null);
}
}
@@ -7688,7 +7694,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
if (mNeedsLetterboxedAnimation) {
- updateLetterboxSurface(findMainWindow(), t);
+ updateLetterboxSurfaceIfNeeded(findMainWindow(), t);
mNeedsAnimationBoundsLayer = true;
}
@@ -7856,7 +7862,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
mNeedsAnimationBoundsLayer = false;
if (mNeedsLetterboxedAnimation) {
mNeedsLetterboxedAnimation = false;
- updateLetterboxSurface(findMainWindow(), t);
+ updateLetterboxSurfaceIfNeeded(findMainWindow(), t);
}
if (mAnimatingActivityRegistry != null) {
@@ -8943,6 +8949,15 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
}
+ // Fixed orientation bounds are the same as its parent container, so clear the fixed
+ // orientation bounds. This can happen in close to square displays where the orientation
+ // is not respected with insets, but the display still matches or is less than the
+ // activity aspect ratio.
+ if (resolvedBounds.equals(parentBounds)) {
+ resolvedBounds.set(prevResolvedBounds);
+ return;
+ }
+
// Calculate app bounds using fixed orientation bounds because they will be needed later
// for comparison with size compat app bounds in {@link resolveSizeCompatModeConfiguration}.
getTaskFragment().computeConfigResourceOverrides(getResolvedOverrideConfiguration(),
diff --git a/services/core/java/com/android/server/wm/ActivitySnapshotController.java b/services/core/java/com/android/server/wm/ActivitySnapshotController.java
index f83003d4e278..62fb4bfc74d7 100644
--- a/services/core/java/com/android/server/wm/ActivitySnapshotController.java
+++ b/services/core/java/com/android/server/wm/ActivitySnapshotController.java
@@ -21,6 +21,7 @@ import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
+import android.graphics.Rect;
import android.os.Environment;
import android.os.SystemProperties;
import android.os.Trace;
@@ -617,6 +618,12 @@ class ActivitySnapshotController extends AbsAppSnapshotController<ActivityRecord
return mPersistInfoProvider.use16BitFormat();
}
+ @Override
+ protected Rect getLetterboxInsets(ActivityRecord topActivity) {
+ // Do not capture letterbox for ActivityRecord
+ return Letterbox.EMPTY_RECT;
+ }
+
@NonNull
private SparseArray<UserSavedFile> getUserFiles(int userId) {
if (mUserSavedFiles.get(userId) == null) {
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index 2c492035140b..0e401ebc94b5 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -434,6 +434,9 @@ public class ActivityStartController {
// Don't modify the client's object!
intent = new Intent(intent);
+ // Remove existing mismatch flag so it can be properly updated later
+ intent.removeExtendedFlags(Intent.EXTENDED_FLAG_FILTER_MISMATCH);
+
// Collect information about the target of the Intent.
ActivityInfo aInfo = mSupervisor.resolveActivity(intent, resolvedTypes[i],
0 /* startFlags */, null /* profilerInfo */, userId, filterCallingUid,
@@ -559,11 +562,14 @@ public class ActivityStartController {
@Nullable IBinder errorCallbackToken) {
final ActivityRecord caller =
resultTo != null ? ActivityRecord.forTokenLocked(resultTo) : null;
+ final String resolvedType =
+ activityIntent.resolveTypeIfNeeded(mService.mContext.getContentResolver());
return obtainStarter(activityIntent, "startActivityInTaskFragment")
.setActivityOptions(activityOptions)
.setInTaskFragment(taskFragment)
.setResultTo(resultTo)
.setRequestCode(-1)
+ .setResolvedType(resolvedType)
.setCallingUid(callingUid)
.setCallingPid(callingPid)
.setRealCallingUid(callingUid)
@@ -639,6 +645,10 @@ public class ActivityStartController {
return mPendingRemoteAnimationRegistry;
}
+ ActivityRecord getLastStartActivity() {
+ return mLastStarter != null ? mLastStarter.mStartActivity : null;
+ }
+
void dumpLastHomeActivityStartResult(PrintWriter pw, String prefix) {
pw.print(prefix);
pw.print("mLastHomeActivityStartResult=");
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index cfd049508e65..63cdf0ef4b43 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -713,9 +713,14 @@ class ActivityStarter {
try {
onExecutionStarted();
- // Refuse possible leaked file descriptors
- if (mRequest.intent != null && mRequest.intent.hasFileDescriptors()) {
- throw new IllegalArgumentException("File descriptors passed in Intent");
+ if (mRequest.intent != null) {
+ // Refuse possible leaked file descriptors
+ if (mRequest.intent.hasFileDescriptors()) {
+ throw new IllegalArgumentException("File descriptors passed in Intent");
+ }
+
+ // Remove existing mismatch flag so it can be properly updated later
+ mRequest.intent.removeExtendedFlags(Intent.EXTENDED_FLAG_FILTER_MISMATCH);
}
final LaunchingState launchingState;
@@ -2910,7 +2915,9 @@ class ActivityStarter {
activity.logStartActivity(EventLogTags.WM_NEW_INTENT, activity.getTask());
activity.deliverNewIntentLocked(mCallingUid, mStartActivity.intent, intentGrants,
- mStartActivity.launchedFromPackage, mStartActivity.mShareIdentity);
+ mStartActivity.launchedFromPackage, mStartActivity.mShareIdentity,
+ mStartActivity.mUserId,
+ UserHandle.getAppId(mStartActivity.info.applicationInfo.uid));
mIntentDelivered = true;
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 3637ab129c36..24c228e88c1c 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -102,6 +102,9 @@ import static com.android.server.wm.ActivityInterceptorCallback.MAINLINE_FIRST_O
import static com.android.server.wm.ActivityInterceptorCallback.MAINLINE_LAST_ORDERED_ID;
import static com.android.server.wm.ActivityInterceptorCallback.SYSTEM_FIRST_ORDERED_ID;
import static com.android.server.wm.ActivityInterceptorCallback.SYSTEM_LAST_ORDERED_ID;
+import static com.android.server.wm.ActivityRecord.State.DESTROYED;
+import static com.android.server.wm.ActivityRecord.State.DESTROYING;
+import static com.android.server.wm.ActivityRecord.State.FINISHING;
import static com.android.server.wm.ActivityRecord.State.PAUSING;
import static com.android.server.wm.ActivityRecord.State.RESUMED;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ACTIVITY_STARTS;
@@ -1134,22 +1137,13 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
* Return the global configuration used by the process corresponding to the input pid. This is
* usually the global configuration with some overrides specific to that process.
*/
- Configuration getGlobalConfigurationForCallingPid() {
+ private Configuration getGlobalConfigurationForCallingPid() {
final int pid = Binder.getCallingPid();
- return getGlobalConfigurationForPid(pid);
- }
-
- /**
- * Return the global configuration used by the process corresponding to the given pid.
- */
- Configuration getGlobalConfigurationForPid(int pid) {
if (pid == MY_PID || pid < 0) {
return getGlobalConfiguration();
}
- synchronized (mGlobalLock) {
- final WindowProcessController app = mProcessMap.getProcess(pid);
- return app != null ? app.getConfiguration() : getGlobalConfiguration();
- }
+ final WindowProcessController app = mProcessMap.getProcess(pid);
+ return app != null ? app.getConfiguration() : getGlobalConfiguration();
}
/**
@@ -1317,9 +1311,13 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
IBinder allowlistToken, Intent fillInIntent, String resolvedType, IBinder resultTo,
String resultWho, int requestCode, int flagsMask, int flagsValues, Bundle bOptions) {
enforceNotIsolatedCaller("startActivityIntentSender");
- // Refuse possible leaked file descriptors
- if (fillInIntent != null && fillInIntent.hasFileDescriptors()) {
- throw new IllegalArgumentException("File descriptors passed in Intent");
+ if (fillInIntent != null) {
+ // Refuse possible leaked file descriptors
+ if (fillInIntent.hasFileDescriptors()) {
+ throw new IllegalArgumentException("File descriptors passed in Intent");
+ }
+ // Remove existing mismatch flag so it can be properly updated later
+ fillInIntent.removeExtendedFlags(Intent.EXTENDED_FLAG_FILTER_MISMATCH);
}
if (!(target instanceof PendingIntentRecord)) {
@@ -1363,6 +1361,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
return false;
}
intent = new Intent(intent);
+ // Remove existing mismatch flag so it can be properly updated later
+ intent.removeExtendedFlags(Intent.EXTENDED_FLAG_FILTER_MISMATCH);
// The caller is not allowed to change the data.
intent.setDataAndType(r.intent.getData(), r.intent.getType());
// And we are resetting to find the next component...
@@ -4167,7 +4167,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
task = mRootWindowContainer.getDefaultTaskDisplayArea().getRootTask(
t -> t.isActivityTypeStandard());
}
- if (task != null && task.getTopMostActivity() != null) {
+ if (task != null && task.getTopMostActivity() != null
+ && !task.getTopMostActivity().isState(FINISHING, DESTROYING, DESTROYED)) {
mWindowManager.mAtmService.mActivityClientController.onPictureInPictureUiStateChanged(
task.getTopMostActivity(), pipState);
}
@@ -5554,7 +5555,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
* Saves the current activity manager state and includes the saved state in the next dump of
* activity manager.
*/
- void saveANRState(String reason) {
+ void saveANRState(ActivityRecord activity, String reason) {
final StringWriter sw = new StringWriter();
final PrintWriter pw = new FastPrintWriter(sw, false, 1024);
pw.println(" ANR time: " + DateFormat.getDateTimeInstance().format(new Date()));
@@ -5562,14 +5563,25 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
pw.println(" Reason: " + reason);
}
pw.println();
- getActivityStartController().dump(pw, " ", null);
- pw.println();
+ if (activity != null) {
+ final Task rootTask = activity.getRootTask();
+ if (rootTask != null) {
+ rootTask.forAllTaskFragments(
+ tf -> tf.dumpInner(" ", pw, true /* dumpAll */, null /* dumpPackage */));
+ pw.println();
+ }
+ mActivityStartController.dump(pw, " ", activity.packageName);
+ if (mActivityStartController.getLastStartActivity() != activity) {
+ activity.dump(pw, " ", true /* dumpAll */);
+ }
+ }
+ ActivityTaskSupervisor.printThisActivity(pw, mRootWindowContainer.getTopResumedActivity(),
+ null /* dumpPackage */, INVALID_DISPLAY, true /* needSep */,
+ " ResumedActivity: ", /* header= */ null /* header */);
+ mLockTaskController.dump(pw, " ");
+ mKeyguardController.dump(pw, " ");
pw.println("-------------------------------------------------------------------"
+ "------------");
- dumpActivitiesLocked(null /* fd */, pw, null /* args */, 0 /* opti */,
- true /* dumpAll */, false /* dumpClient */, null /* dumpPackage */,
- INVALID_DISPLAY, "" /* header */);
- pw.println();
pw.close();
mLastANRState = sw.toString();
diff --git a/services/core/java/com/android/server/wm/AnrController.java b/services/core/java/com/android/server/wm/AnrController.java
index b9f6e177e637..0013c5c63798 100644
--- a/services/core/java/com/android/server/wm/AnrController.java
+++ b/services/core/java/com/android/server/wm/AnrController.java
@@ -367,7 +367,7 @@ class AnrController {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "dumpAnrStateLocked()");
synchronized (mService.mGlobalLock) {
mService.saveANRStateLocked(activity, windowState, reason);
- mService.mAtmService.saveANRState(reason);
+ mService.mAtmService.saveANRState(activity, reason);
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index c2dfa21016fa..b51f89931128 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -35,6 +35,7 @@ import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_PREDICT_BACK;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
+import android.content.res.Configuration;
import android.content.res.ResourceId;
import android.graphics.Point;
import android.graphics.Rect;
@@ -629,7 +630,7 @@ class BackNavigationController {
final ActivityRecord ar = openApps.valueAt(i);
if (mAnimationHandler.isTarget(ar, true /* open */)) {
openApps.removeAt(i);
- mAnimationHandler.markStartingSurfaceMatch();
+ mAnimationHandler.markStartingSurfaceMatch(null /* reparentTransaction */);
}
}
for (int i = closeApps.size() - 1; i >= 0; --i) {
@@ -773,10 +774,15 @@ class BackNavigationController {
for (int i = mTmpOpenApps.size() - 1; i >= 0; --i) {
final WindowContainer wc = mTmpOpenApps.get(i);
if (mAnimationHandler.isTarget(wc, true /* open */)) {
- mAnimationHandler.markStartingSurfaceMatch();
+ mAnimationHandler.markStartingSurfaceMatch(startTransaction);
break;
}
}
+ // release animation leash
+ if (mAnimationHandler.mOpenAnimAdaptor.mCloseTransaction != null) {
+ startTransaction.merge(mAnimationHandler.mOpenAnimAdaptor.mCloseTransaction);
+ mAnimationHandler.mOpenAnimAdaptor.mCloseTransaction = null;
+ }
// Because the target will reparent to transition root, so it cannot be controlled by
// animation leash. Hide the close target when transition starts.
startTransaction.hide(mAnimationHandler.mCloseAdaptor.mTarget.getSurfaceControl());
@@ -993,7 +999,7 @@ class BackNavigationController {
}
final RemoteAnimationTarget[] targets = new RemoteAnimationTarget[2];
targets[0] = mCloseAdaptor.mAnimationTarget;
- targets[1] = mOpenAnimAdaptor.getOrCreateAnimationTarget();
+ targets[1] = mOpenAnimAdaptor.mRemoteAnimationTarget;
return targets;
}
@@ -1060,18 +1066,20 @@ class BackNavigationController {
if (mOpenActivities != null) {
for (int i = mOpenActivities.length - 1; i >= 0; --i) {
- if (mOpenActivities[i].mLaunchTaskBehind) {
- restoreLaunchBehind(mOpenActivities[i]);
+ final ActivityRecord resetActivity = mOpenActivities[i];
+ if (resetActivity.mLaunchTaskBehind) {
+ restoreLaunchBehind(resetActivity);
}
}
}
}
- void markStartingSurfaceMatch() {
- mStartingSurfaceTargetMatch = true;
- for (int i = mOpenAnimAdaptor.mAdaptors.length - 1; i >= 0; --i) {
- mOpenAnimAdaptor.mAdaptors[i].reparentWindowlessSurfaceToTarget();
+ void markStartingSurfaceMatch(SurfaceControl.Transaction reparentTransaction) {
+ if (mStartingSurfaceTargetMatch) {
+ return;
}
+ mStartingSurfaceTargetMatch = true;
+ mOpenAnimAdaptor.reparentWindowlessSurfaceToTarget(reparentTransaction);
}
void clearBackAnimateTarget() {
@@ -1140,14 +1148,23 @@ class BackNavigationController {
private static class BackWindowAnimationAdaptorWrapper {
final BackWindowAnimationAdaptor[] mAdaptors;
+ // The highest remote animation target, which can be a wrapper if multiple adaptors,
+ // or the single opening target.
+ final RemoteAnimationTarget mRemoteAnimationTarget;
SurfaceControl.Transaction mCloseTransaction;
+ // The starting surface task Id. Used to clear the starting surface if the animation has
+ // requested one during animating.
+ private int mRequestedStartingSurfaceId = INVALID_TASK_ID;
+ private SurfaceControl mStartingSurface;
BackWindowAnimationAdaptorWrapper(boolean isOpen, int switchType,
@NonNull WindowContainer... targets) {
mAdaptors = new BackWindowAnimationAdaptor[targets.length];
for (int i = targets.length - 1; i >= 0; --i) {
mAdaptors[i] = createAdaptor(targets[i], isOpen, switchType);
}
+ mRemoteAnimationTarget = targets.length > 1 ? createWrapTarget()
+ : mAdaptors[0].mAnimationTarget;
}
boolean isValid() {
@@ -1160,75 +1177,150 @@ class BackNavigationController {
}
void cleanUp(boolean startingSurfaceMatch) {
+ cleanUpWindowlessSurface(startingSurfaceMatch);
for (int i = mAdaptors.length - 1; i >= 0; --i) {
- mAdaptors[i].cleanUpWindowlessSurface(startingSurfaceMatch);
mAdaptors[i].mTarget.cancelAnimation();
}
+ mRequestedStartingSurfaceId = INVALID_TASK_ID;
+ mStartingSurface = null;
if (mCloseTransaction != null) {
mCloseTransaction.apply();
mCloseTransaction = null;
}
}
- void onAnimationFinish() {
- final SurfaceControl.Transaction pt = mAdaptors[0].mTarget.getPendingTransaction();
- if (mCloseTransaction != null) {
- pt.merge(mCloseTransaction);
- mCloseTransaction = null;
- }
- if (mAdaptors.length > 1) {
- for (int i = mAdaptors.length - 1; i >= 0; --i) {
- final WindowContainer wc = mAdaptors[i].mTarget;
- final WindowContainer parent = wc.getParent();
- if (parent != null) {
- pt.reparent(wc.getSurfaceControl(),
- parent.getSurfaceControl());
- }
- }
- }
- }
-
- @NonNull RemoteAnimationTarget getOrCreateAnimationTarget() {
+ private RemoteAnimationTarget createWrapTarget() {
// Special handle for opening two activities together.
// If we animate both activities separately, the animation area and rounded corner
// would also being handled separately. To make them seem like "open" together, wrap
// their leash with another animation leash.
- if (mAdaptors.length > 1 && mCloseTransaction == null) {
- final Rect unionBounds = new Rect();
- for (int i = mAdaptors.length - 1; i >= 0; --i) {
- unionBounds.union(mAdaptors[i].mAnimationTarget.localBounds);
+ final Rect unionBounds = new Rect();
+ for (int i = mAdaptors.length - 1; i >= 0; --i) {
+ unionBounds.union(mAdaptors[i].mAnimationTarget.localBounds);
+ }
+ final WindowContainer wc = mAdaptors[0].mTarget;
+ final Task task = wc.asActivityRecord() != null
+ ? wc.asActivityRecord().getTask() : wc.asTask();
+ final RemoteAnimationTarget represent = mAdaptors[0].mAnimationTarget;
+ final SurfaceControl leashSurface = new SurfaceControl.Builder()
+ .setName("cross-animation-leash")
+ .setContainerLayer()
+ .setHidden(false)
+ .setParent(task.getSurfaceControl())
+ .build();
+ mCloseTransaction = new SurfaceControl.Transaction();
+ mCloseTransaction.reparent(leashSurface, null);
+ final SurfaceControl.Transaction pt = wc.getPendingTransaction();
+ pt.setLayer(leashSurface, wc.getParent().getLastLayer());
+ for (int i = mAdaptors.length - 1; i >= 0; --i) {
+ BackWindowAnimationAdaptor adaptor = mAdaptors[i];
+ pt.reparent(adaptor.mAnimationTarget.leash, leashSurface);
+ pt.setPosition(adaptor.mAnimationTarget.leash,
+ adaptor.mAnimationTarget.localBounds.left,
+ adaptor.mAnimationTarget.localBounds.top);
+ // For adjacent activity embedded, reparent Activity to TaskFragment when
+ // animation finish
+ final WindowContainer parent = adaptor.mTarget.getParent();
+ if (parent != null) {
+ mCloseTransaction.reparent(adaptor.mTarget.getSurfaceControl(),
+ parent.getSurfaceControl());
}
- final WindowContainer wc = mAdaptors[0].mTarget;
- final Task task = wc.asActivityRecord() != null
- ? wc.asActivityRecord().getTask() : wc.asTask();
- final RemoteAnimationTarget represent = mAdaptors[0].mAnimationTarget;
- final SurfaceControl leashSurface = new SurfaceControl.Builder()
- .setName("cross-animation-leash")
- .setContainerLayer()
- .setHidden(false)
- .setParent(task.getSurfaceControl())
- .build();
- final SurfaceControl.Transaction pt = wc.getPendingTransaction();
- pt.setLayer(leashSurface, wc.getParent().getLastLayer());
- mCloseTransaction = new SurfaceControl.Transaction();
- mCloseTransaction.reparent(leashSurface, null);
- for (int i = mAdaptors.length - 1; i >= 0; --i) {
- BackWindowAnimationAdaptor adaptor = mAdaptors[i];
- pt.reparent(adaptor.mAnimationTarget.leash, leashSurface);
- pt.setPosition(adaptor.mAnimationTarget.leash,
- adaptor.mAnimationTarget.localBounds.left,
- adaptor.mAnimationTarget.localBounds.top);
+ }
+ return new RemoteAnimationTarget(represent.taskId, represent.mode, leashSurface,
+ represent.isTranslucent, represent.clipRect, represent.contentInsets,
+ represent.prefixOrderIndex,
+ new Point(unionBounds.left, unionBounds.top),
+ unionBounds, unionBounds, represent.windowConfiguration,
+ true /* isNotInRecents */, null, null, represent.taskInfo,
+ represent.allowEnterPip);
+ }
+
+ void createStartingSurface(ActivityRecord[] visibleOpenActivities) {
+ if (mAdaptors[0].mSwitchType == DIALOG_CLOSE) {
+ return;
+ }
+ final WindowContainer mainOpen = mAdaptors[0].mTarget;
+ final int switchType = mAdaptors[0].mSwitchType;
+ final Task openTask = switchType == TASK_SWITCH
+ ? mainOpen.asTask() : switchType == ACTIVITY_SWITCH
+ ? mainOpen.asActivityRecord().getTask() : null;
+ if (openTask == null) {
+ return;
+ }
+ final ActivityRecord mainActivity = switchType == ACTIVITY_SWITCH
+ ? mainOpen.asActivityRecord()
+ : openTask.getTopNonFinishingActivity();
+ if (mainActivity == null) {
+ return;
+ }
+ final TaskSnapshot snapshot = getSnapshot(mainOpen, visibleOpenActivities);
+ // If there is only one adaptor, attach the windowless window to top activity,
+ // because fixed rotation only applies on activity.
+ // Note that embedded activity won't use fixed rotation.
+ final Configuration openConfig = mAdaptors.length == 1
+ ? mainActivity.getConfiguration() : openTask.getConfiguration();
+ mRequestedStartingSurfaceId = openTask.mAtmService.mTaskOrganizerController
+ .addWindowlessStartingSurface(openTask, mainActivity,
+ mAdaptors.length == 1 ? mainActivity.getSurfaceControl()
+ : mRemoteAnimationTarget.leash, snapshot, openConfig,
+ new IWindowlessStartingSurfaceCallback.Stub() {
+ // Once the starting surface has been created in shell, it will call
+ // onSurfaceAdded to pass the created surface to core, so if a
+ // transition is triggered by the back gesture, there doesn't need to
+ // create another starting surface for the opening target, just reparent
+ // the starting surface to the opening target.
+ // Note if cleanUpWindowlessSurface happen prior than onSurfaceAdded
+ // called, there won't be able to reparent the starting surface on
+ // opening target. But if that happens and transition target is matched,
+ // the app window should already draw.
+ @Override
+ public void onSurfaceAdded(SurfaceControl sc) {
+ synchronized (openTask.mWmService.mGlobalLock) {
+ if (mRequestedStartingSurfaceId != INVALID_TASK_ID) {
+ mStartingSurface = sc;
+ }
+ }
+ }
+ });
+ }
+
+ // When back gesture has triggered and transition target matches navigation target,
+ // reparent the starting surface to the opening target as it's starting window.
+ void reparentWindowlessSurfaceToTarget(SurfaceControl.Transaction reparentTransaction) {
+ if (mRequestedStartingSurfaceId == INVALID_TASK_ID) {
+ return;
+ }
+ // If open target matches, reparent to open activity or task
+ if (mStartingSurface != null && mStartingSurface.isValid()) {
+ SurfaceControl.Transaction transaction = reparentTransaction != null
+ ? reparentTransaction : mAdaptors[0].mTarget.getPendingTransaction();
+ if (mAdaptors.length != 1) {
+ // More than one opening window, reparent starting surface to leaf task.
+ final WindowContainer wc = mAdaptors[0].mTarget;
+ final Task task = wc.asActivityRecord() != null
+ ? wc.asActivityRecord().getTask() : wc.asTask();
+ transaction.reparent(mStartingSurface, task != null
+ ? task.getSurfaceControl()
+ : mAdaptors[0].mTarget.getSurfaceControl());
}
- return new RemoteAnimationTarget(represent.taskId, represent.mode, leashSurface,
- represent.isTranslucent, represent.clipRect, represent.contentInsets,
- represent.prefixOrderIndex,
- new Point(unionBounds.left, unionBounds.top),
- unionBounds, unionBounds, represent.windowConfiguration,
- true /* isNotInRecents */, null, null, represent.taskInfo,
- represent.allowEnterPip);
- } else {
- return mAdaptors[0].mAnimationTarget;
+ // remove starting surface.
+ mStartingSurface = null;
+ }
+ }
+
+ /**
+ * Ask shell to clear the starting surface.
+ * @param openTransitionMatch if true, shell will play the remove starting window
+ * animation, otherwise remove it directly.
+ */
+ void cleanUpWindowlessSurface(boolean openTransitionMatch) {
+ if (mRequestedStartingSurfaceId == INVALID_TASK_ID) {
+ return;
}
+ mAdaptors[0].mTarget.mWmService.mAtmService.mTaskOrganizerController
+ .removeWindowlessStartingSurface(mRequestedStartingSurfaceId,
+ !openTransitionMatch);
+ mRequestedStartingSurfaceId = INVALID_TASK_ID;
}
}
@@ -1240,11 +1332,6 @@ class BackNavigationController {
private RemoteAnimationTarget mAnimationTarget;
private final int mSwitchType;
- // The starting surface task Id. Used to clear the starting surface if the animation has
- // requested one during animating.
- private int mRequestedStartingSurfaceId = INVALID_TASK_ID;
- private SurfaceControl mStartingSurface;
-
BackWindowAnimationAdaptor(@NonNull WindowContainer target, boolean isOpen,
int switchType) {
mBounds.set(target.getBounds());
@@ -1276,8 +1363,6 @@ class BackNavigationController {
public void onAnimationCancelled(SurfaceControl animationLeash) {
if (mCapturedLeash == animationLeash) {
mCapturedLeash = null;
- mRequestedStartingSurfaceId = INVALID_TASK_ID;
- mStartingSurface = null;
}
}
@@ -1345,84 +1430,6 @@ class BackNavigationController {
r.checkEnterPictureInPictureAppOpsState());
return mAnimationTarget;
}
-
- void createStartingSurface(@NonNull WindowContainer closeWindow,
- @NonNull ActivityRecord[] visibleOpenActivities) {
- if (!mIsOpen) {
- return;
- }
- if (mSwitchType == DIALOG_CLOSE) {
- return;
- }
- final Task openTask = mSwitchType == TASK_SWITCH
- ? mTarget.asTask() : mSwitchType == ACTIVITY_SWITCH
- ? mTarget.asActivityRecord().getTask() : null;
- if (openTask == null) {
- return;
- }
- final ActivityRecord mainActivity = mSwitchType == ACTIVITY_SWITCH
- ? mTarget.asActivityRecord()
- : openTask.getTopNonFinishingActivity();
- if (mainActivity == null) {
- return;
- }
- final TaskSnapshot snapshot = getSnapshot(mTarget, visibleOpenActivities);
- mRequestedStartingSurfaceId = openTask.mAtmService.mTaskOrganizerController
- .addWindowlessStartingSurface(openTask, mainActivity,
- // Choose configuration from closeWindow, because the configuration
- // of opening target may not update before resume, so the starting
- // surface should occlude it entirely.
- mAnimationTarget.leash, snapshot, closeWindow.getConfiguration(),
- new IWindowlessStartingSurfaceCallback.Stub() {
- // Once the starting surface has been created in shell, it will call
- // onSurfaceAdded to pass the created surface to core, so if a
- // transition is triggered by the back gesture, there doesn't need to
- // create another starting surface for the opening target, just reparent
- // the starting surface to the opening target.
- // Note if cleanUpWindowlessSurface happen prior than onSurfaceAdded
- // called, there won't be able to reparent the starting surface on
- // opening target. But if that happens and transition target is matched,
- // the app window should already draw.
- @Override
- public void onSurfaceAdded(SurfaceControl sc) {
- synchronized (mTarget.mWmService.mGlobalLock) {
- if (mRequestedStartingSurfaceId != INVALID_TASK_ID) {
- mStartingSurface = sc;
- }
- }
- }
- });
- }
-
- // When back gesture has triggered and transition target matches navigation target,
- // reparent the starting surface to the opening target as it's starting window.
- void reparentWindowlessSurfaceToTarget() {
- if (mRequestedStartingSurfaceId == INVALID_TASK_ID) {
- return;
- }
- // If open target matches, reparent to open activity or task
- if (mStartingSurface != null && mStartingSurface.isValid()) {
- mTarget.getPendingTransaction()
- .reparent(mStartingSurface, mTarget.getSurfaceControl());
- // remove starting surface.
- mStartingSurface = null;
- }
- }
-
- /**
- * Ask shell to clear the starting surface.
- * @param openTransitionMatch if true, shell will play the remove starting window
- * animation, otherwise remove it directly.
- */
- void cleanUpWindowlessSurface(boolean openTransitionMatch) {
- if (mRequestedStartingSurfaceId == INVALID_TASK_ID) {
- return;
- }
- mTarget.mWmService.mAtmService.mTaskOrganizerController
- .removeWindowlessStartingSurface(mRequestedStartingSurfaceId,
- !openTransitionMatch);
- mRequestedStartingSurfaceId = INVALID_TASK_ID;
- }
}
ScheduleAnimationBuilder prepareAnimation(
@@ -1493,24 +1500,15 @@ class BackNavigationController {
/**
* Apply preview strategy on the opening target
- * @param closeWindow The close window, where it's configuration should cover all
- * open target(s).
+ *
* @param openAnimationAdaptor The animator who can create starting surface.
* @param visibleOpenActivities The visible activities in opening targets.
*/
- private void applyPreviewStrategy(@NonNull WindowContainer closeWindow,
- @NonNull BackWindowAnimationAdaptor[] openAnimationAdaptor,
+ private void applyPreviewStrategy(
+ @NonNull BackWindowAnimationAdaptorWrapper openAnimationAdaptor,
@NonNull ActivityRecord[] visibleOpenActivities) {
- if (isSupportWindowlessSurface() && mShowWindowlessSurface && !mIsLaunchBehind
- // TODO (b/274997067) Draw two snapshot in a single starting surface.
- // We are using TaskId as the key of
- // StartingSurfaceDrawer#StartingWindowRecordManager, so we cannot create
- // two activity snapshot with WindowlessStartingWindow.
- // Try to draw two snapshot within a WindowlessStartingWindow, or find
- // another key for StartingWindowRecordManager.
- && openAnimationAdaptor.length == 1) {
- openAnimationAdaptor[0].createStartingSurface(closeWindow,
- visibleOpenActivities);
+ if (isSupportWindowlessSurface() && mShowWindowlessSurface && !mIsLaunchBehind) {
+ openAnimationAdaptor.createStartingSurface(visibleOpenActivities);
} else {
for (int i = visibleOpenActivities.length - 1; i >= 0; --i) {
setLaunchBehind(visibleOpenActivities[i]);
@@ -1541,7 +1539,7 @@ class BackNavigationController {
}
mCloseTarget.mTransitionController.mSnapshotController
.mActivitySnapshotController.clearOnBackPressedActivities();
- applyPreviewStrategy(mCloseTarget, mOpenAnimAdaptor.mAdaptors, openingActivities);
+ applyPreviewStrategy(mOpenAnimAdaptor, openingActivities);
final IBackAnimationFinishedCallback callback = makeAnimationFinishedCallback();
final RemoteAnimationTarget[] targets = getAnimationTargets();
@@ -1565,7 +1563,6 @@ class BackNavigationController {
// animation was canceled
return;
}
- mOpenAnimAdaptor.onAnimationFinish();
if (!triggerBack) {
clearBackAnimateTarget();
} else {
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index 86ca1eac8a50..fdae53f59ad4 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -94,6 +94,11 @@ public class BackgroundActivityStartController {
private static final long ASM_GRACEPERIOD_TIMEOUT_MS = TIMEOUT_MS;
private static final int ASM_GRACEPERIOD_MAX_REPEATS = 5;
private static final int NO_PROCESS_UID = -1;
+
+ static final String AUTO_OPT_IN_NOT_PENDING_INTENT = "notPendingIntent";
+ static final String AUTO_OPT_IN_CALL_FOR_RESULT = "callForResult";
+ static final String AUTO_OPT_IN_SAME_UID = "sameUid";
+
/** If enabled the creator will not allow BAL on its behalf by default. */
@ChangeId
@EnabledAfter(targetSdkVersion = UPSIDE_DOWN_CAKE)
@@ -232,9 +237,9 @@ public class BackgroundActivityStartController {
private final boolean mCallingUidHasAnyVisibleWindow;
private final @ActivityManager.ProcessState int mCallingUidProcState;
private final boolean mIsCallingUidPersistentSystemProcess;
- private final BackgroundStartPrivileges mBalAllowedByPiSender;
- private final BackgroundStartPrivileges mBalAllowedByPiCreatorWithHardening;
- private final BackgroundStartPrivileges mBalAllowedByPiCreator;
+ final BackgroundStartPrivileges mBalAllowedByPiSender;
+ final BackgroundStartPrivileges mBalAllowedByPiCreatorWithHardening;
+ final BackgroundStartPrivileges mBalAllowedByPiCreator;
private final String mRealCallingPackage;
private final int mRealCallingUid;
private final int mRealCallingPid;
@@ -248,11 +253,12 @@ public class BackgroundActivityStartController {
private final WindowProcessController mRealCallerApp;
private final boolean mIsCallForResult;
private final ActivityOptions mCheckedOptions;
- private final String mAutoOptInReason;
+ final String mAutoOptInReason;
+ private final boolean mAutoOptInCaller;
private BalVerdict mResultForCaller;
private BalVerdict mResultForRealCaller;
- private BalState(int callingUid, int callingPid, final String callingPackage,
+ @VisibleForTesting BalState(int callingUid, int callingPid, final String callingPackage,
int realCallingUid, int realCallingPid,
WindowProcessController callerApp,
PendingIntentRecord originatingPendingIntent,
@@ -280,26 +286,27 @@ public class BackgroundActivityStartController {
if (!balImproveRealCallerVisibilityCheck()) {
// without this fix the auto-opt ins below would violate CTS tests
mAutoOptInReason = null;
- } else if (mIsCallForResult) {
- mAutoOptInReason = "callForResult";
+ mAutoOptInCaller = false;
} else if (originatingPendingIntent == null) {
- mAutoOptInReason = "notPendingIntent";
+ mAutoOptInReason = AUTO_OPT_IN_NOT_PENDING_INTENT;
+ mAutoOptInCaller = true;
+ } else if (mIsCallForResult) {
+ mAutoOptInReason = AUTO_OPT_IN_CALL_FOR_RESULT;
+ mAutoOptInCaller = false;
} else if (callingUid == realCallingUid && !balRequireOptInSameUid()) {
- mAutoOptInReason = "sameUid";
+ mAutoOptInReason = AUTO_OPT_IN_SAME_UID;
+ mAutoOptInCaller = false;
} else {
mAutoOptInReason = null;
+ mAutoOptInCaller = false;
}
- if (mAutoOptInReason != null) {
+ if (mAutoOptInCaller) {
// grant BAL privileges unless explicitly opted out
mBalAllowedByPiCreatorWithHardening = mBalAllowedByPiCreator =
callerBackgroundActivityStartMode == MODE_BACKGROUND_ACTIVITY_START_DENIED
? BackgroundStartPrivileges.NONE
: BackgroundStartPrivileges.ALLOW_BAL;
- mBalAllowedByPiSender = realCallerBackgroundActivityStartMode
- == MODE_BACKGROUND_ACTIVITY_START_DENIED
- ? BackgroundStartPrivileges.NONE
- : BackgroundStartPrivileges.ALLOW_BAL;
} else {
// for PendingIntents we restrict BAL based on target_sdk
mBalAllowedByPiCreatorWithHardening = getBackgroundStartPrivilegesAllowedByCreator(
@@ -312,10 +319,21 @@ public class BackgroundActivityStartController {
mBalAllowedByPiCreator = balRequireOptInByPendingIntentCreator()
? mBalAllowedByPiCreatorWithHardening
: mBalAllowedByPiCreatorWithoutHardening;
+ }
+
+ if (mAutoOptInReason != null) {
+ // grant BAL privileges unless explicitly opted out
+ mBalAllowedByPiSender = realCallerBackgroundActivityStartMode
+ == MODE_BACKGROUND_ACTIVITY_START_DENIED
+ ? BackgroundStartPrivileges.NONE
+ : BackgroundStartPrivileges.ALLOW_BAL;
+ } else {
+ // for PendingIntents we restrict BAL based on target_sdk
mBalAllowedByPiSender =
PendingIntentRecord.getBackgroundStartPrivilegesAllowedByCaller(
checkedOptions, realCallingUid, mRealCallingPackage);
}
+
mAppSwitchState = mService.getBalAppSwitchesState();
mCallingUidProcState = mService.mActiveUids.getUidState(callingUid);
mIsCallingUidPersistentSystemProcess =
@@ -407,7 +425,7 @@ public class BackgroundActivityStartController {
return mRealCallingUid != NO_PROCESS_UID;
}
- private boolean isPendingIntent() {
+ boolean isPendingIntent() {
return mOriginatingPendingIntent != null && hasRealCaller();
}
@@ -485,23 +503,19 @@ public class BackgroundActivityStartController {
}
public boolean callerExplicitOptInOrAutoOptIn() {
- if (mAutoOptInReason == null) {
- return mCheckedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
- == MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
- } else {
- return mCheckedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
- != MODE_BACKGROUND_ACTIVITY_START_DENIED;
+ if (mAutoOptInCaller) {
+ return !callerExplicitOptOut();
}
+ return mCheckedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
+ == MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
}
public boolean realCallerExplicitOptInOrAutoOptIn() {
- if (mAutoOptInReason == null) {
- return mCheckedOptions.getPendingIntentBackgroundActivityStartMode()
- == MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
- } else {
- return mCheckedOptions.getPendingIntentBackgroundActivityStartMode()
- != MODE_BACKGROUND_ACTIVITY_START_DENIED;
+ if (mAutoOptInReason != null) {
+ return !realCallerExplicitOptOut();
}
+ return mCheckedOptions.getPendingIntentBackgroundActivityStartMode()
+ == MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
}
public boolean callerExplicitOptOut() {
@@ -523,6 +537,11 @@ public class BackgroundActivityStartController {
return mCheckedOptions.getPendingIntentBackgroundActivityStartMode()
!= MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
}
+
+ @Override
+ public String toString() {
+ return dump();
+ }
}
static class BalVerdict {
@@ -972,7 +991,7 @@ public class BackgroundActivityStartController {
* String, int, boolean, boolean, boolean, long, long, long)} for details on the
* exceptions.
*/
- private BalVerdict checkProcessAllowsBal(WindowProcessController app,
+ @VisibleForTesting BalVerdict checkProcessAllowsBal(WindowProcessController app,
BalState state) {
if (app == null) {
return BalVerdict.BLOCK;
diff --git a/services/core/java/com/android/server/wm/ClientLifecycleManager.java b/services/core/java/com/android/server/wm/ClientLifecycleManager.java
index 5b4fb3e6971f..e48e4e84d60d 100644
--- a/services/core/java/com/android/server/wm/ClientLifecycleManager.java
+++ b/services/core/java/com/android/server/wm/ClientLifecycleManager.java
@@ -87,11 +87,7 @@ class ClientLifecycleManager {
void scheduleTransactionItemNow(@NonNull IApplicationThread client,
@NonNull ClientTransactionItem transactionItem) throws RemoteException {
final ClientTransaction clientTransaction = ClientTransaction.obtain(client);
- if (transactionItem.isActivityLifecycleItem()) {
- clientTransaction.setLifecycleStateRequest((ActivityLifecycleItem) transactionItem);
- } else {
- clientTransaction.addCallback(transactionItem);
- }
+ clientTransaction.addTransactionItem(transactionItem);
scheduleTransaction(clientTransaction);
}
@@ -115,11 +111,8 @@ class ClientLifecycleManager {
} else {
// TODO(b/260873529): cleanup after launch.
final ClientTransaction clientTransaction = ClientTransaction.obtain(client);
- if (transactionItem.isActivityLifecycleItem()) {
- clientTransaction.setLifecycleStateRequest((ActivityLifecycleItem) transactionItem);
- } else {
- clientTransaction.addCallback(transactionItem);
- }
+ clientTransaction.addTransactionItem(transactionItem);
+
scheduleTransaction(clientTransaction);
}
}
@@ -160,8 +153,8 @@ class ClientLifecycleManager {
} else {
// TODO(b/260873529): cleanup after launch.
final ClientTransaction clientTransaction = ClientTransaction.obtain(client);
- clientTransaction.addCallback(transactionItem);
- clientTransaction.setLifecycleStateRequest(lifecycleItem);
+ clientTransaction.addTransactionItem(transactionItem);
+ clientTransaction.addTransactionItem(lifecycleItem);
scheduleTransaction(clientTransaction);
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index e7431723789d..d3acd716aed3 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1127,7 +1127,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
final ActivityRecord activity = w.mActivityRecord;
if (activity != null && activity.isVisibleRequested()) {
- activity.updateLetterboxSurface(w);
+ activity.updateLetterboxSurfaceIfNeeded(w);
final boolean updateAllDrawn = activity.updateDrawnWindowStates(w);
if (updateAllDrawn && !mTmpUpdateAllDrawn.contains(activity)) {
mTmpUpdateAllDrawn.add(activity);
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index a7bbc25d0bb1..5cf9acdbc0d6 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -104,7 +104,6 @@ import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
import android.util.ArraySet;
-import android.util.PrintWriterPrinter;
import android.util.Slog;
import android.util.SparseArray;
import android.view.DisplayInfo;
@@ -2070,8 +2069,7 @@ public class DisplayPolicy {
}
return false;
}
- if (mCachedDecorInsets != null && !mCachedDecorInsets.canPreserve()
- && !mDisplayContent.isSleeping()) {
+ if (mCachedDecorInsets != null && !mCachedDecorInsets.canPreserve() && mScreenOnFully) {
mCachedDecorInsets = null;
}
mDecorInsets.invalidate();
@@ -2136,16 +2134,6 @@ public class DisplayPolicy {
}
mCachedDecorInsets.mPreserveId =
mDisplayContent.mTransitionController.getCollectingTransitionId();
- // The validator will run after the transition is finished. So if the insets are changed
- // during the transition, it can update to the latest state.
- mDisplayContent.mTransitionController.mStateValidators.add(() -> {
- // The insets provider client may defer to change its window until screen is on. So
- // only validate when awake to avoid the cache being always dropped.
- if (!mDisplayContent.isSleeping() && updateDecorInsetsInfo()) {
- Slog.d(TAG, "Insets changed after display switch transition");
- mDisplayContent.sendNewConfiguration();
- }
- });
}
@NavigationBarPosition
@@ -2891,9 +2879,6 @@ public class DisplayPolicy {
if (!CLIENT_TRANSIENT) {
mSystemGestures.dump(pw, prefix);
}
-
- pw.print(prefix); pw.println("Looper state:");
- mHandler.getLooper().dump(new PrintWriterPrinter(pw), prefix + " ");
}
private boolean supportsPointerLocation() {
diff --git a/services/core/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java
index a3e28693cb21..b68e67e291e7 100644
--- a/services/core/java/com/android/server/wm/DragDropController.java
+++ b/services/core/java/com/android/server/wm/DragDropController.java
@@ -25,6 +25,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACT
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import android.annotation.NonNull;
+import android.app.ActivityManager;
import android.content.ClipData;
import android.content.Context;
import android.hardware.input.InputManagerGlobal;
@@ -43,8 +44,8 @@ import android.view.PointerIcon;
import android.view.SurfaceControl;
import android.view.View;
import android.view.accessibility.AccessibilityManager;
+import android.window.IGlobalDragListener;
import android.window.IUnhandledDragCallback;
-import android.window.IUnhandledDragListener;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.wm.WindowManagerInternal.IDragDropCallback;
@@ -81,9 +82,9 @@ class DragDropController {
private WindowManagerService mService;
private final Handler mHandler;
- // The unhandled drag listener for handling cross-window drags that end with no target window
- private IUnhandledDragListener mUnhandledDragListener;
- private final IBinder.DeathRecipient mUnhandledDragListenerDeathRecipient =
+ // The global drag listener for handling cross-window drags
+ private IGlobalDragListener mGlobalDragListener;
+ private final IBinder.DeathRecipient mGlobalDragListenerDeathRecipient =
new IBinder.DeathRecipient() {
@Override
public void binderDied() {
@@ -91,7 +92,7 @@ class DragDropController {
if (hasPendingUnhandledDropCallback()) {
onUnhandledDropCallback(false /* consumedByListeners */);
}
- setUnhandledDragListener(null);
+ setGlobalDragListener(null);
}
}
};
@@ -129,29 +130,22 @@ class DragDropController {
/**
* Sets the listener for unhandled cross-window drags.
*/
- public void setUnhandledDragListener(IUnhandledDragListener listener) {
- if (mUnhandledDragListener != null && mUnhandledDragListener.asBinder() != null) {
- mUnhandledDragListener.asBinder().unlinkToDeath(
- mUnhandledDragListenerDeathRecipient, 0);
+ public void setGlobalDragListener(IGlobalDragListener listener) {
+ if (mGlobalDragListener != null && mGlobalDragListener.asBinder() != null) {
+ mGlobalDragListener.asBinder().unlinkToDeath(
+ mGlobalDragListenerDeathRecipient, 0);
}
- mUnhandledDragListener = listener;
+ mGlobalDragListener = listener;
if (listener != null && listener.asBinder() != null) {
try {
- mUnhandledDragListener.asBinder().linkToDeath(
- mUnhandledDragListenerDeathRecipient, 0);
+ mGlobalDragListener.asBinder().linkToDeath(
+ mGlobalDragListenerDeathRecipient, 0);
} catch (RemoteException e) {
- mUnhandledDragListener = null;
+ mGlobalDragListener = null;
}
}
}
- /**
- * Returns whether there is an unhandled drag listener set.
- */
- boolean hasUnhandledDragListener() {
- return mUnhandledDragListener != null;
- }
-
void sendDragStartedIfNeededLocked(WindowState window) {
mDragState.sendDragStartedIfNeededLocked(window);
}
@@ -351,7 +345,20 @@ class DragDropController {
final boolean relinquishDragSurfaceToDropTarget =
consumed && mDragState.targetInterceptsGlobalDrag(callingWin);
+ final boolean isCrossWindowDrag = !mDragState.mLocalWin.equals(token);
mDragState.endDragLocked(consumed, relinquishDragSurfaceToDropTarget);
+
+ final Task droppedWindowTask = callingWin.getTask();
+ if (com.android.window.flags.Flags.delegateUnhandledDrags()
+ && mGlobalDragListener != null && droppedWindowTask != null && consumed
+ && isCrossWindowDrag) {
+ try {
+ mGlobalDragListener.onCrossWindowDrop(droppedWindowTask.getTaskInfo());
+ } catch (RemoteException e) {
+ Slog.e(TAG_WM, "Failed to call global drag listener for cross-window "
+ + "drop", e);
+ }
+ }
}
} finally {
mCallback.get().postReportDropResult();
@@ -367,19 +374,19 @@ class DragDropController {
final boolean isLocalDrag =
(mDragState.mFlags & (DRAG_FLAG_GLOBAL_SAME_APPLICATION | DRAG_FLAG_GLOBAL)) == 0;
if (!com.android.window.flags.Flags.delegateUnhandledDrags()
- || mUnhandledDragListener == null
+ || mGlobalDragListener == null
|| isLocalDrag) {
// Skip if the flag is disabled, there is no unhandled-drag listener, or if this is a
// purely local drag
if (DEBUG_DRAG) Slog.d(TAG_WM, "Skipping unhandled listener "
- + "(listener=" + mUnhandledDragListener + ", flags=" + mDragState.mFlags + ")");
+ + "(listener=" + mGlobalDragListener + ", flags=" + mDragState.mFlags + ")");
return false;
}
if (DEBUG_DRAG) Slog.d(TAG_WM, "Sending DROP to unhandled listener (" + reason + ")");
try {
// Schedule timeout for the unhandled drag listener to call back
sendTimeoutMessage(MSG_UNHANDLED_DROP_LISTENER_TIMEOUT, null, DRAG_TIMEOUT_MS);
- mUnhandledDragListener.onUnhandledDrop(dropEvent, new IUnhandledDragCallback.Stub() {
+ mGlobalDragListener.onUnhandledDrop(dropEvent, new IUnhandledDragCallback.Stub() {
@Override
public void notifyUnhandledDropComplete(boolean consumedByListener) {
if (DEBUG_DRAG) Slog.d(TAG_WM, "Unhandled listener finished handling DROP");
@@ -390,7 +397,7 @@ class DragDropController {
});
return true;
} catch (RemoteException e) {
- Slog.e(TAG_WM, "Failed to call unhandled drag listener", e);
+ Slog.e(TAG_WM, "Failed to call global drag listener for unhandled drop", e);
return false;
}
}
diff --git a/services/core/java/com/android/server/wm/EmbeddedWindowController.java b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
index fac62fc8b3db..0978cb49e863 100644
--- a/services/core/java/com/android/server/wm/EmbeddedWindowController.java
+++ b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
@@ -184,7 +184,7 @@ class EmbeddedWindowController {
if (!isValidTouchGestureParams(transferToHostWindowState, ew)) {
return false;
}
- return mInputManagerService.transferTouchFocus(ew.getInputChannelToken(),
+ return mInputManagerService.transferTouchGesture(ew.getInputChannelToken(),
transferToHostWindowState.mInputChannelToken);
}
@@ -194,7 +194,7 @@ class EmbeddedWindowController {
if (!isValidTouchGestureParams(hostWindowState, ew)) {
return false;
}
- return mInputManagerService.transferTouchFocus(hostWindowState.mInputChannelToken,
+ return mInputManagerService.transferTouchGesture(hostWindowState.mInputChannelToken,
ew.getInputChannelToken());
}
diff --git a/services/core/java/com/android/server/wm/Letterbox.java b/services/core/java/com/android/server/wm/Letterbox.java
index e66321ae8219..362d4efa0825 100644
--- a/services/core/java/com/android/server/wm/Letterbox.java
+++ b/services/core/java/com/android/server/wm/Letterbox.java
@@ -49,7 +49,7 @@ import java.util.function.Supplier;
*/
public class Letterbox {
- private static final Rect EMPTY_RECT = new Rect();
+ static final Rect EMPTY_RECT = new Rect();
private static final Point ZERO_POINT = new Point(0, 0);
private final Supplier<SurfaceControl.Builder> mSurfaceControlFactory;
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index edf9da1e0bf5..5d613cf45643 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -52,7 +52,6 @@ import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_FULLSCREEN
import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_SPLIT_SCREEN;
import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_UNSET;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
-import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
import static android.content.res.Configuration.SCREEN_HEIGHT_DP_UNDEFINED;
import static android.content.res.Configuration.SCREEN_WIDTH_DP_UNDEFINED;
@@ -924,21 +923,21 @@ final class LetterboxUiController {
return mLetterbox == null || mLetterbox.notIntersectsOrFullyContains(rect);
}
- void updateLetterboxSurface(WindowState winHint) {
- updateLetterboxSurface(winHint, mActivityRecord.getSyncTransaction());
+ void updateLetterboxSurfaceIfNeeded(WindowState winHint) {
+ updateLetterboxSurfaceIfNeeded(winHint, mActivityRecord.getSyncTransaction());
}
- void updateLetterboxSurface(WindowState winHint, Transaction t) {
+ void updateLetterboxSurfaceIfNeeded(WindowState winHint, Transaction t) {
if (shouldNotLayoutLetterbox(winHint)) {
return;
}
- layoutLetterbox(winHint);
+ layoutLetterboxIfNeeded(winHint);
if (mLetterbox != null && mLetterbox.needsApplySurfaceChanges()) {
mLetterbox.applySurfaceChanges(t);
}
}
- void layoutLetterbox(WindowState w) {
+ void layoutLetterboxIfNeeded(WindowState w) {
if (shouldNotLayoutLetterbox(w)) {
return;
}
@@ -1369,23 +1368,25 @@ final class LetterboxUiController {
*
* <p>Conditions that needs to be met:
* <ul>
- * <li>Activity is portrait-only.
- * <li>Fullscreen window in landscape device orientation.
+ * <li>Windowing mode is fullscreen.
* <li>Horizontal Reachability is enabled.
- * <li>Activity fills parent vertically.
+ * <li>First top opaque activity fills parent vertically, but not horizontally.
* </ul>
*/
private boolean isHorizontalReachabilityEnabled(Configuration parentConfiguration) {
// Use screen resolved bounds which uses resolved bounds or size compat bounds
// as activity bounds can sometimes be empty
+ final Rect opaqueActivityBounds = hasInheritedLetterboxBehavior()
+ ? mFirstOpaqueActivityBeneath.getScreenResolvedBounds()
+ : mActivityRecord.getScreenResolvedBounds();
return mLetterboxConfiguration.getIsHorizontalReachabilityEnabled()
&& parentConfiguration.windowConfiguration.getWindowingMode()
== WINDOWING_MODE_FULLSCREEN
- && (parentConfiguration.orientation == ORIENTATION_LANDSCAPE
- && mActivityRecord.getOrientationForReachability() == ORIENTATION_PORTRAIT)
// Check whether the activity fills the parent vertically.
&& parentConfiguration.windowConfiguration.getAppBounds().height()
- <= mActivityRecord.getScreenResolvedBounds().height();
+ <= opaqueActivityBounds.height()
+ && parentConfiguration.windowConfiguration.getAppBounds().width()
+ > opaqueActivityBounds.width();
}
@VisibleForTesting
@@ -1402,23 +1403,25 @@ final class LetterboxUiController {
*
* <p>Conditions that needs to be met:
* <ul>
- * <li>Activity is landscape-only.
- * <li>Fullscreen window in portrait device orientation.
+ * <li>Windowing mode is fullscreen.
* <li>Vertical Reachability is enabled.
- * <li>Activity fills parent horizontally.
+ * <li>First top opaque activity fills parent horizontally but not vertically.
* </ul>
*/
private boolean isVerticalReachabilityEnabled(Configuration parentConfiguration) {
// Use screen resolved bounds which uses resolved bounds or size compat bounds
// as activity bounds can sometimes be empty
+ final Rect opaqueActivityBounds = hasInheritedLetterboxBehavior()
+ ? mFirstOpaqueActivityBeneath.getScreenResolvedBounds()
+ : mActivityRecord.getScreenResolvedBounds();
return mLetterboxConfiguration.getIsVerticalReachabilityEnabled()
&& parentConfiguration.windowConfiguration.getWindowingMode()
== WINDOWING_MODE_FULLSCREEN
- && (parentConfiguration.orientation == ORIENTATION_PORTRAIT
- && mActivityRecord.getOrientationForReachability() == ORIENTATION_LANDSCAPE)
// Check whether the activity fills the parent horizontally.
- && parentConfiguration.windowConfiguration.getBounds().width()
- == mActivityRecord.getScreenResolvedBounds().width();
+ && parentConfiguration.windowConfiguration.getAppBounds().width()
+ <= opaqueActivityBounds.width()
+ && parentConfiguration.windowConfiguration.getAppBounds().height()
+ > opaqueActivityBounds.height();
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 19a9b3fbb368..b562ccfb3d2b 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2481,6 +2481,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
if (displayShouldSleep == display.isSleeping()) {
continue;
}
+ final boolean wasSleeping = display.isSleeping();
display.setIsSleeping(displayShouldSleep);
if (display.mTransitionController.isShellTransitionsEnabled()
@@ -2506,9 +2507,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
// Use NONE if keyguard is not showing.
int transit = TRANSIT_NONE;
Task startTask = null;
- if (!display.getDisplayPolicy().isAwake()) {
- // Note that currently this only happens on default display because non-default
- // display is always awake.
+ if (wasSleeping) {
transit = TRANSIT_WAKE;
} else if (display.isKeyguardOccluded()) {
// The display was awake so this is resuming activity for occluding keyguard.
diff --git a/services/core/java/com/android/server/wm/SensitiveContentPackages.java b/services/core/java/com/android/server/wm/SensitiveContentPackages.java
index 5fe48d12d498..9ab11b8059a7 100644
--- a/services/core/java/com/android/server/wm/SensitiveContentPackages.java
+++ b/services/core/java/com/android/server/wm/SensitiveContentPackages.java
@@ -16,9 +16,16 @@
package com.android.server.wm;
+import static android.permission.flags.Flags.sensitiveNotificationAppProtection;
+import static android.view.flags.Flags.sensitiveContentAppProtection;
+
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.IBinder;
import android.util.ArraySet;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.io.PrintWriter;
import java.util.Objects;
@@ -29,12 +36,26 @@ import java.util.Objects;
public class SensitiveContentPackages {
private final ArraySet<PackageInfo> mProtectedPackages = new ArraySet<>();
- /** Returns {@code true} if package/uid pair should be blocked from screen capture */
- public boolean shouldBlockScreenCaptureForApp(String pkg, int uid) {
+ /**
+ * Returns {@code true} if package/uid/window combination should be blocked
+ * from screen capture.
+ */
+ public boolean shouldBlockScreenCaptureForApp(String pkg, int uid, IBinder windowToken) {
+ if (!(sensitiveContentAppProtection() || sensitiveNotificationAppProtection())) {
+ return false;
+ }
+
for (int i = 0; i < mProtectedPackages.size(); i++) {
PackageInfo info = mProtectedPackages.valueAt(i);
if (info != null && info.mPkg.equals(pkg) && info.mUid == uid) {
- return true;
+ // sensitiveContentAppProtection blocks specific window where sensitive content
+ // is rendered, whereas sensitiveNotificationAppProtection blocks the package
+ // if the package has a sensitive notification.
+ if ((sensitiveContentAppProtection() && windowToken == info.getWindowToken())
+ || (sensitiveNotificationAppProtection() && info.getWindowToken() == null)
+ ) {
+ return true;
+ }
}
}
return false;
@@ -73,31 +94,50 @@ public class SensitiveContentPackages {
*/
public boolean clearBlockedApps() {
if (mProtectedPackages.isEmpty()) {
- // set was already empty
return false;
}
mProtectedPackages.clear();
return true;
}
+ /**
+ * @return the size of protected packages.
+ */
+ @VisibleForTesting
+ public int size() {
+ return mProtectedPackages.size();
+ }
+
void dump(PrintWriter pw) {
final String innerPrefix = " ";
pw.println("SensitiveContentPackages:");
pw.println(innerPrefix + "Packages that should block screen capture ("
+ mProtectedPackages.size() + "):");
for (PackageInfo info : mProtectedPackages) {
- pw.println(innerPrefix + " package=" + info.mPkg + " uid=" + info.mUid);
+ pw.println(innerPrefix + " package=" + info.mPkg + " uid=" + info.mUid
+ + " windowToken=" + info.mWindowToken);
}
}
- /** Helper class that represents a package/uid pair */
+ /**
+ * Helper class that represents a package, uid, and window token combination, window token
+ * is set to block screen capture at window level.
+ */
public static class PackageInfo {
- private String mPkg;
- private int mUid;
+ private final String mPkg;
+ private final int mUid;
+
+ @Nullable
+ private final IBinder mWindowToken;
public PackageInfo(String pkg, int uid) {
+ this(pkg, uid, null);
+ }
+
+ public PackageInfo(String pkg, int uid, IBinder windowToken) {
this.mPkg = pkg;
this.mUid = uid;
+ this.mWindowToken = windowToken;
}
@Override
@@ -105,12 +145,30 @@ public class SensitiveContentPackages {
if (this == o) return true;
if (!(o instanceof PackageInfo)) return false;
PackageInfo that = (PackageInfo) o;
- return mUid == that.mUid && Objects.equals(mPkg, that.mPkg);
+ return mUid == that.mUid && Objects.equals(mPkg, that.mPkg)
+ && Objects.equals(mWindowToken, that.mWindowToken);
}
@Override
public int hashCode() {
- return Objects.hash(mPkg, mUid);
+ return Objects.hash(mPkg, mUid, mWindowToken);
+ }
+
+ public IBinder getWindowToken() {
+ return mWindowToken;
+ }
+
+ public int getUid() {
+ return mUid;
+ }
+
+ public String getPkg() {
+ return mPkg;
+ }
+
+ @Override
+ public String toString() {
+ return "package=" + mPkg + " uid=" + mUid + " windowToken=" + mWindowToken;
}
}
}
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 975208fb4b4c..908cbd340236 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -110,9 +110,7 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
private final String mStringName;
SurfaceSession mSurfaceSession;
private final ArrayList<WindowState> mAddedWindows = new ArrayList<>();
- // Set of visible application overlay window surfaces connected to this session.
- private final ArraySet<WindowSurfaceController> mAppOverlaySurfaces = new ArraySet<>();
- // Set of visible alert window surfaces connected to this session.
+ /** Set of visible alert/app-overlay window surfaces connected to this session. */
private final ArraySet<WindowSurfaceController> mAlertWindowSurfaces = new ArraySet<>();
private final DragDropController mDragDropController;
final boolean mCanAddInternalSystemWindow;
@@ -796,46 +794,45 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
}
boolean changed;
-
- if (!mCanAddInternalSystemWindow && !mCanCreateSystemApplicationOverlay) {
- // We want to track non-system apps adding alert windows so we can post an
- // on-going notification for the user to control their visibility.
- if (visible) {
- changed = mAlertWindowSurfaces.add(surfaceController);
- MetricsLoggerWrapper.logAppOverlayEnter(mUid, mPackageName, changed, type, true);
- } else {
- changed = mAlertWindowSurfaces.remove(surfaceController);
- MetricsLoggerWrapper.logAppOverlayExit(mUid, mPackageName, changed, type, true);
+ // Track non-system apps adding overlay/alert windows, so a notification can post for the
+ // user to control their visibility.
+ final boolean noSystemOverlayPermission =
+ !mCanAddInternalSystemWindow && !mCanCreateSystemApplicationOverlay;
+ if (visible) {
+ changed = mAlertWindowSurfaces.add(surfaceController);
+ if (type == TYPE_APPLICATION_OVERLAY) {
+ MetricsLoggerWrapper.logAppOverlayEnter(mUid, mPackageName, changed, type,
+ false /* set false to only log for TYPE_APPLICATION_OVERLAY */);
+ } else if (noSystemOverlayPermission) {
+ MetricsLoggerWrapper.logAppOverlayEnter(mUid, mPackageName, changed, type,
+ true /* only log for non-TYPE_APPLICATION_OVERLAY */);
}
-
- if (changed) {
- if (mAlertWindowSurfaces.isEmpty()) {
- cancelAlertWindowNotification();
- } else if (mAlertWindowNotification == null){
- mAlertWindowNotification = new AlertWindowNotification(mService, mPackageName);
- if (mShowingAlertWindowNotificationAllowed) {
- mAlertWindowNotification.post();
- }
- }
+ } else {
+ changed = mAlertWindowSurfaces.remove(surfaceController);
+ if (type == TYPE_APPLICATION_OVERLAY) {
+ MetricsLoggerWrapper.logAppOverlayExit(mUid, mPackageName, changed, type,
+ false /* set false to only log for TYPE_APPLICATION_OVERLAY */);
+ } else if (noSystemOverlayPermission) {
+ MetricsLoggerWrapper.logAppOverlayExit(mUid, mPackageName, changed, type,
+ true /* only log for non-TYPE_APPLICATION_OVERLAY */);
}
}
- if (type != TYPE_APPLICATION_OVERLAY) {
- return;
- }
-
- if (visible) {
- changed = mAppOverlaySurfaces.add(surfaceController);
- MetricsLoggerWrapper.logAppOverlayEnter(mUid, mPackageName, changed, type, false);
- } else {
- changed = mAppOverlaySurfaces.remove(surfaceController);
- MetricsLoggerWrapper.logAppOverlayExit(mUid, mPackageName, changed, type, false);
+ if (changed && noSystemOverlayPermission) {
+ if (mAlertWindowSurfaces.isEmpty()) {
+ cancelAlertWindowNotification();
+ } else if (mAlertWindowNotification == null) {
+ mAlertWindowNotification = new AlertWindowNotification(mService, mPackageName);
+ if (mShowingAlertWindowNotificationAllowed) {
+ mAlertWindowNotification.post();
+ }
+ }
}
- if (changed) {
- // Notify activity manager of changes to app overlay windows so it can adjust the
- // importance score for the process.
- setHasOverlayUi(!mAppOverlaySurfaces.isEmpty());
+ if (changed && mPid != WindowManagerService.MY_PID) {
+ // Notify activity manager that the process contains overlay/alert windows, so it can
+ // adjust the importance score for the process.
+ setHasOverlayUi(!mAlertWindowSurfaces.isEmpty());
}
}
@@ -870,12 +867,12 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
mSurfaceSession = null;
mAddedWindows.clear();
mAlertWindowSurfaces.clear();
- mAppOverlaySurfaces.clear();
setHasOverlayUi(false);
cancelAlertWindowNotification();
}
- private void setHasOverlayUi(boolean hasOverlayUi) {
+ @VisibleForTesting
+ void setHasOverlayUi(boolean hasOverlayUi) {
mService.mH.obtainMessage(H.SET_HAS_OVERLAY_UI, mPid, hasOverlayUi ? 1 : 0).sendToTarget();
}
@@ -890,7 +887,6 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
void dump(PrintWriter pw, String prefix) {
pw.print(prefix); pw.print("numWindow="); pw.print(mAddedWindows.size());
pw.print(" mCanAddInternalSystemWindow="); pw.print(mCanAddInternalSystemWindow);
- pw.print(" mAppOverlaySurfaces="); pw.print(mAppOverlaySurfaces);
pw.print(" mAlertWindowSurfaces="); pw.print(mAlertWindowSurfaces);
pw.print(" mClientDead="); pw.print(mClientDead);
pw.print(" mSurfaceSession="); pw.println(mSurfaceSession);
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 838ce86515cd..10cbc6633533 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -1590,7 +1590,7 @@ class TaskFragment extends WindowContainer<WindowContainer> {
mAtmService.getLifecycleManager().scheduleTransactionItem(
appThread, activityResultItem);
} else {
- transaction.addCallback(activityResultItem);
+ transaction.addTransactionItem(activityResultItem);
}
}
}
@@ -1602,7 +1602,7 @@ class TaskFragment extends WindowContainer<WindowContainer> {
mAtmService.getLifecycleManager().scheduleTransactionItem(
appThread, newIntentItem);
} else {
- transaction.addCallback(newIntentItem);
+ transaction.addTransactionItem(newIntentItem);
}
}
@@ -1624,7 +1624,7 @@ class TaskFragment extends WindowContainer<WindowContainer> {
mAtmService.getLifecycleManager().scheduleTransactionItem(
appThread, resumeActivityItem);
} else {
- transaction.setLifecycleStateRequest(resumeActivityItem);
+ transaction.addTransactionItem(resumeActivityItem);
mAtmService.getLifecycleManager().scheduleTransaction(transaction);
}
diff --git a/services/core/java/com/android/server/wm/TaskPositioningController.java b/services/core/java/com/android/server/wm/TaskPositioningController.java
index 55a3decdca08..6f548ab01d74 100644
--- a/services/core/java/com/android/server/wm/TaskPositioningController.java
+++ b/services/core/java/com/android/server/wm/TaskPositioningController.java
@@ -198,14 +198,14 @@ class TaskPositioningController {
// resizing/scrolling are not sent to the app. 'win' is the main window
// of the app, it may not have focus since there might be other windows
// on top (eg. a dialog window).
- WindowState transferFocusFromWin = win;
+ WindowState transferTouchFromWin = win;
if (displayContent.mCurrentFocus != null && displayContent.mCurrentFocus != win
&& displayContent.mCurrentFocus.mActivityRecord == win.mActivityRecord) {
- transferFocusFromWin = displayContent.mCurrentFocus;
+ transferTouchFromWin = displayContent.mCurrentFocus;
}
- if (!mService.mInputManager.transferTouchFocus(
- transferFocusFromWin.mInputChannel, mTaskPositioner.mClientChannel,
- false /* isDragDrop */)) {
+ if (!mService.mInputManager.transferTouchGesture(
+ transferTouchFromWin.mInputChannel.getToken(),
+ mTaskPositioner.mClientChannel.getToken())) {
Slog.e(TAG_WM, "startPositioningLocked: Unable to transfer touch focus");
cleanUpTaskPositioner();
return false;
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index 8b622d28b651..4218f8f88a07 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -270,6 +270,11 @@ class TaskSnapshotController extends AbsAppSnapshotController<Task, TaskSnapshot
return source.getTaskDescription();
}
+ @Override
+ protected Rect getLetterboxInsets(ActivityRecord topActivity) {
+ return topActivity.getLetterboxInsets();
+ }
+
void getClosingTasksInner(Task task, ArraySet<Task> outClosingTasks) {
// Since RecentsAnimation will handle task snapshot while switching apps with the
// best capture timing (e.g. IME window capture),
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 5d9c42d4c3a8..7edc3a2b9786 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -691,12 +691,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
recordDisplay(wc.getDisplayContent());
if (info.mShowWallpaper) {
// Collect the wallpaper token (for isWallpaper(wc)) so it is part of the sync set.
- final List<WindowState> wallpapers =
- wc.getDisplayContent().mWallpaperController.getAllTopWallpapers();
- for (int i = wallpapers.size() - 1; i >= 0; i--) {
- WindowState wallpaper = wallpapers.get(i);
- collect(wallpaper.mToken);
- }
+ wc.mDisplayContent.mWallpaperController.collectTopWallpapers(this);
}
}
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 70775530d0e2..503f925bf557 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -267,7 +267,8 @@ class TransitionController {
mSyncEngine.addOnIdleListener(this::tryStartCollectFromQueue);
}
- private void detachPlayer() {
+ @VisibleForTesting
+ void detachPlayer() {
if (mTransitionPlayer == null) return;
// Immediately set to null so that nothing inadvertently starts/queues.
mTransitionPlayer = null;
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 6949a874b533..001f46d4c36c 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -23,7 +23,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
-import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WALLPAPER;
@@ -55,7 +54,6 @@ import android.view.Display;
import android.view.DisplayInfo;
import android.view.SurfaceControl;
import android.view.WindowManager;
-import android.view.animation.Animation;
import android.window.ScreenCapture;
import com.android.internal.R;
@@ -80,6 +78,7 @@ class WallpaperController {
private WallpaperCropUtils mWallpaperCropUtils = null;
private DisplayContent mDisplayContent;
+ // Larger index has higher z-order.
private final ArrayList<WallpaperWindowToken> mWallpaperTokens = new ArrayList<>();
// If non-null, this is the currently visible window that is associated
@@ -126,18 +125,6 @@ class WallpaperController {
*/
private volatile boolean mIsWallpaperNotifiedOnDisplaySwitch;
- private final Consumer<WindowState> mFindWallpapers = w -> {
- if (w.mAttrs.type == TYPE_WALLPAPER) {
- WallpaperWindowToken token = w.mToken.asWallpaperToken();
- if (token.canShowWhenLocked() && !mFindResults.hasTopShowWhenLockedWallpaper()) {
- mFindResults.setTopShowWhenLockedWallpaper(w);
- } else if (!token.canShowWhenLocked()
- && !mFindResults.hasTopHideWhenLockedWallpaper()) {
- mFindResults.setTopHideWhenLockedWallpaper(w);
- }
- }
- };
-
private final ToBooleanFunction<WindowState> mFindWallpaperTargetFunction = w -> {
final boolean useShellTransition = w.mTransitionController.isShellTransitionsEnabled();
if (!useShellTransition) {
@@ -321,16 +308,6 @@ class WallpaperController {
return false;
}
- /**
- * Starts {@param a} on all wallpaper windows.
- */
- void startWallpaperAnimation(Animation a) {
- for (int curTokenNdx = mWallpaperTokens.size() - 1; curTokenNdx >= 0; curTokenNdx--) {
- final WallpaperWindowToken token = mWallpaperTokens.get(curTokenNdx);
- token.startAnimation(a);
- }
- }
-
boolean isWallpaperTargetAnimating() {
return mWallpaperTarget != null && mWallpaperTarget.isAnimating(TRANSITION | PARENTS)
&& (mWallpaperTarget.mActivityRecord == null
@@ -621,7 +598,8 @@ class WallpaperController {
if (Float.compare(window.mWallpaperZoomOut, zoom) != 0) {
window.mWallpaperZoomOut = zoom;
computeLastWallpaperZoomOut();
- for (WallpaperWindowToken token : mWallpaperTokens) {
+ for (int i = mWallpaperTokens.size() - 1; i >= 0; i--) {
+ final WallpaperWindowToken token = mWallpaperTokens.get(i);
token.updateWallpaperOffset(false);
}
}
@@ -744,7 +722,7 @@ class WallpaperController {
mFindResults.setUseTopWallpaperAsTarget(true);
}
- mDisplayContent.forAllWindows(mFindWallpapers, true /* traverseTopToBottom */);
+ findWallpapers();
mDisplayContent.forAllWindows(mFindWallpaperTargetFunction, true /* traverseTopToBottom */);
if (mFindResults.mNeedsShowWhenLockedWallpaper) {
// Keep wallpaper visible if the show-when-locked activities doesn't fill screen.
@@ -757,15 +735,29 @@ class WallpaperController {
}
}
- List<WindowState> getAllTopWallpapers() {
- ArrayList<WindowState> wallpapers = new ArrayList<>(2);
+ private void findWallpapers() {
+ for (int i = mWallpaperTokens.size() - 1; i >= 0; i--) {
+ final WallpaperWindowToken token = mWallpaperTokens.get(i);
+ final boolean canShowWhenLocked = token.canShowWhenLocked();
+ for (int j = token.getChildCount() - 1; j >= 0; j--) {
+ final WindowState w = token.getChildAt(j);
+ if (!w.mIsWallpaper) continue;
+ if (canShowWhenLocked && !mFindResults.hasTopShowWhenLockedWallpaper()) {
+ mFindResults.setTopShowWhenLockedWallpaper(w);
+ } else if (!canShowWhenLocked && !mFindResults.hasTopHideWhenLockedWallpaper()) {
+ mFindResults.setTopHideWhenLockedWallpaper(w);
+ }
+ }
+ }
+ }
+
+ void collectTopWallpapers(Transition transition) {
if (mFindResults.hasTopShowWhenLockedWallpaper()) {
- wallpapers.add(mFindResults.mTopWallpaper.mTopShowWhenLockedWallpaper);
+ transition.collect(mFindResults.mTopWallpaper.mTopShowWhenLockedWallpaper);
}
if (mFindResults.hasTopHideWhenLockedWallpaper()) {
- wallpapers.add(mFindResults.mTopWallpaper.mTopHideWhenLockedWallpaper);
+ transition.collect(mFindResults.mTopWallpaper.mTopHideWhenLockedWallpaper);
}
- return wallpapers;
}
private boolean isFullscreen(WindowManager.LayoutParams attrs) {
@@ -1016,6 +1008,12 @@ class WallpaperController {
mWallpaperTokens.remove(token);
}
+ void onWallpaperTokenReordered() {
+ if (mWallpaperTokens.size() > 1) {
+ mWallpaperTokens.sort(null /* by WindowContainer#compareTo */);
+ }
+ }
+
@VisibleForTesting
boolean canScreenshotWallpaper() {
return canScreenshotWallpaper(getTopVisibleWallpaper());
@@ -1160,7 +1158,8 @@ class WallpaperController {
pw.print(prefix); pw.print("mPrevWallpaperTarget="); pw.println(mPrevWallpaperTarget);
}
- for (WallpaperWindowToken t : mWallpaperTokens) {
+ for (int i = mWallpaperTokens.size() - 1; i >= 0; i--) {
+ final WallpaperWindowToken t = mWallpaperTokens.get(i);
pw.print(prefix); pw.println("token " + t + ":");
pw.print(prefix); pw.print(" canShowWhenLocked="); pw.println(t.canShowWhenLocked());
dumpValue(pw, prefix, "mWallpaperX", t.mWallpaperX);
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index 1bcd882b5d64..dc500a2748cf 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -30,7 +30,6 @@ import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.SparseArray;
-import android.view.animation.Animation;
import com.android.internal.protolog.common.ProtoLog;
@@ -90,16 +89,14 @@ class WallpaperWindowToken extends WindowToken {
return;
}
mShowWhenLocked = showWhenLocked;
- // Move the window token to the front (private) or back (showWhenLocked). This is
- // possible
- // because the DisplayArea underneath TaskDisplayArea only contains TYPE_WALLPAPER
- // windows.
+ // Move the window token to the front (private) or back (showWhenLocked). This is possible
+ // because the DisplayArea underneath TaskDisplayArea only contains TYPE_WALLPAPER windows.
final int position = showWhenLocked ? POSITION_BOTTOM : POSITION_TOP;
- // Note: Moving all the way to the front or back breaks ordering based on addition
- // times.
- // We should never have more than one non-animating token of each type.
+ // Note: Moving all the way to the front or back breaks ordering based on addition times.
+ // There should never have more than one non-animating token of each type.
getParent().positionChildAt(position, this /* child */, false /*includingParents */);
+ mDisplayContent.mWallpaperController.onWallpaperTokenReordered();
}
boolean canShowWhenLocked() {
@@ -139,16 +136,6 @@ class WallpaperWindowToken extends WindowToken {
}
}
- /**
- * Starts {@param anim} on all children.
- */
- void startAnimation(Animation anim) {
- for (int ndx = mChildren.size() - 1; ndx >= 0; ndx--) {
- final WindowState windowState = mChildren.get(ndx);
- windowState.startAnimation(anim);
- }
- }
-
void updateWallpaperWindows(boolean visible) {
if (mVisibleRequested != visible) {
ProtoLog.d(WM_DEBUG_WALLPAPER, "Wallpaper token %s visible=%b",
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 669c61c4b40c..4698b6b2925c 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -25,6 +25,7 @@ import android.annotation.UserIdInt;
import android.content.ClipData;
import android.content.Context;
import android.graphics.Matrix;
+import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
import android.hardware.display.DisplayManagerInternal;
@@ -158,7 +159,9 @@ public abstract class WindowManagerInternal {
public interface WindowsForAccessibilityCallback {
/**
- * Called when the windows for accessibility changed.
+ * Called when the windows for accessibility changed. This is called if
+ * {@link com.android.server.accessibility.Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y} is
+ * false.
*
* @param forceSend Send the windows for accessibility even if they haven't changed.
* @param topFocusedDisplayId The display Id which has the top focused window.
@@ -167,6 +170,23 @@ public abstract class WindowManagerInternal {
*/
void onWindowsForAccessibilityChanged(boolean forceSend, int topFocusedDisplayId,
IBinder topFocusedWindowToken, @NonNull List<WindowInfo> windows);
+
+ /**
+ * Called when the windows for accessibility changed. This is called if
+ * {@link com.android.server.accessibility.Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y} is
+ * true.
+ * TODO(b/322444245): Remove screenSize parameter by getting it from
+ * DisplayManager#getDisplay(int).getRealSize() on the a11y side.
+ *
+ * @param forceSend Send the windows for accessibility even if they haven't changed.
+ * @param topFocusedDisplayId The display Id which has the top focused window.
+ * @param topFocusedWindowToken The window token of top focused window.
+ * @param screenSize The size of the display that the change happened.
+ * @param windows The windows for accessibility.
+ */
+ void onAccessibilityWindowsChanged(boolean forceSend, int topFocusedDisplayId,
+ @NonNull IBinder topFocusedWindowToken, @NonNull Point screenSize,
+ @NonNull List<AccessibilityWindowsPopulator.AccessibilityWindow> windows);
}
/**
@@ -315,8 +335,7 @@ public abstract class WindowManagerInternal {
InputChannel source) {
return state.register(display)
.thenApply(unused ->
- service.transferTouchFocus(source, state.getInputChannel(),
- true /* isDragDrop */));
+ service.startDragAndDrop(source, state.getInputChannel()));
}
/**
@@ -409,13 +428,12 @@ public abstract class WindowManagerInternal {
public abstract void setMagnificationSpec(int displayId, MagnificationSpec spec);
/**
- * Set by the accessibility framework to indicate whether the magnifiable regions of the display
- * should be shown.
+ * Set by the accessibility framework to indicate whether fullscreen magnification is activated.
*
* @param displayId The logical display id.
- * @param show {@code true} to show magnifiable region bounds, {@code false} to hide
+ * @param activated The activation of fullscreen magnification
*/
- public abstract void setForceShowMagnifiableBounds(int displayId, boolean show);
+ public abstract void setFullscreenMagnificationActivated(int displayId, boolean activated);
/**
* Obtains the magnification regions.
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index e538f5d5cf41..9b7bc4383e0c 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -306,11 +306,11 @@ import android.view.displayhash.VerifiedDisplayHash;
import android.view.inputmethod.ImeTracker;
import android.window.AddToSurfaceSyncGroupResult;
import android.window.ClientWindowFrames;
+import android.window.IGlobalDragListener;
import android.window.IScreenRecordingCallback;
import android.window.ISurfaceSyncGroupCompletedListener;
import android.window.ITaskFpsCallback;
import android.window.ITrustedPresentationListener;
-import android.window.IUnhandledDragListener;
import android.window.InputTransferToken;
import android.window.ScreenCapture;
import android.window.SystemPerformanceHinter;
@@ -6750,11 +6750,6 @@ public class WindowManagerService extends IWindowManager.Stub
private void dumpWindowsLocked(PrintWriter pw, boolean dumpAll,
ArrayList<WindowState> windows) {
pw.println("WINDOW MANAGER WINDOWS (dumpsys window windows)");
- dumpWindowsNoHeaderLocked(pw, dumpAll, windows);
- }
-
- private void dumpWindowsNoHeaderLocked(PrintWriter pw, boolean dumpAll,
- ArrayList<WindowState> windows) {
mRoot.dumpWindowsNoHeader(pw, dumpAll, windows);
if (!mHidingNonSystemOverlayWindows.isEmpty()) {
@@ -6989,9 +6984,15 @@ public class WindowManagerService extends IWindowManager.Stub
if (reason != null) {
pw.println(" Reason: " + reason);
}
+ pw.println();
+ final ArrayList<WindowState> relatedWindows = new ArrayList<>();
for (int i = mRoot.getChildCount() - 1; i >= 0; i--) {
final DisplayContent dc = mRoot.getChildAt(i);
final int displayId = dc.getDisplayId();
+ final WindowState currentFocus = dc.mCurrentFocus;
+ final ActivityRecord focusedApp = dc.mFocusedApp;
+ pw.println(" Display #" + displayId + " currentFocus=" + currentFocus
+ + " focusedApp=" + focusedApp);
if (!dc.mWinAddedSinceNullFocus.isEmpty()) {
pw.println(" Windows added in display #" + displayId + " since null focus: "
+ dc.mWinAddedSinceNullFocus);
@@ -7000,12 +7001,25 @@ public class WindowManagerService extends IWindowManager.Stub
pw.println(" Windows removed in display #" + displayId + " since null focus: "
+ dc.mWinRemovedSinceNullFocus);
}
+ pw.println(" Tasks in top down Z order:");
+ dc.forAllTaskDisplayAreas(tda -> {
+ tda.dump(pw, " ", false /* dumpAll */);
+ });
+ dc.getInputMonitor().dump(pw, " ");
+ pw.println();
+ dc.forAllWindows(w -> {
+ if ((currentFocus != null && Objects.equals(w.mAttrs.packageName,
+ currentFocus.mAttrs.packageName)) || (focusedApp != null
+ && Objects.equals(w.mAttrs.packageName, focusedApp.packageName))) {
+ relatedWindows.add(w);
+ }
+ }, true /* traverseTopToBottom */);
}
+ if (windowState != null && !relatedWindows.contains(windowState)) {
+ relatedWindows.add(windowState);
+ }
+ mRoot.dumpWindowsNoHeader(pw, true /* dumpAll */, relatedWindows);
pw.println();
- dumpWindowsNoHeaderLocked(pw, true, null);
- pw.println();
- pw.println("Last ANR continued");
- mRoot.dumpDisplayContents(pw);
pw.close();
mLastANRState = sw.toString();
@@ -7844,10 +7858,11 @@ public class WindowManagerService extends IWindowManager.Stub
}
@Override
- public void setForceShowMagnifiableBounds(int displayId, boolean show) {
+ public void setFullscreenMagnificationActivated(int displayId, boolean activated) {
synchronized (mGlobalLock) {
if (mAccessibilityController.hasCallbacks()) {
- mAccessibilityController.setForceShowMagnifiableBounds(displayId, show);
+ mAccessibilityController
+ .setFullscreenMagnificationActivated(displayId, activated);
} else {
throw new IllegalStateException("Magnification callbacks not set!");
}
@@ -10005,14 +10020,13 @@ public class WindowManagerService extends IWindowManager.Stub
}
/**
- * Sets the listener to be called back when a cross-window drag and drop operation is unhandled
- * (ie. not handled by any window which can handle the drag).
+ * Sets the listener to be called back when a cross-window drag and drop operation happens.
*/
@Override
- public void setUnhandledDragListener(IUnhandledDragListener listener) throws RemoteException {
+ public void setGlobalDragListener(IGlobalDragListener listener) throws RemoteException {
mAtmService.enforceTaskPermission("setUnhandledDragListener");
synchronized (mGlobalLock) {
- mDragDropController.setUnhandledDragListener(listener);
+ mDragDropController.setGlobalDragListener(listener);
}
}
}
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index a8de9198943c..d6fc01aeadd2 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -2254,6 +2254,11 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
ownerTask.addChild(taskFragment, position);
taskFragment.setWindowingMode(creationParams.getWindowingMode());
if (!creationParams.getInitialRelativeBounds().isEmpty()) {
+ // The surface operations for the task fragment should sync with the transition.
+ // This avoid using pending transaction before collectExistenceChange is called.
+ if (transition != null) {
+ addToSyncSet(transition.getSyncId(), taskFragment);
+ }
// Set relative bounds instead of using setBounds. This will avoid unnecessary update in
// case the parent has resized since the last time parent info is sent to the organizer.
taskFragment.setRelativeEmbeddedBounds(creationParams.getInitialRelativeBounds());
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 6acf1f3f84af..ee16a37d6baf 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -28,7 +28,6 @@ import static android.view.WindowManager.TRANSIT_FLAG_APP_CRASHED;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
import static com.android.internal.util.Preconditions.checkArgument;
import static com.android.server.am.ProcessList.INVALID_ADJ;
-import static com.android.server.grammaticalinflection.GrammaticalInflectionUtils.checkSystemGrammaticalGenderPermission;
import static com.android.server.wm.ActivityRecord.State.DESTROYED;
import static com.android.server.wm.ActivityRecord.State.DESTROYING;
import static com.android.server.wm.ActivityRecord.State.PAUSED;
@@ -299,7 +298,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
*/
private volatile int mActivityStateFlags = ACTIVITY_STATE_FLAG_MASK_MIN_TASK_LAYER;
- private boolean mCanUseSystemGrammaticalGender;
+ private final boolean mCanUseSystemGrammaticalGender;
public WindowProcessController(@NonNull ActivityTaskManagerService atm,
@NonNull ApplicationInfo info, String name, int uid, int userId, Object owner,
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 6e993b340352..90f5b62b4a08 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1332,7 +1332,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
updateSourceFrame(windowFrames.mFrame);
if (mActivityRecord != null && !mIsChildWindow) {
- mActivityRecord.layoutLetterbox(this);
+ mActivityRecord.layoutLetterboxIfNeeded(this);
}
mSurfacePlacementNeeded = true;
mHaveFrame = true;
@@ -1897,11 +1897,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
return true;
}
- if (android.permission.flags.Flags.sensitiveNotificationAppProtection()) {
- if (mWmService.mSensitiveContentPackages
- .shouldBlockScreenCaptureForApp(getOwningPackage(), getOwningUid())) {
- return true;
- }
+ // block screen capture to protect sensitive notifications or content on the screen.
+ if (mWmService.mSensitiveContentPackages.shouldBlockScreenCaptureForApp(
+ getOwningPackage(), getOwningUid(), getWindowToken())) {
+ return true;
}
return !DevicePolicyCache.getInstance().isScreenCaptureAllowed(mShowUserId);
@@ -2603,7 +2602,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
/**
* Move the touch gesture from the currently touched window on this display to this window.
+ *
+ * @deprecated Use {@link
+ * com.android.server.input.InputManagerInternal#transferTouchGesture(IBinder, IBinder)}.
*/
+ @Deprecated
public boolean transferTouch() {
return mWmService.mInputManager.transferTouch(mInputChannelToken, getDisplayId());
}
@@ -2835,10 +2838,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
// For child windows we want to use the pid for the parent window in case the the child
// window was added from another process.
final WindowState parentWindow = getParentWindow();
- final int pid = parentWindow != null ? parentWindow.mSession.mPid : mSession.mPid;
- final Configuration processConfig =
- mWmService.mAtmService.getGlobalConfigurationForPid(pid);
- return processConfig;
+ final Session session = parentWindow != null ? parentWindow.mSession : mSession;
+ return session.mPid == MY_PID ? mWmService.mRoot.getConfiguration()
+ : session.mProcess.getConfiguration();
}
private Configuration getLastReportedConfiguration() {
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index cbbcd965f4af..c778398342dc 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -1509,15 +1509,14 @@ bool NativeInputManager::filterInputEvent(const InputEvent& inputEvent, uint32_t
ScopedLocalRef<jobject> inputEventObj(env);
switch (inputEvent.getType()) {
case InputEventType::KEY:
- inputEventObj.reset(
- android_view_KeyEvent_fromNative(env,
- static_cast<const KeyEvent&>(inputEvent)));
+ inputEventObj =
+ android_view_KeyEvent_obtainAsCopy(env,
+ static_cast<const KeyEvent&>(inputEvent));
break;
case InputEventType::MOTION:
- inputEventObj.reset(
- android_view_MotionEvent_obtainAsCopy(env,
- static_cast<const MotionEvent&>(
- inputEvent)));
+ inputEventObj = android_view_MotionEvent_obtainAsCopy(env,
+ static_cast<const MotionEvent&>(
+ inputEvent));
break;
default:
return true; // dispatch the event normally
@@ -1559,7 +1558,7 @@ void NativeInputManager::interceptKeyBeforeQueueing(const KeyEvent& keyEvent,
const nsecs_t when = keyEvent.getEventTime();
JNIEnv* env = jniEnv();
- ScopedLocalRef<jobject> keyEventObj(env, android_view_KeyEvent_fromNative(env, keyEvent));
+ ScopedLocalRef<jobject> keyEventObj = android_view_KeyEvent_obtainAsCopy(env, keyEvent);
if (!keyEventObj.get()) {
ALOGE("Failed to obtain key event object for interceptKeyBeforeQueueing.");
return;
@@ -1639,7 +1638,7 @@ nsecs_t NativeInputManager::interceptKeyBeforeDispatching(const sp<IBinder>& tok
// Token may be null
ScopedLocalRef<jobject> tokenObj(env, javaObjectForIBinder(env, token));
- ScopedLocalRef<jobject> keyEventObj(env, android_view_KeyEvent_fromNative(env, keyEvent));
+ ScopedLocalRef<jobject> keyEventObj = android_view_KeyEvent_obtainAsCopy(env, keyEvent);
if (!keyEventObj.get()) {
ALOGE("Failed to obtain key event object for interceptKeyBeforeDispatching.");
return 0;
@@ -1670,7 +1669,7 @@ std::optional<KeyEvent> NativeInputManager::dispatchUnhandledKey(const sp<IBinde
// Note: tokenObj may be null.
ScopedLocalRef<jobject> tokenObj(env, javaObjectForIBinder(env, token));
- ScopedLocalRef<jobject> keyEventObj(env, android_view_KeyEvent_fromNative(env, keyEvent));
+ ScopedLocalRef<jobject> keyEventObj = android_view_KeyEvent_obtainAsCopy(env, keyEvent);
if (!keyEventObj.get()) {
ALOGE("Failed to obtain key event object for dispatchUnhandledKey.");
return {};
@@ -1689,7 +1688,8 @@ std::optional<KeyEvent> NativeInputManager::dispatchUnhandledKey(const sp<IBinde
return {};
}
- const KeyEvent fallbackEvent = android_view_KeyEvent_toNative(env, fallbackKeyEventObj.get());
+ const KeyEvent fallbackEvent =
+ android_view_KeyEvent_obtainAsCopy(env, fallbackKeyEventObj.get());
android_view_KeyEvent_recycle(env, fallbackKeyEventObj.get());
return fallbackEvent;
}
@@ -2070,7 +2070,7 @@ static jint nativeInjectInputEvent(JNIEnv* env, jobject nativeImplObj, jobject i
InputEventInjectionSync mode = static_cast<InputEventInjectionSync>(syncMode);
if (env->IsInstanceOf(inputEventObj, gKeyEventClassInfo.clazz)) {
- const KeyEvent keyEvent = android_view_KeyEvent_toNative(env, inputEventObj);
+ const KeyEvent keyEvent = android_view_KeyEvent_obtainAsCopy(env, inputEventObj);
const InputEventInjectionResult result =
im->getInputManager()->getDispatcher().injectInputEvent(&keyEvent, targetUid, mode,
std::chrono::milliseconds(
@@ -2101,7 +2101,7 @@ static jobject nativeVerifyInputEvent(JNIEnv* env, jobject nativeImplObj, jobjec
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
if (env->IsInstanceOf(inputEventObj, gKeyEventClassInfo.clazz)) {
- const KeyEvent keyEvent = android_view_KeyEvent_toNative(env, inputEventObj);
+ const KeyEvent keyEvent = android_view_KeyEvent_obtainAsCopy(env, inputEventObj);
std::unique_ptr<VerifiedInputEvent> verifiedEvent =
im->getInputManager()->getDispatcher().verifyInputEvent(keyEvent);
if (verifiedEvent == nullptr) {
@@ -2186,9 +2186,9 @@ static void nativeSetSystemUiLightsOut(JNIEnv* env, jobject nativeImplObj, jbool
im->setSystemUiLightsOut(lightsOut);
}
-static jboolean nativeTransferTouchFocus(JNIEnv* env, jobject nativeImplObj,
- jobject fromChannelTokenObj, jobject toChannelTokenObj,
- jboolean isDragDrop) {
+static jboolean nativeTransferTouchGesture(JNIEnv* env, jobject nativeImplObj,
+ jobject fromChannelTokenObj, jobject toChannelTokenObj,
+ jboolean isDragDrop) {
if (fromChannelTokenObj == nullptr || toChannelTokenObj == nullptr) {
return JNI_FALSE;
}
@@ -2197,21 +2197,22 @@ static jboolean nativeTransferTouchFocus(JNIEnv* env, jobject nativeImplObj,
sp<IBinder> toChannelToken = ibinderForJavaObject(env, toChannelTokenObj);
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
- if (im->getInputManager()->getDispatcher().transferTouchFocus(fromChannelToken, toChannelToken,
- isDragDrop)) {
+ if (im->getInputManager()->getDispatcher().transferTouchGesture(fromChannelToken,
+ toChannelToken, isDragDrop)) {
return JNI_TRUE;
} else {
return JNI_FALSE;
}
}
-static jboolean nativeTransferTouch(JNIEnv* env, jobject nativeImplObj, jobject destChannelTokenObj,
- jint displayId) {
+static jboolean nativeTransferTouchOnDisplay(JNIEnv* env, jobject nativeImplObj,
+ jobject destChannelTokenObj, jint displayId) {
sp<IBinder> destChannelToken = ibinderForJavaObject(env, destChannelTokenObj);
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
- if (im->getInputManager()->getDispatcher().transferTouch(destChannelToken,
- static_cast<int32_t>(displayId))) {
+ if (im->getInputManager()->getDispatcher().transferTouchOnDisplay(destChannelToken,
+ static_cast<int32_t>(
+ displayId))) {
return JNI_TRUE;
} else {
return JNI_FALSE;
@@ -2875,9 +2876,9 @@ static const JNINativeMethod gInputManagerMethods[] = {
{"requestPointerCapture", "(Landroid/os/IBinder;Z)V", (void*)nativeRequestPointerCapture},
{"setInputDispatchMode", "(ZZ)V", (void*)nativeSetInputDispatchMode},
{"setSystemUiLightsOut", "(Z)V", (void*)nativeSetSystemUiLightsOut},
- {"transferTouchFocus", "(Landroid/os/IBinder;Landroid/os/IBinder;Z)Z",
- (void*)nativeTransferTouchFocus},
- {"transferTouch", "(Landroid/os/IBinder;I)Z", (void*)nativeTransferTouch},
+ {"transferTouchGesture", "(Landroid/os/IBinder;Landroid/os/IBinder;Z)Z",
+ (void*)nativeTransferTouchGesture},
+ {"transferTouch", "(Landroid/os/IBinder;I)Z", (void*)nativeTransferTouchOnDisplay},
{"getMousePointerSpeed", "()I", (void*)nativeGetMousePointerSpeed},
{"setPointerSpeed", "(I)V", (void*)nativeSetPointerSpeed},
{"setMousePointerAccelerationEnabled", "(IZ)V",
diff --git a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
index be4b9e1fc7b1..173cb36a1a34 100644
--- a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
@@ -104,7 +104,6 @@ public final class CreateRequestSession extends RequestSession<CreateCredentialR
flattenedPrimaryProviders.add(cn.flattenToString());
}
- final boolean isShowAllOptionsRequested = false;
mPendingIntent = mCredentialManagerUi.createPendingIntent(
RequestInfo.newCreateRequestInfo(
mRequestId, mClientRequest,
@@ -112,8 +111,8 @@ public final class CreateRequestSession extends RequestSession<CreateCredentialR
PermissionUtils.hasPermission(mContext, mClientAppInfo.getPackageName(),
Manifest.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS),
/*defaultProviderId=*/flattenedPrimaryProviders,
- isShowAllOptionsRequested),
- providerDataList, /*isRequestForAllOptions=*/ isShowAllOptionsRequested);
+ /*isShowAllOptionsRequested=*/ false),
+ providerDataList);
mClientCallback.onPendingIntent(mPendingIntent);
} catch (RemoteException e) {
mRequestSessionMetric.collectUiReturnedFinalPhase(/*uiReturned=*/ false);
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
index 534c842f6b07..9d45cfb2fb7e 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
@@ -18,6 +18,7 @@ package com.android.server.credentials;
import static android.credentials.selection.Constants.EXTRA_FINAL_RESPONSE_RECEIVER;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
@@ -152,16 +153,34 @@ public class CredentialManagerUi {
/**
* Creates a {@link PendingIntent} to be used to invoke the credential manager selector UI,
- * by the calling app process.
+ * by the calling app process. The bottom-sheet navigates to the default page when the intent
+ * is invoked.
*
* @param requestInfo the information about the request
* @param providerDataList the list of provider data from remote providers
- * @param isRequestForAllOptions whether the bottom sheet should directly navigate to the
- * all options page
*/
public PendingIntent createPendingIntent(
- RequestInfo requestInfo, ArrayList<ProviderData> providerDataList,
- boolean isRequestForAllOptions) {
+ RequestInfo requestInfo, ArrayList<ProviderData> providerDataList) {
+ return createPendingIntent(requestInfo, providerDataList, /*forAutofill=*/ false);
+ }
+
+ /**
+ * Creates a {@link PendingIntent} to be used to invoke the credential manager selector UI,
+ * by the calling app process. This intent is invoked from the Autofill flow, when the user
+ * requests to bring up the 'All Options' page of the credential bottom-sheet. When the user
+ * clicks on the pinned entry, the intent will bring up the 'All Options' page of the
+ * bottom-sheet. The provider data list is processed by the credential autofill service for
+ * each autofill id and passed in as an auth extra.
+ *
+ * @param requestInfo the information about the request
+ */
+ public PendingIntent createPendingIntentForAutofill(RequestInfo requestInfo) {
+ return createPendingIntent(requestInfo, /*providerDataList=*/ null, /*forAutofill=*/ true);
+ }
+
+ private PendingIntent createPendingIntent(
+ RequestInfo requestInfo, @Nullable ArrayList<ProviderData> providerDataList,
+ boolean forAutofill) {
List<CredentialProviderInfo> allProviders =
CredentialProviderInfoFactory.getCredentialProviderServices(
mContext,
@@ -176,11 +195,17 @@ public class CredentialManagerUi {
.map(disabledProvider -> new DisabledProviderData(
disabledProvider.getComponentName().flattenToString())).toList();
- Intent intent = IntentFactory.createCredentialSelectorIntent(mContext, requestInfo,
- providerDataList,
- new ArrayList<>(disabledProviderDataList), mResultReceiver,
- isRequestForAllOptions)
- .setAction(UUID.randomUUID().toString());
+ Intent intent;
+ if (forAutofill) {
+ intent = IntentFactory.createCredentialSelectorIntentForAutofill(
+ mContext, requestInfo, new ArrayList<>(disabledProviderDataList),
+ mResultReceiver);
+ } else {
+ intent = IntentFactory.createCredentialSelectorIntent(
+ mContext, requestInfo, providerDataList,
+ new ArrayList<>(disabledProviderDataList), mResultReceiver);
+ }
+ intent.setAction(UUID.randomUUID().toString());
//TODO: Create unique pending intent using request code and cancel any pre-existing pending
// intents
return PendingIntent.getActivityAsUser(
diff --git a/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java b/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
index d06d4d869192..723c52f37574 100644
--- a/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
@@ -115,15 +115,12 @@ public class GetCandidateRequestSession extends RequestSession<GetCredentialRequ
}
cancelExistingPendingIntent();
- final boolean isShowAllOptionsRequested = true;
- mPendingIntent = mCredentialManagerUi.createPendingIntent(
+ mPendingIntent = mCredentialManagerUi.createPendingIntentForAutofill(
RequestInfo.newGetRequestInfo(
mRequestId, mClientRequest, mClientAppInfo.getPackageName(),
PermissionUtils.hasPermission(mContext, mClientAppInfo.getPackageName(),
Manifest.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS),
- isShowAllOptionsRequested),
- /*providerDataList=*/ null,
- /*isRequestForAllOptions=*/ isShowAllOptionsRequested);
+ /*isShowAllOptionsRequested=*/ true));
List<GetCredentialProviderData> candidateProviderDataList = new ArrayList<>();
for (ProviderData providerData : providerDataList) {
diff --git a/services/credentials/java/com/android/server/credentials/GetRequestSession.java b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
index a279337698f2..6513ae1af369 100644
--- a/services/credentials/java/com/android/server/credentials/GetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
@@ -103,7 +103,6 @@ public class GetRequestSession extends RequestSession<GetCredentialRequest,
Binder.withCleanCallingIdentity(() -> {
try {
cancelExistingPendingIntent();
- final boolean isShowAllOptionsRequested = false;
mPendingIntent = mCredentialManagerUi.createPendingIntent(
RequestInfo.newGetRequestInfo(
mRequestId, mClientRequest, mClientAppInfo.getPackageName(),
@@ -111,9 +110,8 @@ public class GetRequestSession extends RequestSession<GetCredentialRequest,
mClientAppInfo.getPackageName(),
Manifest.permission
.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS),
- isShowAllOptionsRequested),
- providerDataList,
- /*isRequestForAllOptions=*/ isShowAllOptionsRequested);
+ /*isShowAllOptionsRequested=*/ false),
+ providerDataList);
mClientCallback.onPendingIntent(mPendingIntent);
} catch (RemoteException e) {
mRequestSessionMetric.collectUiReturnedFinalPhase(/*uiReturned=*/ false);
diff --git a/services/credentials/java/com/android/server/credentials/MetricUtilities.java b/services/credentials/java/com/android/server/credentials/MetricUtilities.java
index 23aa3742175a..96ef2ed61f1a 100644
--- a/services/credentials/java/com/android/server/credentials/MetricUtilities.java
+++ b/services/credentials/java/com/android/server/credentials/MetricUtilities.java
@@ -531,7 +531,7 @@ public class MetricUtilities {
int index = 0;
for (CandidateBrowsingPhaseMetric metric : browsingPhaseMetrics) {
browsedClickedEntries[index] = metric.getEntryEnum();
- browsedProviderUid[index] = metric.getProviderUid();
+ browsedProviderUid[index] = DEFAULT_INT_32;
index++;
}
FrameworkStatsLog.write(FrameworkStatsLog.CREDENTIAL_MANAGER_FINALNOUID_REPORTED,
diff --git a/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java b/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
index 6b313fda1dd2..6e8f7c8d7722 100644
--- a/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
@@ -187,14 +187,13 @@ public class PrepareGetRequestSession extends GetRequestSession {
}
}
if (!providerDataList.isEmpty()) {
- final boolean isShowAllOptionsRequested = false;
return mCredentialManagerUi.createPendingIntent(
RequestInfo.newGetRequestInfo(
mRequestId, mClientRequest, mClientAppInfo.getPackageName(),
PermissionUtils.hasPermission(mContext, mClientAppInfo.getPackageName(),
Manifest.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS),
- isShowAllOptionsRequested),
- providerDataList, /*isRequestForAllOptions=*/ isShowAllOptionsRequested);
+ /*isShowAllOptionsRequested=*/ false),
+ providerDataList);
} else {
return null;
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
index 17638fcaba68..dc8cec91001b 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
@@ -21,8 +21,6 @@ import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
import static android.app.admin.WifiSsidPolicy.WIFI_SSID_POLICY_TYPE_ALLOWLIST;
import static android.app.admin.WifiSsidPolicy.WIFI_SSID_POLICY_TYPE_DENYLIST;
-import static android.app.admin.flags.Flags.dumpsysPolicyEngineMigrationEnabled;
-import static android.app.admin.flags.Flags.policyEngineMigrationV2Enabled;
import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1;
import static com.android.server.devicepolicy.DevicePolicyManagerService.LOG_TAG;
@@ -41,6 +39,7 @@ import android.app.admin.PackagePolicy;
import android.app.admin.PasswordPolicy;
import android.app.admin.PreferentialNetworkServiceConfig;
import android.app.admin.WifiSsidPolicy;
+import android.app.admin.flags.Flags;
import android.graphics.Color;
import android.net.wifi.WifiSsid;
import android.os.Bundle;
@@ -1297,7 +1296,7 @@ class ActiveAdmin {
pw.print("encryptionRequested=");
pw.println(encryptionRequested);
- if (!dumpsysPolicyEngineMigrationEnabled()) {
+ if (!Flags.dumpsysPolicyEngineMigrationEnabled()) {
pw.print("disableCamera=");
pw.println(disableCamera);
@@ -1316,7 +1315,8 @@ class ActiveAdmin {
UserRestrictionsUtils.dumpRestrictions(pw, " ", userRestrictions);
}
- if (!policyEngineMigrationV2Enabled() || !dumpsysPolicyEngineMigrationEnabled()) {
+ if (!Flags.policyEngineMigrationV2Enabled()
+ || !Flags.dumpsysPolicyEngineMigrationEnabled()) {
pw.print("mUsbDataSignaling=");
pw.println(mUsbDataSignalingEnabled);
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index 3e066f23a520..12f44074a4ad 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -24,7 +24,6 @@ import static android.app.admin.PolicyUpdateResult.RESULT_FAILURE_HARDWARE_LIMIT
import static android.app.admin.PolicyUpdateResult.RESULT_FAILURE_STORAGE_LIMIT_REACHED;
import static android.app.admin.PolicyUpdateResult.RESULT_POLICY_CLEARED;
import static android.app.admin.PolicyUpdateResult.RESULT_POLICY_SET;
-import static android.app.admin.flags.Flags.devicePolicySizeTrackingEnabled;
import static android.content.pm.UserProperties.INHERIT_DEVICE_POLICY_FROM_PARENT;
import android.Manifest;
@@ -42,6 +41,7 @@ import android.app.admin.PolicyUpdateReceiver;
import android.app.admin.PolicyValue;
import android.app.admin.TargetUser;
import android.app.admin.UserRestrictionPolicyKey;
+import android.app.admin.flags.Flags;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -225,7 +225,7 @@ final class DevicePolicyEngine {
synchronized (mLock) {
PolicyState<V> localPolicyState = getLocalPolicyStateLocked(policyDefinition, userId);
- if (devicePolicySizeTrackingEnabled() && false) {
+ if (Flags.devicePolicySizeTrackingEnabled() && false) {
if (!handleAdminPolicySizeLimit(localPolicyState, enforcingAdmin, value,
policyDefinition, userId)) {
return;
@@ -350,7 +350,7 @@ final class DevicePolicyEngine {
}
PolicyState<V> localPolicyState = getLocalPolicyStateLocked(policyDefinition, userId);
- if (devicePolicySizeTrackingEnabled() && false) {
+ if (Flags.devicePolicySizeTrackingEnabled() && false) {
decreasePolicySizeForAdmin(localPolicyState, enforcingAdmin);
}
@@ -496,7 +496,7 @@ final class DevicePolicyEngine {
synchronized (mLock) {
PolicyState<V> globalPolicyState = getGlobalPolicyStateLocked(policyDefinition);
- if (devicePolicySizeTrackingEnabled() && false) {
+ if (Flags.devicePolicySizeTrackingEnabled() && false) {
if (!handleAdminPolicySizeLimit(globalPolicyState, enforcingAdmin, value,
policyDefinition, UserHandle.USER_ALL)) {
return;
@@ -568,7 +568,7 @@ final class DevicePolicyEngine {
synchronized (mLock) {
PolicyState<V> policyState = getGlobalPolicyStateLocked(policyDefinition);
- if (devicePolicySizeTrackingEnabled() && false) {
+ if (Flags.devicePolicySizeTrackingEnabled() && false) {
decreasePolicySizeForAdmin(policyState, enforcingAdmin);
}
@@ -1892,7 +1892,7 @@ final class DevicePolicyEngine {
private void writeEnforcingAdminSizeInner(TypedXmlSerializer serializer)
throws IOException {
- if (devicePolicySizeTrackingEnabled() && false) {
+ if (Flags.devicePolicySizeTrackingEnabled() && false) {
if (mAdminPolicySize != null) {
for (int i = 0; i < mAdminPolicySize.size(); i++) {
int userId = mAdminPolicySize.keyAt(i);
@@ -1916,7 +1916,7 @@ final class DevicePolicyEngine {
private void writeMaxPolicySizeInner(TypedXmlSerializer serializer)
throws IOException {
- if (!devicePolicySizeTrackingEnabled() || true) {
+ if (!Flags.devicePolicySizeTrackingEnabled() || true) {
return;
}
serializer.startTag(/* namespace= */ null, TAG_MAX_POLICY_SIZE_LIMIT);
@@ -2081,7 +2081,7 @@ final class DevicePolicyEngine {
private void readMaxPolicySizeInner(TypedXmlPullParser parser)
throws XmlPullParserException, IOException {
- if (!devicePolicySizeTrackingEnabled() || true) {
+ if (!Flags.devicePolicySizeTrackingEnabled() || true) {
return;
}
mPolicySizeLimit = parser.getAttributeInt(/* namespace= */ null, ATTR_POLICY_SUM_SIZE);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 9d84d6fc40b4..9c48f2991267 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -239,14 +239,6 @@ import static android.app.admin.ProvisioningException.ERROR_REMOVE_NON_REQUIRED_
import static android.app.admin.ProvisioningException.ERROR_SETTING_PROFILE_OWNER_FAILED;
import static android.app.admin.ProvisioningException.ERROR_SET_DEVICE_OWNER_FAILED;
import static android.app.admin.ProvisioningException.ERROR_STARTING_PROFILE_FAILED;
-import static android.app.admin.flags.Flags.backupServiceSecurityLogEventEnabled;
-import static android.app.admin.flags.Flags.devicePolicySizeTrackingEnabled;
-import static android.app.admin.flags.Flags.dumpsysPolicyEngineMigrationEnabled;
-import static android.app.admin.flags.Flags.headlessDeviceOwnerSingleUserEnabled;
-import static android.app.admin.flags.Flags.permissionMigrationForZeroTrustImplEnabled;
-import static android.app.admin.flags.Flags.policyEngineMigrationV2Enabled;
-import static android.app.admin.flags.Flags.assistContentUserRestrictionEnabled;
-import static android.app.admin.flags.Flags.securityLogV2Enabled;
import static android.content.Intent.ACTION_MANAGED_PROFILE_AVAILABLE;
import static android.content.Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
@@ -272,6 +264,7 @@ import static android.security.keystore.AttestationUtils.USE_INDIVIDUAL_ATTESTAT
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.PROVISIONING_ENTRY_POINT_ADB;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW;
import static com.android.server.SystemTimeZone.TIME_ZONE_CONFIDENCE_HIGH;
import static com.android.server.am.ActivityManagerService.STOCK_PM_FLAGS;
@@ -360,6 +353,7 @@ import android.app.admin.SystemUpdatePolicy;
import android.app.admin.UnsafeStateException;
import android.app.admin.UserRestrictionPolicyKey;
import android.app.admin.WifiSsidPolicy;
+import android.app.admin.flags.Flags;
import android.app.backup.IBackupManager;
import android.app.compat.CompatChanges;
import android.app.role.OnRoleHoldersChangedListener;
@@ -513,7 +507,6 @@ 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;
@@ -2728,7 +2721,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return;
}
- if (securityLogV2Enabled()) {
+ if (Flags.securityLogV2Enabled()) {
boolean auditLoggingEnabled = Boolean.TRUE.equals(
mDevicePolicyEngine.getResolvedPolicy(
PolicyDefinition.AUDIT_LOGGING, UserHandle.USER_ALL));
@@ -3418,7 +3411,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@GuardedBy("getLockObject()")
private void maybeMigrateSecurityLoggingPolicyLocked() {
- if (!securityLogV2Enabled() || mOwners.isSecurityLoggingMigrated()) {
+ if (!Flags.securityLogV2Enabled() || mOwners.isSecurityLoggingMigrated()) {
return;
}
@@ -3522,7 +3515,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
revertTransferOwnershipIfNecessaryLocked();
- if (!policyEngineMigrationV2Enabled()) {
+ if (!Flags.policyEngineMigrationV2Enabled()) {
updateUsbDataSignal(mContext, isUsbDataSignalingEnabledInternalLocked());
}
}
@@ -11151,7 +11144,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
pw.println();
mStatLogger.dump(pw);
pw.println();
- if (dumpsysPolicyEngineMigrationEnabled()) {
+ if (Flags.dumpsysPolicyEngineMigrationEnabled()) {
mDevicePolicyEngine.dump(pw);
pw.println();
}
@@ -12068,7 +12061,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
if (packageList != null) {
- if (!devicePolicySizeTrackingEnabled()) {
+ if (!Flags.devicePolicySizeTrackingEnabled()) {
for (String pkg : packageList) {
PolicySizeVerifier.enforceMaxPackageNameLength(pkg);
}
@@ -12313,7 +12306,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_CREATE_AND_MANAGE_USER);
- if (headlessDeviceOwnerSingleUserEnabled()) {
+ if (Flags.headlessDeviceOwnerSingleUserEnabled()) {
// Block this method if the device is in headless main user mode
Preconditions.checkCallAuthorization(
getHeadlessDeviceOwnerMode() != HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER,
@@ -13438,12 +13431,12 @@ 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()) {
+ if (com.android.net.thread.platform.flags.Flags.threadUserRestrictionEnabled()) {
USER_RESTRICTION_PERMISSIONS.put(
UserManager.DISALLOW_THREAD_NETWORK,
new String[]{MANAGE_DEVICE_POLICY_THREAD_NETWORK});
}
- if (assistContentUserRestrictionEnabled()) {
+ if (Flags.assistContentUserRestrictionEnabled()) {
USER_RESTRICTION_PERMISSIONS.put(
UserManager.DISALLOW_ASSIST_CONTENT,
new String[]{MANAGE_DEVICE_POLICY_ASSIST_CONTENT});
@@ -13777,7 +13770,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return;
}
- if (!devicePolicySizeTrackingEnabled()) {
+ if (!Flags.devicePolicySizeTrackingEnabled()) {
PolicySizeVerifier.enforceMaxStringLength(accountType, "account type");
}
@@ -14391,7 +14384,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
public void setLockTaskPackages(ComponentName who, String callerPackageName, String[] packages)
throws SecurityException {
Objects.requireNonNull(packages, "packages is null");
- if (!devicePolicySizeTrackingEnabled()) {
+ if (!Flags.devicePolicySizeTrackingEnabled()) {
for (String pkg : packages) {
PolicySizeVerifier.enforceMaxPackageNameLength(pkg);
}
@@ -15798,7 +15791,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public void enforceSecurityLoggingPolicy(boolean enabled) {
- if (!securityLogV2Enabled()) {
+ if (!Flags.securityLogV2Enabled()) {
return;
}
Boolean auditLoggingEnabled = mDevicePolicyEngine.getResolvedPolicy(
@@ -15808,7 +15801,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public void enforceAuditLoggingPolicy(boolean enabled) {
- if (!securityLogV2Enabled()) {
+ if (!Flags.securityLogV2Enabled()) {
return;
}
Boolean securityLoggingEnabled = mDevicePolicyEngine.getResolvedPolicy(
@@ -16345,7 +16338,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
mContext.sendBroadcastAsUser(intent, UserHandle.of(userId));
}
- if (permissionMigrationForZeroTrustImplEnabled()) {
+ if (Flags.permissionMigrationForZeroTrustImplEnabled()) {
final UserHandle user = UserHandle.of(userId);
final String roleHolderPackage = getRoleHolderPackageNameOnUser(
RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT, userId);
@@ -16359,7 +16352,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public SystemUpdateInfo getPendingSystemUpdate(ComponentName admin, String callerPackage) {
- if (permissionMigrationForZeroTrustImplEnabled()) {
+ if (Flags.permissionMigrationForZeroTrustImplEnabled()) {
CallerIdentity caller = getCallerIdentity(admin, callerPackage);
enforcePermissions(new String[] {NOTIFY_PENDING_SYSTEM_UPDATE,
MANAGE_DEVICE_POLICY_QUERY_SYSTEM_UPDATES}, caller.getPackageName(),
@@ -16816,7 +16809,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return STATUS_HEADLESS_SYSTEM_USER_MODE_NOT_SUPPORTED;
}
- if (headlessDeviceOwnerSingleUserEnabled() && isHeadlessModeSingleUser) {
+ if (Flags.headlessDeviceOwnerSingleUserEnabled() && isHeadlessModeSingleUser) {
ensureSetUpUser = mUserManagerInternal.getMainUserId();
if (ensureSetUpUser == UserHandle.USER_NULL) {
return STATUS_HEADLESS_ONLY_SYSTEM_USER;
@@ -17723,7 +17716,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
final CallerIdentity caller = getCallerIdentity(who, packageName);
- if (securityLogV2Enabled()) {
+ if (Flags.securityLogV2Enabled()) {
EnforcingAdmin admin = enforcePermissionAndGetEnforcingAdmin(
who,
MANAGE_DEVICE_POLICY_SECURITY_LOGGING,
@@ -17783,7 +17776,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return mInjector.securityLogGetLoggingEnabledProperty();
}
- if (securityLogV2Enabled()) {
+ if (Flags.securityLogV2Enabled()) {
final EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
admin,
MANAGE_DEVICE_POLICY_SECURITY_LOGGING,
@@ -17881,7 +17874,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
final CallerIdentity caller = getCallerIdentity(admin, packageName);
- if (securityLogV2Enabled()) {
+ if (Flags.securityLogV2Enabled()) {
EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
admin,
MANAGE_DEVICE_POLICY_SECURITY_LOGGING,
@@ -17936,7 +17929,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
final CallerIdentity caller = getCallerIdentity(callingPackage);
- if (!securityLogV2Enabled()) {
+ if (!Flags.securityLogV2Enabled()) {
throw new UnsupportedOperationException("Audit log not enabled");
}
@@ -17964,7 +17957,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return false;
}
- if (!securityLogV2Enabled()) {
+ if (!Flags.securityLogV2Enabled()) {
throw new UnsupportedOperationException("Audit log not enabled");
}
@@ -18230,7 +18223,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
toggleBackupServiceActive(caller.getUserId(), enabled);
- if (backupServiceSecurityLogEventEnabled()) {
+ if (Flags.backupServiceSecurityLogEventEnabled()) {
if (SecurityLog.isLoggingEnabled()) {
SecurityLog.writeEvent(SecurityLog.TAG_BACKUP_SERVICE_TOGGLED,
caller.getPackageName(), caller.getUserId(), enabled ? 1 : 0);
@@ -20951,7 +20944,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
final CallerIdentity caller = getCallerIdentity(callerPackage);
- if (permissionMigrationForZeroTrustImplEnabled()) {
+ if (Flags.permissionMigrationForZeroTrustImplEnabled()) {
enforcePermission(MANAGE_DEVICE_POLICY_CERTIFICATES, caller.getPackageName());
} else {
Preconditions.checkCallAuthorization(
@@ -21555,7 +21548,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
setTimeAndTimezone(provisioningParams.getTimeZone(), provisioningParams.getLocalTime());
setLocale(provisioningParams.getLocale());
- int deviceOwnerUserId = headlessDeviceOwnerSingleUserEnabled()
+ int deviceOwnerUserId = Flags.headlessDeviceOwnerSingleUserEnabled()
&& getHeadlessDeviceOwnerMode() == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER
? mUserManagerInternal.getMainUserId()
: UserHandle.USER_SYSTEM;
@@ -21932,7 +21925,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
Objects.requireNonNull(packageName, "Admin package name must be provided");
final CallerIdentity caller = getCallerIdentity(packageName);
- if (!policyEngineMigrationV2Enabled()) {
+ if (!Flags.policyEngineMigrationV2Enabled()) {
Preconditions.checkCallAuthorization(
isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
"USB data signaling can only be controlled by a device owner or "
@@ -21942,7 +21935,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
synchronized (getLockObject()) {
- if (policyEngineMigrationV2Enabled()) {
+ if (Flags.policyEngineMigrationV2Enabled()) {
EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
/* admin= */ null, MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING,
caller.getPackageName(),
@@ -21982,7 +21975,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public boolean isUsbDataSignalingEnabled(String packageName) {
final CallerIdentity caller = getCallerIdentity(packageName);
- if (policyEngineMigrationV2Enabled()) {
+ if (Flags.policyEngineMigrationV2Enabled()) {
Boolean enabled = mDevicePolicyEngine.getResolvedPolicy(
PolicyDefinition.USB_DATA_SIGNALING,
caller.getUserId());
@@ -22107,9 +22100,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
enforcePermission(MANAGE_DEVICE_POLICY_THEFT_DETECTION, caller.getPackageName(),
caller.getUserId());
- //STOPSHIP: replace 1<<9 with
- // LockPatternUtils.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST once ag/26042068 lands
- return 0 != (mLockPatternUtils.getStrongAuthForUser(caller.getUserId()) & (1 << 9));
+ return mInjector.binderWithCleanCallingIdentity(() ->
+ 0 != (mLockPatternUtils.getStrongAuthForUser(caller.getUserId())
+ & SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST));
}
@Override
@@ -24235,7 +24228,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public void setMaxPolicyStorageLimit(String callerPackageName, int storageLimit) {
- if (!devicePolicySizeTrackingEnabled() || true) {
+ if (!Flags.devicePolicySizeTrackingEnabled() || true) {
return;
}
CallerIdentity caller = getCallerIdentity(callerPackageName);
@@ -24247,7 +24240,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public int getMaxPolicyStorageLimit(String callerPackageName) {
- if (!devicePolicySizeTrackingEnabled() || true) {
+ if (!Flags.devicePolicySizeTrackingEnabled() || true) {
return -1;
}
CallerIdentity caller = getCallerIdentity(callerPackageName);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
index d9fef10ee41b..9d73ed0070c8 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
@@ -16,11 +16,11 @@
package com.android.server.devicepolicy;
import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_DEFAULT;
-import static android.app.admin.flags.Flags.securityLogV2Enabled;
import android.annotation.Nullable;
import android.app.admin.SystemUpdateInfo;
import android.app.admin.SystemUpdatePolicy;
+import android.app.admin.flags.Flags;
import android.content.ComponentName;
import android.os.UserHandle;
import android.util.ArrayMap;
@@ -400,7 +400,7 @@ class OwnersData {
out.startTag(null, TAG_POLICY_ENGINE_MIGRATION);
out.attributeBoolean(null, ATTR_MIGRATED_TO_POLICY_ENGINE, mMigratedToPolicyEngine);
- if (securityLogV2Enabled()) {
+ if (Flags.securityLogV2Enabled()) {
out.attributeBoolean(null, ATTR_SECURITY_LOG_MIGRATED, mSecurityLoggingMigrated);
}
out.endTag(null, TAG_POLICY_ENGINE_MIGRATION);
@@ -463,7 +463,7 @@ class OwnersData {
case TAG_POLICY_ENGINE_MIGRATION:
mMigratedToPolicyEngine = parser.getAttributeBoolean(
null, ATTR_MIGRATED_TO_POLICY_ENGINE, false);
- mSecurityLoggingMigrated = securityLogV2Enabled()
+ mSecurityLoggingMigrated = Flags.securityLogV2Enabled()
&& parser.getAttributeBoolean(null, ATTR_SECURITY_LOG_MIGRATED, false);
break;
default:
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java b/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java
index e8c5658ca941..8cb511e8727c 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java
@@ -17,11 +17,11 @@
package com.android.server.devicepolicy;
import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_ALL_MASK;
-import static android.app.admin.flags.Flags.defaultSmsPersonalAppSuspensionFixEnabled;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.app.admin.flags.Flags;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -206,7 +206,7 @@ public final class PersonalAppsSuspensionHelper {
private String getDefaultSmsPackage() {
//TODO(b/319449037): Unflag the following change.
- if (defaultSmsPersonalAppSuspensionFixEnabled()) {
+ if (Flags.defaultSmsPersonalAppSuspensionFixEnabled()) {
return SmsApplication.getDefaultSmsApplicationAsUser(
mContext, /*updateIfNeeded=*/ false, mContext.getUser())
.getPackageName();
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java b/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java
index b6ab4c759166..c582a462db81 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java
@@ -16,8 +16,6 @@
package com.android.server.devicepolicy;
-import static android.app.admin.flags.Flags.securityLogV2Enabled;
-
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
@@ -25,6 +23,7 @@ import android.app.admin.DeviceAdminReceiver;
import android.app.admin.IAuditLogEventsCallback;
import android.app.admin.SecurityLog;
import android.app.admin.SecurityLog.SecurityEvent;
+import android.app.admin.flags.Flags;
import android.os.Handler;
import android.os.IBinder;
import android.os.Process;
@@ -468,11 +467,11 @@ class SecurityLogMonitor implements Runnable {
assignLogId(event);
}
- if (!securityLogV2Enabled() || mLegacyLogEnabled) {
+ if (!Flags.securityLogV2Enabled() || mLegacyLogEnabled) {
addToLegacyBufferLocked(dedupedLogs);
}
- if (securityLogV2Enabled() && mAuditLogEnabled) {
+ if (Flags.securityLogV2Enabled() && mAuditLogEnabled) {
addAuditLogEventsLocked(dedupedLogs);
}
}
@@ -549,7 +548,7 @@ class SecurityLogMonitor implements Runnable {
saveLastEvents(newLogs);
newLogs.clear();
- if (!securityLogV2Enabled() || mLegacyLogEnabled) {
+ if (!Flags.securityLogV2Enabled() || mLegacyLogEnabled) {
notifyDeviceOwnerOrProfileOwnerIfNeeded(force);
}
} catch (IOException e) {
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index ee758dbd0516..e19f08cb04a1 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -1486,7 +1486,6 @@ public final class SystemServer implements Dumpable {
VcnManagementService vcnManagement = null;
NetworkPolicyManagerService networkPolicy = null;
WindowManagerService wm = null;
- SerialService serial = null;
NetworkTimeUpdateService networkTimeUpdater = null;
InputManagerService inputManager = null;
TelephonyRegistry telephonyRegistry = null;
@@ -2362,13 +2361,7 @@ public final class SystemServer implements Dumpable {
if (!isWatch) {
t.traceBegin("StartSerialService");
- try {
- // Serial port support
- serial = new SerialService(context);
- ServiceManager.addService(Context.SERIAL_SERVICE, serial);
- } catch (Throwable e) {
- Slog.e(TAG, "Failure starting SerialService", e);
- }
+ mSystemServiceManager.startService(SerialService.Lifecycle.class);
t.traceEnd();
}
diff --git a/services/midi/java/com/android/server/midi/MidiService.java b/services/midi/java/com/android/server/midi/MidiService.java
index a212812b0768..c16c61271280 100644
--- a/services/midi/java/com/android/server/midi/MidiService.java
+++ b/services/midi/java/com/android/server/midi/MidiService.java
@@ -1012,7 +1012,11 @@ public class MidiService extends IMidiManager.Stub {
}
}
- if (user.getUserIdentifier() == mUserManager.getMainUser().getIdentifier()) {
+ // Allow only the main user to create BluetoothMidiService.
+ // If there is no main user, allow all users to create it.
+ UserHandle mainUser = mUserManager.getMainUser();
+ if ((mainUser == null)
+ || (user.getUserIdentifier() == mainUser.getIdentifier())) {
PackageInfo info;
try {
info = mPackageManager.getPackageInfoAsUser(
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeClamperTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeClamperTest.java
index 3458b08245a2..306b4f86024e 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeClamperTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessWearBedtimeModeClamperTest.java
@@ -85,7 +85,7 @@ public class BrightnessWearBedtimeModeClamperTest {
@Test
public void testType() {
- assertEquals(BrightnessClamper.Type.BEDTIME_MODE, mClamper.getType());
+ assertEquals(BrightnessClamper.Type.WEAR_BEDTIME_MODE, mClamper.getType());
}
@Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceContentTest.java b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceContentTest.java
new file mode 100644
index 000000000000..dad36e787360
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceContentTest.java
@@ -0,0 +1,145 @@
+/*
+ * 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;
+
+import static android.view.flags.Flags.FLAG_SENSITIVE_CONTENT_APP_PROTECTION;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+import android.media.projection.MediaProjectionInfo;
+import android.media.projection.MediaProjectionManager;
+import android.os.Binder;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.provider.Settings;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableContext;
+import android.util.ArraySet;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.wm.SensitiveContentPackages.PackageInfo;
+import com.android.server.wm.WindowManagerInternal;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@RequiresFlagsEnabled(FLAG_SENSITIVE_CONTENT_APP_PROTECTION)
+/**
+ * Test {@link SensitiveContentProtectionManagerService} for sensitive on screen content
+ * protection, the service protects sensitive content during screen share.
+ */
+public class SensitiveContentProtectionManagerServiceContentTest {
+ private final PackageInfo mPackageInfo =
+ new PackageInfo("test.package", 12345, new Binder());
+ private SensitiveContentProtectionManagerService mSensitiveContentProtectionManagerService;
+ private MediaProjectionManager.Callback mMediaPorjectionCallback;
+
+ @Mock private WindowManagerInternal mWindowManager;
+ @Mock private MediaProjectionManager mProjectionManager;
+ @Mock private MediaProjectionInfo mMediaProjectionInfo;
+
+ @Captor
+ private ArgumentCaptor<MediaProjectionManager.Callback> mMediaProjectionCallbackCaptor;
+ @Captor
+ private ArgumentCaptor<ArraySet<PackageInfo>> mPackageInfoCaptor;
+
+ @Rule
+ public final TestableContext mContext =
+ new TestableContext(getInstrumentation().getTargetContext(), null);
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mSensitiveContentProtectionManagerService =
+ new SensitiveContentProtectionManagerService(mContext);
+ mSensitiveContentProtectionManagerService.init(mProjectionManager, mWindowManager);
+ verify(mProjectionManager).addCallback(mMediaProjectionCallbackCaptor.capture(), any());
+ mMediaPorjectionCallback = mMediaProjectionCallbackCaptor.getValue();
+ }
+
+ @Test
+ public void testBlockAppWindowForScreenCapture() {
+ mMediaPorjectionCallback.onStart(mMediaProjectionInfo);
+ mSensitiveContentProtectionManagerService.setSensitiveContentProtection(
+ mPackageInfo.getWindowToken(), mPackageInfo.getPkg(), mPackageInfo.getUid(), true);
+ verify(mWindowManager, atLeast(1))
+ .addBlockScreenCaptureForApps(mPackageInfoCaptor.capture());
+ assertThat(mPackageInfoCaptor.getValue()).containsExactly(mPackageInfo);
+ }
+
+ @Test
+ public void testUnblockAppWindowForScreenCapture() {
+ mMediaPorjectionCallback.onStart(mMediaProjectionInfo);
+ mSensitiveContentProtectionManagerService.setSensitiveContentProtection(
+ mPackageInfo.getWindowToken(), mPackageInfo.getPkg(), mPackageInfo.getUid(), false);
+ verify(mWindowManager).removeBlockScreenCaptureForApps(mPackageInfoCaptor.capture());
+ assertThat(mPackageInfoCaptor.getValue()).containsExactly(mPackageInfo);
+ }
+
+ @Test
+ public void testAppWindowIsUnblockedBeforeScreenCapture() {
+ // when screen sharing is not active, no app window should be blocked.
+ mSensitiveContentProtectionManagerService.setSensitiveContentProtection(
+ mPackageInfo.getWindowToken(), mPackageInfo.getPkg(), mPackageInfo.getUid(), true);
+ verifyZeroInteractions(mWindowManager);
+ }
+
+ @Test
+ public void testAppWindowsAreUnblockedOnScreenCaptureEnd() {
+ mMediaPorjectionCallback.onStart(mMediaProjectionInfo);
+ mSensitiveContentProtectionManagerService.setSensitiveContentProtection(
+ mPackageInfo.getWindowToken(), mPackageInfo.getPkg(), mPackageInfo.getUid(), true);
+ // when screen sharing ends, all blocked app windows should be cleared.
+ mMediaPorjectionCallback.onStop(mMediaProjectionInfo);
+ verify(mWindowManager).clearBlockedApps();
+ }
+
+ @Test
+ public void testDeveloperOptionDisableFeature() {
+ mockDisabledViaDeveloperOption();
+ mMediaProjectionCallbackCaptor.getValue().onStart(mMediaProjectionInfo);
+ mSensitiveContentProtectionManagerService.setSensitiveContentProtection(
+ mPackageInfo.getWindowToken(), mPackageInfo.getPkg(), mPackageInfo.getUid(), true);
+ verifyZeroInteractions(mWindowManager);
+ }
+
+ private void mockDisabledViaDeveloperOption() {
+ Settings.Global.putInt(
+ mContext.getContentResolver(),
+ Settings.Global.DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS,
+ 1);
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java
index c298d516c89e..08050a9b30e0 100644
--- a/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java
@@ -16,10 +16,10 @@
package com.android.server;
-import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
-
import static android.permission.flags.Flags.FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doCallRealMethod;
@@ -67,7 +67,11 @@ import java.util.Set;
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
@RequiresFlagsEnabled(FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION)
-public class SensitiveContentProtectionManagerServiceTest {
+/**
+ * Test {@link SensitiveContentProtectionManagerService} for sensitive notification protection,
+ * the service protects sensitive content during screen share.
+ */
+public class SensitiveContentProtectionManagerServiceNotificationTest {
private static final String NOTIFICATION_KEY_1 = "com.android.server.notification.TEST_KEY_1";
private static final String NOTIFICATION_KEY_2 = "com.android.server.notification.TEST_KEY_2";
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java
index dfb8fda56edf..240ddf51ffdc 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java
@@ -751,7 +751,6 @@ public class CacheOomRankerTest {
app.mState.setCurAdj(setAdj);
app.setLastActivityTime(lastActivityTime);
mPidToRss.put(app.getPid(), lastRss);
- app.mState.setCached(false);
for (int i = 0; i < wentToForegroundCount; ++i) {
app.mState.setSetProcState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
app.mState.setSetProcState(ActivityManager.PROCESS_STATE_CACHED_RECENT);
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index 47928bcffb9a..1226f0c0b315 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -470,6 +470,7 @@ public class MockingOomAdjusterTests {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
app.mState.setCurRawAdj(CACHED_APP_MIN_ADJ);
+ app.mState.setCurAdj(CACHED_APP_MIN_ADJ);
doReturn(null).when(sService).getTopApp();
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
@@ -494,6 +495,8 @@ public class MockingOomAdjusterTests {
field.set(callback, PROCESS_STATE_TOP);
field = callback.getClass().getDeclaredField("schedGroup");
field.set(callback, SCHED_GROUP_TOP_APP);
+ field = callback.getClass().getDeclaredField("mAdjType");
+ field.set(callback, "vis-activity");
return 0;
})).when(wpc).computeOomAdjFromActivities(
any(WindowProcessController.ComputeOomAdjCallback.class));
@@ -501,6 +504,9 @@ public class MockingOomAdjusterTests {
updateOomAdj(app);
assertProcStates(app, PROCESS_STATE_TOP, VISIBLE_APP_ADJ, SCHED_GROUP_TOP_APP);
+ assertFalse(app.mState.isCached());
+ assertFalse(app.mState.isEmpty());
+ assertEquals("vis-activity", app.mState.getAdjType());
}
@SuppressWarnings("GuardedBy")
@@ -871,8 +877,8 @@ public class MockingOomAdjusterTests {
public void testUpdateOomAdj_DoOne_NonCachedToCached() {
ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
- app.mState.setCached(false);
app.mState.setCurRawAdj(SERVICE_ADJ);
+ app.mState.setCurAdj(SERVICE_ADJ);
doReturn(null).when(sService).getTopApp();
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
@@ -2546,7 +2552,6 @@ public class MockingOomAdjusterTests {
s.startRequested = true;
s.lastActivity = now;
- app.mState.setCached(false);
app.mServices.startService(s);
app.mState.setHasShownUi(true);
@@ -2559,7 +2564,6 @@ public class MockingOomAdjusterTests {
s2.startRequested = true;
s2.lastActivity = now - sService.mConstants.MAX_SERVICE_INACTIVITY - 1;
- app2.mState.setCached(false);
app2.mServices.startService(s2);
app2.mState.setHasShownUi(false);
@@ -2577,7 +2581,6 @@ public class MockingOomAdjusterTests {
assertProcStates(app, false, PROCESS_STATE_SERVICE, SERVICE_ADJ, "started-services");
- app.mState.setCached(false);
app.mState.setSetProcState(PROCESS_STATE_NONEXISTENT);
app.mState.setAdjType(null);
app.mState.setSetAdj(UNKNOWN_ADJ);
@@ -2605,7 +2608,6 @@ public class MockingOomAdjusterTests {
assertProcStates(app, false, PROCESS_STATE_SERVICE, SERVICE_ADJ, "started-services");
assertProcStates(app2, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-services");
- app.mState.setCached(true);
app.mState.setSetProcState(PROCESS_STATE_NONEXISTENT);
app.mState.setAdjType(null);
app.mState.setSetAdj(UNKNOWN_ADJ);
@@ -3035,7 +3037,6 @@ public class MockingOomAdjusterTests {
state.setHasTopUi(mHasTopUi);
state.setRunningRemoteAnimation(mRunningRemoteAnimation);
state.setHasOverlayUi(mHasOverlayUi);
- state.setCached(mCached);
state.setLastTopTime(mLastTopTime);
state.setForcingToImportant(mForcingToImportant);
services.setConnectionGroup(mConnectionGroup);
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ServiceBindingOomAdjPolicyTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ServiceBindingOomAdjPolicyTest.java
index 2f12a3b858d2..709a8049719c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ServiceBindingOomAdjPolicyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ServiceBindingOomAdjPolicyTest.java
@@ -21,7 +21,6 @@ import static android.app.ActivityManager.PROCESS_CAPABILITY_NONE;
import static android.app.ActivityManager.PROCESS_STATE_CACHED_EMPTY;
import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
import static android.app.ActivityManager.PROCESS_STATE_HOME;
-import static android.app.ActivityManager.PROCESS_STATE_LAST_ACTIVITY;
import static android.app.ActivityManager.PROCESS_STATE_SERVICE;
import static android.content.Context.BIND_AUTO_CREATE;
import static android.content.Context.BIND_INCLUDE_CAPABILITIES;
@@ -30,10 +29,10 @@ import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SYSTEM_EXEM
import static android.os.UserHandle.USER_SYSTEM;
import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
+import static com.android.server.am.ProcessList.CACHED_APP_MIN_ADJ;
import static com.android.server.am.ProcessList.HOME_APP_ADJ;
import static com.android.server.am.ProcessList.PERCEPTIBLE_APP_ADJ;
import static com.android.server.am.ProcessList.SERVICE_ADJ;
-import static com.android.server.am.ProcessList.CACHED_APP_MIN_ADJ;
import static org.junit.Assert.assertNotEquals;
import static org.mockito.ArgumentMatchers.any;
@@ -47,8 +46,8 @@ import static org.mockito.Mockito.clearInvocations;
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.never;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -582,7 +581,6 @@ public final class ServiceBindingOomAdjPolicyTest {
app.mState.setSetAdj(adj);
app.mState.setCurCapability(cap);
app.mState.setSetCapability(cap);
- app.mState.setCached(procState >= PROCESS_STATE_LAST_ACTIVITY || adj >= CACHED_APP_MIN_ADJ);
return app;
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java b/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java
index a14073006c31..d6e246fc7ee1 100644
--- a/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java
@@ -23,7 +23,13 @@ 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.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
@@ -33,14 +39,17 @@ import android.content.pm.VersionedPackage;
import android.content.rollback.PackageRollbackInfo;
import android.content.rollback.RollbackInfo;
import android.content.rollback.RollbackManager;
-import android.util.Log;
-import android.util.Xml;
+import android.crashrecovery.flags.Flags;
+import android.os.Handler;
+import android.os.MessageQueue;
+import android.platform.test.flag.junit.SetFlagsRule;
import androidx.test.runner.AndroidJUnit4;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.server.PackageWatchdog;
import com.android.server.SystemConfig;
+import com.android.server.pm.ApexManager;
import org.junit.After;
import org.junit.Before;
@@ -49,18 +58,16 @@ import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.mockito.Answers;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;
import org.mockito.stubbing.Answer;
-import org.xmlpull.v1.XmlPullParser;
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.FileWriter;
-import java.io.IOException;
+import java.time.Duration;
import java.util.List;
-import java.util.Scanner;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
@RunWith(AndroidJUnit4.class)
@@ -78,10 +85,18 @@ public class RollbackPackageHealthObserverTest {
@Mock
PackageManager mMockPackageManager;
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private ApexManager mApexManager;
+
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
private MockitoSession mSession;
private static final String APP_A = "com.package.a";
private static final String APP_B = "com.package.b";
+ private static final String APP_C = "com.package.c";
private static final long VERSION_CODE = 1L;
+ private static final long VERSION_CODE_2 = 2L;
private static final String LOG_TAG = "RollbackPackageHealthObserverTest";
private SystemConfig mSysConfig;
@@ -101,7 +116,6 @@ public class RollbackPackageHealthObserverTest {
// Mock PackageWatchdog
doAnswer((Answer<PackageWatchdog>) invocationOnMock -> mMockPackageWatchdog)
.when(() -> PackageWatchdog.getInstance(mMockContext));
-
}
@After
@@ -121,7 +135,7 @@ public class RollbackPackageHealthObserverTest {
@Test
public void testHealthCheckLevels() {
RollbackPackageHealthObserver observer =
- spy(new RollbackPackageHealthObserver(mMockContext));
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
VersionedPackage testFailedPackage = new VersionedPackage(APP_A, VERSION_CODE);
VersionedPackage secondFailedPackage = new VersionedPackage(APP_B, VERSION_CODE);
@@ -165,14 +179,14 @@ public class RollbackPackageHealthObserverTest {
@Test
public void testIsPersistent() {
RollbackPackageHealthObserver observer =
- spy(new RollbackPackageHealthObserver(mMockContext));
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
assertTrue(observer.isPersistent());
}
@Test
public void testMayObservePackage_withoutAnyRollback() {
RollbackPackageHealthObserver observer =
- spy(new RollbackPackageHealthObserver(mMockContext));
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of());
assertFalse(observer.mayObservePackage(APP_A));
@@ -182,7 +196,7 @@ public class RollbackPackageHealthObserverTest {
public void testMayObservePackage_forPersistentApp()
throws PackageManager.NameNotFoundException {
RollbackPackageHealthObserver observer =
- spy(new RollbackPackageHealthObserver(mMockContext));
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
ApplicationInfo info = new ApplicationInfo();
info.flags = ApplicationInfo.FLAG_PERSISTENT | ApplicationInfo.FLAG_SYSTEM;
when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
@@ -197,7 +211,7 @@ public class RollbackPackageHealthObserverTest {
public void testMayObservePackage_forNonPersistentApp()
throws PackageManager.NameNotFoundException {
RollbackPackageHealthObserver observer =
- spy(new RollbackPackageHealthObserver(mMockContext));
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(mRollbackInfo));
when(mRollbackInfo.getPackages()).thenReturn(List.of(mPackageRollbackInfo));
@@ -208,96 +222,720 @@ public class RollbackPackageHealthObserverTest {
}
/**
- * Test that isAutomaticRollbackDenied works correctly when packages that are not
- * denied are sent.
+ * Test that when impactLevel is low returns user impact level 70
*/
@Test
- public void isRollbackAllowedTest_false() throws IOException {
- final String contents =
- "<config>\n"
- + " <automatic-rollback-denylisted-app package=\"com.android.vending\" />\n"
- + "</config>";
- final File folder = createTempSubfolder("folder");
- createTempFile(folder, "automatic-rollback-denylisted-app.xml", contents);
-
- readPermissions(folder, /* Grant all permission flags */ ~0);
-
- assertThat(RollbackPackageHealthObserver.isAutomaticRollbackDenied(mSysConfig,
- new VersionedPackage("com.test.package", 1))).isEqualTo(false);
+ public void healthCheckFailed_impactLevelLow_onePackage()
+ throws PackageManager.NameNotFoundException {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
+ VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo,
+ null, null , false, false,
+ null);
+ RollbackInfo rollbackInfo1 = new RollbackInfo(1, List.of(packageRollbackInfo),
+ false, null, 111,
+ PackageManager.ROLLBACK_USER_IMPACT_LOW);
+ RollbackPackageHealthObserver observer =
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+ VersionedPackage secondFailedPackage = new VersionedPackage(APP_B, VERSION_CODE);
+
+ when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
+ // Make the rollbacks available
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(rollbackInfo1));
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
+
+ assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_70,
+ observer.onHealthCheckFailed(secondFailedPackage,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 1));
}
/**
- * Test that isAutomaticRollbackDenied works correctly when packages that are
- * denied are sent.
+ * HealthCheckFailed should only return low impact rollbacks. High impact rollbacks are only
+ * for bootloop.
*/
@Test
- public void isRollbackAllowedTest_true() throws IOException {
- final String contents =
- "<config>\n"
- + " <automatic-rollback-denylisted-app package=\"com.android.vending\" />\n"
- + "</config>";
- final File folder = createTempSubfolder("folder");
- createTempFile(folder, "automatic-rollback-denylisted-app.xml", contents);
-
- readPermissions(folder, /* Grant all permission flags */ ~0);
-
- assertThat(RollbackPackageHealthObserver.isAutomaticRollbackDenied(mSysConfig,
- new VersionedPackage("com.android.vending", 1))).isEqualTo(true);
+ public void healthCheckFailed_impactLevelHigh_onePackage()
+ throws PackageManager.NameNotFoundException {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
+ VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo,
+ null, null, false, false,
+ null);
+ RollbackInfo rollbackInfo1 = new RollbackInfo(1, List.of(packageRollbackInfo),
+ false, null, 111,
+ PackageManager.ROLLBACK_USER_IMPACT_HIGH);
+ RollbackPackageHealthObserver observer =
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+ VersionedPackage secondFailedPackage = new VersionedPackage(APP_B, VERSION_CODE);
+
+ when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
+ // Make the rollbacks available
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(rollbackInfo1));
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
+
+ assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_0,
+ observer.onHealthCheckFailed(secondFailedPackage,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 1));
}
/**
- * Test that isAutomaticRollbackDenied works correctly when no config is present
+ * When the rollback impact level is manual only return user impact level 0. (User impact level
+ * 0 is ignored by package watchdog)
*/
@Test
- public void isRollbackAllowedTest_noConfig() throws IOException {
- final File folder = createTempSubfolder("folder");
+ public void healthCheckFailed_impactLevelManualOnly_onePackage()
+ throws PackageManager.NameNotFoundException {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
+ VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo,
+ null, null, false, false,
+ null);
+ RollbackInfo rollbackInfo1 = new RollbackInfo(1, List.of(packageRollbackInfo),
+ false, null, 111,
+ PackageManager.ROLLBACK_USER_IMPACT_ONLY_MANUAL);
+ RollbackPackageHealthObserver observer =
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+ VersionedPackage secondFailedPackage = new VersionedPackage(APP_B, VERSION_CODE);
- readPermissions(folder, /* Grant all permission flags */ ~0);
+ when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
+ // Make the rollbacks available
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(rollbackInfo1));
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
- assertThat(RollbackPackageHealthObserver.isAutomaticRollbackDenied(mSysConfig,
- new VersionedPackage("com.android.vending", 1))).isEqualTo(false);
+ assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_0,
+ observer.onHealthCheckFailed(secondFailedPackage,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 1));
}
/**
- * Creates folderName/fileName in the mTemporaryFolder and fills it with the contents.
- *
- * @param folder pre-existing subdirectory of mTemporaryFolder to put the file
- * @param fileName name of the file (e.g. filename.xml) to create
- * @param contents contents to write to the file
- * @return the newly created file
+ * When both low impact and high impact are present, return 70.
*/
- private File createTempFile(File folder, String fileName, String contents)
- throws IOException {
- File file = new File(folder, fileName);
- BufferedWriter bw = new BufferedWriter(new FileWriter(file));
- bw.write(contents);
- bw.close();
-
- // Print to logcat for test debugging.
- Log.d(LOG_TAG, "Contents of file " + file.getAbsolutePath());
- Scanner input = new Scanner(file);
- while (input.hasNextLine()) {
- Log.d(LOG_TAG, input.nextLine());
- }
+ @Test
+ public void healthCheckFailed_impactLevelLowAndHigh_onePackage()
+ throws PackageManager.NameNotFoundException {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
+ VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo,
+ null, null, false, false,
+ null);
+ RollbackInfo rollbackInfo1 = new RollbackInfo(1, List.of(packageRollbackInfo),
+ false, null, 111,
+ PackageManager.ROLLBACK_USER_IMPACT_LOW);
+ VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2);
+ VersionedPackage appBTo = new VersionedPackage(APP_B, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfoB = new PackageRollbackInfo(appBFrom, appBTo,
+ null, null, false, false,
+ null);
+ RollbackInfo rollbackInfo2 = new RollbackInfo(2, List.of(packageRollbackInfoB),
+ false, null, 222,
+ PackageManager.ROLLBACK_USER_IMPACT_HIGH);
+ RollbackPackageHealthObserver observer =
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+ VersionedPackage failedPackage = new VersionedPackage(APP_C, VERSION_CODE);
+
+ when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
+ // Make the rollbacks available
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(
+ List.of(rollbackInfo1, rollbackInfo2));
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
+
+ assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_70,
+ observer.onHealthCheckFailed(failedPackage,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 1));
+ }
+
+ /**
+ * When low impact rollback is available roll it back.
+ */
+ @Test
+ public void execute_impactLevelLow_nativeCrash_rollback()
+ throws PackageManager.NameNotFoundException {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ int rollbackId = 1;
+ VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
+ VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo,
+ null, null , false, false,
+ null);
+ RollbackInfo rollbackInfo1 = new RollbackInfo(rollbackId, List.of(packageRollbackInfo),
+ false, null, 111,
+ PackageManager.ROLLBACK_USER_IMPACT_LOW);
+ VersionedPackage secondFailedPackage = new VersionedPackage(APP_B, VERSION_CODE);
+ RollbackPackageHealthObserver observer =
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+
+ when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
+ // Make the rollbacks available
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(rollbackInfo1));
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
+
+ observer.execute(secondFailedPackage,
+ PackageWatchdog.FAILURE_REASON_NATIVE_CRASH, 1);
+ waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
+
+ verify(mRollbackManager).getAvailableRollbacks();
+ verify(mRollbackManager).commitRollback(eq(rollbackId), any(), any());
+ }
+
+ /**
+ * Rollback the failing package if rollback is available for it
+ */
+ @Test
+ public void execute_impactLevelLow_rollbackFailedPackage()
+ throws PackageManager.NameNotFoundException {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ int rollbackId1 = 1;
+ VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
+ VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfoA = new PackageRollbackInfo(appAFrom, appATo,
+ null, null , false, false,
+ null);
+ RollbackInfo rollbackInfo1 = new RollbackInfo(rollbackId1, List.of(packageRollbackInfoA),
+ false, null, 111,
+ PackageManager.ROLLBACK_USER_IMPACT_LOW);
+ int rollbackId2 = 2;
+ VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2);
+ VersionedPackage appBTo = new VersionedPackage(APP_B, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfoB = new PackageRollbackInfo(appBFrom, appBTo,
+ null, null , false, false,
+ null);
+ RollbackInfo rollbackInfo2 = new RollbackInfo(rollbackId2, List.of(packageRollbackInfoB),
+ false, null, 222,
+ PackageManager.ROLLBACK_USER_IMPACT_LOW);
+ RollbackPackageHealthObserver observer =
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+ ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);
+
+ when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
+ // Make the rollbacks available
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(
+ List.of(rollbackInfo1, rollbackInfo2));
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
+
+ observer.execute(appBFrom, PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+ waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
+
+ verify(mRollbackManager).commitRollback(argument.capture(), any(), any());
+ // Rollback package App B as the failing package is B
+ assertThat(argument.getValue()).isEqualTo(rollbackId2);
+ }
+
+ /**
+ * Rollback all available rollbacks if the rollback is not available for failing package.
+ */
+ @Test
+ public void execute_impactLevelLow_rollbackAll()
+ throws PackageManager.NameNotFoundException {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ int rollbackId1 = 1;
+ VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
+ VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfoA = new PackageRollbackInfo(appAFrom, appATo,
+ null, null , false, false,
+ null);
+ RollbackInfo rollbackInfo1 = new RollbackInfo(rollbackId1, List.of(packageRollbackInfoA),
+ false, null, 111,
+ PackageManager.ROLLBACK_USER_IMPACT_LOW);
+ int rollbackId2 = 2;
+ VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2);
+ VersionedPackage appBTo = new VersionedPackage(APP_B, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfoB = new PackageRollbackInfo(appBFrom, appBTo,
+ null, null , false, false,
+ null);
+ RollbackInfo rollbackInfo2 = new RollbackInfo(rollbackId2, List.of(packageRollbackInfoB),
+ false, null, 222,
+ PackageManager.ROLLBACK_USER_IMPACT_LOW);
+ VersionedPackage failedPackage = new VersionedPackage(APP_C, VERSION_CODE);
+ RollbackPackageHealthObserver observer =
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+ ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);
+
+ when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
+ // Make the rollbacks available
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(
+ List.of(rollbackInfo1, rollbackInfo2));
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
+
+ observer.execute(failedPackage, PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+ waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
+
+ verify(mRollbackManager, times(2)).commitRollback(
+ argument.capture(), any(), any());
+ // Rollback A and B when the failing package doesn't have a rollback
+ assertThat(argument.getAllValues()).isEqualTo(List.of(rollbackId1, rollbackId2));
+ }
+
+ /**
+ * rollback low impact package if both low and high impact packages are available
+ */
+ @Test
+ public void execute_impactLevelLowAndHigh_rollbackLow()
+ throws PackageManager.NameNotFoundException {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ int rollbackId1 = 1;
+ VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
+ VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfoA = new PackageRollbackInfo(appAFrom, appATo,
+ null, null , false, false,
+ null);
+ RollbackInfo rollbackInfo1 = new RollbackInfo(rollbackId1, List.of(packageRollbackInfoA),
+ false, null, 111,
+ PackageManager.ROLLBACK_USER_IMPACT_LOW);
+ int rollbackId2 = 2;
+ VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2);
+ VersionedPackage appBTo = new VersionedPackage(APP_B, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfoB = new PackageRollbackInfo(appBFrom, appBTo,
+ null, null , false, false,
+ null);
+ RollbackInfo rollbackInfo2 = new RollbackInfo(rollbackId2, List.of(packageRollbackInfoB),
+ false, null, 222,
+ PackageManager.ROLLBACK_USER_IMPACT_HIGH);
+ VersionedPackage failedPackage = new VersionedPackage(APP_C, VERSION_CODE);
+ RollbackPackageHealthObserver observer =
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+ ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);
+
+ when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
+ // Make the rollbacks available
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(
+ List.of(rollbackInfo1, rollbackInfo2));
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
+
+ observer.execute(failedPackage, PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+ waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
+
+ verify(mRollbackManager, times(1)).commitRollback(
+ argument.capture(), any(), any());
+ // Rollback A and B when the failing package doesn't have a rollback
+ assertThat(argument.getAllValues()).isEqualTo(List.of(rollbackId1));
+ }
+
+ /**
+ * Don't roll back high impact package if only high impact package is available. high impact
+ * rollback to be rolled back only on bootloop.
+ */
+ @Test
+ public void execute_impactLevelHigh_rollbackHigh()
+ throws PackageManager.NameNotFoundException {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ int rollbackId2 = 2;
+ VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2);
+ VersionedPackage appBTo = new VersionedPackage(APP_B, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfoB = new PackageRollbackInfo(appBFrom, appBTo,
+ null, null , false, false,
+ null);
+ RollbackInfo rollbackInfo2 = new RollbackInfo(rollbackId2, List.of(packageRollbackInfoB),
+ false, null, 111,
+ PackageManager.ROLLBACK_USER_IMPACT_HIGH);
+ VersionedPackage failedPackage = new VersionedPackage(APP_C, VERSION_CODE);
+ RollbackPackageHealthObserver observer =
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+ ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);
+
+ when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
+ // Make the rollbacks available
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(rollbackInfo2));
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
+
+ observer.execute(failedPackage, PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+ waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
+
+ verify(mRollbackManager, never()).commitRollback(argument.capture(), any(), any());
+
+ }
+
+ /**
+ * Test that when impactLevel is low returns user impact level 70
+ */
+ @Test
+ public void onBootLoop_impactLevelLow_onePackage() throws PackageManager.NameNotFoundException {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
+ VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo,
+ null, null , false, false,
+ null);
+ RollbackInfo rollbackInfo1 = new RollbackInfo(1, List.of(packageRollbackInfo),
+ false, null, 111,
+ PackageManager.ROLLBACK_USER_IMPACT_LOW);
+ RollbackPackageHealthObserver observer =
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+
+ when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
+ // Make the rollbacks available
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(rollbackInfo1));
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
+
+ assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_70,
+ observer.onBootLoop(1));
+ }
+
+ @Test
+ public void onBootLoop_impactLevelHigh_onePackage()
+ throws PackageManager.NameNotFoundException {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
+ VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo,
+ null, null, false, false,
+ null);
+ RollbackInfo rollbackInfo1 = new RollbackInfo(1, List.of(packageRollbackInfo),
+ false, null, 111,
+ PackageManager.ROLLBACK_USER_IMPACT_HIGH);
+ RollbackPackageHealthObserver observer =
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+
+ when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
+ // Make the rollbacks available
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(rollbackInfo1));
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
- return file;
+ assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_90,
+ observer.onBootLoop(1));
}
- private void readPermissions(File libraryDir, int permissionFlag) {
- final XmlPullParser parser = Xml.newPullParser();
- mSysConfig.readPermissions(parser, libraryDir, permissionFlag);
+ /**
+ * When the rollback impact level is manual only return user impact level 0. (User impact level
+ * 0 is ignored by package watchdog)
+ */
+ @Test
+ public void onBootLoop_impactLevelManualOnly_onePackage()
+ throws PackageManager.NameNotFoundException {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
+ VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo,
+ null, null, false, false,
+ null);
+ RollbackInfo rollbackInfo1 = new RollbackInfo(1, List.of(packageRollbackInfo),
+ false, null, 111,
+ PackageManager.ROLLBACK_USER_IMPACT_ONLY_MANUAL);
+ RollbackPackageHealthObserver observer =
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+
+ when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
+ // Make the rollbacks available
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(rollbackInfo1));
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
+
+ assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_0,
+ observer.onBootLoop(1));
}
/**
- * Creates folderName/fileName in the mTemporaryFolder and fills it with the contents.
- *
- * @param folderName subdirectory of mTemporaryFolder to put the file, creating if needed
- * @return the folder
+ * When both low impact and high impact are present, return 70.
*/
- private File createTempSubfolder(String folderName)
- throws IOException {
- File folder = new File(mTemporaryFolder.getRoot(), folderName);
- folder.mkdirs();
- return folder;
+ @Test
+ public void onBootLoop_impactLevelLowAndHigh_onePackage()
+ throws PackageManager.NameNotFoundException {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
+ VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo,
+ null, null, false, false,
+ null);
+ RollbackInfo rollbackInfo1 = new RollbackInfo(1, List.of(packageRollbackInfo),
+ false, null, 111,
+ PackageManager.ROLLBACK_USER_IMPACT_LOW);
+ VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2);
+ VersionedPackage appBTo = new VersionedPackage(APP_B, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfoB = new PackageRollbackInfo(appBFrom, appBTo,
+ null, null, false, false,
+ null);
+ RollbackInfo rollbackInfo2 = new RollbackInfo(2, List.of(packageRollbackInfoB),
+ false, null, 222,
+ PackageManager.ROLLBACK_USER_IMPACT_HIGH);
+ RollbackPackageHealthObserver observer =
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+
+ when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
+ // Make the rollbacks available
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(
+ List.of(rollbackInfo1, rollbackInfo2));
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
+
+ assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_70,
+ observer.onBootLoop(1));
+ }
+
+ /**
+ * Rollback all available rollbacks if the rollback is not available for failing package.
+ */
+ @Test
+ public void executeBootLoopMitigation_impactLevelLow_rollbackAll()
+ throws PackageManager.NameNotFoundException {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ int rollbackId1 = 1;
+ VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
+ VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfoA = new PackageRollbackInfo(appAFrom, appATo,
+ null, null , false, false,
+ null);
+ RollbackInfo rollbackInfo1 = new RollbackInfo(rollbackId1, List.of(packageRollbackInfoA),
+ false, null, 111,
+ PackageManager.ROLLBACK_USER_IMPACT_LOW);
+ int rollbackId2 = 2;
+ VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2);
+ VersionedPackage appBTo = new VersionedPackage(APP_B, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfoB = new PackageRollbackInfo(appBFrom, appBTo,
+ null, null , false, false,
+ null);
+ RollbackInfo rollbackInfo2 = new RollbackInfo(rollbackId2, List.of(packageRollbackInfoB),
+ false, null, 222,
+ PackageManager.ROLLBACK_USER_IMPACT_LOW);
+ RollbackPackageHealthObserver observer =
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+ ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);
+
+ when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
+ // Make the rollbacks available
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(
+ List.of(rollbackInfo1, rollbackInfo2));
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
+
+ observer.executeBootLoopMitigation(1);
+ waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
+
+ verify(mRollbackManager, times(2)).commitRollback(
+ argument.capture(), any(), any());
+ // Rollback A and B when the failing package doesn't have a rollback
+ assertThat(argument.getAllValues()).isEqualTo(List.of(rollbackId1, rollbackId2));
+ }
+
+ /**
+ * rollback low impact package if both low and high impact packages are available
+ */
+ @Test
+ public void executeBootLoopMitigation_impactLevelLowAndHigh_rollbackLow()
+ throws PackageManager.NameNotFoundException {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ int rollbackId1 = 1;
+ VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
+ VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfoA = new PackageRollbackInfo(appAFrom, appATo,
+ null, null , false, false,
+ null);
+ RollbackInfo rollbackInfo1 = new RollbackInfo(rollbackId1, List.of(packageRollbackInfoA),
+ false, null, 111,
+ PackageManager.ROLLBACK_USER_IMPACT_LOW);
+ int rollbackId2 = 2;
+ VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2);
+ VersionedPackage appBTo = new VersionedPackage(APP_B, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfoB = new PackageRollbackInfo(appBFrom, appBTo,
+ null, null , false, false,
+ null);
+ RollbackInfo rollbackInfo2 = new RollbackInfo(rollbackId2, List.of(packageRollbackInfoB),
+ false, null, 222,
+ PackageManager.ROLLBACK_USER_IMPACT_HIGH);
+ RollbackPackageHealthObserver observer =
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+ ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);
+
+ when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
+ // Make the rollbacks available
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(
+ List.of(rollbackInfo1, rollbackInfo2));
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
+
+ observer.executeBootLoopMitigation(1);
+ waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
+
+ verify(mRollbackManager, times(1)).commitRollback(
+ argument.capture(), any(), any());
+ // Rollback A and B when the failing package doesn't have a rollback
+ assertThat(argument.getAllValues()).isEqualTo(List.of(rollbackId1));
+ }
+
+ /**
+ * Rollback high impact package if only high impact package is available
+ */
+ @Test
+ public void executeBootLoopMitigation_impactLevelHigh_rollbackHigh()
+ throws PackageManager.NameNotFoundException {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ int rollbackId2 = 2;
+ VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2);
+ VersionedPackage appBTo = new VersionedPackage(APP_B, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfoB = new PackageRollbackInfo(appBFrom, appBTo,
+ null, null , false, false,
+ null);
+ RollbackInfo rollbackInfo2 = new RollbackInfo(rollbackId2, List.of(packageRollbackInfoB),
+ false, null, 111,
+ PackageManager.ROLLBACK_USER_IMPACT_HIGH);
+ RollbackPackageHealthObserver observer =
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+ ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);
+
+ when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
+ // Make the rollbacks available
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(rollbackInfo2));
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
+
+ observer.executeBootLoopMitigation(1);
+ waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
+
+ verify(mRollbackManager, times(1)).commitRollback(
+ argument.capture(), any(), any());
+ // Rollback high impact packages when no other rollback available
+ assertThat(argument.getAllValues()).isEqualTo(List.of(rollbackId2));
+ }
+
+ /**
+ * Rollback only low impact available rollbacks if both low and manual only are available.
+ */
+ @Test
+ public void execute_impactLevelLowAndManual_rollbackLowImpactOnly()
+ throws PackageManager.NameNotFoundException, InterruptedException {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ int rollbackId1 = 1;
+ VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
+ VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfoA = new PackageRollbackInfo(appAFrom, appATo,
+ null, null , false, false,
+ null);
+ RollbackInfo rollbackInfo1 = new RollbackInfo(rollbackId1, List.of(packageRollbackInfoA),
+ false, null, 111,
+ PackageManager.ROLLBACK_USER_IMPACT_LOW);
+ int rollbackId2 = 2;
+ VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2);
+ VersionedPackage appBTo = new VersionedPackage(APP_B, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfoB = new PackageRollbackInfo(appBFrom, appBTo,
+ null, null , false, false,
+ null);
+ RollbackInfo rollbackInfo2 = new RollbackInfo(rollbackId2, List.of(packageRollbackInfoB),
+ false, null, 222,
+ PackageManager.ROLLBACK_USER_IMPACT_ONLY_MANUAL);
+ VersionedPackage failedPackage = new VersionedPackage(APP_C, VERSION_CODE);
+ RollbackPackageHealthObserver observer =
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+ ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);
+
+ when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
+ // Make the rollbacks available
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(
+ List.of(rollbackInfo1, rollbackInfo2));
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
+
+ observer.execute(failedPackage, PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+ waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
+
+ verify(mRollbackManager, times(1)).commitRollback(
+ argument.capture(), any(), any());
+ assertThat(argument.getAllValues()).isEqualTo(List.of(rollbackId1));
+ }
+
+ /**
+ * Do not roll back if only manual rollback is available.
+ */
+ @Test
+ public void execute_impactLevelManual_rollbackLowImpactOnly()
+ throws PackageManager.NameNotFoundException {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ int rollbackId1 = 1;
+ VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
+ VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfoA = new PackageRollbackInfo(appAFrom, appATo,
+ null, null , false, false,
+ null);
+ RollbackInfo rollbackInfo1 = new RollbackInfo(rollbackId1, List.of(packageRollbackInfoA),
+ false, null, 111,
+ PackageManager.ROLLBACK_USER_IMPACT_ONLY_MANUAL);
+ VersionedPackage failedPackage = new VersionedPackage(APP_C, VERSION_CODE);
+ RollbackPackageHealthObserver observer =
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+ ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);
+
+ when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
+ // Make the rollbacks available
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(rollbackInfo1));
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
+
+ observer.execute(failedPackage, PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
+ waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
+
+ verify(mRollbackManager, never()).commitRollback(argument.capture(), any(), any());
+ }
+
+ /**
+ * Rollback alphabetically first package if multiple high impact rollbacks are available.
+ */
+ @Test
+ public void executeBootLoopMitigation_impactLevelHighMultiplePackage_rollbackHigh()
+ throws PackageManager.NameNotFoundException {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ int rollbackId1 = 1;
+ VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2);
+ VersionedPackage appBTo = new VersionedPackage(APP_B, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfoB = new PackageRollbackInfo(appBFrom, appBTo,
+ null, null , false, false,
+ null);
+ RollbackInfo rollbackInfo1 = new RollbackInfo(rollbackId1, List.of(packageRollbackInfoB),
+ false, null, 111,
+ PackageManager.ROLLBACK_USER_IMPACT_HIGH);
+ int rollbackId2 = 2;
+ VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
+ VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfoA = new PackageRollbackInfo(appAFrom, appATo,
+ null, null , false, false,
+ null);
+ RollbackInfo rollbackInfo2 = new RollbackInfo(rollbackId2, List.of(packageRollbackInfoA),
+ false, null, 111,
+ PackageManager.ROLLBACK_USER_IMPACT_HIGH);
+ VersionedPackage failedPackage = new VersionedPackage(APP_C, VERSION_CODE);
+ RollbackPackageHealthObserver observer =
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+ ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);
+
+ when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
+ // Make the rollbacks available
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(
+ List.of(rollbackInfo1, rollbackInfo2));
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
+
+ observer.executeBootLoopMitigation(1);
+ waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
+
+ verify(mRollbackManager, times(1)).commitRollback(
+ argument.capture(), any(), any());
+ // Rollback APP_A because it is first alphabetically
+ assertThat(argument.getAllValues()).isEqualTo(List.of(rollbackId2));
+ }
+
+ private void waitForIdleHandler(Handler handler, Duration timeout) {
+ final MessageQueue queue = handler.getLooper().getQueue();
+ final CountDownLatch latch = new CountDownLatch(1);
+ queue.addIdleHandler(() -> {
+ latch.countDown();
+ // Remove idle handler
+ return false;
+ });
+ try {
+ latch.await(timeout.toMillis(), TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ fail("Interrupted unexpectedly: " + e);
+ }
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/rollback/TEST_MAPPING b/services/tests/mockingservicestests/src/com/android/server/rollback/TEST_MAPPING
new file mode 100644
index 000000000000..e42bdad97730
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/rollback/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+ "postsubmit": [
+ {
+ "name": "FrameworksMockingServicesTests",
+ "options": [
+ {
+ "include-filter": "com.android.server.rollback"
+ }
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
index b224773a9eb1..f3cd0d6b961e 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
@@ -1360,7 +1360,7 @@ public class FullScreenMagnificationControllerTest {
DISPLAY_0, scale, Float.NaN, Float.NaN, true, SERVICE_ID_1);
checkActivatedAndMagnifying(/* activated= */ true, /* magnifying= */ false, DISPLAY_0);
- verify(mMockWindowManager).setForceShowMagnifiableBounds(DISPLAY_0, true);
+ verify(mMockWindowManager).setFullscreenMagnificationActivated(DISPLAY_0, true);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index 4307ec5aa7e1..cea10ea9ade4 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -87,6 +87,7 @@ import android.os.HandlerThread;
import android.os.IRemoteCallback;
import android.os.Looper;
import android.os.Message;
+import android.os.PowerManagerInternal;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
@@ -1223,6 +1224,7 @@ public class UserControllerTest {
private final UserManagerInternal mUserManagerInternalMock;
private final WindowManagerService mWindowManagerMock;
private final ActivityTaskManagerInternal mActivityTaskManagerInternal;
+ private final PowerManagerInternal mPowerManagerInternal;
private final KeyguardManager mKeyguardManagerMock;
private final LockPatternUtils mLockPatternUtilsMock;
@@ -1244,6 +1246,7 @@ public class UserControllerTest {
mWindowManagerMock = mock(WindowManagerService.class);
mActivityTaskManagerInternal = mock(ActivityTaskManagerInternal.class);
mStorageManagerMock = mock(IStorageManager.class);
+ mPowerManagerInternal = mock(PowerManagerInternal.class);
mKeyguardManagerMock = mock(KeyguardManager.class);
when(mKeyguardManagerMock.isDeviceSecure(anyInt())).thenReturn(true);
mLockPatternUtilsMock = mock(LockPatternUtils.class);
@@ -1309,6 +1312,11 @@ public class UserControllerTest {
}
@Override
+ PowerManagerInternal getPowerManagerInternal() {
+ return mPowerManagerInternal;
+ }
+
+ @Override
KeyguardManager getKeyguardManager() {
return mKeyguardManagerMock;
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricNotificationLoggerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricNotificationLoggerTest.java
new file mode 100644
index 000000000000..8319623eec0a
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricNotificationLoggerTest.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 com.android.server.biometrics;
+
+import android.hardware.biometrics.BiometricsProtoEnums;
+import android.platform.test.annotations.Presubmit;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.StatusBarNotification;
+
+import com.android.server.biometrics.log.BiometricFrameworkStatsLogger;
+import com.android.server.biometrics.sensors.BiometricNotificationUtils;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@Presubmit
+@SmallTest
+public class BiometricNotificationLoggerTest {
+ @Rule
+ public MockitoRule mockitorule = MockitoJUnit.rule();
+
+ @Mock
+ private BiometricFrameworkStatsLogger mLogger;
+ private BiometricNotificationLogger mNotificationLogger;
+
+ @Before
+ public void setUp() {
+ mNotificationLogger = new BiometricNotificationLogger(
+ mLogger);
+ }
+
+ @Test
+ public void testNotification_nullNotification_doesNothing() {
+ mNotificationLogger.onNotificationPosted(null, null);
+
+ verify(mLogger, never()).logFrameworkNotification(anyInt(), anyInt());
+ }
+
+ @Test
+ public void testNotification_emptyStringTag_doesNothing() {
+ final StatusBarNotification noti = createNotificationWithNullTag();
+ mNotificationLogger.onNotificationPosted(noti, null);
+
+ verify(mLogger, never()).logFrameworkNotification(anyInt(), anyInt());
+ }
+
+ @Test
+ public void testFaceNotification_posted() {
+ final StatusBarNotification noti = createFaceNotification();
+ mNotificationLogger.onNotificationPosted(noti, null);
+
+ verify(mLogger).logFrameworkNotification(
+ BiometricsProtoEnums.FRR_NOTIFICATION_ACTION_SHOWN,
+ BiometricsProtoEnums.MODALITY_FACE);
+ }
+
+ @Test
+ public void testFingerprintNotification_posted() {
+ final StatusBarNotification noti = createFingerprintNotification();
+ mNotificationLogger.onNotificationPosted(noti, null);
+
+ verify(mLogger).logFrameworkNotification(
+ BiometricsProtoEnums.FRR_NOTIFICATION_ACTION_SHOWN,
+ BiometricsProtoEnums.MODALITY_FINGERPRINT);
+ }
+
+ @Test
+ public void testFaceNotification_clicked() {
+ final StatusBarNotification noti = createFaceNotification();
+ mNotificationLogger.onNotificationRemoved(noti, null,
+ NotificationListenerService.REASON_CLICK);
+
+ verify(mLogger).logFrameworkNotification(
+ BiometricsProtoEnums.FRR_NOTIFICATION_ACTION_CLICKED,
+ BiometricsProtoEnums.MODALITY_FACE);
+ }
+
+ @Test
+ public void testFingerprintNotification_clicked() {
+ final StatusBarNotification noti = createFingerprintNotification();
+ mNotificationLogger.onNotificationRemoved(noti, null,
+ NotificationListenerService.REASON_CLICK);
+
+ verify(mLogger).logFrameworkNotification(
+ BiometricsProtoEnums.FRR_NOTIFICATION_ACTION_CLICKED,
+ BiometricsProtoEnums.MODALITY_FINGERPRINT);
+ }
+
+ @Test
+ public void testFaceNotification_dismissed() {
+ final StatusBarNotification noti = createFaceNotification();
+ mNotificationLogger.onNotificationRemoved(noti, null,
+ NotificationListenerService.REASON_CANCEL);
+
+ verify(mLogger).logFrameworkNotification(
+ BiometricsProtoEnums.FRR_NOTIFICATION_ACTION_DISMISSED,
+ BiometricsProtoEnums.MODALITY_FACE);
+ }
+
+ @Test
+ public void testFingerprintNotification_dismissed() {
+ final StatusBarNotification noti = createFingerprintNotification();
+ mNotificationLogger.onNotificationRemoved(noti, null,
+ NotificationListenerService.REASON_CANCEL);
+
+ verify(mLogger).logFrameworkNotification(
+ BiometricsProtoEnums.FRR_NOTIFICATION_ACTION_DISMISSED,
+ BiometricsProtoEnums.MODALITY_FINGERPRINT);
+ }
+
+ private StatusBarNotification createNotificationWithNullTag() {
+ final StatusBarNotification notification = mock(StatusBarNotification.class);
+ return notification;
+ }
+
+ private StatusBarNotification createFaceNotification() {
+ final StatusBarNotification notification = mock(StatusBarNotification.class);
+ when(notification.getTag())
+ .thenReturn(BiometricNotificationUtils.FACE_ENROLL_NOTIFICATION_TAG);
+ return notification;
+ }
+
+ private StatusBarNotification createFingerprintNotification() {
+ final StatusBarNotification notification = mock(StatusBarNotification.class);
+ when(notification.getTag())
+ .thenReturn(BiometricNotificationUtils.FINGERPRINT_ENROLL_NOTIFICATION_TAG);
+ return notification;
+ }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
index 35ad55c0938e..49583ef5194b 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -187,6 +187,9 @@ public class BiometricServiceTest {
@Mock
private IGateKeeperService mGateKeeperService;
+ @Mock
+ private BiometricNotificationLogger mNotificationLogger;
+
BiometricContextProvider mBiometricContextProvider;
@Before
@@ -242,6 +245,7 @@ public class BiometricServiceTest {
when(mInjector.getBiometricContext(any())).thenReturn(mBiometricContextProvider);
when(mInjector.getKeystoreAuthorizationService()).thenReturn(mKeystoreAuthService);
when(mInjector.getGateKeeperService()).thenReturn(mGateKeeperService);
+ when(mInjector.getNotificationLogger()).thenReturn(mNotificationLogger);
when(mGateKeeperService.getSecureUserId(anyInt())).thenReturn(42L);
if (com.android.server.biometrics.Flags.deHidl()) {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java
index b69b55448bcd..238a9289c05b 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java
@@ -137,11 +137,11 @@ public class BiometricLoggerTest {
final long latency = 44;
final boolean enrollSuccessful = true;
- mLogger.logOnEnrolled(targetUserId, latency, enrollSuccessful);
+ mLogger.logOnEnrolled(targetUserId, latency, enrollSuccessful, -1);
verify(mSink).enroll(
eq(DEFAULT_MODALITY), eq(DEFAULT_ACTION), eq(DEFAULT_CLIENT),
- eq(targetUserId), eq(latency), eq(enrollSuccessful), anyFloat());
+ eq(targetUserId), eq(latency), eq(enrollSuccessful), anyFloat(), anyInt());
}
@Test
@@ -192,7 +192,8 @@ public class BiometricLoggerTest {
true/* isBiometricPrompt */);
mLogger.logOnEnrolled(2 /* targetUserId */,
10 /* latency */,
- true /* enrollSuccessful */);
+ true /* enrollSuccessful */,
+ 30 /* source */);
mLogger.logOnError(mContext, mOpContext,
4 /* error */,
0 /* vendorCode */,
@@ -205,7 +206,8 @@ public class BiometricLoggerTest {
anyInt(), anyInt(), anyInt(), anyBoolean(),
anyLong(), anyInt(), anyBoolean(), anyInt(), anyFloat());
verify(mSink, never()).enroll(
- anyInt(), anyInt(), anyInt(), anyInt(), anyLong(), anyBoolean(), anyFloat());
+ anyInt(), anyInt(), anyInt(), anyInt(), anyLong(), anyBoolean(), anyFloat(),
+ anyInt());
verify(mSink, never()).error(eq(mOpContext),
anyInt(), anyInt(), anyInt(), anyBoolean(),
anyLong(), anyInt(), anyInt(), anyInt());
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
index f7480dea780b..981eba55a430 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
@@ -1139,7 +1139,7 @@ public class BiometricSchedulerTest {
super(context, lazyDaemon, token, listener, 0 /* userId */, new byte[69],
"test" /* owner */, mock(BiometricUtils.class), 5 /* timeoutSec */,
TEST_SENSOR_ID, true /* shouldVibrate */, mock(BiometricLogger.class),
- mock(BiometricContext.class));
+ mock(BiometricContext.class), 0 /* enrollReason */);
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java
index 43ed07a0bf76..02363cd66349 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClientTest.java
@@ -21,15 +21,20 @@ 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.anyByte;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.same;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.common.OperationContext;
import android.hardware.biometrics.face.ISession;
import android.hardware.face.Face;
+import android.hardware.face.FaceEnrollOptions;
import android.os.IBinder;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
@@ -68,6 +73,7 @@ public class FaceEnrollClientTest {
private static final byte[] HAT = new byte[69];
private static final int USER_ID = 12;
+ private static final int ENROLL_SOURCE = FaceEnrollOptions.ENROLL_REASON_SUW;
@Rule
public final TestableContext mContext = new TestableContext(
@@ -208,6 +214,16 @@ public class FaceEnrollClientTest {
verify(mHal).enrollWithOptions(any());
}
+ @Test
+ public void testEnrollWithReasonLogsMetric() throws RemoteException {
+ final FaceEnrollClient client = createClient(4);
+ client.start(mCallback);
+ client.onEnrollResult(new Face("face", 1 /* faceId */, 20 /* deviceId */), 0);
+
+ verify(mBiometricLogger).logOnEnrolled(anyInt(), anyLong(), anyBoolean(),
+ eq(BiometricsProtoEnums.ENROLLMENT_SOURCE_SUW));
+ }
+
private FaceEnrollClient createClient() throws RemoteException {
return createClient(200 /* version */);
}
@@ -221,6 +237,7 @@ public class FaceEnrollClientTest {
mUtils, new int[0] /* disabledFeatures */, 6 /* timeoutSec */,
null /* previewSurface */, 8 /* sensorId */,
mBiometricLogger, mBiometricContext, 5 /* maxTemplatesPerUser */,
- true /* debugConsent */);
+ true /* debugConsent */,
+ (new FaceEnrollOptions.Builder()).setEnrollReason(ENROLL_SOURCE).build());
}
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapterTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapterTest.java
index 940fe69925b5..7a778d5dd211 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapterTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/HidlToAidlSensorAdapterTest.java
@@ -34,6 +34,7 @@ import android.hardware.biometrics.face.V1_0.IBiometricsFace;
import android.hardware.biometrics.face.V1_0.OptionalUint64;
import android.hardware.biometrics.face.V1_0.Status;
import android.hardware.face.Face;
+import android.hardware.face.FaceEnrollOptions;
import android.hardware.face.HidlFaceSensorConfig;
import android.os.Handler;
import android.os.RemoteException;
@@ -201,7 +202,7 @@ public class HidlToAidlSensorAdapterTest {
USER_ID, HAT, TAG, 1 /* requestId */, mBiometricUtils,
new int[]{} /* disabledFeatures */, ENROLL_TIMEOUT_SEC, null /* previewSurface */,
SENSOR_ID, mLogger, mBiometricContext, 1 /* maxTemplatesPerUser */,
- false /* debugConsent */));
+ false /* debugConsent */, (new FaceEnrollOptions.Builder()).build()));
mLooper.dispatchAll();
verify(mAidlResponseHandlerCallback).onEnrollSuccess();
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
index 3ee54f53d44f..916f6960efc9 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
@@ -21,6 +21,7 @@ import static com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.inOrder;
@@ -30,10 +31,12 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.common.OperationContext;
import android.hardware.biometrics.fingerprint.ISession;
import android.hardware.biometrics.fingerprint.PointerContext;
import android.hardware.fingerprint.Fingerprint;
+import android.hardware.fingerprint.FingerprintEnrollOptions;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.ISidefpsController;
@@ -78,6 +81,8 @@ import java.util.function.Consumer;
@SmallTest
public class FingerprintEnrollClientTest {
+ private static final int ENROLL_SOURCE = FingerprintEnrollOptions.ENROLL_REASON_SUW;
+
@Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Rule
public final CheckFlagsRule mCheckFlagsRule =
@@ -353,6 +358,16 @@ public class FingerprintEnrollClientTest {
c -> c.onEnrollResult(new Fingerprint("", 1, 1), 0));
}
+ @Test
+ public void testEnrollWithReasonLogsMetric() throws RemoteException {
+ final FingerprintEnrollClient client = createClient(4);
+ client.start(mCallback);
+ client.onEnrollResult(new Fingerprint("fingerprint", 1 /* faceId */, 20 /* deviceId */), 0);
+
+ verify(mBiometricLogger).logOnEnrolled(anyInt(), anyLong(), anyBoolean(),
+ eq(BiometricsProtoEnums.ENROLLMENT_SOURCE_SUW));
+ }
+
private void showHideOverlay_sidefpsControllerRemovalRefactor(
Consumer<FingerprintEnrollClient> block) throws RemoteException {
mSetFlagsRule.enableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR);
@@ -382,6 +397,8 @@ public class FingerprintEnrollClientTest {
HAT, "owner", mBiometricUtils, 8 /* sensorId */,
mBiometricLogger, mBiometricContext, mSensorProps, mUdfpsOverlayController,
mSideFpsController, mAuthenticationStateListeners, 6 /* maxTemplatesPerUser */,
- FingerprintManager.ENROLL_ENROLL);
+ FingerprintManager.ENROLL_ENROLL, (new FingerprintEnrollOptions.Builder())
+ .setEnrollReason(ENROLL_SOURCE).build()
+ );
}
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapterTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapterTest.java
index cbbc54547bf6..6c3bfe80510e 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapterTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapterTest.java
@@ -34,6 +34,7 @@ import android.app.AlarmManager;
import android.hardware.biometrics.IBiometricService;
import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
import android.hardware.fingerprint.Fingerprint;
+import android.hardware.fingerprint.FingerprintEnrollOptions;
import android.hardware.fingerprint.HidlFingerprintSensorConfig;
import android.os.Handler;
import android.os.HandlerThread;
@@ -217,7 +218,8 @@ public class HidlToAidlSensorAdapterTest {
1 /* requestId */, null /* listener */, USER_ID, HAT, TAG, mBiometricUtils,
SENSOR_ID, mLogger, mBiometricContext,
mHidlToAidlSensorAdapter.getSensorProperties(), null, null,
- mAuthenticationStateListeners, 5 /* maxTemplatesPerUser */, ENROLL_ENROLL));
+ mAuthenticationStateListeners, 5 /* maxTemplatesPerUser */, ENROLL_ENROLL,
+ (new FingerprintEnrollOptions.Builder()).build()));
mLooper.dispatchAll();
verify(mAidlResponseHandlerCallback).onEnrollSuccess();
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java
index 132b6219977a..ec3e97b641b8 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java
@@ -38,6 +38,7 @@ import static org.mockito.Mockito.when;
import android.app.WindowConfiguration;
import android.companion.virtual.IVirtualDeviceIntentInterceptor;
import android.companion.virtual.VirtualDeviceManager;
+import android.content.AttributionSource;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -712,6 +713,7 @@ public class GenericWindowPolicyControllerTest {
return new GenericWindowPolicyController(
0,
0,
+ AttributionSource.myAttributionSource(),
/* allowedUsers= */ new ArraySet<>(getCurrentUserId()),
/* activityLaunchAllowedByDefault= */ true,
/* activityPolicyExemptions= */ new ArraySet<>(),
@@ -732,6 +734,7 @@ public class GenericWindowPolicyControllerTest {
return new GenericWindowPolicyController(
0,
0,
+ AttributionSource.myAttributionSource(),
/* allowedUsers= */ new ArraySet<>(),
/* activityLaunchAllowedByDefault= */ true,
/* activityPolicyExemptions= */ new ArraySet<>(),
@@ -753,6 +756,7 @@ public class GenericWindowPolicyControllerTest {
return new GenericWindowPolicyController(
0,
0,
+ AttributionSource.myAttributionSource(),
/* allowedUsers= */ new ArraySet<>(getCurrentUserId()),
/* activityLaunchAllowedByDefault= */ true,
/* activityPolicyExemptions= */ new ArraySet<>(),
@@ -774,6 +778,7 @@ public class GenericWindowPolicyControllerTest {
return new GenericWindowPolicyController(
0,
0,
+ AttributionSource.myAttributionSource(),
/* allowedUsers= */ new ArraySet<>(getCurrentUserId()),
/* activityLaunchAllowedByDefault= */ true,
/* activityPolicyExemptions= */ Collections.singleton(blockedComponent),
@@ -795,6 +800,7 @@ public class GenericWindowPolicyControllerTest {
return new GenericWindowPolicyController(
0,
0,
+ AttributionSource.myAttributionSource(),
/* allowedUsers= */ new ArraySet<>(getCurrentUserId()),
/* activityLaunchAllowedByDefault= */ false,
/* activityPolicyExemptions= */ Collections.singleton(allowedComponent),
@@ -816,6 +822,7 @@ public class GenericWindowPolicyControllerTest {
return new GenericWindowPolicyController(
0,
0,
+ AttributionSource.myAttributionSource(),
/* allowedUsers= */ new ArraySet<>(getCurrentUserId()),
/* activityLaunchAllowedByDefault= */ true,
/* activityPolicyExemptions= */ new ArraySet<>(),
@@ -837,6 +844,7 @@ public class GenericWindowPolicyControllerTest {
return new GenericWindowPolicyController(
0,
0,
+ AttributionSource.myAttributionSource(),
/* allowedUsers= */ new ArraySet<>(getCurrentUserId()),
/* activityLaunchAllowedByDefault= */ true,
/* activityPolicyExemptions= */ new ArraySet<>(),
@@ -858,6 +866,7 @@ public class GenericWindowPolicyControllerTest {
return new GenericWindowPolicyController(
0,
0,
+ AttributionSource.myAttributionSource(),
/* allowedUsers= */ new ArraySet<>(getCurrentUserId()),
/* activityLaunchAllowedByDefault= */ true,
/* activityPolicyExemptions= */ new ArraySet<>(),
@@ -880,6 +889,7 @@ public class GenericWindowPolicyControllerTest {
return new GenericWindowPolicyController(
0,
0,
+ AttributionSource.myAttributionSource(),
/* allowedUsers= */ new ArraySet<>(getCurrentUserId()),
/* activityLaunchAllowedByDefault= */ true,
/* activityPolicyExemptions= */ new ArraySet<>(),
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 07e6ab2d08fb..fd880dd23f25 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
@@ -25,6 +25,7 @@ import static org.mockito.ArgumentMatchers.startsWith;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.verify;
+import android.content.AttributionSource;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.input.IInputManager;
import android.hardware.input.InputManagerGlobal;
@@ -95,6 +96,7 @@ public class InputControllerTest {
mInputController = new InputController(mNativeWrapperMock,
new Handler(TestableLooper.get(this).getLooper()),
InstrumentationRegistry.getTargetContext().getSystemService(WindowManager.class),
+ AttributionSource.myAttributionSource(),
threadVerifier);
}
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/SensorControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/SensorControllerTest.java
index faece4fbb325..67fc564fa778 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/SensorControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/SensorControllerTest.java
@@ -32,6 +32,7 @@ import android.companion.virtual.sensor.IVirtualSensorCallback;
import android.companion.virtual.sensor.VirtualSensor;
import android.companion.virtual.sensor.VirtualSensorConfig;
import android.companion.virtual.sensor.VirtualSensorEvent;
+import android.content.AttributionSource;
import android.hardware.Sensor;
import android.os.Binder;
import android.os.IBinder;
@@ -96,6 +97,7 @@ public class SensorControllerTest {
Throwable thrown = assertThrows(
RuntimeException.class,
() -> new SensorController(mVirtualDevice, VIRTUAL_DEVICE_ID,
+ AttributionSource.myAttributionSource(),
mVirtualSensorCallback, List.of(mVirtualSensorConfig)));
assertThat(thrown.getCause().getMessage())
@@ -168,6 +170,7 @@ public class SensorControllerTest {
doReturn(VIRTUAL_DEVICE_ID).when(mVirtualDevice).getDeviceId();
SensorController sensorController = new SensorController(mVirtualDevice, VIRTUAL_DEVICE_ID,
+ AttributionSource.myAttributionSource(),
mVirtualSensorCallback, List.of(mVirtualSensorConfig));
List<VirtualSensor> sensors = sensorController.getSensorList();
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 157e8931edba..5e0806d58af3 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
@@ -389,7 +389,8 @@ public class VirtualDeviceManagerServiceTest {
final InputController.DeviceCreationThreadVerifier threadVerifier = () -> true;
mInputController = new InputController(mNativeWrapperMock,
new Handler(TestableLooper.get(this).getLooper()),
- mContext.getSystemService(WindowManager.class), threadVerifier);
+ mContext.getSystemService(WindowManager.class),
+ AttributionSource.myAttributionSource(), threadVerifier);
mCameraAccessController =
new CameraAccessController(mContext, mLocalService, mCameraAccessBlockedCallback);
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
index ef5270e8f003..52f28b9bdc50 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
@@ -27,6 +27,7 @@ import static org.mockito.Mockito.verify;
import android.companion.virtual.audio.IAudioConfigChangedCallback;
import android.companion.virtual.audio.IAudioRoutingCallback;
+import android.content.AttributionSource;
import android.content.Context;
import android.content.ContextWrapper;
import android.media.AudioPlaybackConfiguration;
@@ -72,11 +73,13 @@ public class VirtualAudioControllerTest {
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = Mockito.spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
- mVirtualAudioController = new VirtualAudioController(mContext);
+ mVirtualAudioController = new VirtualAudioController(mContext,
+ AttributionSource.myAttributionSource());
mGenericWindowPolicyController =
new GenericWindowPolicyController(
FLAG_SECURE,
SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
+ AttributionSource.myAttributionSource(),
/* allowedUsers= */ new ArraySet<>(),
/* activityLaunchAllowedByDefault= */ true,
/* activityPolicyExemptions= */ new ArraySet<>(),
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 81981e6b16ca..9ca1df09ffe9 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
@@ -37,6 +37,7 @@ import android.companion.virtual.camera.VirtualCameraCallback;
import android.companion.virtual.camera.VirtualCameraConfig;
import android.companion.virtualcamera.IVirtualCameraService;
import android.companion.virtualcamera.VirtualCameraConfiguration;
+import android.content.AttributionSource;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.Looper;
@@ -104,7 +105,7 @@ public class VirtualCameraControllerTest {
public void registerCamera_registersCamera(int lensFacing) throws Exception {
mVirtualCameraController.registerCamera(createVirtualCameraConfig(
CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT_1, CAMERA_MAX_FPS_1, CAMERA_NAME_1,
- CAMERA_SENSOR_ORIENTATION_1, lensFacing));
+ CAMERA_SENSOR_ORIENTATION_1, lensFacing), AttributionSource.myAttributionSource());
ArgumentCaptor<VirtualCameraConfiguration> configurationCaptor =
ArgumentCaptor.forClass(VirtualCameraConfiguration.class);
@@ -121,7 +122,7 @@ public class VirtualCameraControllerTest {
VirtualCameraConfig config = createVirtualCameraConfig(
CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT_1, CAMERA_MAX_FPS_1, CAMERA_NAME_1,
CAMERA_SENSOR_ORIENTATION_1, CAMERA_LENS_FACING_1);
- mVirtualCameraController.registerCamera(config);
+ mVirtualCameraController.registerCamera(config, AttributionSource.myAttributionSource());
mVirtualCameraController.unregisterCamera(config);
@@ -131,11 +132,15 @@ public class VirtualCameraControllerTest {
@Test
public void close_unregistersAllCameras() throws Exception {
mVirtualCameraController.registerCamera(createVirtualCameraConfig(
- CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT_1, CAMERA_MAX_FPS_1, CAMERA_NAME_1,
- CAMERA_SENSOR_ORIENTATION_1, CAMERA_LENS_FACING_1));
+ CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT_1, CAMERA_MAX_FPS_1,
+ CAMERA_NAME_1,
+ CAMERA_SENSOR_ORIENTATION_1, CAMERA_LENS_FACING_1),
+ AttributionSource.myAttributionSource());
mVirtualCameraController.registerCamera(createVirtualCameraConfig(
- CAMERA_WIDTH_2, CAMERA_HEIGHT_2, CAMERA_FORMAT_2, CAMERA_MAX_FPS_2, CAMERA_NAME_2,
- CAMERA_SENSOR_ORIENTATION_2, CAMERA_LENS_FACING_2));
+ CAMERA_WIDTH_2, CAMERA_HEIGHT_2, CAMERA_FORMAT_2, CAMERA_MAX_FPS_2,
+ CAMERA_NAME_2,
+ CAMERA_SENSOR_ORIENTATION_2, CAMERA_LENS_FACING_2),
+ AttributionSource.myAttributionSource());
mVirtualCameraController.close();
@@ -160,11 +165,12 @@ public class VirtualCameraControllerTest {
int lensFacing) {
mVirtualCameraController.registerCamera(createVirtualCameraConfig(
CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT_1, CAMERA_MAX_FPS_1, CAMERA_NAME_1,
- CAMERA_SENSOR_ORIENTATION_1, lensFacing));
+ CAMERA_SENSOR_ORIENTATION_1, lensFacing), AttributionSource.myAttributionSource());
assertThrows(IllegalArgumentException.class,
() -> mVirtualCameraController.registerCamera(createVirtualCameraConfig(
- CAMERA_WIDTH_2, CAMERA_HEIGHT_2, CAMERA_FORMAT_2, CAMERA_MAX_FPS_2,
- CAMERA_NAME_2, CAMERA_SENSOR_ORIENTATION_2, lensFacing)));
+ CAMERA_WIDTH_2, CAMERA_HEIGHT_2, CAMERA_FORMAT_2, CAMERA_MAX_FPS_2,
+ CAMERA_NAME_2, CAMERA_SENSOR_ORIENTATION_2, lensFacing),
+ AttributionSource.myAttributionSource()));
}
@Parameters(method = "getAllLensFacingDirections")
@@ -176,8 +182,9 @@ public class VirtualCameraControllerTest {
assertThrows(IllegalArgumentException.class,
() -> mVirtualCameraController.registerCamera(createVirtualCameraConfig(
- CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT_1, CAMERA_MAX_FPS_1,
- CAMERA_NAME_1, CAMERA_SENSOR_ORIENTATION_1, lensFacing)));
+ CAMERA_WIDTH_1, CAMERA_HEIGHT_1, CAMERA_FORMAT_1, CAMERA_MAX_FPS_1,
+ CAMERA_NAME_1, CAMERA_SENSOR_ORIENTATION_1, lensFacing),
+ AttributionSource.myAttributionSource()));
}
private VirtualCameraConfig createVirtualCameraConfig(
@@ -203,7 +210,7 @@ public class VirtualCameraControllerTest {
}
private static Integer[] getAllLensFacingDirections() {
- return new Integer[] {
+ return new Integer[]{
LENS_FACING_BACK,
LENS_FACING_FRONT
};
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
index 34929356fe8c..5c6f3c92204a 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
@@ -205,7 +205,6 @@ import libcore.io.Streams;
import org.junit.After;
import org.junit.Assume;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.MethodRule;
@@ -2150,7 +2149,6 @@ public class NetworkPolicyManagerServiceTest {
verify(mNetworkManager).setFirewallChainEnabled(FIREWALL_CHAIN_BACKGROUND, true);
}
-
@Test
@RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
public void testBackgroundChainOnProcStateChange() throws Exception {
@@ -2218,7 +2216,6 @@ public class NetworkPolicyManagerServiceTest {
assertFalse(mService.isUidNetworkingBlocked(UID_B, false));
}
- @Ignore("Temporarily disabled until the feature is enabled")
@Test
@RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
public void testBackgroundChainOnTempAllowlistChange() throws Exception {
@@ -2248,7 +2245,6 @@ public class NetworkPolicyManagerServiceTest {
assertTrue(mService.isUidNetworkingBlocked(UID_A, false));
}
- @Ignore("Temporarily disabled until the feature is enabled")
@Test
@RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
public void testUidObserverFiltersProcStateChanges() throws Exception {
@@ -2311,7 +2307,6 @@ public class NetworkPolicyManagerServiceTest {
waitForUidEventHandlerIdle();
}
- @Ignore("Temporarily disabled until the feature is enabled")
@Test
@RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
public void testUidObserverFiltersStaleChanges() throws Exception {
@@ -2332,7 +2327,6 @@ public class NetworkPolicyManagerServiceTest {
waitForUidEventHandlerIdle();
}
- @Ignore("Temporarily disabled until the feature is enabled")
@Test
@RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
public void testUidObserverFiltersCapabilityChanges() throws Exception {
diff --git a/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java
index 1ae6e63c3ff1..4a43c2e6c180 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java
@@ -43,8 +43,10 @@ import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.InstallSourceInfo;
import android.content.pm.PackageInfo;
+import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
+import android.content.pm.ParceledListSlice;
import android.os.FileUtils;
import android.os.Looper;
import android.os.RemoteException;
@@ -115,9 +117,6 @@ public final class BackgroundInstallControlServiceTest {
private BackgroundInstallControlCallbackHelper mCallbackHelper;
@Captor
- private ArgumentCaptor<PackageManagerInternal.PackageListObserver> mPackageListObserverCaptor;
-
- @Captor
private ArgumentCaptor<UsageEventListener> mUsageEventListenerCaptor;
@Before
@@ -137,8 +136,8 @@ public final class BackgroundInstallControlServiceTest {
mUsageEventListener = mUsageEventListenerCaptor.getValue();
mBackgroundInstallControlService.onStart(true);
- verify(mPackageManagerInternal).getPackageList(mPackageListObserverCaptor.capture());
- mPackageListObserver = mPackageListObserverCaptor.getValue();
+
+ mPackageListObserver = mBackgroundInstallControlService.mPackageObserver;
}
@After
@@ -554,6 +553,7 @@ public final class BackgroundInstallControlServiceTest {
assertEquals(0, foregroundTimeFrames.size());
}
+ //package installed, but no UI interaction found
@Test
public void testHandleUsageEvent_packageAddedNoUsageEvent()
throws NoSuchFieldException, PackageManager.NameNotFoundException {
@@ -571,12 +571,10 @@ public final class BackgroundInstallControlServiceTest {
when(mPackageManager.getApplicationInfoAsUser(eq(PACKAGE_NAME_1), any(), anyInt()))
.thenReturn(appInfo);
- long createTimestamp =
- PACKAGE_ADD_TIMESTAMP_1 - (System.currentTimeMillis() - SystemClock.uptimeMillis());
FieldSetter.setField(
appInfo,
ApplicationInfo.class.getDeclaredField("createTimestamp"),
- createTimestamp);
+ convertToTestAdjustTimestamp(PACKAGE_ADD_TIMESTAMP_1));
int uid = USER_ID_1 * UserHandle.PER_USER_RANGE;
assertEquals(USER_ID_1, UserHandle.getUserId(uid));
@@ -590,6 +588,10 @@ public final class BackgroundInstallControlServiceTest {
assertTrue(packages.contains(USER_ID_1, PACKAGE_NAME_1));
}
+ private long convertToTestAdjustTimestamp(long timestamp) {
+ return timestamp - (System.currentTimeMillis() - SystemClock.uptimeMillis());
+ }
+
@Test
public void testHandleUsageEvent_packageAddedInsideTimeFrame()
throws NoSuchFieldException, PackageManager.NameNotFoundException {
@@ -607,12 +609,10 @@ public final class BackgroundInstallControlServiceTest {
when(mPackageManager.getApplicationInfoAsUser(eq(PACKAGE_NAME_1), any(), anyInt()))
.thenReturn(appInfo);
- long createTimestamp =
- PACKAGE_ADD_TIMESTAMP_1 - (System.currentTimeMillis() - SystemClock.uptimeMillis());
FieldSetter.setField(
appInfo,
ApplicationInfo.class.getDeclaredField("createTimestamp"),
- createTimestamp);
+ convertToTestAdjustTimestamp(PACKAGE_ADD_TIMESTAMP_1));
int uid = USER_ID_1 * UserHandle.PER_USER_RANGE;
assertEquals(USER_ID_1, UserHandle.getUserId(uid));
@@ -639,6 +639,122 @@ public final class BackgroundInstallControlServiceTest {
}
@Test
+ public void testHandleUsageEvent_fallsBackToAppInfoTimeWhenHistoricalSessionsNotFound()
+ throws NoSuchFieldException, PackageManager.NameNotFoundException {
+ assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
+ InstallSourceInfo installSourceInfo =
+ new InstallSourceInfo(
+ /* initiatingPackageName= */ INSTALLER_NAME_1,
+ /* initiatingPackageSigningInfo= */ null,
+ /* originatingPackageName= */ null,
+ /* installingPackageName= */ INSTALLER_NAME_1);
+ assertEquals(installSourceInfo.getInstallingPackageName(), INSTALLER_NAME_1);
+ when(mPackageManager.getInstallSourceInfo(anyString())).thenReturn(installSourceInfo);
+ ApplicationInfo appInfo = mock(ApplicationInfo.class);
+
+ when(mPackageManager.getApplicationInfoAsUser(eq(PACKAGE_NAME_1), any(), anyInt()))
+ .thenReturn(appInfo);
+
+ FieldSetter.setField(
+ appInfo,
+ ApplicationInfo.class.getDeclaredField("createTimestamp"),
+ // create timestamp is after generated foreground events (hence not considered
+ // foreground install)
+ convertToTestAdjustTimestamp(USAGE_EVENT_TIMESTAMP_2 + 100000));
+
+ int uid = USER_ID_1 * UserHandle.PER_USER_RANGE;
+ assertEquals(USER_ID_1, UserHandle.getUserId(uid));
+
+ createPackageManagerHistoricalSessions(List.of(), USER_ID_1);
+
+ // The 2 relevants usage events are before the timeframe, the app is not considered
+ // foreground installed.
+ doReturn(PERMISSION_GRANTED)
+ .when(mPermissionManager)
+ .checkPermission(anyString(), anyString(), anyString(), anyInt());
+ generateUsageEvent(
+ UsageEvents.Event.ACTIVITY_RESUMED,
+ USER_ID_1,
+ INSTALLER_NAME_1,
+ USAGE_EVENT_TIMESTAMP_1);
+ generateUsageEvent(
+ Event.ACTIVITY_STOPPED, USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_2);
+
+ mPackageListObserver.onPackageAdded(PACKAGE_NAME_1, uid);
+ mTestLooper.dispatchAll();
+
+ var packages = mBackgroundInstallControlService.getBackgroundInstalledPackages();
+ assertNotNull(packages);
+ assertEquals(1, packages.size());
+ assertTrue(packages.contains(USER_ID_1, PACKAGE_NAME_1));
+ }
+
+ @Test
+ public void testHandleUsageEvent_usesHistoricalSessionCreateTimeWhenHistoricalSessionsFound()
+ throws NoSuchFieldException, PackageManager.NameNotFoundException {
+ assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
+ InstallSourceInfo installSourceInfo =
+ new InstallSourceInfo(
+ /* initiatingPackageName= */ INSTALLER_NAME_1,
+ /* initiatingPackageSigningInfo= */ null,
+ /* originatingPackageName= */ null,
+ /* installingPackageName= */ INSTALLER_NAME_1);
+ assertEquals(installSourceInfo.getInstallingPackageName(), INSTALLER_NAME_1);
+ when(mPackageManager.getInstallSourceInfo(anyString())).thenReturn(installSourceInfo);
+ ApplicationInfo appInfo = mock(ApplicationInfo.class);
+
+ when(mPackageManager.getApplicationInfoAsUser(eq(PACKAGE_NAME_1), any(), anyInt()))
+ .thenReturn(appInfo);
+
+ FieldSetter.setField(
+ appInfo,
+ ApplicationInfo.class.getDeclaredField("createTimestamp"),
+ //create timestamp is out of window of (after) the interact events
+ convertToTestAdjustTimestamp(USAGE_EVENT_TIMESTAMP_2 + 1));
+
+ int uid = USER_ID_1 * UserHandle.PER_USER_RANGE;
+ assertEquals(USER_ID_1, UserHandle.getUserId(uid));
+
+ PackageInstaller.SessionInfo installSession1 = mock(PackageInstaller.SessionInfo.class);
+ PackageInstaller.SessionInfo installSession2 = mock(PackageInstaller.SessionInfo.class);
+ doReturn(convertToTestAdjustTimestamp(0L)).when(installSession1).getCreatedMillis();
+ doReturn(convertToTestAdjustTimestamp(PACKAGE_ADD_TIMESTAMP_1)).when(installSession2)
+ .getCreatedMillis();
+ doReturn(PACKAGE_NAME_1).when(installSession1).getAppPackageName();
+ doReturn(PACKAGE_NAME_1).when(installSession2).getAppPackageName();
+ createPackageManagerHistoricalSessions(List.of(installSession1, installSession2),
+ USER_ID_1);
+
+ // The following 2 generated usage events occur after historical session create times hence,
+ // considered foreground install. The appInfo createTimestamp occurs after events, so the
+ // app would be considered background install if it falls back to it as reference create
+ // timestamp.
+ doReturn(PERMISSION_GRANTED)
+ .when(mPermissionManager)
+ .checkPermission(anyString(), anyString(), anyString(), anyInt());
+ generateUsageEvent(
+ UsageEvents.Event.ACTIVITY_RESUMED,
+ USER_ID_1,
+ INSTALLER_NAME_1,
+ USAGE_EVENT_TIMESTAMP_1);
+ generateUsageEvent(
+ Event.ACTIVITY_STOPPED, USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_2);
+
+ mPackageListObserver.onPackageAdded(PACKAGE_NAME_1, uid);
+ mTestLooper.dispatchAll();
+
+ assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
+ }
+
+ private void createPackageManagerHistoricalSessions(
+ List<PackageInstaller.SessionInfo> sessions, int userId) {
+ ParceledListSlice<PackageInstaller.SessionInfo> mockParcelList =
+ mock(ParceledListSlice.class);
+ when(mockParcelList.getList()).thenReturn(sessions);
+ when(mPackageManagerInternal.getHistoricalSessions(userId)).thenReturn(mockParcelList);
+ }
+
+ @Test
public void testHandleUsageEvent_packageAddedOutsideTimeFrame1()
throws NoSuchFieldException, PackageManager.NameNotFoundException {
assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages());
@@ -655,12 +771,10 @@ public final class BackgroundInstallControlServiceTest {
when(mPackageManager.getApplicationInfoAsUser(eq(PACKAGE_NAME_1), any(), anyInt()))
.thenReturn(appInfo);
- long createTimestamp =
- PACKAGE_ADD_TIMESTAMP_1 - (System.currentTimeMillis() - SystemClock.uptimeMillis());
FieldSetter.setField(
appInfo,
ApplicationInfo.class.getDeclaredField("createTimestamp"),
- createTimestamp);
+ convertToTestAdjustTimestamp(PACKAGE_ADD_TIMESTAMP_1));
int uid = USER_ID_1 * UserHandle.PER_USER_RANGE;
assertEquals(USER_ID_1, UserHandle.getUserId(uid));
@@ -708,12 +822,10 @@ public final class BackgroundInstallControlServiceTest {
when(mPackageManager.getApplicationInfoAsUser(eq(PACKAGE_NAME_1), any(), anyInt()))
.thenReturn(appInfo);
- long createTimestamp =
- PACKAGE_ADD_TIMESTAMP_1 - (System.currentTimeMillis() - SystemClock.uptimeMillis());
FieldSetter.setField(
appInfo,
ApplicationInfo.class.getDeclaredField("createTimestamp"),
- createTimestamp);
+ convertToTestAdjustTimestamp(PACKAGE_ADD_TIMESTAMP_1));
int uid = USER_ID_1 * UserHandle.PER_USER_RANGE;
assertEquals(USER_ID_1, UserHandle.getUserId(uid));
@@ -765,12 +877,10 @@ public final class BackgroundInstallControlServiceTest {
when(mPackageManager.getApplicationInfoAsUser(eq(PACKAGE_NAME_1), any(), anyInt()))
.thenReturn(appInfo);
- long createTimestamp =
- PACKAGE_ADD_TIMESTAMP_1 - (System.currentTimeMillis() - SystemClock.uptimeMillis());
FieldSetter.setField(
appInfo,
ApplicationInfo.class.getDeclaredField("createTimestamp"),
- createTimestamp);
+ convertToTestAdjustTimestamp(PACKAGE_ADD_TIMESTAMP_1));
int uid = USER_ID_1 * UserHandle.PER_USER_RANGE;
assertEquals(USER_ID_1, UserHandle.getUserId(uid));
@@ -818,12 +928,10 @@ public final class BackgroundInstallControlServiceTest {
when(mPackageManager.getApplicationInfoAsUser(eq(PACKAGE_NAME_1), any(), anyInt()))
.thenReturn(appInfo);
- long createTimestamp =
- PACKAGE_ADD_TIMESTAMP_1 - (System.currentTimeMillis() - SystemClock.uptimeMillis());
FieldSetter.setField(
appInfo,
ApplicationInfo.class.getDeclaredField("createTimestamp"),
- createTimestamp);
+ convertToTestAdjustTimestamp(PACKAGE_ADD_TIMESTAMP_1));
int uid = USER_ID_1 * UserHandle.PER_USER_RANGE;
assertEquals(USER_ID_1, UserHandle.getUserId(uid));
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
index f1d3ba9db489..1331ae173b18 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
@@ -197,8 +197,6 @@ public class UserManagerServiceUserPropertiesTest {
assertEqualGetterOrThrows(orig::getAlwaysVisible, copy::getAlwaysVisible, exposeAll);
assertEqualGetterOrThrows(orig::getAllowStoppingUserWithDelayedLocking,
copy::getAllowStoppingUserWithDelayedLocking, exposeAll);
- assertEqualGetterOrThrows(orig::areItemsRestrictedOnHomeScreen,
- copy::areItemsRestrictedOnHomeScreen, exposeAll);
// Items requiring hasManagePermission - put them here using hasManagePermission.
assertEqualGetterOrThrows(orig::getShowInSettings, copy::getShowInSettings,
@@ -220,6 +218,8 @@ public class UserManagerServiceUserPropertiesTest {
copy::getCrossProfileContentSharingStrategy, true);
assertEqualGetterOrThrows(orig::getProfileApiVisibility, copy::getProfileApiVisibility,
true);
+ assertEqualGetterOrThrows(orig::areItemsRestrictedOnHomeScreen,
+ copy::areItemsRestrictedOnHomeScreen, true);
}
/**
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 7f7cc35ced55..44c464ed6adf 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -350,8 +350,8 @@ public final class UserManagerTest {
privateProfileUserProperties::getAllowStoppingUserWithDelayedLocking);
assertThat(typeProps.getProfileApiVisibility()).isEqualTo(
privateProfileUserProperties.getProfileApiVisibility());
- assertThrows(SecurityException.class,
- privateProfileUserProperties::areItemsRestrictedOnHomeScreen);
+ assertThat(typeProps.areItemsRestrictedOnHomeScreen())
+ .isEqualTo(privateProfileUserProperties.areItemsRestrictedOnHomeScreen());
compareDrawables(mUserManager.getUserBadge(),
Resources.getSystem().getDrawable(userTypeDetails.getBadgePlain()));
diff --git a/services/tests/servicestests/src/com/android/server/search/SearchablesTest.java b/services/tests/servicestests/src/com/android/server/search/SearchablesTest.java
index 8bccce102887..f5c6795484fa 100644
--- a/services/tests/servicestests/src/com/android/server/search/SearchablesTest.java
+++ b/services/tests/servicestests/src/com/android/server/search/SearchablesTest.java
@@ -16,41 +16,60 @@
package com.android.server.search;
-import android.app.SearchManager;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doReturn;
+
import android.app.SearchableInfo;
import android.app.SearchableInfo.ActionKeyInfo;
import android.content.ComponentName;
import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ProviderInfo;
-import android.content.pm.ResolveInfo;
-import android.content.res.Resources;
-import android.content.res.XmlResourceParser;
-import android.os.RemoteException;
-import com.android.server.search.Searchables;
-import android.test.AndroidTestCase;
+import android.content.pm.PackageManagerInternal;
import android.test.MoreAsserts;
-import android.test.mock.MockContext;
-import android.test.mock.MockPackageManager;
-import android.test.suitebuilder.annotation.SmallTest;
import android.view.KeyEvent;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.LocalServices;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
import java.util.ArrayList;
-import java.util.List;
-/**
- * To launch this test from the command line:
- *
- * adb shell am instrument -w \
- * -e class com.android.unit_tests.SearchablesTest \
- * com.android.unit_tests/android.test.InstrumentationTestRunner
- */
+@RunWith(AndroidJUnit4.class)
@SmallTest
-public class SearchablesTest extends AndroidTestCase {
-
+public class SearchablesTest {
+ @Mock protected PackageManagerInternal mPackageManagerInternal;
+
+ private Context mContext;
+
+ @Before
+ public final void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ LocalServices.removeServiceForTest(PackageManagerInternal.class);
+ LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternal);
+
+ mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ }
+
+ @After
+ public final void tearDown() {
+ Mockito.framework().clearInlineMocks();
+ }
+
/*
* SearchableInfo tests
* Mock the context so I can provide very specific input data
@@ -69,15 +88,15 @@ public class SearchablesTest extends AndroidTestCase {
* Test that non-searchable activities return no searchable info (this would typically
* trigger the use of the default searchable e.g. contacts)
*/
+ @Test
public void testNonSearchable() {
// test basic array & hashmap
Searchables searchables = new Searchables(mContext, 0);
searchables.updateSearchableList();
// confirm that we return null for non-searchy activities
- ComponentName nonActivity = new ComponentName(
- "com.android.frameworks.coretests",
- "com.android.frameworks.coretests.activity.NO_SEARCH_ACTIVITY");
+ ComponentName nonActivity = new ComponentName("com.android.frameworks.servicestests",
+ "com.android.frameworks.servicestests.activity.NO_SEARCH_ACTIVITY");
SearchableInfo si = searchables.getSearchableInfo(nonActivity);
assertNull(si);
}
@@ -97,13 +116,11 @@ public class SearchablesTest extends AndroidTestCase {
* getIcon works
*/
+ @Test
public void testSearchablesListReal() {
- MyMockPackageManager mockPM = new MyMockPackageManager(mContext.getPackageManager());
- MyMockContext mockContext = new MyMockContext(mContext, mockPM);
+ doReturn(true).when(mPackageManagerInternal).canAccessComponent(anyInt(), any(), anyInt());
- // build item list with real-world source data
- mockPM.setSearchablesMode(MyMockPackageManager.SEARCHABLES_PASSTHROUGH);
- Searchables searchables = new Searchables(mockContext, 0);
+ Searchables searchables = new Searchables(mContext, 0);
searchables.updateSearchableList();
// tests with "real" searchables (deprecate, this should be a unit test)
ArrayList<SearchableInfo> searchablesList = searchables.getSearchablesList();
@@ -117,12 +134,11 @@ public class SearchablesTest extends AndroidTestCase {
/**
* This round of tests confirms good operations with "zero" searchables found
*/
+ @Test
public void testSearchablesListEmpty() {
- MyMockPackageManager mockPM = new MyMockPackageManager(mContext.getPackageManager());
- MyMockContext mockContext = new MyMockContext(mContext, mockPM);
+ doReturn(false).when(mPackageManagerInternal).canAccessComponent(anyInt(), any(), anyInt());
- mockPM.setSearchablesMode(MyMockPackageManager.SEARCHABLES_MOCK_ZERO);
- Searchables searchables = new Searchables(mockContext, 0);
+ Searchables searchables = new Searchables(mContext, 0);
searchables.updateSearchableList();
ArrayList<SearchableInfo> searchablesList = searchables.getSearchablesList();
assertNotNull(searchablesList);
@@ -219,231 +235,6 @@ public class SearchablesTest extends AndroidTestCase {
if (s != null) {
MoreAsserts.assertNotEqual(s, "");
}
- }
-
- /**
- * This is a mock for context. Used to perform a true unit test on SearchableInfo.
- *
- */
- private class MyMockContext extends MockContext {
-
- protected Context mRealContext;
- protected PackageManager mPackageManager;
-
- /**
- * Constructor.
- *
- * @param realContext Please pass in a real context for some pass-throughs to function.
- */
- MyMockContext(Context realContext, PackageManager packageManager) {
- mRealContext = realContext;
- mPackageManager = packageManager;
- }
-
- /**
- * Resources. Pass through for now.
- */
- @Override
- public Resources getResources() {
- return mRealContext.getResources();
- }
-
- /**
- * Package manager. Pass through for now.
- */
- @Override
- public PackageManager getPackageManager() {
- return mPackageManager;
- }
-
- /**
- * Package manager. Pass through for now.
- */
- @Override
- public Context createPackageContext(String packageName, int flags)
- throws PackageManager.NameNotFoundException {
- return mRealContext.createPackageContext(packageName, flags);
- }
-
- /**
- * Message broadcast. Pass through for now.
- */
- @Override
- public void sendBroadcast(Intent intent) {
- mRealContext.sendBroadcast(intent);
- }
- }
-
-/**
- * This is a mock for package manager. Used to perform a true unit test on SearchableInfo.
- *
- */
- private class MyMockPackageManager extends MockPackageManager {
-
- public final static int SEARCHABLES_PASSTHROUGH = 0;
- public final static int SEARCHABLES_MOCK_ZERO = 1;
- public final static int SEARCHABLES_MOCK_ONEGOOD = 2;
- public final static int SEARCHABLES_MOCK_ONEGOOD_ONEBAD = 3;
-
- protected PackageManager mRealPackageManager;
- protected int mSearchablesMode;
-
- public MyMockPackageManager(PackageManager realPM) {
- mRealPackageManager = realPM;
- mSearchablesMode = SEARCHABLES_PASSTHROUGH;
- }
-
- /**
- * Set the mode for various tests.
- */
- public void setSearchablesMode(int newMode) {
- switch (newMode) {
- case SEARCHABLES_PASSTHROUGH:
- case SEARCHABLES_MOCK_ZERO:
- mSearchablesMode = newMode;
- break;
-
- default:
- throw new UnsupportedOperationException();
- }
- }
-
- /**
- * Find activities that support a given intent.
- *
- * Retrieve all activities that can be performed for the given intent.
- *
- * @param intent The desired intent as per resolveActivity().
- * @param flags Additional option flags. The most important is
- * MATCH_DEFAULT_ONLY, to limit the resolution to only
- * those activities that support the CATEGORY_DEFAULT.
- *
- * @return A List<ResolveInfo> containing one entry for each matching
- * Activity. These are ordered from best to worst match -- that
- * is, the first item in the list is what is returned by
- * resolveActivity(). If there are no matching activities, an empty
- * list is returned.
- */
- @Override
- public List<ResolveInfo> queryIntentActivities(Intent intent, int flags) {
- assertNotNull(intent);
- assertTrue(intent.getAction().equals(Intent.ACTION_SEARCH)
- || intent.getAction().equals(Intent.ACTION_WEB_SEARCH)
- || intent.getAction().equals(SearchManager.INTENT_ACTION_GLOBAL_SEARCH));
- switch (mSearchablesMode) {
- case SEARCHABLES_PASSTHROUGH:
- return mRealPackageManager.queryIntentActivities(intent, flags);
- case SEARCHABLES_MOCK_ZERO:
- return null;
- default:
- throw new UnsupportedOperationException();
- }
- }
-
- @Override
- public ResolveInfo resolveActivity(Intent intent, int flags) {
- assertNotNull(intent);
- assertTrue(intent.getAction().equals(Intent.ACTION_WEB_SEARCH)
- || intent.getAction().equals(SearchManager.INTENT_ACTION_GLOBAL_SEARCH));
- switch (mSearchablesMode) {
- case SEARCHABLES_PASSTHROUGH:
- return mRealPackageManager.resolveActivity(intent, flags);
- case SEARCHABLES_MOCK_ZERO:
- return null;
- default:
- throw new UnsupportedOperationException();
- }
- }
-
- /**
- * Retrieve an XML file from a package. This is a low-level API used to
- * retrieve XML meta data.
- *
- * @param packageName The name of the package that this xml is coming from.
- * Can not be null.
- * @param resid The resource identifier of the desired xml. Can not be 0.
- * @param appInfo Overall information about <var>packageName</var>. This
- * may be null, in which case the application information will be retrieved
- * for you if needed; if you already have this information around, it can
- * be much more efficient to supply it here.
- *
- * @return Returns an TypedXmlPullParser allowing you to parse out the XML
- * data. Returns null if the xml resource could not be found for any
- * reason.
- */
- @Override
- public XmlResourceParser getXml(String packageName, int resid, ApplicationInfo appInfo) {
- assertNotNull(packageName);
- MoreAsserts.assertNotEqual(packageName, "");
- MoreAsserts.assertNotEqual(resid, 0);
- switch (mSearchablesMode) {
- case SEARCHABLES_PASSTHROUGH:
- return mRealPackageManager.getXml(packageName, resid, appInfo);
- case SEARCHABLES_MOCK_ZERO:
- default:
- throw new UnsupportedOperationException();
- }
- }
-
- /**
- * Find a single content provider by its base path name.
- *
- * @param name The name of the provider to find.
- * @param flags Additional option flags. Currently should always be 0.
- *
- * @return ContentProviderInfo Information about the provider, if found,
- * else null.
- */
- @Override
- public ProviderInfo resolveContentProvider(String name, int flags) {
- assertNotNull(name);
- MoreAsserts.assertNotEqual(name, "");
- assertEquals(flags, 0);
- switch (mSearchablesMode) {
- case SEARCHABLES_PASSTHROUGH:
- return mRealPackageManager.resolveContentProvider(name, flags);
- case SEARCHABLES_MOCK_ZERO:
- default:
- throw new UnsupportedOperationException();
- }
- }
-
- /**
- * Get the activity information for a particular activity.
- *
- * @param name The name of the activity to find.
- * @param flags Additional option flags.
- *
- * @return ActivityInfo Information about the activity, if found, else null.
- */
- @Override
- public ActivityInfo getActivityInfo(ComponentName name, int flags)
- throws NameNotFoundException {
- assertNotNull(name);
- MoreAsserts.assertNotEqual(name, "");
- switch (mSearchablesMode) {
- case SEARCHABLES_PASSTHROUGH:
- return mRealPackageManager.getActivityInfo(name, flags);
- case SEARCHABLES_MOCK_ZERO:
- throw new NameNotFoundException();
- default:
- throw new UnsupportedOperationException();
- }
- }
-
- @Override
- public int checkPermission(String permName, String pkgName) {
- assertNotNull(permName);
- assertNotNull(pkgName);
- switch (mSearchablesMode) {
- case SEARCHABLES_PASSTHROUGH:
- return mRealPackageManager.checkPermission(permName, pkgName);
- case SEARCHABLES_MOCK_ZERO:
- return PackageManager.PERMISSION_DENIED;
- default:
- throw new UnsupportedOperationException();
- }
- }
}
}
diff --git a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
index eddff9abec21..3bc089fb3f5d 100644
--- a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
@@ -602,56 +602,6 @@ public class SystemConfigTest {
}
/**
- * Test that getRollbackDenylistedPackages works correctly for the tag:
- * {@code automatic-rollback-denylisted-app}.
- */
- @Test
- public void automaticRollbackDeny_vending() throws IOException {
- final String contents =
- "<config>\n"
- + " <automatic-rollback-denylisted-app package=\"com.android.vending\" />\n"
- + "</config>";
- final File folder = createTempSubfolder("folder");
- createTempFile(folder, "automatic-rollback-denylisted-app.xml", contents);
-
- readPermissions(folder, /* Grant all permission flags */ ~0);
-
- assertThat(mSysConfig.getAutomaticRollbackDenylistedPackages())
- .containsExactly("com.android.vending");
- }
-
- /**
- * Test that getRollbackDenylistedPackages works correctly for the tag:
- * {@code automatic-rollback-denylisted-app} without any packages.
- */
- @Test
- public void automaticRollbackDeny_empty() throws IOException {
- final String contents =
- "<config>\n"
- + " <automatic-rollback-denylisted-app />\n"
- + "</config>";
- final File folder = createTempSubfolder("folder");
- createTempFile(folder, "automatic-rollback-denylisted-app.xml", contents);
-
- readPermissions(folder, /* Grant all permission flags */ ~0);
-
- assertThat(mSysConfig.getAutomaticRollbackDenylistedPackages()).isEmpty();
- }
-
- /**
- * Test that getRollbackDenylistedPackages works correctly for the tag:
- * {@code automatic-rollback-denylisted-app} without the corresponding config.
- */
- @Test
- public void automaticRollbackDeny_noConfig() throws IOException {
- final File folder = createTempSubfolder("folder");
-
- readPermissions(folder, /* Grant all permission flags */ ~0);
-
- assertThat(mSysConfig.getAutomaticRollbackDenylistedPackages()).isEmpty();
- }
-
- /**
* Tests that readPermissions works correctly for the tag: {@code update-ownership}.
*/
@Test
@@ -712,7 +662,7 @@ public class SystemConfigTest {
android.permission.flags.Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED)
public void getEnhancedConfirmationTrustedInstallers_returnsTrustedInstallers()
throws IOException {
- String pkgName = "com.example.app";
+ String packageName = "com.example.app";
String certificateDigestStr = "E9:7A:BC:2C:D1:CA:8D:58:6A:57:0B:8C:F8:60:AA:D2:"
+ "8D:13:30:2A:FB:C9:00:2C:5D:53:B2:6C:09:A4:85:A0";
@@ -720,7 +670,7 @@ public class SystemConfigTest {
.toByteArray();
String contents = "<config>"
+ "<" + "enhanced-confirmation-trusted-installer" + " "
- + "package=\"" + pkgName + "\""
+ + "package=\"" + packageName + "\""
+ " sha256-cert-digest=\"" + certificateDigestStr + "\""
+ "/>"
+ "</config>";
@@ -734,10 +684,10 @@ public class SystemConfigTest {
assertThat(actualTrustedInstallers.size()).isEqualTo(1);
SignedPackage actual = actualTrustedInstallers.stream().findFirst().orElseThrow();
- SignedPackage expected = new SignedPackage(pkgName, certificateDigest);
+ SignedPackage expected = new SignedPackage(packageName, certificateDigest);
assertThat(actual.getCertificateDigest()).isEqualTo(expected.getCertificateDigest());
- assertThat(actual.getPkgName()).isEqualTo(expected.getPkgName());
+ assertThat(actual.getPackageName()).isEqualTo(expected.getPackageName());
assertThat(actual).isEqualTo(expected);
}
diff --git a/services/tests/servicestests/src/com/android/server/utils/AnrTimerTest.java b/services/tests/servicestests/src/com/android/server/utils/AnrTimerTest.java
index 6c085e085f4e..c8bef45af839 100644
--- a/services/tests/servicestests/src/com/android/server/utils/AnrTimerTest.java
+++ b/services/tests/servicestests/src/com/android/server/utils/AnrTimerTest.java
@@ -16,9 +16,7 @@
package com.android.server.utils;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
import android.os.Handler;
import android.os.Looper;
@@ -110,7 +108,7 @@ public class AnrTimerTest {
*/
TestArg[] messages(int expected) {
synchronized (mLock) {
- assertEquals(expected, mMessages.size());
+ assertThat(mMessages.size()).isEqualTo(expected);
return mMessages.toArray(new TestArg[expected]);
}
}
@@ -154,8 +152,8 @@ public class AnrTimerTest {
}
void validate(TestArg expected, TestArg actual) {
- assertEquals(expected, actual);
- assertEquals(actual.what, MSG_TIMEOUT);
+ assertThat(actual).isEqualTo(expected);
+ assertThat(actual.what).isEqualTo(MSG_TIMEOUT);
}
@Parameters(name = "featureEnabled={0}")
@@ -180,11 +178,11 @@ public class AnrTimerTest {
Helper helper = new Helper(1);
try (TestAnrTimer timer = new TestAnrTimer(helper)) {
// One-time check that the injector is working as expected.
- assertEquals(mEnabled, timer.serviceEnabled());
+ assertThat(mEnabled).isEqualTo(timer.serviceEnabled());
TestArg t = new TestArg(1, 1);
timer.start(t, 10);
// Delivery is immediate but occurs on a different thread.
- assertTrue(helper.await(5000));
+ assertThat(helper.await(5000)).isTrue();
TestArg[] result = helper.messages(1);
validate(t, result[0]);
}
@@ -201,10 +199,10 @@ public class AnrTimerTest {
TestArg t = new TestArg(1, 1);
timer.start(t, 10000);
// Briefly pause.
- assertFalse(helper.await(10));
+ assertThat(helper.await(10)).isFalse();
timer.start(t, 10);
// Delivery is immediate but occurs on a different thread.
- assertTrue(helper.await(5000));
+ assertThat(helper.await(5000)).isTrue();
TestArg[] result = helper.messages(1);
validate(t, result[0]);
}
@@ -221,7 +219,7 @@ public class AnrTimerTest {
TestArg t = new TestArg(1, 1);
timer.start(t, 0);
// Delivery is immediate but occurs on a different thread.
- assertTrue(helper.await(5000));
+ assertThat(helper.await(5000)).isTrue();
TestArg[] result = helper.messages(1);
validate(t, result[0]);
}
@@ -243,7 +241,7 @@ public class AnrTimerTest {
timer.start(t2, 60);
timer.start(t3, 40);
// Delivery is immediate but occurs on a different thread.
- assertTrue(helper.await(5000));
+ assertThat(helper.await(5000)).isTrue();
TestArg[] result = helper.messages(3);
validate(t3, result[0]);
validate(t1, result[1]);
@@ -269,7 +267,7 @@ public class AnrTimerTest {
x2.start(t2, 60);
x3.start(t3, 40);
// Delivery is immediate but occurs on a different thread.
- assertTrue(helper.await(5000));
+ assertThat(helper.await(5000)).isTrue();
TestArg[] result = helper.messages(3);
validate(t3, result[0]);
validate(t1, result[1]);
@@ -292,10 +290,10 @@ public class AnrTimerTest {
timer.start(t2, 60);
timer.start(t3, 40);
// Briefly pause.
- assertFalse(helper.await(10));
+ assertThat(helper.await(10)).isFalse();
timer.cancel(t1);
// Delivery is immediate but occurs on a different thread.
- assertTrue(helper.await(5000));
+ assertThat(helper.await(5000)).isTrue();
TestArg[] result = helper.messages(2);
validate(t3, result[0]);
validate(t2, result[1]);
@@ -319,7 +317,7 @@ public class AnrTimerTest {
@Test
public void testDumpOutput() throws Exception {
String r1 = getDumpOutput();
- assertEquals(false, r1.contains("timer:"));
+ assertThat(r1).doesNotContain("timer:");
Helper helper = new Helper(2);
TestArg t1 = new TestArg(1, 1);
@@ -332,12 +330,15 @@ public class AnrTimerTest {
String r2 = getDumpOutput();
// There are timers in the list if and only if the feature is enabled.
- final boolean expected = mEnabled;
- assertEquals(expected, r2.contains("timer:"));
+ if (mEnabled) {
+ assertThat(r2).contains("timer:");
+ } else {
+ assertThat(r2).doesNotContain("timer:");
+ }
}
String r3 = getDumpOutput();
- assertEquals(false, r3.contains("timer:"));
+ assertThat(r3).doesNotContain("timer:");
}
/**
@@ -351,7 +352,7 @@ public class AnrTimerTest {
if (!mEnabled) return;
String r1 = getDumpOutput();
- assertEquals(false, r1.contains("timer:"));
+ assertThat(r1).doesNotContain("timer:");
Helper helper = new Helper(2);
TestArg t1 = new TestArg(1, 1);
@@ -366,8 +367,11 @@ public class AnrTimerTest {
String r2 = getDumpOutput();
// There are timers in the list if and only if the feature is enabled.
- final boolean expected = mEnabled;
- assertEquals(expected, r2.contains("timer:"));
+ if (mEnabled) {
+ assertThat(r2).contains("timer:");
+ } else {
+ assertThat(r2).doesNotContain("timer:");
+ }
}
// Try to make finalizers run. The timer object above should be a candidate. Finalizers
@@ -382,7 +386,7 @@ public class AnrTimerTest {
}
// The timer was not explicitly closed but it should have been implicitly closed by GC.
- assertEquals(false, r3.contains("timer:"));
+ assertThat(r3).doesNotContain("timer:");
}
// TODO: [b/302724778] Remove manual JNI load
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 6e5c180de347..8c50ef406ec6 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -260,6 +260,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
AppOpsManager mAppOps;
TestableFlagResolver mTestFlagResolver = new TestableFlagResolver();
ZenModeEventLoggerFake mZenModeEventLogger;
+ private String mPkg;
@Before
public void setUp() throws PackageManager.NameNotFoundException {
@@ -270,7 +271,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
mContentResolver = mContext.getContentResolver();
mResources = mock(Resources.class, withSettings()
.spiedInstance(mContext.getResources()));
- String pkg = mContext.getPackageName();
+ mPkg = mContext.getPackageName();
try {
when(mResources.getXml(R.xml.default_zen_mode_config)).thenReturn(
getDefaultConfigParser());
@@ -301,14 +302,14 @@ public class ZenModeHelperTest extends UiServiceTestCase {
when(mPackageManager.getPackageUidAsUser(eq(CUSTOM_PKG_NAME), anyInt()))
.thenReturn(CUSTOM_PKG_UID);
when(mPackageManager.getPackagesForUid(anyInt())).thenReturn(
- new String[]{pkg});
+ new String[]{mPkg});
ApplicationInfo appInfoSpy = spy(new ApplicationInfo());
appInfoSpy.icon = ICON_RES_ID;
when(appInfoSpy.loadLabel(any())).thenReturn(CUSTOM_APP_LABEL);
when(mPackageManager.getApplicationInfo(eq(CUSTOM_PKG_NAME), anyInt()))
.thenReturn(appInfoSpy);
- when(mPackageManager.getApplicationInfo(eq(mContext.getPackageName()), anyInt()))
+ when(mPackageManager.getApplicationInfo(eq(mPkg), anyInt()))
.thenReturn(appInfoSpy);
mZenModeHelper.mPm = mPackageManager;
@@ -2512,16 +2513,16 @@ public class ZenModeHelperTest extends UiServiceTestCase {
scheduleInfo.endHour = 1;
Uri sharedUri = ZenModeConfig.toScheduleConditionId(scheduleInfo);
AutomaticZenRule zenRule = new AutomaticZenRule("name",
- new ComponentName("android", "ScheduleConditionProvider"),
+ new ComponentName(mPkg, "ScheduleConditionProvider"),
sharedUri,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelper.addAutomaticZenRule("android", zenRule,
+ String id = mZenModeHelper.addAutomaticZenRule(mPkg, zenRule,
UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
AutomaticZenRule zenRule2 = new AutomaticZenRule("name2",
- new ComponentName("android", "ScheduleConditionProvider"),
+ new ComponentName(mPkg, "ScheduleConditionProvider"),
sharedUri,
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id2 = mZenModeHelper.addAutomaticZenRule("android", zenRule2,
+ String id2 = mZenModeHelper.addAutomaticZenRule(mPkg, zenRule2,
UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID);
Condition condition = new Condition(sharedUri, "", STATE_TRUE);
@@ -2531,11 +2532,11 @@ public class ZenModeHelperTest extends UiServiceTestCase {
for (ZenModeConfig.ZenRule rule : mZenModeHelper.mConfig.automaticRules.values()) {
if (rule.id.equals(id)) {
assertNotNull(rule.condition);
- assertTrue(rule.condition.state == STATE_TRUE);
+ assertEquals(STATE_TRUE, rule.condition.state);
}
if (rule.id.equals(id2)) {
assertNotNull(rule.condition);
- assertTrue(rule.condition.state == STATE_TRUE);
+ assertEquals(STATE_TRUE, rule.condition.state);
}
}
@@ -2546,11 +2547,11 @@ public class ZenModeHelperTest extends UiServiceTestCase {
for (ZenModeConfig.ZenRule rule : mZenModeHelper.mConfig.automaticRules.values()) {
if (rule.id.equals(id)) {
assertNotNull(rule.condition);
- assertTrue(rule.condition.state == STATE_FALSE);
+ assertEquals(STATE_FALSE, rule.condition.state);
}
if (rule.id.equals(id2)) {
assertNotNull(rule.condition);
- assertTrue(rule.condition.state == STATE_FALSE);
+ assertEquals(STATE_FALSE, rule.condition.state);
}
}
}
@@ -3637,14 +3638,14 @@ public class ZenModeHelperTest extends UiServiceTestCase {
AutomaticZenRule bedtime = new AutomaticZenRule.Builder("Bedtime Mode (TM)", CONDITION_ID)
.setType(TYPE_BEDTIME)
.build();
- String bedtimeRuleId = mZenModeHelper.addAutomaticZenRule("pkg", bedtime, UPDATE_ORIGIN_APP,
+ String bedtimeRuleId = mZenModeHelper.addAutomaticZenRule(mPkg, bedtime, UPDATE_ORIGIN_APP,
"reason", CUSTOM_PKG_UID);
// Create immersive rule
AutomaticZenRule immersive = new AutomaticZenRule.Builder("Immersed", CONDITION_ID)
.setType(TYPE_IMMERSIVE)
.build();
- String immersiveId = mZenModeHelper.addAutomaticZenRule("pkg", immersive, UPDATE_ORIGIN_APP,
+ String immersiveId = mZenModeHelper.addAutomaticZenRule(mPkg, immersive, UPDATE_ORIGIN_APP,
"reason", CUSTOM_PKG_UID);
// Event 2: Activate bedtime rule
@@ -5384,6 +5385,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
ZenRule rule = new ZenRule();
rule.pkg = pkg;
rule.creationTime = createdAt.toEpochMilli();
+ rule.enabled = true;
rule.deletionInstant = deletedAt;
// Plus stuff so that isValidAutomaticRule() passes
rule.name = "A rule from " + pkg + " created on " + createdAt;
@@ -5392,6 +5394,89 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
+ @EnableFlags(Flags.FLAG_MODES_API)
+ public void getAutomaticZenRuleState_ownedRule_returnsRuleState() {
+ String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
+ new AutomaticZenRule.Builder("Rule", CONDITION_ID)
+ .setConfigurationActivity(
+ new ComponentName(mContext.getPackageName(), "Blah"))
+ .build(),
+ UPDATE_ORIGIN_APP, "reasons", CUSTOM_PKG_UID);
+
+ // Null condition -> STATE_FALSE
+ assertThat(mZenModeHelper.getAutomaticZenRuleState(id)).isEqualTo(Condition.STATE_FALSE);
+
+ mZenModeHelper.setAutomaticZenRuleState(id, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+ CUSTOM_PKG_UID);
+ assertThat(mZenModeHelper.getAutomaticZenRuleState(id)).isEqualTo(Condition.STATE_TRUE);
+
+ mZenModeHelper.setAutomaticZenRuleState(id, CONDITION_FALSE, UPDATE_ORIGIN_APP,
+ CUSTOM_PKG_UID);
+ assertThat(mZenModeHelper.getAutomaticZenRuleState(id)).isEqualTo(Condition.STATE_FALSE);
+
+ mZenModeHelper.removeAutomaticZenRule(id, UPDATE_ORIGIN_APP, "", CUSTOM_PKG_UID);
+ assertThat(mZenModeHelper.getAutomaticZenRuleState(id)).isEqualTo(Condition.STATE_UNKNOWN);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_API)
+ public void getAutomaticZenRuleState_notOwnedRule_returnsStateUnknown() {
+ // Assume existence of a system-owned rule that is currently ACTIVE.
+ ZenRule systemRule = newZenRule("android", Instant.now(), null);
+ systemRule.zenMode = ZEN_MODE_ALARMS;
+ systemRule.condition = new Condition(systemRule.conditionId, "on", Condition.STATE_TRUE);
+ ZenModeConfig config = mZenModeHelper.mConfig.copy();
+ config.automaticRules.put("systemRule", systemRule);
+ mZenModeHelper.setConfig(config, null, UPDATE_ORIGIN_INIT, "", Process.SYSTEM_UID);
+ assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_ALARMS);
+
+ assertThat(mZenModeHelper.getAutomaticZenRuleState("systemRule")).isEqualTo(
+ Condition.STATE_UNKNOWN);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_API)
+ public void setAutomaticZenRuleState_idForNotOwnedRule_ignored() {
+ // Assume existence of an other-package-owned rule that is currently ACTIVE.
+ assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
+ ZenRule otherRule = newZenRule("another.package", Instant.now(), null);
+ otherRule.zenMode = ZEN_MODE_ALARMS;
+ otherRule.condition = new Condition(otherRule.conditionId, "on", Condition.STATE_TRUE);
+ ZenModeConfig config = mZenModeHelper.mConfig.copy();
+ config.automaticRules.put("otherRule", otherRule);
+ mZenModeHelper.setConfig(config, null, UPDATE_ORIGIN_INIT, "", Process.SYSTEM_UID);
+ assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_ALARMS);
+
+ // Should be ignored.
+ mZenModeHelper.setAutomaticZenRuleState("otherRule",
+ new Condition(otherRule.conditionId, "off", Condition.STATE_FALSE),
+ UPDATE_ORIGIN_APP, CUSTOM_PKG_UID);
+
+ assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_ALARMS);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_API)
+ public void setAutomaticZenRuleState_conditionForNotOwnedRule_ignored() {
+ // Assume existence of an other-package-owned rule that is currently ACTIVE.
+ assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
+ ZenRule otherRule = newZenRule("another.package", Instant.now(), null);
+ otherRule.zenMode = ZEN_MODE_ALARMS;
+ otherRule.condition = new Condition(otherRule.conditionId, "on", Condition.STATE_TRUE);
+ ZenModeConfig config = mZenModeHelper.mConfig.copy();
+ config.automaticRules.put("otherRule", otherRule);
+ mZenModeHelper.setConfig(config, null, UPDATE_ORIGIN_INIT, "", Process.SYSTEM_UID);
+ assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_ALARMS);
+
+ // Should be ignored.
+ mZenModeHelper.setAutomaticZenRuleState(otherRule.conditionId,
+ new Condition(otherRule.conditionId, "off", Condition.STATE_FALSE),
+ UPDATE_ORIGIN_APP, CUSTOM_PKG_UID);
+
+ assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_ALARMS);
+ }
+
+ @Test
@EnableFlags(android.app.Flags.FLAG_MODES_API)
public void testCallbacks_policy() throws Exception {
setupZenConfig();
@@ -5541,13 +5626,13 @@ public class ZenModeHelperTest extends UiServiceTestCase {
public void applyGlobalZenModeAsImplicitZenRule_modeOff_deactivatesImplicitRule() {
mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
mZenModeHelper.mConfig.automaticRules.clear();
- mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
+ mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(mPkg, CUSTOM_PKG_UID,
ZEN_MODE_IMPORTANT_INTERRUPTIONS);
assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(1);
assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).condition.state)
.isEqualTo(STATE_TRUE);
- mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
+ mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(mPkg, CUSTOM_PKG_UID,
ZEN_MODE_OFF);
assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).condition.state)
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java
index f0803418376f..f54c7e57828b 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java
@@ -100,6 +100,8 @@ public class VibrationSettingsTest {
@Rule
public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+ private static final int OLD_USER_ID = 123;
+ private static final int NEW_USER_ID = 456;
private static final int UID = 1;
private static final int VIRTUAL_DEVICE_ID = 1;
private static final String SYSUI_PACKAGE_NAME = "sysui";
@@ -211,10 +213,10 @@ public class VibrationSettingsTest {
mVibrationSettings.addListener(mListenerMock);
// Testing the broadcast flow manually.
- mVibrationSettings.mSettingChangeReceiver.onReceive(mContextSpy,
- new Intent(Intent.ACTION_USER_SWITCHED));
+ mVibrationSettings.mUserSwitchObserver.onUserSwitching(NEW_USER_ID);
+ mVibrationSettings.mUserSwitchObserver.onUserSwitchComplete(NEW_USER_ID);
- verify(mListenerMock).onChange();
+ verify(mListenerMock, times(2)).onChange();
}
@Test
@@ -265,8 +267,7 @@ public class VibrationSettingsTest {
// Trigger multiple observers manually.
mVibrationSettings.mSettingObserver.onChange(false);
mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
- mVibrationSettings.mSettingChangeReceiver.onReceive(mContextSpy,
- new Intent(Intent.ACTION_USER_SWITCHED));
+ mVibrationSettings.mUserSwitchObserver.onUserSwitchComplete(NEW_USER_ID);
mVibrationSettings.mSettingChangeReceiver.onReceive(mContextSpy,
new Intent(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION));
@@ -834,13 +835,17 @@ public class VibrationSettingsTest {
assertEquals(VIBRATION_INTENSITY_HIGH,
mVibrationSettings.getCurrentIntensity(USAGE_RINGTONE));
- // Switching user is not working with FakeSettingsProvider.
- // Testing the broadcast flow manually.
- Settings.System.putIntForUser(mContextSpy.getContentResolver(),
- Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_LOW,
+ // Test early update of settings based on new user id.
+ putUserSetting(Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_LOW,
+ NEW_USER_ID);
+ mVibrationSettings.mUserSwitchObserver.onUserSwitching(NEW_USER_ID);
+ assertEquals(VIBRATION_INTENSITY_LOW,
+ mVibrationSettings.getCurrentIntensity(USAGE_RINGTONE));
+
+ // Test later update of settings for UserHandle.USER_CURRENT.
+ putUserSetting(Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_LOW,
UserHandle.USER_CURRENT);
- mVibrationSettings.mSettingChangeReceiver.onReceive(mContextSpy,
- new Intent(Intent.ACTION_USER_SWITCHED));
+ mVibrationSettings.mUserSwitchObserver.onUserSwitchComplete(NEW_USER_ID);
assertEquals(VIBRATION_INTENSITY_LOW,
mVibrationSettings.getCurrentIntensity(USAGE_RINGTONE));
}
@@ -956,12 +961,16 @@ public class VibrationSettingsTest {
}
private void setUserSetting(String settingName, int value) {
- Settings.System.putIntForUser(
- mContextSpy.getContentResolver(), settingName, value, UserHandle.USER_CURRENT);
+ putUserSetting(settingName, value, UserHandle.USER_CURRENT);
// FakeSettingsProvider doesn't support testing triggering ContentObserver yet.
mVibrationSettings.mSettingObserver.onChange(false);
}
+ private void putUserSetting(String settingName, int value, int userHandle) {
+ Settings.System.putIntForUser(
+ mContextSpy.getContentResolver(), settingName, value, userHandle);
+ }
+
private void setRingerMode(int ringerMode) {
when(mAudioManagerMock.getRingerModeInternal()).thenReturn(ringerMode);
// Mock AudioManager broadcast of internal ringer mode change.
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index 7db707a42ff0..e7571ef47864 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -1421,6 +1421,7 @@ public class VibratorManagerServiceTest {
public void vibrate_withIntensitySettings_appliesSettingsToScaleVibrations() throws Exception {
int defaultNotificationIntensity =
mVibrator.getDefaultVibrationIntensity(VibrationAttributes.USAGE_NOTIFICATION);
+ // This will scale up notification vibrations.
setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
defaultNotificationIntensity < Vibrator.VIBRATION_INTENSITY_HIGH
? defaultNotificationIntensity + 1
@@ -1428,6 +1429,7 @@ public class VibratorManagerServiceTest {
int defaultTouchIntensity =
mVibrator.getDefaultVibrationIntensity(VibrationAttributes.USAGE_TOUCH);
+ // This will scale down touch vibrations.
setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
defaultTouchIntensity > Vibrator.VIBRATION_INTENSITY_LOW
? defaultTouchIntensity - 1
@@ -1482,6 +1484,42 @@ public class VibratorManagerServiceTest {
}
@Test
+ public void vibrate_withBypassScaleFlag_ignoresIntensitySettingsAndResolvesAmplitude()
+ throws Exception {
+ // Permission needed for bypassing user settings
+ grantPermission(android.Manifest.permission.MODIFY_PHONE_STATE);
+
+ int defaultTouchIntensity =
+ mVibrator.getDefaultVibrationIntensity(VibrationAttributes.USAGE_TOUCH);
+ // This will scale down touch vibrations.
+ setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
+ defaultTouchIntensity > Vibrator.VIBRATION_INTENSITY_LOW
+ ? defaultTouchIntensity - 1
+ : defaultTouchIntensity);
+
+ int defaultAmplitude = mContextSpy.getResources().getInteger(
+ com.android.internal.R.integer.config_defaultVibrationAmplitude);
+
+ mockVibrators(1);
+ FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
+ fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+ VibratorManagerService service = createSystemReadyService();
+
+ vibrateAndWaitUntilFinished(service,
+ VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE),
+ new VibrationAttributes.Builder()
+ .setUsage(VibrationAttributes.USAGE_TOUCH)
+ .setFlags(VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_SCALE)
+ .build());
+
+ assertEquals(1, fakeVibrator.getAllEffectSegments().size());
+
+ assertEquals(defaultAmplitude / 255f, fakeVibrator.getAmplitudes().get(0), 1e-5);
+
+ cancelVibrate(service); // Clean up long-ish effect.
+ }
+
+ @Test
public void vibrate_withPowerModeChange_cancelVibrationIfNotAllowed() throws Exception {
mockVibrators(1, 2);
VibratorManagerService service = createSystemReadyService();
@@ -1879,6 +1917,9 @@ public class VibratorManagerServiceTest {
@Test
public void onExternalVibration_withBypassMuteAudioFlag_ignoresUserSettings() {
+ // Permission needed for bypassing user settings
+ grantPermission(android.Manifest.permission.MODIFY_PHONE_STATE);
+
mockVibrators(1);
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
setUserSetting(Settings.System.ALARM_VIBRATION_INTENSITY,
@@ -1892,12 +1933,12 @@ public class VibratorManagerServiceTest {
.build();
createSystemReadyService();
- int scale = mExternalVibratorService.onExternalVibrationStart(
- new ExternalVibration(UID, PACKAGE_NAME, audioAttrs,
- mock(IExternalVibrationController.class)));
+ ExternalVibration vib = new ExternalVibration(UID, PACKAGE_NAME, audioAttrs,
+ mock(IExternalVibrationController.class));
+ int scale = mExternalVibratorService.onExternalVibrationStart(vib);
assertEquals(IExternalVibratorService.SCALE_MUTE, scale);
- createSystemReadyService();
+ mExternalVibratorService.onExternalVibrationStop(vib);
scale = mExternalVibratorService.onExternalVibrationStart(
new ExternalVibration(UID, PACKAGE_NAME, flaggedAudioAttrs,
mock(IExternalVibrationController.class)));
@@ -1912,7 +1953,6 @@ public class VibratorManagerServiceTest {
Vibrator.VIBRATION_INTENSITY_OFF);
AudioAttributes flaggedAudioAttrs = new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_UNKNOWN)
- .setFlags(AudioAttributes.FLAG_BYPASS_MUTE)
.build();
createSystemReadyService();
diff --git a/services/tests/wmtests/AndroidTest.xml b/services/tests/wmtests/AndroidTest.xml
index 46e87dceb8d4..2512ee592ef3 100644
--- a/services/tests/wmtests/AndroidTest.xml
+++ b/services/tests/wmtests/AndroidTest.xml
@@ -34,4 +34,11 @@
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
<option name="run-command" value="settings put secure immersive_mode_confirmations confirmed" />
</target_preparer>
+
+ <!-- Collect the dumped files for debugging -->
+ <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+ <option name="directory-keys" value="/storage/emulated/0/ScreenshotTests" />
+ <option name="clean-up" value="true" />
+ <option name="collect-on-run-ended-only" value="true" />
+ </metrics_collector>
</configuration>
diff --git a/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
index 6853c4cdea0e..e904eae00802 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
@@ -223,7 +223,11 @@ public class ShortcutLoggingTests extends ShortcutKeyTestBase {
KeyboardLogEvent.LAUNCH_DEFAULT_MAPS, KeyEvent.KEYCODE_M, META_ON},
{"Meta + S -> Launch Default Messaging App",
new int[]{META_KEY, KeyEvent.KEYCODE_S},
- KeyboardLogEvent.LAUNCH_DEFAULT_MESSAGING, KeyEvent.KEYCODE_S, META_ON}};
+ KeyboardLogEvent.LAUNCH_DEFAULT_MESSAGING, KeyEvent.KEYCODE_S, META_ON},
+ {"Meta + Ctrl + DPAD_DOWN -> Enter desktop mode",
+ new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DPAD_DOWN},
+ KeyboardLogEvent.DESKTOP_MODE, KeyEvent.KEYCODE_DPAD_DOWN,
+ META_ON | CTRL_ON}};
}
@Keep
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStartControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStartControllerTests.java
index db241de246f6..0544b89d70fd 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStartControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStartControllerTests.java
@@ -23,7 +23,10 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.google.common.truth.Truth.assertThat;
+
import android.content.Intent;
+import android.os.Binder;
import android.platform.test.annotations.Presubmit;
import androidx.test.filters.SmallTest;
@@ -48,6 +51,8 @@ public class ActivityStartControllerTests extends WindowTestsBase {
private Factory mFactory;
private ActivityStarter mStarter;
+ private static final String TEST_TYPE = "testType";
+
@Before
public void setUp() throws Exception {
mFactory = mock(Factory.class);
@@ -58,6 +63,28 @@ public class ActivityStartControllerTests extends WindowTestsBase {
}
/**
+ * Ensures that when an [Activity] is started in a [TaskFragment] the associated
+ * [ActivityStarter.Request] has the intent's resolved type correctly set.
+ */
+ @Test
+ public void testStartActivityInTaskFragment_setsActivityStarterRequestResolvedType() {
+ final Intent intent = new Intent();
+ intent.setType(TEST_TYPE);
+
+ mController.startActivityInTaskFragment(
+ mock(TaskFragment.class),
+ intent,
+ null /* activityOptions */,
+ null /* resultTo */ ,
+ Binder.getCallingPid(),
+ Binder.getCallingUid(),
+ null /* errorCallbackToken */
+ );
+
+ assertThat(mStarter.mRequest.resolvedType).isEqualTo(TEST_TYPE);
+ }
+
+ /**
* Ensures instances are recycled after execution.
*/
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
index 0b466b2b8a2c..6b614fadba39 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
@@ -243,7 +243,7 @@ public class ActivityTaskSupervisorTests extends WindowTestsBase {
activity.deliverNewIntentLocked(ActivityBuilder.DEFAULT_FAKE_UID,
new Intent(), null /* intentGrants */, "other.package2",
- /* isShareIdentityEnabled */ false);
+ /* isShareIdentityEnabled */ false, /* userId */ -1, /* recipientAppId */ -1);
verify(activity).getFilteredReferrer(eq("other.package2"));
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java
index c99fda9c06d3..ef131ac337b1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java
@@ -16,6 +16,10 @@
package com.android.server.wm;
+import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_PENDING_INTENT;
+import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_PERMISSION;
+import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_VISIBLE_WINDOW;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -25,6 +29,7 @@ import static org.mockito.ArgumentMatchers.eq;
import android.app.ActivityOptions;
import android.app.AppOpsManager;
import android.app.BackgroundStartPrivileges;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManagerInternal;
@@ -51,7 +56,10 @@ import org.mockito.quality.Strictness;
import java.lang.reflect.Field;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
+import java.util.Optional;
/**
* Tests for the {@link ActivityStarter} class.
@@ -73,9 +81,12 @@ public class BackgroundActivityStartControllerTests {
private static final String REGULAR_PACKAGE_1 = "package.app1";
private static final String REGULAR_PACKAGE_2 = "package.app2";
- public @Rule MockitoRule mMockitoRule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS);
+ private static final Intent TEST_INTENT = new Intent()
+ .setComponent(new ComponentName("package.app3", "someClass"));
+
+ public @Rule MockitoRule mMockitoRule = MockitoJUnit.rule().strictness(Strictness.LENIENT);
- BackgroundActivityStartController mController;
+ TestableBackgroundActivityStartController mController;
@Mock
ActivityMetricsLogger mActivityMetricsLogger;
@Mock
@@ -112,6 +123,56 @@ public class BackgroundActivityStartControllerTests {
List<String> mShownToasts = new ArrayList<>();
List<BalAllowedLog> mBalAllowedLogs = new ArrayList<>();
+ class TestableBackgroundActivityStartController extends BackgroundActivityStartController {
+ Optional<BalVerdict> mCallerVerdict = Optional.empty();
+ Optional<BalVerdict> mRealCallerVerdict = Optional.empty();
+ Map<WindowProcessController, BalVerdict> mProcessVerdicts = new HashMap<>();
+
+ TestableBackgroundActivityStartController(ActivityTaskManagerService service,
+ ActivityTaskSupervisor supervisor) {
+ super(service, supervisor);
+ }
+
+ @Override
+ protected void showToast(String toastText) {
+ mShownToasts.add(toastText);
+ }
+
+ @Override
+ protected void writeBalAllowedLog(String activityName, int code,
+ BackgroundActivityStartController.BalState state) {
+ mBalAllowedLogs.add(new BalAllowedLog(activityName, code));
+ }
+
+ @Override
+ BalVerdict checkBackgroundActivityStartAllowedByCaller(BalState state) {
+ return mCallerVerdict.orElseGet(
+ () -> super.checkBackgroundActivityStartAllowedByCaller(state));
+ }
+
+ public void setCallerVerdict(BalVerdict verdict) {
+ this.mCallerVerdict = Optional.of(verdict);
+ }
+
+ @Override
+ BalVerdict checkBackgroundActivityStartAllowedBySender(BalState state) {
+ return mRealCallerVerdict.orElseGet(
+ () -> super.checkBackgroundActivityStartAllowedBySender(state));
+ }
+
+ public void setRealCallerVerdict(BalVerdict verdict) {
+ this.mRealCallerVerdict = Optional.of(verdict);
+ }
+
+ @Override
+ BalVerdict checkProcessAllowsBal(WindowProcessController app, BalState state) {
+ if (mProcessVerdicts.containsKey(app)) {
+ return mProcessVerdicts.get(app);
+ }
+ return super.checkProcessAllowsBal(app, state);
+ }
+ }
+
@Before
public void setUp() throws Exception {
// wire objects
@@ -127,18 +188,10 @@ public class BackgroundActivityStartControllerTests {
//Mockito.when(mSupervisor.getBackgroundActivityLaunchController()).thenReturn(mController);
setViaReflection(mSupervisor, "mRecentTasks", mRecentTasks);
- mController = new BackgroundActivityStartController(mService, mSupervisor) {
- @Override
- protected void showToast(String toastText) {
- mShownToasts.add(toastText);
- }
+ mController = new TestableBackgroundActivityStartController(mService, mSupervisor);
- @Override
- protected void writeBalAllowedLog(String activityName, int code,
- BackgroundActivityStartController.BalState state) {
- mBalAllowedLogs.add(new BalAllowedLog(activityName, code));
- }
- };
+ // nicer toString
+ Mockito.when(mPendingIntentRecord.toString()).thenReturn("PendingIntentRecord");
// safe defaults
Mockito.when(mAppOpsManager.checkOpNoThrow(
@@ -146,7 +199,6 @@ public class BackgroundActivityStartControllerTests {
anyInt(), anyString())).thenReturn(AppOpsManager.MODE_DEFAULT);
Mockito.when(mCallerApp.areBackgroundActivityStartsAllowed(anyInt())).thenReturn(
BalVerdict.BLOCK);
-
}
private void setViaReflection(Object o, String property, Object value) {
@@ -175,7 +227,7 @@ public class BackgroundActivityStartControllerTests {
int realCallingPid = NO_PID;
PendingIntentRecord originatingPendingIntent = null;
BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE;
- Intent intent = new Intent();
+ Intent intent = TEST_INTENT;
ActivityOptions checkedOptions = ActivityOptions.makeBasic();
// call
@@ -186,17 +238,46 @@ public class BackgroundActivityStartControllerTests {
// assertions
assertThat(verdict.getCode()).isEqualTo(BackgroundActivityStartController.BAL_BLOCK);
+ assertThat(mBalAllowedLogs).isEmpty(); // not allowed
+ }
- assertThat(mBalAllowedLogs).isEmpty();
+ // Tests for BackgroundActivityStartController.checkBackgroundActivityStart
+
+ @Test
+ public void testRegularActivityStart_notAllowed_isBlocked() {
+ // setup state
+ mController.setCallerVerdict(BalVerdict.BLOCK);
+ mController.setRealCallerVerdict(BalVerdict.BLOCK);
+
+ // prepare call
+ int callingUid = REGULAR_UID_1;
+ int callingPid = REGULAR_PID_1;
+ final String callingPackage = REGULAR_PACKAGE_1;
+ int realCallingUid = NO_UID;
+ int realCallingPid = NO_PID;
+ PendingIntentRecord originatingPendingIntent = null;
+ BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE;
+ Intent intent = TEST_INTENT;
+ ActivityOptions checkedOptions = ActivityOptions.makeBasic();
+
+ // call
+ BalVerdict verdict = mController.checkBackgroundActivityStart(callingUid, callingPid,
+ callingPackage, realCallingUid, realCallingPid, mCallerApp,
+ originatingPendingIntent, forcedBalByPiSender, mResultRecord, intent,
+ checkedOptions);
+
+ // assertions
+ assertThat(verdict).isEqualTo(BalVerdict.BLOCK);
+ assertThat(mBalAllowedLogs).isEmpty(); // not allowed
}
@Test
- public void testRegularActivityStart_allowedBLPC_isAllowed() {
+ public void testRegularActivityStart_allowedByCaller_isAllowed() {
// setup state
- BalVerdict blpcVerdict = new BalVerdict(
- BackgroundActivityStartController.BAL_ALLOW_PERMISSION, true, "Allowed by BLPC");
- Mockito.when(mCallerApp.areBackgroundActivityStartsAllowed(anyInt())).thenReturn(
- blpcVerdict);
+ BalVerdict callerVerdict = new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, false,
+ "CallerIsVisible");
+ mController.setCallerVerdict(callerVerdict);
+ mController.setRealCallerVerdict(BalVerdict.BLOCK);
// prepare call
int callingUid = REGULAR_UID_1;
@@ -206,7 +287,7 @@ public class BackgroundActivityStartControllerTests {
int realCallingPid = NO_PID;
PendingIntentRecord originatingPendingIntent = null;
BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE;
- Intent intent = new Intent();
+ Intent intent = TEST_INTENT;
ActivityOptions checkedOptions = ActivityOptions.makeBasic();
// call
@@ -216,28 +297,61 @@ public class BackgroundActivityStartControllerTests {
checkedOptions);
// assertions
- assertThat(verdict).isEqualTo(blpcVerdict);
+ assertThat(verdict).isEqualTo(callerVerdict);
+ assertThat(mBalAllowedLogs).isEmpty(); // non-critical exception
+ }
+
+ @Test
+ public void testRegularActivityStart_allowedByRealCaller_isAllowed() {
+ // setup state
+ BalVerdict realCallerVerdict = new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, false,
+ "RealCallerIsVisible");
+ mController.setCallerVerdict(BalVerdict.BLOCK);
+ mController.setRealCallerVerdict(realCallerVerdict);
+
+ // prepare call
+ int callingUid = REGULAR_UID_1;
+ int callingPid = REGULAR_PID_1;
+ final String callingPackage = REGULAR_PACKAGE_1;
+ int realCallingUid = NO_UID;
+ int realCallingPid = NO_PID;
+ PendingIntentRecord originatingPendingIntent = null;
+ BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE;
+ Intent intent = TEST_INTENT;
+ ActivityOptions checkedOptions = ActivityOptions.makeBasic();
+
+ // call
+ BalVerdict verdict = mController.checkBackgroundActivityStart(callingUid, callingPid,
+ callingPackage, realCallingUid, realCallingPid, mCallerApp,
+ originatingPendingIntent, forcedBalByPiSender, mResultRecord, intent,
+ checkedOptions);
+
+ // assertions
+ assertThat(verdict).isEqualTo(realCallerVerdict);
assertThat(mBalAllowedLogs).containsExactly(
- new BalAllowedLog("", BackgroundActivityStartController.BAL_ALLOW_PERMISSION));
+ new BalAllowedLog("package.app3/someClass", realCallerVerdict.getCode()));
+ // TODO questionable log (should we only log PIs?)
}
@Test
- public void testRegularActivityStart_allowedByCallerBLPC_isAllowed() {
+ public void testRegularActivityStart_allowedByCallerAndRealCaller_returnsCallerVerdict() {
// setup state
- BalVerdict blpcVerdict = new BalVerdict(
- BackgroundActivityStartController.BAL_ALLOW_PERMISSION, true, "Allowed by BLPC");
- Mockito.when(mCallerApp.areBackgroundActivityStartsAllowed(anyInt())).thenReturn(
- blpcVerdict);
+ BalVerdict callerVerdict =
+ new BalVerdict(BAL_ALLOW_PERMISSION, false, "CallerHasPermission");
+ BalVerdict realCallerVerdict =
+ new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, false, "RealCallerIsVisible");
+ mController.setCallerVerdict(callerVerdict);
+ mController.setRealCallerVerdict(realCallerVerdict);
// prepare call
int callingUid = REGULAR_UID_1;
int callingPid = REGULAR_PID_1;
final String callingPackage = REGULAR_PACKAGE_1;
- int realCallingUid = REGULAR_UID_2;
- int realCallingPid = REGULAR_PID_2;
- PendingIntentRecord originatingPendingIntent = mPendingIntentRecord;
+ int realCallingUid = NO_UID;
+ int realCallingPid = NO_PID;
+ PendingIntentRecord originatingPendingIntent = null;
BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE;
- Intent intent = new Intent();
+ Intent intent = TEST_INTENT;
ActivityOptions checkedOptions = ActivityOptions.makeBasic();
// call
@@ -247,8 +361,248 @@ public class BackgroundActivityStartControllerTests {
checkedOptions);
// assertions
- assertThat(verdict).isEqualTo(blpcVerdict);
+ assertThat(verdict).isEqualTo(callerVerdict);
+ assertThat(mBalAllowedLogs).containsExactly(new BalAllowedLog("", callerVerdict.getCode()));
+ }
+
+ @Test
+ public void testPendingIntent_allowedByCallerAndRealCallerButOptOut_isBlocked() {
+ // setup state
+ BalVerdict callerVerdict =
+ new BalVerdict(BAL_ALLOW_PERMISSION, false, "CallerhasPermission");
+ BalVerdict realCallerVerdict =
+ new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, false, "RealCallerIsVisible");
+ mController.setCallerVerdict(callerVerdict);
+ mController.setRealCallerVerdict(realCallerVerdict);
+
+ // prepare call
+ int callingUid = REGULAR_UID_1;
+ int callingPid = REGULAR_PID_1;
+ final String callingPackage = REGULAR_PACKAGE_1;
+ int realCallingUid = NO_UID;
+ int realCallingPid = NO_PID;
+ PendingIntentRecord originatingPendingIntent = mPendingIntentRecord;
+ BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE;
+ Intent intent = TEST_INTENT;
+ ActivityOptions checkedOptions = ActivityOptions.makeBasic()
+ .setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED)
+ .setPendingIntentCreatorBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED);
+
+ // call
+ BalVerdict verdict = mController.checkBackgroundActivityStart(callingUid, callingPid,
+ callingPackage, realCallingUid, realCallingPid, mCallerApp,
+ originatingPendingIntent, forcedBalByPiSender, mResultRecord, intent,
+ checkedOptions);
+
+ // assertions
+ assertThat(verdict).isEqualTo(BalVerdict.BLOCK);
+ assertThat(mBalAllowedLogs).isEmpty();
+ }
+
+ @Test
+ public void testPendingIntent_allowedByCallerAndOptIn_isAllowed() {
+ // setup state
+ BalVerdict callerVerdict =
+ new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, false, "CallerIsVisible");
+ mController.setCallerVerdict(callerVerdict);
+ mController.setRealCallerVerdict(BalVerdict.BLOCK);
+
+ // prepare call
+ int callingUid = REGULAR_UID_1;
+ int callingPid = REGULAR_PID_1;
+ final String callingPackage = REGULAR_PACKAGE_1;
+ int realCallingUid = NO_UID;
+ int realCallingPid = NO_PID;
+ PendingIntentRecord originatingPendingIntent = mPendingIntentRecord;
+ BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE;
+ Intent intent = TEST_INTENT;
+ ActivityOptions checkedOptions = ActivityOptions.makeBasic()
+ .setPendingIntentCreatorBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
+
+ // call
+ BalVerdict verdict = mController.checkBackgroundActivityStart(callingUid, callingPid,
+ callingPackage, realCallingUid, realCallingPid, mCallerApp,
+ originatingPendingIntent, forcedBalByPiSender, mResultRecord, intent,
+ checkedOptions);
+
+ // assertions
+ assertThat(verdict).isEqualTo(callerVerdict);
+ assertThat(mBalAllowedLogs).isEmpty();
+ }
+
+ @Test
+ public void testPendingIntent_allowedByRealCallerAndOptIn_isAllowed() {
+ // setup state
+ BalVerdict realCallerVerdict =
+ new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, false, "RealCallerIsVisible");
+ mController.setCallerVerdict(BalVerdict.BLOCK);
+ mController.setRealCallerVerdict(realCallerVerdict);
+
+ // prepare call
+ int callingUid = REGULAR_UID_1;
+ int callingPid = REGULAR_PID_1;
+ final String callingPackage = REGULAR_PACKAGE_1;
+ int realCallingUid = NO_UID;
+ int realCallingPid = NO_PID;
+ PendingIntentRecord originatingPendingIntent = mPendingIntentRecord;
+ BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE;
+ Intent intent = TEST_INTENT;
+ ActivityOptions checkedOptions = ActivityOptions.makeBasic()
+ .setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
+
+ // call
+ BalVerdict verdict = mController.checkBackgroundActivityStart(callingUid, callingPid,
+ callingPackage, realCallingUid, realCallingPid, mCallerApp,
+ originatingPendingIntent, forcedBalByPiSender, mResultRecord, intent,
+ checkedOptions);
+
+ // assertions
+ assertThat(verdict).isEqualTo(realCallerVerdict);
assertThat(mBalAllowedLogs).containsExactly(
- new BalAllowedLog("", BackgroundActivityStartController.BAL_ALLOW_PERMISSION));
+ new BalAllowedLog("package.app3/someClass", BAL_ALLOW_PENDING_INTENT));
+
+ }
+
+ // Tests for BackgroundActivityStartController.checkBackgroundActivityStartAllowedByCaller
+
+ // Tests for BackgroundActivityStartController.checkBackgroundActivityStartAllowedBySender
+
+ // Tests for BalState
+
+ @Test
+ public void testBalState_regularStart_isAutoOptIn() {
+ // setup state
+
+ // prepare call
+ int callingUid = REGULAR_UID_1;
+ int callingPid = REGULAR_PID_1;
+ final String callingPackage = REGULAR_PACKAGE_1;
+ int realCallingUid = NO_UID;
+ int realCallingPid = NO_PID;
+ PendingIntentRecord originatingPendingIntent = null;
+ BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE;
+ Intent intent = TEST_INTENT;
+ ActivityOptions checkedOptions = ActivityOptions.makeBasic();
+ WindowProcessController callerApp = mCallerApp;
+ ActivityRecord resultRecord = null;
+
+ // call
+ BackgroundActivityStartController.BalState balState = mController
+ .new BalState(callingUid, callingPid, callingPackage, realCallingUid,
+ realCallingPid, callerApp, originatingPendingIntent, forcedBalByPiSender,
+ resultRecord, intent, checkedOptions);
+
+ // assertions
+ assertThat(balState.mAutoOptInReason).isEqualTo("notPendingIntent");
+ assertThat(balState.mBalAllowedByPiCreator).isEqualTo(BackgroundStartPrivileges.ALLOW_BAL);
+ assertThat(balState.mBalAllowedByPiSender).isEqualTo(BackgroundStartPrivileges.ALLOW_BAL);
+ assertThat(balState.callerExplicitOptInOrAutoOptIn()).isTrue();
+ assertThat(balState.callerExplicitOptInOrOut()).isFalse();
+ assertThat(balState.realCallerExplicitOptInOrAutoOptIn()).isTrue();
+ assertThat(balState.realCallerExplicitOptInOrOut()).isFalse();
+ assertThat(balState.toString()).isEqualTo(
+ "[callingPackage: package.app1; callingPackageTargetSdk: -1; callingUid: 10001; "
+ + "callingPid: 11001; appSwitchState: 0; callingUidHasAnyVisibleWindow: "
+ + "false; callingUidProcState: NONEXISTENT; "
+ + "isCallingUidPersistentSystemProcess: false; forcedBalByPiSender: BSP"
+ + ".NONE; intent: Intent { cmp=package.app3/someClass }; callerApp: "
+ + "mCallerApp; inVisibleTask: false; balAllowedByPiCreator: BSP"
+ + ".ALLOW_BAL; balAllowedByPiCreatorWithHardening: BSP.ALLOW_BAL; "
+ + "resultIfPiCreatorAllowsBal: null; hasRealCaller: true; "
+ + "isCallForResult: false; isPendingIntent: false; autoOptInReason: "
+ + "notPendingIntent; realCallingPackage: uid=1[debugOnly]; "
+ + "realCallingPackageTargetSdk: -1; realCallingUid: 1; realCallingPid: 1;"
+ + " realCallingUidHasAnyVisibleWindow: false; realCallingUidProcState: "
+ + "NONEXISTENT; isRealCallingUidPersistentSystemProcess: false; "
+ + "originatingPendingIntent: null; realCallerApp: null; "
+ + "balAllowedByPiSender: BSP.ALLOW_BAL; resultIfPiSenderAllowsBal: null]");
+ }
+
+ @Test
+ public void testBalState_pendingIntentForResult_isOptedInForSenderOnly() {
+ // setup state
+ Mockito.when(mPendingIntentRecord.toString()).thenReturn("PendingIntentRecord");
+
+ // prepare call
+ int callingUid = REGULAR_UID_1;
+ int callingPid = REGULAR_PID_1;
+ final String callingPackage = REGULAR_PACKAGE_1;
+ int realCallingUid = NO_UID;
+ int realCallingPid = NO_PID;
+ PendingIntentRecord originatingPendingIntent = mPendingIntentRecord;
+ BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE;
+ Intent intent = TEST_INTENT;
+ ActivityOptions checkedOptions = ActivityOptions.makeBasic();
+ WindowProcessController callerApp = mCallerApp;
+ ActivityRecord resultRecord = mResultRecord;
+
+ // call
+ BackgroundActivityStartController.BalState balState = mController
+ .new BalState(callingUid, callingPid, callingPackage, realCallingUid,
+ realCallingPid, callerApp, originatingPendingIntent, forcedBalByPiSender,
+ resultRecord, intent, checkedOptions);
+
+ // assertions
+ assertThat(balState.mAutoOptInReason).isEqualTo("callForResult");
+ assertThat(balState.mBalAllowedByPiCreator).isEqualTo(BackgroundStartPrivileges.NONE);
+ assertThat(balState.mBalAllowedByPiSender).isEqualTo(BackgroundStartPrivileges.ALLOW_BAL);
+ assertThat(balState.callerExplicitOptInOrAutoOptIn()).isFalse();
+ assertThat(balState.callerExplicitOptInOrOut()).isFalse();
+ assertThat(balState.realCallerExplicitOptInOrAutoOptIn()).isTrue();
+ assertThat(balState.realCallerExplicitOptInOrOut()).isFalse();
+ }
+
+ @Test
+ public void testBalState_pendingIntentWithDefaults_isOptedOut() {
+ // setup state
+
+ // prepare call
+ int callingUid = REGULAR_UID_1;
+ int callingPid = REGULAR_PID_1;
+ final String callingPackage = REGULAR_PACKAGE_1;
+ int realCallingUid = NO_UID;
+ int realCallingPid = NO_PID;
+ PendingIntentRecord originatingPendingIntent = mPendingIntentRecord;
+ BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE;
+ Intent intent = TEST_INTENT;
+ ActivityOptions checkedOptions = ActivityOptions.makeBasic();
+ WindowProcessController callerApp = mCallerApp;
+ ActivityRecord resultRecord = null;
+
+ // call
+ BackgroundActivityStartController.BalState balState = mController
+ .new BalState(callingUid, callingPid, callingPackage, realCallingUid,
+ realCallingPid, callerApp, originatingPendingIntent, forcedBalByPiSender,
+ resultRecord, intent, checkedOptions);
+
+ // assertions
+ assertThat(balState.mAutoOptInReason).isNull();
+ assertThat(balState.mBalAllowedByPiCreator).isEqualTo(BackgroundStartPrivileges.NONE);
+ assertThat(balState.mBalAllowedByPiSender).isEqualTo(BackgroundStartPrivileges.ALLOW_FGS);
+ assertThat(balState.isPendingIntent()).isTrue();
+ assertThat(balState.callerExplicitOptInOrAutoOptIn()).isFalse();
+ assertThat(balState.callerExplicitOptInOrOut()).isFalse();
+ assertThat(balState.realCallerExplicitOptInOrAutoOptIn()).isFalse();
+ assertThat(balState.realCallerExplicitOptInOrOut()).isFalse();
+ assertThat(balState.toString()).isEqualTo(
+ "[callingPackage: package.app1; callingPackageTargetSdk: -1; callingUid: 10001; "
+ + "callingPid: 11001; appSwitchState: 0; callingUidHasAnyVisibleWindow: "
+ + "false; callingUidProcState: NONEXISTENT; "
+ + "isCallingUidPersistentSystemProcess: false; forcedBalByPiSender: BSP"
+ + ".NONE; intent: Intent { cmp=package.app3/someClass }; callerApp: "
+ + "mCallerApp; inVisibleTask: false; balAllowedByPiCreator: BSP"
+ + ".NONE; balAllowedByPiCreatorWithHardening: BSP.NONE; "
+ + "resultIfPiCreatorAllowsBal: null; hasRealCaller: true; "
+ + "isCallForResult: false; isPendingIntent: true; autoOptInReason: "
+ + "null; realCallingPackage: uid=1[debugOnly]; "
+ + "realCallingPackageTargetSdk: -1; realCallingUid: 1; realCallingPid: 1;"
+ + " realCallingUidHasAnyVisibleWindow: false; realCallingUidProcState: "
+ + "NONEXISTENT; isRealCallingUidPersistentSystemProcess: false; "
+ + "originatingPendingIntent: PendingIntentRecord; realCallerApp: null; "
+ + "balAllowedByPiSender: BSP.ALLOW_FGS; resultIfPiSenderAllowsBal: null]");
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
index 5d14334aaf69..5965fae74dcc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -39,6 +39,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_BOTTOM;
import static org.junit.Assert.assertEquals;
@@ -388,6 +389,24 @@ public class DisplayPolicyTests extends WindowTestsBase {
// The current insets are restored from cache directly.
assertEquals(prevConfigFrame, displayPolicy.getDecorInsetsInfo(info.rotation,
info.logicalWidth, info.logicalHeight).mConfigFrame);
+
+ // If screen is not fully turned on, then the cache should be preserved.
+ displayPolicy.screenTurnedOff();
+ final TransitionController transitionController = mDisplayContent.mTransitionController;
+ spyOn(transitionController);
+ doReturn(true).when(transitionController).isCollecting();
+ doReturn(Integer.MAX_VALUE).when(transitionController).getCollectingTransitionId();
+ // Make CachedDecorInsets.canPreserve return false.
+ displayPolicy.physicalDisplayUpdated();
+ assertFalse(displayPolicy.shouldKeepCurrentDecorInsets());
+ displayPolicy.getDecorInsetsInfo(info.rotation, info.logicalWidth, info.logicalHeight)
+ .mConfigFrame.offset(1, 1);
+ // Even if CachedDecorInsets.canPreserve returns false, the cache won't be cleared.
+ displayPolicy.updateDecorInsetsInfo();
+ // Successful to restore from cache.
+ displayPolicy.updateCachedDecorInsets();
+ assertEquals(prevConfigFrame, displayPolicy.getDecorInsetsInfo(info.rotation,
+ info.logicalWidth, info.logicalHeight).mConfigFrame);
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
index 9e2b1eccc3b2..4c32a586ae78 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
@@ -67,7 +67,7 @@ import android.view.SurfaceSession;
import android.view.View;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
-import android.window.IUnhandledDragListener;
+import android.window.IGlobalDragListener;
import androidx.test.filters.SmallTest;
@@ -177,8 +177,8 @@ public class DragDropControllerTests extends WindowTestsBase {
TEST_PID, TEST_UID);
mWindow = createDropTargetWindow("Drag test window", 0);
doReturn(mWindow).when(mDisplayContent).getTouchableWinAtPointLocked(0, 0);
- when(mWm.mInputManager.transferTouchFocus(any(InputChannel.class),
- any(InputChannel.class), any(boolean.class))).thenReturn(true);
+ when(mWm.mInputManager.startDragAndDrop(any(InputChannel.class),
+ any(InputChannel.class))).thenReturn(true);
mWm.mWindowMap.put(mWindow.mClient.asBinder(), mWindow);
}
@@ -544,9 +544,9 @@ public class DragDropControllerTests extends WindowTestsBase {
public void testUnhandledDragListenerNotCalledForNormalDrags() throws RemoteException {
assumeTrue(com.android.window.flags.Flags.delegateUnhandledDrags());
- final IUnhandledDragListener listener = mock(IUnhandledDragListener.class);
+ final IGlobalDragListener listener = mock(IGlobalDragListener.class);
doReturn(mock(Binder.class)).when(listener).asBinder();
- mTarget.setUnhandledDragListener(listener);
+ mTarget.setGlobalDragListener(listener);
doDragAndDrop(0, ClipData.newPlainText("label", "Test"), 0, 0);
verify(listener, times(0)).onUnhandledDrop(any(), any());
}
@@ -555,9 +555,9 @@ public class DragDropControllerTests extends WindowTestsBase {
public void testUnhandledDragListenerReceivesUnhandledDropOverWindow() {
assumeTrue(com.android.window.flags.Flags.delegateUnhandledDrags());
- final IUnhandledDragListener listener = mock(IUnhandledDragListener.class);
+ final IGlobalDragListener listener = mock(IGlobalDragListener.class);
doReturn(mock(Binder.class)).when(listener).asBinder();
- mTarget.setUnhandledDragListener(listener);
+ mTarget.setGlobalDragListener(listener);
final int invalidXY = 100_000;
startDrag(View.DRAG_FLAG_GLOBAL, ClipData.newPlainText("label", "Test"), () -> {
// Notify the unhandled drag listener
@@ -578,9 +578,9 @@ public class DragDropControllerTests extends WindowTestsBase {
public void testUnhandledDragListenerReceivesUnhandledDropOverNoValidWindow() {
assumeTrue(com.android.window.flags.Flags.delegateUnhandledDrags());
- final IUnhandledDragListener listener = mock(IUnhandledDragListener.class);
+ final IGlobalDragListener listener = mock(IGlobalDragListener.class);
doReturn(mock(Binder.class)).when(listener).asBinder();
- mTarget.setUnhandledDragListener(listener);
+ mTarget.setGlobalDragListener(listener);
final int invalidXY = 100_000;
startDrag(View.DRAG_FLAG_GLOBAL, ClipData.newPlainText("label", "Test"), () -> {
// Notify the unhandled drag listener
@@ -600,9 +600,9 @@ public class DragDropControllerTests extends WindowTestsBase {
public void testUnhandledDragListenerCallbackTimeout() {
assumeTrue(com.android.window.flags.Flags.delegateUnhandledDrags());
- final IUnhandledDragListener listener = mock(IUnhandledDragListener.class);
+ final IGlobalDragListener listener = mock(IGlobalDragListener.class);
doReturn(mock(Binder.class)).when(listener).asBinder();
- mTarget.setUnhandledDragListener(listener);
+ mTarget.setGlobalDragListener(listener);
final int invalidXY = 100_000;
startDrag(View.DRAG_FLAG_GLOBAL, ClipData.newPlainText("label", "Test"), () -> {
// Notify the unhandled drag listener
@@ -641,8 +641,8 @@ public class DragDropControllerTests extends WindowTestsBase {
.setFormat(PixelFormat.TRANSLUCENT)
.build();
- assertTrue(mWm.mInputManager.transferTouchFocus(new InputChannel(),
- new InputChannel(), true /* isDragDrop */));
+ assertTrue(mWm.mInputManager.startDragAndDrop(new InputChannel(),
+ new InputChannel()));
mToken = mTarget.performDrag(TEST_PID, 0, mWindow.mClient,
flag, surface, 0, 0, 0, 0, 0, 0, 0, data);
assertNotNull(mToken);
diff --git a/services/tests/wmtests/src/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncherTest.java b/services/tests/wmtests/src/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncherTest.java
index 08e63963c88f..402b704c1681 100644
--- a/services/tests/wmtests/src/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncherTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncherTest.java
@@ -16,33 +16,23 @@
package com.android.server.wm;
-import static android.view.WindowManager.TRANSIT_CHANGE;
-
+import static com.android.internal.R.bool.config_unfoldTransitionEnabled;
+import static com.android.server.wm.DeviceStateController.DeviceState.REAR;
import static com.android.server.wm.DeviceStateController.DeviceState.FOLDED;
import static com.android.server.wm.DeviceStateController.DeviceState.HALF_FOLDED;
import static com.android.server.wm.DeviceStateController.DeviceState.OPEN;
-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.eq;
-import static org.mockito.ArgumentMatchers.isNull;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.when;
import android.animation.ValueAnimator;
-import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
-import android.window.TransitionRequestInfo.DisplayChange;
-
-import static com.android.internal.R.bool.config_unfoldTransitionEnabled;
-import static com.android.server.wm.DeviceStateController.DeviceState.REAR;
import androidx.test.filters.SmallTest;
@@ -50,7 +40,6 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -62,20 +51,19 @@ import org.mockito.MockitoAnnotations;
*/
@SmallTest
@Presubmit
-public class PhysicalDisplaySwitchTransitionLauncherTest {
+@RunWith(WindowTestRunner.class)
+public class PhysicalDisplaySwitchTransitionLauncherTest extends WindowTestsBase {
@Mock
- DisplayContent mDisplayContent;
- @Mock
Context mContext;
@Mock
Resources mResources;
@Mock
- ActivityTaskManagerService mActivityTaskManagerService;
- @Mock
BLASTSyncEngine mSyncEngine;
- @Mock
+
+ WindowTestsBase.TestTransitionPlayer mPlayer;
TransitionController mTransitionController;
+ DisplayContent mDisplayContent;
private PhysicalDisplaySwitchTransitionLauncher mTarget;
private float mOriginalAnimationScale;
@@ -83,9 +71,14 @@ public class PhysicalDisplaySwitchTransitionLauncherTest {
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
+ mTransitionController = new WindowTestsBase.TestTransitionController(mAtm);
+ mTransitionController.setSyncEngine(mSyncEngine);
+ mPlayer = new WindowTestsBase.TestTransitionPlayer(
+ mTransitionController, mAtm.mWindowOrganizerController);
when(mContext.getResources()).thenReturn(mResources);
- mTarget = new PhysicalDisplaySwitchTransitionLauncher(mDisplayContent,
- mActivityTaskManagerService, mContext, mTransitionController);
+ mDisplayContent = new TestDisplayContent.Builder(mAtm, 100, 150).build();
+ mTarget = new PhysicalDisplaySwitchTransitionLauncher(mDisplayContent, mAtm, mContext,
+ mTransitionController);
mOriginalAnimationScale = ValueAnimator.getDurationScale();
}
@@ -100,24 +93,23 @@ public class PhysicalDisplaySwitchTransitionLauncherTest {
mTarget.foldStateChanged(FOLDED);
mTarget.foldStateChanged(OPEN);
+ final Rect origBounds = new Rect();
+ mDisplayContent.getBounds(origBounds);
+ origBounds.offsetTo(0, 0);
mTarget.requestDisplaySwitchTransitionIfNeeded(
- /* displayId= */ 123,
- /* oldDisplayWidth= */ 100,
- /* oldDisplayHeight= */ 150,
+ mDisplayContent.getDisplayId(),
+ origBounds.width(),
+ origBounds.height(),
/* newDisplayWidth= */ 200,
/* newDisplayHeight= */ 250
);
- ArgumentCaptor<DisplayChange> displayChangeArgumentCaptor =
- ArgumentCaptor.forClass(DisplayChange.class);
- verify(mTransitionController).requestTransitionIfNeeded(eq(TRANSIT_CHANGE), /* flags= */
- eq(0), eq(mDisplayContent), eq(mDisplayContent), /* remoteTransition= */ isNull(),
- displayChangeArgumentCaptor.capture());
- assertThat(displayChangeArgumentCaptor.getValue().getDisplayId()).isEqualTo(123);
- assertThat(displayChangeArgumentCaptor.getValue().getStartAbsBounds()).isEqualTo(
- new Rect(0, 0, 100, 150));
- assertThat(displayChangeArgumentCaptor.getValue().getEndAbsBounds()).isEqualTo(
- new Rect(0, 0, 200, 250));
+ assertNotNull(mPlayer.mLastRequest);
+ assertEquals(mDisplayContent.getDisplayId(),
+ mPlayer.mLastRequest.getDisplayChange().getDisplayId());
+ assertEquals(origBounds, mPlayer.mLastRequest.getDisplayChange().getStartAbsBounds());
+ assertEquals(new Rect(0, 0, 200, 250),
+ mPlayer.mLastRequest.getDisplayChange().getEndAbsBounds());
}
@Test
@@ -148,7 +140,7 @@ public class PhysicalDisplaySwitchTransitionLauncherTest {
mTarget.foldStateChanged(FOLDED);
mTarget.foldStateChanged(OPEN);
requestDisplaySwitch();
- clearInvocations(mTransitionController);
+ mPlayer.mLastRequest = null;
requestDisplaySwitch();
@@ -220,7 +212,6 @@ public class PhysicalDisplaySwitchTransitionLauncherTest {
@Test
public void testDisplaySwitchAfterUnfolding_otherCollectingTransition_collectsDisplaySwitch() {
- givenCollectingTransition(createTransition(TRANSIT_CHANGE));
givenAllAnimationsEnabled();
mTarget.foldStateChanged(FOLDED);
@@ -228,7 +219,8 @@ public class PhysicalDisplaySwitchTransitionLauncherTest {
requestDisplaySwitch();
// Collects to the current transition
- verify(mTransitionController).collect(mDisplayContent);
+ assertTrue(mTransitionController.getCollectingTransition().mParticipants.contains(
+ mDisplayContent));
}
@@ -245,20 +237,18 @@ public class PhysicalDisplaySwitchTransitionLauncherTest {
}
private void assertTransitionRequested() {
- verify(mTransitionController).requestTransitionIfNeeded(anyInt(), anyInt(), any(), any(),
- any(), any());
+ assertNotNull(mPlayer.mLastRequest);
}
private void assertTransitionNotRequested() {
- verify(mTransitionController, never()).requestTransitionIfNeeded(anyInt(), anyInt(), any(),
- any(), any(), any());
+ assertNull(mPlayer.mLastRequest);
}
private void requestDisplaySwitch() {
mTarget.requestDisplaySwitchTransitionIfNeeded(
- /* displayId= */ 123,
- /* oldDisplayWidth= */ 100,
- /* oldDisplayHeight= */ 150,
+ mDisplayContent.getDisplayId(),
+ mDisplayContent.getBounds().width(),
+ mDisplayContent.getBounds().height(),
/* newDisplayWidth= */ 200,
/* newDisplayHeight= */ 250
);
@@ -280,16 +270,11 @@ public class PhysicalDisplaySwitchTransitionLauncherTest {
}
private void givenShellTransitionsEnabled(boolean enabled) {
- when(mTransitionController.isShellTransitionsEnabled()).thenReturn(enabled);
- }
-
- private void givenCollectingTransition(@Nullable Transition transition) {
- when(mTransitionController.isCollecting()).thenReturn(transition != null);
- when(mTransitionController.getCollectingTransition()).thenReturn(transition);
- }
-
- private Transition createTransition(int type) {
- return new Transition(type, /* flags= */ 0, mTransitionController, mSyncEngine);
+ if (enabled) {
+ mTransitionController.registerTransitionPlayer(mPlayer, null /* proc */);
+ } else {
+ mTransitionController.detachPlayer();
+ }
}
private void givenDisplayContentHasContent(boolean hasContent) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java b/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java
index 0fcae92fdbfc..280fe4c30739 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java
@@ -16,12 +16,14 @@
package com.android.server.wm;
+import static android.server.wm.CtsWindowInfoUtils.waitForStableWindowGeometry;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowInsets.Type.displayCutout;
import static android.view.WindowInsets.Type.statusBars;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@@ -43,6 +45,7 @@ import android.os.Handler;
import android.os.Looper;
import android.os.ServiceManager;
import android.platform.test.annotations.Presubmit;
+import android.server.wm.BuildUtils;
import android.view.IWindowManager;
import android.view.PointerIcon;
import android.view.SurfaceControl;
@@ -50,7 +53,6 @@ import android.view.cts.surfacevalidator.BitmapPixelChecker;
import android.view.cts.surfacevalidator.SaveBitmapHelper;
import android.window.ScreenCapture;
import android.window.ScreenCapture.ScreenshotHardwareBuffer;
-import android.window.ScreenCapture.SynchronousScreenCaptureListener;
import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest;
@@ -74,6 +76,7 @@ import java.util.concurrent.TimeUnit;
@SmallTest
@Presubmit
public class ScreenshotTests {
+ private static final long WAIT_TIME_S = 5L * BuildUtils.HW_TIMEOUT_MULTIPLIER;
private static final int BUFFER_WIDTH = 100;
private static final int BUFFER_HEIGHT = 100;
@@ -119,32 +122,61 @@ public class ScreenshotTests {
canvas.drawColor(Color.RED);
buffer.unlockCanvasAndPost(canvas);
+ CountDownLatch countDownLatch = new CountDownLatch(1);
t.show(secureSC)
.setBuffer(secureSC, HardwareBuffer.createFromGraphicBuffer(buffer))
.setDataSpace(secureSC, DataSpace.DATASPACE_SRGB)
- .apply(true);
+ .addTransactionCommittedListener(Runnable::run, countDownLatch::countDown)
+ .apply();
+ assertTrue("Failed to wait for transaction to get committed",
+ countDownLatch.await(WAIT_TIME_S, TimeUnit.SECONDS));
+ assertTrue("Failed to wait for stable geometry",
+ waitForStableWindowGeometry(WAIT_TIME_S, TimeUnit.SECONDS));
ScreenCapture.LayerCaptureArgs args = new ScreenCapture.LayerCaptureArgs.Builder(secureSC)
.setCaptureSecureLayers(true)
.setChildrenOnly(false)
.build();
- ScreenCapture.ScreenshotHardwareBuffer hardwareBuffer = ScreenCapture.captureLayers(args);
- assertNotNull(hardwareBuffer);
- Bitmap screenshot = hardwareBuffer.asBitmap();
- assertNotNull(screenshot);
-
- Bitmap swBitmap = screenshot.copy(Bitmap.Config.ARGB_8888, false);
- screenshot.recycle();
-
- BitmapPixelChecker bitmapPixelChecker = new BitmapPixelChecker(Color.RED);
- Rect bounds = new Rect(0, 0, swBitmap.getWidth(), swBitmap.getHeight());
- int numMatchingPixels = bitmapPixelChecker.getNumMatchingPixels(swBitmap, bounds);
- int sizeOfBitmap = bounds.width() * bounds.height();
- boolean success = numMatchingPixels == sizeOfBitmap;
- swBitmap.recycle();
-
- assertTrue(success);
+ ScreenshotHardwareBuffer[] screenCapture = new ScreenshotHardwareBuffer[1];
+ Bitmap screenshot = null;
+ Bitmap swBitmap = null;
+ try {
+ CountDownLatch screenshotComplete = new CountDownLatch(1);
+ ScreenCapture.captureLayers(args, new ScreenCapture.ScreenCaptureListener(
+ (screenshotHardwareBuffer, result) -> {
+ if (result == 0) {
+ screenCapture[0] = screenshotHardwareBuffer;
+ }
+ screenshotComplete.countDown();
+ }));
+ assertTrue("Failed to wait for screen capture",
+ screenshotComplete.await(WAIT_TIME_S, TimeUnit.SECONDS));
+ assertNotNull("Screen capture buffer is null", screenCapture[0]);
+
+ screenshot = screenCapture[0].asBitmap();
+ assertNotNull("Screenshot from bitmap is null", screenshot);
+
+ swBitmap = screenshot.copy(Bitmap.Config.ARGB_8888, false);
+
+ BitmapPixelChecker bitmapPixelChecker = new BitmapPixelChecker(Color.RED);
+ Rect bounds = new Rect(0, 0, swBitmap.getWidth(), swBitmap.getHeight());
+ int numMatchingPixels = bitmapPixelChecker.getNumMatchingPixels(swBitmap, bounds);
+ int sizeOfBitmap = bounds.width() * bounds.height();
+
+ assertEquals("numMatchingPixels=" + numMatchingPixels + " sizeOfBitmap=" + sizeOfBitmap,
+ sizeOfBitmap, numMatchingPixels);
+ } finally {
+ if (screenshot != null) {
+ screenshot.recycle();
+ }
+ if (swBitmap != null) {
+ swBitmap.recycle();
+ }
+ if (screenCapture[0].getHardwareBuffer() != null) {
+ screenCapture[0].getHardwareBuffer().close();
+ }
+ }
}
@Test
@@ -169,36 +201,65 @@ public class ScreenshotTests {
buffer.unlockCanvasAndPost(canvas);
Point point = mActivity.getPositionBelowStatusBar();
+ CountDownLatch countDownLatch = new CountDownLatch(1);
t.show(sc)
.setBuffer(sc, HardwareBuffer.createFromGraphicBuffer(buffer))
.setDataSpace(sc, DataSpace.DATASPACE_SRGB)
.setPosition(sc, point.x, point.y)
- .apply(true);
-
- SynchronousScreenCaptureListener syncScreenCapture =
- ScreenCapture.createSyncCaptureListener();
- windowManager.captureDisplay(DEFAULT_DISPLAY, null, syncScreenCapture);
- ScreenshotHardwareBuffer hardwareBuffer = syncScreenCapture.getBuffer();
- assertNotNull(hardwareBuffer);
-
- Bitmap screenshot = hardwareBuffer.asBitmap();
- assertNotNull(screenshot);
-
- Bitmap swBitmap = screenshot.copy(Bitmap.Config.ARGB_8888, false);
- screenshot.recycle();
-
- BitmapPixelChecker bitmapPixelChecker = new BitmapPixelChecker(Color.RED);
- Rect bounds = new Rect(point.x, point.y, BUFFER_WIDTH + point.x, BUFFER_HEIGHT + point.y);
- int numMatchingPixels = bitmapPixelChecker.getNumMatchingPixels(swBitmap, bounds);
- int pixelMatchSize = bounds.width() * bounds.height();
- boolean success = numMatchingPixels == pixelMatchSize;
-
- if (!success) {
- SaveBitmapHelper.saveBitmap(swBitmap, getClass(), mTestName, "failedImage");
+ .addTransactionCommittedListener(Runnable::run, countDownLatch::countDown)
+ .apply();
+
+ assertTrue("Failed to wait for transaction to get committed",
+ countDownLatch.await(WAIT_TIME_S, TimeUnit.SECONDS));
+ assertTrue("Failed to wait for stable geometry",
+ waitForStableWindowGeometry(WAIT_TIME_S, TimeUnit.SECONDS));
+
+ ScreenshotHardwareBuffer[] screenCapture = new ScreenshotHardwareBuffer[1];
+ Bitmap screenshot = null;
+ Bitmap swBitmap = null;
+ try {
+ CountDownLatch screenshotComplete = new CountDownLatch(1);
+ windowManager.captureDisplay(DEFAULT_DISPLAY, null,
+ new ScreenCapture.ScreenCaptureListener(
+ (screenshotHardwareBuffer, result) -> {
+ if (result == 0) {
+ screenCapture[0] = screenshotHardwareBuffer;
+ }
+ screenshotComplete.countDown();
+ }));
+ assertTrue("Failed to wait for screen capture",
+ screenshotComplete.await(WAIT_TIME_S, TimeUnit.SECONDS));
+ assertNotNull("Screen capture buffer is null", screenCapture[0]);
+
+ screenshot = screenCapture[0].asBitmap();
+ assertNotNull("Screenshot from bitmap is null", screenshot);
+
+ swBitmap = screenshot.copy(Bitmap.Config.ARGB_8888, false);
+
+ BitmapPixelChecker bitmapPixelChecker = new BitmapPixelChecker(Color.RED);
+ Rect bounds = new Rect(point.x, point.y, BUFFER_WIDTH + point.x,
+ BUFFER_HEIGHT + point.y);
+ int numMatchingPixels = bitmapPixelChecker.getNumMatchingPixels(swBitmap, bounds);
+ int pixelMatchSize = bounds.width() * bounds.height();
+ boolean success = numMatchingPixels == pixelMatchSize;
+
+ if (!success) {
+ SaveBitmapHelper.saveBitmap(swBitmap, getClass(), mTestName, "failedImage");
+ }
+ assertTrue(
+ "numMatchingPixels=" + numMatchingPixels + " pixelMatchSize=" + pixelMatchSize,
+ success);
+ } finally {
+ if (screenshot != null) {
+ screenshot.recycle();
+ }
+ if (swBitmap != null) {
+ swBitmap.recycle();
+ }
+ if (screenCapture[0].getHardwareBuffer() != null) {
+ screenCapture[0].getHardwareBuffer().close();
+ }
}
- swBitmap.recycle();
- assertTrue("numMatchingPixels=" + numMatchingPixels + " pixelMatchSize=" + pixelMatchSize,
- success);
}
public static class ScreenshotActivity extends Activity {
diff --git a/services/tests/wmtests/src/com/android/server/wm/SensitiveContentPackagesTest.java b/services/tests/wmtests/src/com/android/server/wm/SensitiveContentPackagesTest.java
index 298637266cc3..824b8d7f6727 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SensitiveContentPackagesTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SensitiveContentPackagesTest.java
@@ -16,10 +16,18 @@
package com.android.server.wm;
+import static android.permission.flags.Flags.FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION;
+import static android.view.flags.Flags.FLAG_SENSITIVE_CONTENT_APP_PROTECTION;
+
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import android.os.Binder;
+import android.os.IBinder;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.util.ArraySet;
import androidx.test.filters.SmallTest;
@@ -27,6 +35,7 @@ import androidx.test.filters.SmallTest;
import com.android.server.wm.SensitiveContentPackages.PackageInfo;
import org.junit.After;
+import org.junit.Rule;
import org.junit.Test;
import java.util.Set;
@@ -44,23 +53,30 @@ public class SensitiveContentPackagesTest {
private static final int APP_UID_1 = 5;
private static final int APP_UID_2 = 6;
- private static final int APP_UID_3 = 7;
+ private static final IBinder WINDOW_TOKEN_1 = new Binder();
+ private static final IBinder WINDOW_TOKEN_2 = new Binder();
private final SensitiveContentPackages mSensitiveContentPackages =
new SensitiveContentPackages();
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
@After
public void tearDown() {
mSensitiveContentPackages.clearBlockedApps();
}
+ private boolean shouldBlockScreenCaptureForApp(String pkg, int uid, IBinder windowToken) {
+ return mSensitiveContentPackages.shouldBlockScreenCaptureForApp(pkg, uid, windowToken);
+ }
+
@Test
- public void addBlockScreenCaptureForApps() {
+ @RequiresFlagsEnabled(FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION)
+ public void shouldBlockScreenCaptureForNotificationApps() {
ArraySet<PackageInfo> blockedApps = new ArraySet(
Set.of(new PackageInfo(APP_PKG_1, APP_UID_1),
- new PackageInfo(APP_PKG_1, APP_UID_2),
- new PackageInfo(APP_PKG_2, APP_UID_1),
new PackageInfo(APP_PKG_2, APP_UID_2)
));
@@ -68,17 +84,50 @@ public class SensitiveContentPackagesTest {
assertTrue(modified);
- assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_1));
- assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_2));
- assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_3));
+ assertTrue(shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_1, WINDOW_TOKEN_1));
+ assertTrue(shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_1, WINDOW_TOKEN_2));
+ assertFalse(shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_2, WINDOW_TOKEN_2));
+ assertTrue(shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_2, WINDOW_TOKEN_1));
+ assertTrue(shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_2, WINDOW_TOKEN_2));
+ assertFalse(shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_1, WINDOW_TOKEN_2));
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_SENSITIVE_CONTENT_APP_PROTECTION)
+ public void shouldBlockScreenCaptureForSensitiveContentOnScreenApps() {
+ ArraySet<PackageInfo> blockedApps = new ArraySet(
+ Set.of(new PackageInfo(APP_PKG_1, APP_UID_1, WINDOW_TOKEN_1),
+ new PackageInfo(APP_PKG_2, APP_UID_2, WINDOW_TOKEN_2)
+ ));
+
+ boolean modified = mSensitiveContentPackages.addBlockScreenCaptureForApps(blockedApps);
+ assertTrue(modified);
+
+ assertTrue(shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_1, WINDOW_TOKEN_1));
+ assertFalse(shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_1, WINDOW_TOKEN_2));
+
+ assertTrue(shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_2, WINDOW_TOKEN_2));
+ assertFalse(shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_2, WINDOW_TOKEN_1));
+ }
+
+ @Test
+ @RequiresFlagsEnabled(
+ {FLAG_SENSITIVE_CONTENT_APP_PROTECTION, FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION})
+ public void shouldBlockScreenCaptureForApps() {
+ ArraySet<PackageInfo> blockedApps = new ArraySet(
+ Set.of(new PackageInfo(APP_PKG_1, APP_UID_1, WINDOW_TOKEN_1),
+ new PackageInfo(APP_PKG_1, APP_UID_1),
+ new PackageInfo(APP_PKG_2, APP_UID_2, WINDOW_TOKEN_2)
+ ));
+
+ boolean modified = mSensitiveContentPackages.addBlockScreenCaptureForApps(blockedApps);
+ assertTrue(modified);
- assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_1));
- assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_2));
- assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_3));
+ assertTrue(shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_1, WINDOW_TOKEN_1));
+ assertTrue(shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_1, WINDOW_TOKEN_2));
- assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_1));
- assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_2));
- assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_3));
+ assertTrue(shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_2, WINDOW_TOKEN_2));
+ assertFalse(shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_2, WINDOW_TOKEN_1));
}
@Test
@@ -92,20 +141,8 @@ public class SensitiveContentPackagesTest {
mSensitiveContentPackages.addBlockScreenCaptureForApps(blockedApps);
boolean modified = mSensitiveContentPackages.addBlockScreenCaptureForApps(blockedApps);
-
assertFalse(modified);
-
- assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_1));
- assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_2));
- assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_3));
-
- assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_1));
- assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_2));
- assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_3));
-
- assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_1));
- assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_2));
- assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_3));
+ assertTrue(mSensitiveContentPackages.size() == 4);
}
@Test
@@ -121,65 +158,28 @@ public class SensitiveContentPackagesTest {
boolean modified = mSensitiveContentPackages
.addBlockScreenCaptureForApps(
new ArraySet(Set.of(new PackageInfo(APP_PKG_3, APP_UID_1))));
-
assertTrue(modified);
-
- assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_1));
- assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_2));
- assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_3));
-
- assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_1));
- assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_2));
- assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_3));
-
- assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_1));
- assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_2));
- assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_3));
+ assertTrue(mSensitiveContentPackages.size() == 5);
}
@Test
public void clearBlockedApps() {
ArraySet<PackageInfo> blockedApps = new ArraySet(
Set.of(new PackageInfo(APP_PKG_1, APP_UID_1),
- new PackageInfo(APP_PKG_1, APP_UID_2),
- new PackageInfo(APP_PKG_2, APP_UID_1),
- new PackageInfo(APP_PKG_2, APP_UID_2)
+ new PackageInfo(APP_PKG_2, APP_UID_2, WINDOW_TOKEN_2)
));
mSensitiveContentPackages.addBlockScreenCaptureForApps(blockedApps);
boolean modified = mSensitiveContentPackages.clearBlockedApps();
assertTrue(modified);
-
- assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_1));
- assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_2));
- assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_3));
-
- assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_1));
- assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_2));
- assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_3));
-
- assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_1));
- assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_2));
- assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_3));
+ assertTrue(mSensitiveContentPackages.size() == 0);
}
@Test
public void clearBlockedApps_alreadyEmpty() {
boolean modified = mSensitiveContentPackages.clearBlockedApps();
-
assertFalse(modified);
-
- assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_1));
- assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_2));
- assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_3));
-
- assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_1));
- assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_2));
- assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_3));
-
- assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_1));
- assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_2));
- assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_3));
+ assertTrue(mSensitiveContentPackages.size() == 0);
}
}
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 f42cdb88021e..b96f39d7a4e1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -22,6 +22,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE;
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
@@ -46,6 +47,7 @@ import static android.view.WindowInsets.Type.statusBars;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
@@ -100,6 +102,7 @@ import android.content.pm.ActivityInfo.ScreenOrientation;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
+import android.graphics.Insets;
import android.graphics.Rect;
import android.os.Binder;
import android.os.RemoteException;
@@ -153,6 +156,8 @@ public class SizeCompatTests extends WindowTestsBase {
private static final String CONFIG_NEVER_CONSTRAIN_DISPLAY_APIS_ALL_PACKAGES =
"never_constrain_display_apis_all_packages";
+ private static final float DELTA_ASPECT_RATIO_TOLERANCE = 0.005f;
+
@Rule
public TestRule compatChangeRule = new PlatformCompatChangeRule();
@@ -211,90 +216,46 @@ public class SizeCompatTests extends WindowTestsBase {
@Test
public void testHorizontalReachabilityEnabledForTranslucentActivities() {
- setUpDisplaySizeWithApp(2500, 1000);
- mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
- final LetterboxConfiguration config = mWm.mLetterboxConfiguration;
- config.setTranslucentLetterboxingOverrideEnabled(true);
- config.setLetterboxHorizontalPositionMultiplier(0.5f);
- config.setIsHorizontalReachabilityEnabled(true);
-
- // Opaque activity
- prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
- addWindowToActivity(mActivity);
- mActivity.mRootWindowContainer.performSurfacePlacement();
-
- // Translucent Activity
- final ActivityRecord translucentActivity = new ActivityBuilder(mAtm)
- .setActivityTheme(android.R.style.Theme_Translucent)
- .setLaunchedFromUid(mActivity.getUid())
- .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
- .build();
- mTask.addChild(translucentActivity);
-
- spyOn(translucentActivity.mLetterboxUiController);
- doReturn(true).when(translucentActivity.mLetterboxUiController)
- .shouldShowLetterboxUi(any());
-
- addWindowToActivity(translucentActivity);
- translucentActivity.mRootWindowContainer.performSurfacePlacement();
-
- final Function<ActivityRecord, Rect> innerBoundsOf =
- (ActivityRecord a) -> {
- final Rect bounds = new Rect();
- a.mLetterboxUiController.getLetterboxInnerBounds(bounds);
- return bounds;
- };
- final Runnable checkLetterboxPositions = () -> assertEquals(innerBoundsOf.apply(mActivity),
- innerBoundsOf.apply(translucentActivity));
- final Runnable checkIsLeft = () -> assertThat(
- innerBoundsOf.apply(translucentActivity).left).isEqualTo(0);
- final Runnable checkIsRight = () -> assertThat(
- innerBoundsOf.apply(translucentActivity).right).isEqualTo(2500);
- final Runnable checkIsCentered = () -> assertThat(
- innerBoundsOf.apply(translucentActivity).left > 0
- && innerBoundsOf.apply(translucentActivity).right < 2500).isTrue();
-
- final Consumer<Integer> doubleClick =
- (Integer x) -> {
- mActivity.mLetterboxUiController.handleHorizontalDoubleTap(x);
- mActivity.mRootWindowContainer.performSurfacePlacement();
- };
-
- // Initial state
- checkIsCentered.run();
-
- // Double-click left
- doubleClick.accept(/* x */ 10);
- checkLetterboxPositions.run();
- checkIsLeft.run();
-
- // Double-click right
- doubleClick.accept(/* x */ 1990);
- checkLetterboxPositions.run();
- checkIsCentered.run();
-
- // Double-click right
- doubleClick.accept(/* x */ 1990);
- checkLetterboxPositions.run();
- checkIsRight.run();
+ testReachabilityEnabledForTranslucentActivity(/* dw */ 2500, /* dh */1000,
+ SCREEN_ORIENTATION_PORTRAIT, /* minAspectRatio */ 0f,
+ /* horizontalReachability */ true);
+ }
- // Double-click left
- doubleClick.accept(/* x */ 10);
- checkLetterboxPositions.run();
- checkIsCentered.run();
+ @Test
+ public void testHorizontalReachabilityEnabled_TranslucentPortraitActivities_portraitDisplay() {
+ testReachabilityEnabledForTranslucentActivity(/* dw */ 1400, /* dh */1600,
+ SCREEN_ORIENTATION_PORTRAIT, OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE,
+ /* horizontalReachability */ true);
}
@Test
public void testVerticalReachabilityEnabledForTranslucentActivities() {
- setUpDisplaySizeWithApp(1000, 2500);
+ testReachabilityEnabledForTranslucentActivity(/* dw */ 1000, /* dh */2500,
+ SCREEN_ORIENTATION_LANDSCAPE, /* minAspectRatio */ 0f,
+ /* horizontalReachability */ false);
+ }
+
+ @Test
+ public void testVerticalReachabilityEnabled_TranslucentLandscapeActivities_landscapeDisplay() {
+ testReachabilityEnabledForTranslucentActivity(/* dw */ 1600, /* dh */1400,
+ SCREEN_ORIENTATION_LANDSCAPE, OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE,
+ /* horizontalReachability */ false);
+ }
+
+ private void testReachabilityEnabledForTranslucentActivity(int displayWidth, int displayHeight,
+ @ScreenOrientation int screenOrientation, float minAspectRatio,
+ boolean horizontalReachability) {
+ setUpDisplaySizeWithApp(displayWidth, displayHeight);
mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
final LetterboxConfiguration config = mWm.mLetterboxConfiguration;
config.setTranslucentLetterboxingOverrideEnabled(true);
config.setLetterboxVerticalPositionMultiplier(0.5f);
config.setIsVerticalReachabilityEnabled(true);
+ config.setLetterboxHorizontalPositionMultiplier(0.5f);
+ config.setIsHorizontalReachabilityEnabled(true);
// Opaque activity
- prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE);
+ prepareMinAspectRatio(mActivity, minAspectRatio, screenOrientation);
addWindowToActivity(mActivity);
mActivity.mRootWindowContainer.performSurfacePlacement();
@@ -302,7 +263,7 @@ public class SizeCompatTests extends WindowTestsBase {
final ActivityRecord translucentActivity = new ActivityBuilder(mAtm)
.setActivityTheme(android.R.style.Theme_Translucent)
.setLaunchedFromUid(mActivity.getUid())
- .setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE)
+ .setScreenOrientation(screenOrientation)
.build();
mTask.addChild(translucentActivity);
@@ -324,39 +285,78 @@ public class SizeCompatTests extends WindowTestsBase {
final Runnable checkIsTop = () -> assertThat(
innerBoundsOf.apply(translucentActivity).top).isEqualTo(0);
final Runnable checkIsBottom = () -> assertThat(
- innerBoundsOf.apply(translucentActivity).bottom).isEqualTo(2500);
- final Runnable checkIsCentered = () -> assertThat(
+ innerBoundsOf.apply(translucentActivity).bottom).isEqualTo(displayHeight);
+ final Runnable checkIsLeft = () -> assertThat(
+ innerBoundsOf.apply(translucentActivity).left).isEqualTo(0);
+ final Runnable checkIsRight = () -> assertThat(
+ innerBoundsOf.apply(translucentActivity).right).isEqualTo(displayWidth);
+ final Runnable checkIsHorizontallyCentered = () -> assertThat(
+ innerBoundsOf.apply(translucentActivity).left > 0
+ && innerBoundsOf.apply(translucentActivity).right < displayWidth).isTrue();
+ final Runnable checkIsVerticallyCentered = () -> assertThat(
innerBoundsOf.apply(translucentActivity).top > 0
- && innerBoundsOf.apply(translucentActivity).bottom < 2500).isTrue();
-
- final Consumer<Integer> doubleClick =
- (Integer y) -> {
- mActivity.mLetterboxUiController.handleVerticalDoubleTap(y);
- mActivity.mRootWindowContainer.performSurfacePlacement();
- };
-
- // Initial state
- checkIsCentered.run();
-
- // Double-click top
- doubleClick.accept(/* y */ 10);
- checkLetterboxPositions.run();
- checkIsTop.run();
-
- // Double-click bottom
- doubleClick.accept(/* y */ 1990);
- checkLetterboxPositions.run();
- checkIsCentered.run();
-
- // Double-click bottom
- doubleClick.accept(/* y */ 1990);
- checkLetterboxPositions.run();
- checkIsBottom.run();
-
- // Double-click top
- doubleClick.accept(/* y */ 10);
- checkLetterboxPositions.run();
- checkIsCentered.run();
+ && innerBoundsOf.apply(translucentActivity).bottom < displayHeight)
+ .isTrue();
+
+ if (horizontalReachability) {
+ final Consumer<Integer> doubleClick =
+ (Integer x) -> {
+ mActivity.mLetterboxUiController.handleHorizontalDoubleTap(x);
+ mActivity.mRootWindowContainer.performSurfacePlacement();
+ };
+
+ // Initial state
+ checkIsHorizontallyCentered.run();
+
+ // Double-click left
+ doubleClick.accept(/* x */ 10);
+ checkLetterboxPositions.run();
+ checkIsLeft.run();
+
+ // Double-click right
+ doubleClick.accept(/* x */ displayWidth - 100);
+ checkLetterboxPositions.run();
+ checkIsHorizontallyCentered.run();
+
+ // Double-click right
+ doubleClick.accept(/* x */ displayWidth - 100);
+ checkLetterboxPositions.run();
+ checkIsRight.run();
+
+ // Double-click left
+ doubleClick.accept(/* x */ 10);
+ checkLetterboxPositions.run();
+ checkIsHorizontallyCentered.run();
+ } else {
+ final Consumer<Integer> doubleClick =
+ (Integer y) -> {
+ mActivity.mLetterboxUiController.handleVerticalDoubleTap(y);
+ mActivity.mRootWindowContainer.performSurfacePlacement();
+ };
+
+ // Initial state
+ checkIsVerticallyCentered.run();
+
+ // Double-click top
+ doubleClick.accept(/* y */ 10);
+ checkLetterboxPositions.run();
+ checkIsTop.run();
+
+ // Double-click bottom
+ doubleClick.accept(/* y */ displayHeight - 100);
+ checkLetterboxPositions.run();
+ checkIsVerticallyCentered.run();
+
+ // Double-click bottom
+ doubleClick.accept(/* y */ displayHeight - 100);
+ checkLetterboxPositions.run();
+ checkIsBottom.run();
+
+ // Double-click top
+ doubleClick.accept(/* y */ 10);
+ checkLetterboxPositions.run();
+ checkIsVerticallyCentered.run();
+ }
}
@Test
@@ -2143,7 +2143,7 @@ public class SizeCompatTests extends WindowTestsBase {
final Rect afterBounds = mActivity.getBounds();
final float actualAspectRatio = 1f * afterBounds.height() / afterBounds.width();
assertEquals(LetterboxConfiguration.DEFAULT_LETTERBOX_ASPECT_RATIO_FOR_MULTI_WINDOW,
- actualAspectRatio, 0.001f);
+ actualAspectRatio, DELTA_ASPECT_RATIO_TOLERANCE);
assertTrue(mActivity.areBoundsLetterboxed());
}
@@ -2179,7 +2179,7 @@ public class SizeCompatTests extends WindowTestsBase {
// default letterbox aspect ratio for multi-window.
final Rect afterBounds = mActivity.getBounds();
final float actualAspectRatio = 1f * afterBounds.height() / afterBounds.width();
- assertEquals(minAspectRatio, actualAspectRatio, 0.001f);
+ assertEquals(minAspectRatio, actualAspectRatio, DELTA_ASPECT_RATIO_TOLERANCE);
assertTrue(mActivity.areBoundsLetterboxed());
}
@@ -2487,7 +2487,7 @@ public class SizeCompatTests extends WindowTestsBase {
final float afterAspectRatio =
(float) Math.max(width, height) / (float) Math.min(width, height);
- assertEquals(expectedAspectRatio, afterAspectRatio, 0.001f);
+ assertEquals(expectedAspectRatio, afterAspectRatio, DELTA_ASPECT_RATIO_TOLERANCE);
}
@Test
@@ -2512,7 +2512,7 @@ public class SizeCompatTests extends WindowTestsBase {
float expectedAspectRatio = 1f * displayWidth / getExpectedSplitSize(displayHeight);
final Rect afterBounds = activity.getBounds();
final float afterAspectRatio = (float) (afterBounds.height()) / afterBounds.width();
- assertEquals(expectedAspectRatio, afterAspectRatio, 0.001f);
+ assertEquals(expectedAspectRatio, afterAspectRatio, DELTA_ASPECT_RATIO_TOLERANCE);
}
@Test
@@ -2537,7 +2537,7 @@ public class SizeCompatTests extends WindowTestsBase {
float expectedAspectRatio = 1f * displayHeight / getExpectedSplitSize(displayWidth);
final Rect afterBounds = activity.getBounds();
final float afterAspectRatio = (float) (afterBounds.height()) / afterBounds.width();
- assertEquals(expectedAspectRatio, afterAspectRatio, 0.001f);
+ assertEquals(expectedAspectRatio, afterAspectRatio, DELTA_ASPECT_RATIO_TOLERANCE);
}
@Test
@@ -2563,7 +2563,7 @@ public class SizeCompatTests extends WindowTestsBase {
float expectedAspectRatio = 1f * displayWidth / getExpectedSplitSize(displayHeight);
final Rect afterBounds = activity.getBounds();
final float afterAspectRatio = (float) (afterBounds.width()) / afterBounds.height();
- assertEquals(expectedAspectRatio, afterAspectRatio, 0.001f);
+ assertEquals(expectedAspectRatio, afterAspectRatio, DELTA_ASPECT_RATIO_TOLERANCE);
}
@Test
@@ -2589,7 +2589,7 @@ public class SizeCompatTests extends WindowTestsBase {
float expectedAspectRatio = 1f * displayHeight / getExpectedSplitSize(displayWidth);
final Rect afterBounds = activity.getBounds();
final float afterAspectRatio = (float) (afterBounds.width()) / afterBounds.height();
- assertEquals(expectedAspectRatio, afterAspectRatio, 0.001f);
+ assertEquals(expectedAspectRatio, afterAspectRatio, DELTA_ASPECT_RATIO_TOLERANCE);
}
@Test
@@ -2629,7 +2629,7 @@ public class SizeCompatTests extends WindowTestsBase {
float expectedAspectRatio = 1f * screenHeight / getExpectedSplitSize(screenWidth);
final Rect afterBounds = activity.getBounds();
final float afterAspectRatio = (float) (afterBounds.height()) / afterBounds.width();
- assertEquals(expectedAspectRatio, afterAspectRatio, 0.001f);
+ assertEquals(expectedAspectRatio, afterAspectRatio, DELTA_ASPECT_RATIO_TOLERANCE);
assertFalse(activity.areBoundsLetterboxed());
}
@@ -2670,7 +2670,7 @@ public class SizeCompatTests extends WindowTestsBase {
float expectedAspectRatio = 1f * screenWidth / getExpectedSplitSize(screenHeight);
final Rect afterBounds = activity.getBounds();
final float afterAspectRatio = (float) (afterBounds.width()) / afterBounds.height();
- assertEquals(expectedAspectRatio, afterAspectRatio, 0.001f);
+ assertEquals(expectedAspectRatio, afterAspectRatio, DELTA_ASPECT_RATIO_TOLERANCE);
assertFalse(activity.areBoundsLetterboxed());
}
@@ -2847,9 +2847,8 @@ public class SizeCompatTests extends WindowTestsBase {
assertFitted();
// Check that the display aspect ratio is used by the app.
final float targetMinAspectRatio = 1f * displayHeight / displayWidth;
- final float delta = 0.01f;
assertEquals(targetMinAspectRatio, ActivityRecord
- .computeAspectRatio(mActivity.getBounds()), delta);
+ .computeAspectRatio(mActivity.getBounds()), DELTA_ASPECT_RATIO_TOLERANCE);
}
@Test
@@ -2883,9 +2882,8 @@ public class SizeCompatTests extends WindowTestsBase {
assertFitted();
// Check that the display aspect ratio is used by the app.
final float targetMinAspectRatio = 1f * displayWidth / displayHeight;
- final float delta = 0.01f;
assertEquals(targetMinAspectRatio, ActivityRecord
- .computeAspectRatio(mActivity.getBounds()), delta);
+ .computeAspectRatio(mActivity.getBounds()), DELTA_ASPECT_RATIO_TOLERANCE);
}
@Test
@@ -2910,9 +2908,8 @@ public class SizeCompatTests extends WindowTestsBase {
assertFitted();
// Check that the display aspect ratio is used by the app.
final float targetMinAspectRatio = 1f * displayHeight / displayWidth;
- final float delta = 0.01f;
assertEquals(targetMinAspectRatio, ActivityRecord
- .computeAspectRatio(mActivity.getBounds()), delta);
+ .computeAspectRatio(mActivity.getBounds()), DELTA_ASPECT_RATIO_TOLERANCE);
}
@Test
@@ -2937,9 +2934,8 @@ public class SizeCompatTests extends WindowTestsBase {
assertFitted();
// Check that the display aspect ratio is used by the app.
final float targetMinAspectRatio = 1f * displayWidth / displayHeight;
- final float delta = 0.01f;
assertEquals(targetMinAspectRatio, ActivityRecord
- .computeAspectRatio(mActivity.getBounds()), delta);
+ .computeAspectRatio(mActivity.getBounds()), DELTA_ASPECT_RATIO_TOLERANCE);
}
@Test
@@ -3609,6 +3605,32 @@ public class SizeCompatTests extends WindowTestsBase {
}
@Test
+ public void testIsHorizontalReachabilityEnabled_portraitDisplayAndApp_true() {
+ // Portrait display
+ setUpDisplaySizeWithApp(1400, 1600);
+ mActivity.mWmService.mLetterboxConfiguration.setIsHorizontalReachabilityEnabled(true);
+
+ // 16:9f unresizable portrait app
+ prepareMinAspectRatio(mActivity, OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE,
+ SCREEN_ORIENTATION_PORTRAIT);
+
+ assertTrue(mActivity.mLetterboxUiController.isHorizontalReachabilityEnabled());
+ }
+
+ @Test
+ public void testIsVerticalReachabilityEnabled_landscapeDisplayAndApp_true() {
+ // Landscape display
+ setUpDisplaySizeWithApp(1600, 1500);
+ mActivity.mWmService.mLetterboxConfiguration.setIsVerticalReachabilityEnabled(true);
+
+ // 16:9f unresizable landscape app
+ prepareMinAspectRatio(mActivity, OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE,
+ SCREEN_ORIENTATION_LANDSCAPE);
+
+ assertTrue(mActivity.mLetterboxUiController.isVerticalReachabilityEnabled());
+ }
+
+ @Test
public void testIsHorizontalReachabilityEnabled_doesNotMatchParentHeight_false() {
setUpDisplaySizeWithApp(2800, 1000);
mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
@@ -4053,6 +4075,32 @@ public class SizeCompatTests extends WindowTestsBase {
}
@Test
+ public void testPortraitCloseToSquareDisplayWithTaskbar_notLetterboxed() {
+ // Set up portrait close to square display
+ setUpDisplaySizeWithApp(2200, 2280);
+ final DisplayContent display = mActivity.mDisplayContent;
+ // Simulate taskbar, final app bounds are (0, 0, 2200, 2130) - landscape
+ final WindowState navbar = createWindow(null, TYPE_NAVIGATION_BAR, mDisplayContent,
+ "navbar");
+ final Binder owner = new Binder();
+ navbar.mAttrs.providedInsets = new InsetsFrameProvider[] {
+ new InsetsFrameProvider(owner, 0, WindowInsets.Type.navigationBars())
+ .setInsetsSize(Insets.of(0, 0, 0, 150))
+ };
+ display.getDisplayPolicy().addWindowLw(navbar, navbar.mAttrs);
+ assertTrue(navbar.providesDisplayDecorInsets()
+ && display.getDisplayPolicy().updateDecorInsetsInfo());
+ display.sendNewConfiguration();
+
+ prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
+
+ // Activity is fullscreen even though orientation is not respected with insets, because
+ // the display still matches or is less than the activity aspect ratio
+ assertEquals(display.getBounds(), mActivity.getBounds());
+ assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+ }
+
+ @Test
public void testApplyAspectRatio_activityAlignWithParentAppVertical() {
// The display's app bounds will be (0, 100, 1000, 2350)
final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1000, 2500)
@@ -4275,7 +4323,7 @@ public class SizeCompatTests extends WindowTestsBase {
.getFixedOrientationLetterboxAspectRatio(parentConfig);
float expected = mActivity.mLetterboxUiController.getSplitScreenAspectRatio();
- assertEquals(expected, actual, 0.01);
+ assertEquals(expected, actual, DELTA_ASPECT_RATIO_TOLERANCE);
}
@Test
@@ -4671,13 +4719,12 @@ public class SizeCompatTests extends WindowTestsBase {
.windowConfiguration.getAppBounds());
// Check that aspect ratio of app bounds is equal to the min aspect ratio.
- final float delta = 0.01f;
assertEquals(targetMinAspectRatio, ActivityRecord
- .computeAspectRatio(fixedOrientationAppBounds), delta);
+ .computeAspectRatio(fixedOrientationAppBounds), DELTA_ASPECT_RATIO_TOLERANCE);
assertEquals(targetMinAspectRatio, ActivityRecord
- .computeAspectRatio(minAspectRatioAppBounds), delta);
+ .computeAspectRatio(minAspectRatioAppBounds), DELTA_ASPECT_RATIO_TOLERANCE);
assertEquals(targetMinAspectRatio, ActivityRecord
- .computeAspectRatio(sizeCompatAppBounds), delta);
+ .computeAspectRatio(sizeCompatAppBounds), DELTA_ASPECT_RATIO_TOLERANCE);
}
@Test
@@ -4859,6 +4906,12 @@ public class SizeCompatTests extends WindowTestsBase {
.build();
}
+ static void prepareMinAspectRatio(ActivityRecord activity, float minAspect,
+ int screenOrientation) {
+ prepareLimitedBounds(activity, -1 /* maxAspect */, minAspect, screenOrientation,
+ true /* isUnresizable */);
+ }
+
static void prepareUnresizable(ActivityRecord activity, int screenOrientation) {
prepareUnresizable(activity, -1 /* maxAspect */, screenOrientation);
}
@@ -4873,11 +4926,17 @@ public class SizeCompatTests extends WindowTestsBase {
prepareLimitedBounds(activity, -1 /* maxAspect */, screenOrientation, isUnresizable);
}
+ static void prepareLimitedBounds(ActivityRecord activity, float maxAspect,
+ int screenOrientation, boolean isUnresizable) {
+ prepareLimitedBounds(activity, maxAspect, -1 /* minAspect */, screenOrientation,
+ isUnresizable);
+ }
+
/**
- * Setups {@link #mActivity} with restriction on its bounds, such as maxAspect, fixed
- * orientation, and/or whether it is resizable.
+ * Setups {@link #mActivity} with restriction on its bounds, such as maxAspect, minAspect,
+ * fixed orientation, and/or whether it is resizable.
*/
- static void prepareLimitedBounds(ActivityRecord activity, float maxAspect,
+ static void prepareLimitedBounds(ActivityRecord activity, float maxAspect, float minAspect,
int screenOrientation, boolean isUnresizable) {
activity.info.resizeMode = isUnresizable
? RESIZE_MODE_UNRESIZEABLE
@@ -4892,6 +4951,9 @@ public class SizeCompatTests extends WindowTestsBase {
if (maxAspect >= 0) {
activity.info.setMaxAspectRatio(maxAspect);
}
+ if (minAspect >= 0) {
+ activity.info.setMinAspectRatio(minAspect);
+ }
if (screenOrientation != SCREEN_ORIENTATION_UNSPECIFIED) {
activity.info.screenOrientation = screenOrientation;
activity.setRequestedOrientation(screenOrientation);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java
index db08eabac699..bfc13d3d2ef2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java
@@ -61,10 +61,7 @@ public class TaskPositioningControllerTests extends WindowTestsBase {
assertNotNull(mWm.mTaskPositioningController);
mTarget = mWm.mTaskPositioningController;
- when(mWm.mInputManager.transferTouchFocus(
- any(InputChannel.class),
- any(InputChannel.class),
- any(boolean.class))).thenReturn(true);
+ when(mWm.mInputManager.transferTouchGesture(any(), any())).thenReturn(true);
mWindow = createWindow(null, TYPE_BASE_APPLICATION, "window");
mWindow.getTask().setResizeMode(RESIZE_MODE_RESIZEABLE);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TrustedOverlayTests.java b/services/tests/wmtests/src/com/android/server/wm/TrustedOverlayTests.java
index 6a15b0594428..f1d84cfc636d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TrustedOverlayTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TrustedOverlayTests.java
@@ -40,8 +40,10 @@ import android.view.WindowManager;
import androidx.test.ext.junit.rules.ActivityScenarioRule;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.server.wm.utils.CommonUtils;
import com.android.window.flags.Flags;
+import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -77,6 +79,11 @@ public class TrustedOverlayTests {
});
}
+ @After
+ public void tearDown() {
+ CommonUtils.waitUntilActivityRemoved(mActivity);
+ }
+
@RequiresFlagsDisabled(Flags.FLAG_SURFACE_TRUSTED_OVERLAY)
@Test
public void setTrustedOverlayInputWindow() throws InterruptedException {
diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
index 73d386a328f5..80fb44a9b50f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
@@ -88,8 +88,6 @@ public class WallpaperControllerTests extends WindowTestsBase {
@Test
public void testWallpaperScreenshot() {
- WindowSurfaceController windowSurfaceController = mock(WindowSurfaceController.class);
-
// No wallpaper
final DisplayContent dc = createNewDisplay();
assertFalse(dc.mWallpaperController.canScreenshotWallpaper());
@@ -99,11 +97,9 @@ public class WallpaperControllerTests extends WindowTestsBase {
assertFalse(dc.mWallpaperController.canScreenshotWallpaper());
// Wallpaper with not visible WSA surface.
- wallpaperWindow.mWinAnimator.mSurfaceController = windowSurfaceController;
- wallpaperWindow.mWinAnimator.mLastAlpha = 1;
assertFalse(dc.mWallpaperController.canScreenshotWallpaper());
- when(windowSurfaceController.getShown()).thenReturn(true);
+ makeWallpaperWindowShown(wallpaperWindow);
// Wallpaper with WSA alpha set to 0.
wallpaperWindow.mWinAnimator.mLastAlpha = 0;
@@ -306,14 +302,25 @@ public class WallpaperControllerTests extends WindowTestsBase {
spyOn(mWm.mPolicy);
doReturn(true).when(mWm.mPolicy).isKeyguardLocked();
doReturn(true).when(mWm.mPolicy).isKeyguardOccluded();
- mDisplayContent.mWallpaperController.adjustWallpaperWindows();
+ final WallpaperController wallpaperController = mDisplayContent.mWallpaperController;
+ wallpaperController.adjustWallpaperWindows();
// Wallpaper is visible because the show-when-locked activity is translucent.
- assertTrue(mDisplayContent.mWallpaperController.isWallpaperTarget(wallpaperWindow));
+ assertTrue(wallpaperController.isWallpaperTarget(wallpaperWindow));
behind.mActivityRecord.setShowWhenLocked(true);
- mDisplayContent.mWallpaperController.adjustWallpaperWindows();
+ wallpaperController.adjustWallpaperWindows();
// Wallpaper is invisible because the lowest show-when-locked activity is opaque.
- assertTrue(mDisplayContent.mWallpaperController.isWallpaperTarget(null));
+ assertTrue(wallpaperController.isWallpaperTarget(null));
+
+ // A show-when-locked wallpaper is used for lockscreen. So the top wallpaper should
+ // be the one that is not show-when-locked.
+ final WindowState wallpaperWindow2 = createWallpaperWindow(mDisplayContent);
+ makeWallpaperWindowShown(wallpaperWindow2);
+ makeWallpaperWindowShown(wallpaperWindow);
+ assertEquals(wallpaperWindow2, wallpaperController.getTopVisibleWallpaper());
+ wallpaperWindow2.mToken.asWallpaperToken().setShowWhenLocked(true);
+ wallpaperWindow.mToken.asWallpaperToken().setShowWhenLocked(false);
+ assertEquals(wallpaperWindow, wallpaperController.getTopVisibleWallpaper());
}
/**
@@ -527,6 +534,13 @@ public class WallpaperControllerTests extends WindowTestsBase {
assertThat(wallpaperWindow.mYOffset).isEqualTo(0);
}
+ private static void makeWallpaperWindowShown(WindowState w) {
+ final WindowSurfaceController windowSurfaceController = mock(WindowSurfaceController.class);
+ w.mWinAnimator.mSurfaceController = windowSurfaceController;
+ w.mWinAnimator.mLastAlpha = 1;
+ when(windowSurfaceController.getShown()).thenReturn(true);
+ }
+
private WindowState createWallpaperWindow(DisplayContent dc, int width, int height) {
final WindowState wallpaperWindow = createWallpaperWindow(dc);
// Wallpaper is cropped to match first display.
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 4da519c212df..a0e64bf94393 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -21,9 +21,11 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.permission.flags.Flags.FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.FLAG_OWN_FOCUS;
import static android.view.Display.INVALID_DISPLAY;
+import static android.view.flags.Flags.FLAG_SENSITIVE_CONTENT_APP_PROTECTION;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_SPY;
@@ -80,6 +82,7 @@ import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
import android.util.ArraySet;
import android.util.MergedConfiguration;
import android.view.ContentRecordingSession;
@@ -240,6 +243,22 @@ public class WindowManagerServiceTests extends WindowTestsBase {
}
@Test
+ public void testTrackOverlayWindow() {
+ final WindowProcessController wpc = mSystemServicesTestRule.addProcess(
+ "pkgName", "processName", 1000 /* pid */, Process.SYSTEM_UID);
+ final Session session = createTestSession(mAtm, wpc.getPid(), wpc.mUid);
+ spyOn(session);
+ assertTrue(session.mCanAddInternalSystemWindow);
+ final WindowSurfaceController winSurface = mock(WindowSurfaceController.class);
+ session.onWindowSurfaceVisibilityChanged(winSurface, true /* visible */,
+ LayoutParams.TYPE_PHONE);
+ verify(session).setHasOverlayUi(true);
+ session.onWindowSurfaceVisibilityChanged(winSurface, false /* visible */,
+ LayoutParams.TYPE_PHONE);
+ verify(session).setHasOverlayUi(false);
+ }
+
+ @Test
public void testRelayoutExitingWindow() {
final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, "appWin");
final WindowSurfaceController surfaceController = mock(WindowSurfaceController.class);
@@ -824,7 +843,8 @@ public class WindowManagerServiceTests extends WindowTestsBase {
}
@Test
- public void addBlockScreenCaptureForApps() {
+ @RequiresFlagsEnabled(FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION)
+ public void shouldBlockScreenCaptureForNotificationApps() {
String testPackage = "test";
int ownerId1 = 20;
int ownerId2 = 21;
@@ -834,12 +854,59 @@ public class WindowManagerServiceTests extends WindowTestsBase {
WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class);
wmInternal.addBlockScreenCaptureForApps(blockedPackages);
+ verify(mWm).refreshScreenCaptureDisabled();
+
+ // window client token parameter is ignored for this feature.
+ assertTrue(mWm.mSensitiveContentPackages
+ .shouldBlockScreenCaptureForApp(testPackage, ownerId1, new Binder()));
+ assertFalse(mWm.mSensitiveContentPackages
+ .shouldBlockScreenCaptureForApp(testPackage, ownerId2, new Binder()));
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_SENSITIVE_CONTENT_APP_PROTECTION)
+ public void shouldBlockScreenCaptureForSensitiveContentOnScreenApps() {
+ String testPackage = "test";
+ int ownerId1 = 20;
+ final IBinder windowClientToken = new Binder("window client token");
+ PackageInfo blockedPackage = new PackageInfo(testPackage, ownerId1, windowClientToken);
+ ArraySet<PackageInfo> blockedPackages = new ArraySet();
+ blockedPackages.add(blockedPackage);
+
+ WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class);
+ wmInternal.addBlockScreenCaptureForApps(blockedPackages);
+ verify(mWm).refreshScreenCaptureDisabled();
assertTrue(mWm.mSensitiveContentPackages
- .shouldBlockScreenCaptureForApp(testPackage, ownerId1));
+ .shouldBlockScreenCaptureForApp(testPackage, ownerId1, windowClientToken));
assertFalse(mWm.mSensitiveContentPackages
- .shouldBlockScreenCaptureForApp(testPackage, ownerId2));
+ .shouldBlockScreenCaptureForApp(testPackage, ownerId1, new Binder()));
+ }
+
+ @Test
+ @RequiresFlagsEnabled(
+ {FLAG_SENSITIVE_CONTENT_APP_PROTECTION, FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION})
+ public void shouldBlockScreenCaptureForApps() {
+ String testPackage = "test";
+ int ownerId1 = 20;
+ int ownerId2 = 21;
+ final IBinder windowClientToken = new Binder("window client token");
+ PackageInfo blockedPackage1 = new PackageInfo(testPackage, ownerId1);
+ PackageInfo blockedPackage2 = new PackageInfo(testPackage, ownerId1, windowClientToken);
+ ArraySet<PackageInfo> blockedPackages = new ArraySet();
+ blockedPackages.add(blockedPackage1);
+ blockedPackages.add(blockedPackage2);
+
+ WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class);
+ wmInternal.addBlockScreenCaptureForApps(blockedPackages);
verify(mWm).refreshScreenCaptureDisabled();
+
+ assertTrue(mWm.mSensitiveContentPackages
+ .shouldBlockScreenCaptureForApp(testPackage, ownerId1, windowClientToken));
+ assertTrue(mWm.mSensitiveContentPackages
+ .shouldBlockScreenCaptureForApp(testPackage, ownerId1, new Binder()));
+ assertFalse(mWm.mSensitiveContentPackages
+ .shouldBlockScreenCaptureForApp(testPackage, ownerId2, new Binder()));
}
@Test
@@ -875,10 +942,6 @@ public class WindowManagerServiceTests extends WindowTestsBase {
wmInternal.addBlockScreenCaptureForApps(blockedPackages);
wmInternal.addBlockScreenCaptureForApps(blockedPackages2);
- assertTrue(mWm.mSensitiveContentPackages
- .shouldBlockScreenCaptureForApp(testPackage, ownerId1));
- assertTrue(mWm.mSensitiveContentPackages
- .shouldBlockScreenCaptureForApp(testPackage, ownerId2));
verify(mWm, times(2)).refreshScreenCaptureDisabled();
}
@@ -893,9 +956,6 @@ public class WindowManagerServiceTests extends WindowTestsBase {
WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class);
wmInternal.addBlockScreenCaptureForApps(blockedPackages);
wmInternal.clearBlockedApps();
-
- assertFalse(mWm.mSensitiveContentPackages
- .shouldBlockScreenCaptureForApp(testPackage, ownerId1));
verify(mWm, times(2)).refreshScreenCaptureDisabled();
}
diff --git a/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
index aed8fb8c4503..a63db88cb614 100644
--- a/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
+++ b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
@@ -32,6 +32,8 @@ import android.os.RemoteException;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
+import android.provider.Telephony;
+import android.provider.Telephony.Carriers.EditStatus;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyFrameworkInitializer;
import android.telephony.TelephonyManager;
@@ -226,6 +228,23 @@ public final class TelephonyUtils {
}
/**
+ * Convert APN edited status to string.
+ *
+ * @param apnEditStatus APN edited status.
+ * @return APN edited status in string format.
+ */
+ public static @NonNull String apnEditedStatusToString(@EditStatus int apnEditStatus) {
+ return switch (apnEditStatus) {
+ case Telephony.Carriers.UNEDITED -> "UNEDITED";
+ case Telephony.Carriers.USER_EDITED -> "USER_EDITED";
+ case Telephony.Carriers.USER_DELETED -> "USER_DELETED";
+ case Telephony.Carriers.CARRIER_EDITED -> "CARRIER_EDITED";
+ case Telephony.Carriers.CARRIER_DELETED -> "CARRIER_DELETED";
+ default -> "UNKNOWN(" + apnEditStatus + ")";
+ };
+ }
+
+ /**
* Utility method to get user handle associated with this subscription.
*
* This method should be used internally as it returns null instead of throwing
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index d99abe882405..5d99acd87dd3 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -4803,12 +4803,51 @@ public class CarrierConfigManager {
*/
public static final String KEY_FCM_SENDER_ID_STRING = KEY_PREFIX + "fcm_sender_id_string";
+ /**
+ * Indicates the supported protocol version in the parameter entitlement_version.
+ * The default value is 2. The possible value is 2 and 8.
+ *
+ * Reference: GSMA TS.43-v8 section 2.5 Protocol version control and
+ * Table 3. GET Parameters for Entitlement Configuration in section 2.3
+ * HTTP GET method Parameters.
+ * @hide
+ */
+ public static final String KEY_ENTITLEMENT_VERSION_INT =
+ KEY_PREFIX + "entitlement_version_int";
+
+ /**
+ * Controls the service entitlement status when receiving the VERS characteristic
+ * with both version and validity set to -1 or -2.
+ * If {@code true}, default service entitlement status is enabled.
+ * If {@code false}, default service entitlement status is disabled.
+ *
+ * Reference: GSMA TS.14-v8 section 2.1, overview
+ * @hide
+ */
+ public static final String KEY_DEFAULT_SERVICE_ENTITLEMENT_STATUS_BOOL =
+ KEY_PREFIX + "default_service_entitlement_status_bool";
+
+ /**
+ * Indicates if UE can skip service entitlement check when the user turns on Wi-Fi Calling.
+ * UE still shows Wi-Fi Calling emergency address update web view when the user clicks
+ * "Update Emergency Address" on the WiFi calling setting.
+ *
+ * Note: this is effective only if the {@link #KEY_WFC_EMERGENCY_ADDRESS_CARRIER_APP_STRING}
+ * is set to this app.
+ * @hide
+ */
+ public static final String KEY_SKIP_WFC_ACTIVATION_BOOL =
+ KEY_PREFIX + "skip_wfc_activation_bool";
+
private static PersistableBundle getDefaults() {
PersistableBundle defaults = new PersistableBundle();
defaults.putString(KEY_ENTITLEMENT_SERVER_URL_STRING, "");
defaults.putString(KEY_FCM_SENDER_ID_STRING, "");
defaults.putBoolean(KEY_SHOW_VOWIFI_WEBVIEW_BOOL, false);
defaults.putBoolean(KEY_IMS_PROVISIONING_BOOL, false);
+ defaults.putBoolean(KEY_DEFAULT_SERVICE_ENTITLEMENT_STATUS_BOOL, false);
+ defaults.putBoolean(KEY_SKIP_WFC_ACTIVATION_BOOL, false);
+ defaults.putInt(KEY_ENTITLEMENT_VERSION_INT, 2);
return defaults;
}
}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index fd9aae9ff835..626a2e574881 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -15014,7 +15014,8 @@ public class TelephonyManager {
* Get the emergency assistance package name.
*
* @return the package name of the emergency assistance app.
- * @throws IllegalStateException if emergency assistance is not enabled.
+ * @throws IllegalStateException if emergency assistance is not enabled or the device is
+ * not voice capable.
*
* @hide
*/
@@ -15023,8 +15024,9 @@ public class TelephonyManager {
@NonNull
@SystemApi
public String getEmergencyAssistancePackage() {
- if (!isEmergencyAssistanceEnabled()) {
- throw new IllegalStateException("isEmergencyAssistanceEnabled() is false.");
+ if (!isEmergencyAssistanceEnabled() || !isVoiceCapable()) {
+ throw new IllegalStateException("isEmergencyAssistanceEnabled() is false or device"
+ + " not voice capable.");
}
String emergencyRole = mContext.getSystemService(RoleManager.class)
.getEmergencyRoleHolder(mContext.getUserId());
diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java
index 8679bd4baf2e..44d3fca6aec6 100644
--- a/telephony/java/android/telephony/data/ApnSetting.java
+++ b/telephony/java/android/telephony/data/ApnSetting.java
@@ -29,6 +29,7 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.provider.Telephony;
import android.provider.Telephony.Carriers;
+import android.provider.Telephony.Carriers.EditStatus;
import android.telephony.Annotation.NetworkType;
import android.telephony.ServiceState;
import android.telephony.TelephonyManager;
@@ -37,6 +38,7 @@ import android.util.ArrayMap;
import android.util.Log;
import com.android.internal.telephony.flags.Flags;
+import com.android.internal.telephony.util.TelephonyUtils;
import com.android.telephony.Rlog;
import java.lang.annotation.Retention;
@@ -571,6 +573,13 @@ public class ApnSetting implements Parcelable {
private final boolean mEsimBootstrapProvisioning;
/**
+ * The APN edited status.
+ *
+ * Note it is intended not using this field for {@link #equals(Object)} or {@link #hashCode()}.
+ */
+ private final @EditStatus int mEditedStatus;
+
+ /**
* Returns the default MTU (Maximum Transmission Unit) size in bytes of the IPv4 routes brought
* up by this APN setting. Note this value will only be used when MTU size is not provided
* in {@code DataCallResponse#getMtuV4()} during network bring up.
@@ -992,6 +1001,22 @@ public class ApnSetting implements Parcelable {
return mEsimBootstrapProvisioning;
}
+ /**
+ * @return APN edited status. APN could be added/edited/deleted by a user or carrier.
+ *
+ * @see Carriers#UNEDITED
+ * @see Carriers#USER_EDITED
+ * @see Carriers#USER_DELETED
+ * @see Carriers#CARRIER_EDITED
+ * @see Carriers#CARRIER_DELETED
+ *
+ * @hide
+ */
+ @EditStatus
+ public int getEditedStatus() {
+ return mEditedStatus;
+ }
+
private ApnSetting(Builder builder) {
this.mEntryName = builder.mEntryName;
this.mApnName = builder.mApnName;
@@ -1030,6 +1055,7 @@ public class ApnSetting implements Parcelable {
this.mAlwaysOn = builder.mAlwaysOn;
this.mInfrastructureBitmask = builder.mInfrastructureBitmask;
this.mEsimBootstrapProvisioning = builder.mEsimBootstrapProvisioning;
+ this.mEditedStatus = builder.mEditedStatus;
}
/**
@@ -1113,6 +1139,8 @@ public class ApnSetting implements Parcelable {
Telephony.Carriers.INFRASTRUCTURE_BITMASK)))
.setEsimBootstrapProvisioning(cursor.getInt(
cursor.getColumnIndexOrThrow(Carriers.ESIM_BOOTSTRAP_PROVISIONING)) == 1)
+ .setEditedStatus(cursor.getInt(
+ cursor.getColumnIndexOrThrow(Carriers.EDITED_STATUS)))
.buildWithoutCheck();
}
@@ -1154,6 +1182,7 @@ public class ApnSetting implements Parcelable {
.setAlwaysOn(apn.mAlwaysOn)
.setInfrastructureBitmask(apn.mInfrastructureBitmask)
.setEsimBootstrapProvisioning(apn.mEsimBootstrapProvisioning)
+ .setEditedStatus(apn.mEditedStatus)
.buildWithoutCheck();
}
@@ -1202,6 +1231,7 @@ public class ApnSetting implements Parcelable {
sb.append(", ").append(mInfrastructureBitmask);
sb.append(", ").append(Objects.hash(mUser, mPassword));
sb.append(", ").append(mEsimBootstrapProvisioning);
+ sb.append(", ").append(TelephonyUtils.apnEditedStatusToString(mEditedStatus));
return sb.toString();
}
@@ -1748,6 +1778,7 @@ public class ApnSetting implements Parcelable {
dest.writeBoolean(mAlwaysOn);
dest.writeInt(mInfrastructureBitmask);
dest.writeBoolean(mEsimBootstrapProvisioning);
+ dest.writeInt(mEditedStatus);
}
private static ApnSetting readFromParcel(Parcel in) {
@@ -1785,6 +1816,7 @@ public class ApnSetting implements Parcelable {
.setAlwaysOn(in.readBoolean())
.setInfrastructureBitmask(in.readInt())
.setEsimBootstrapProvisioning(in.readBoolean())
+ .setEditedStatus(in.readInt())
.buildWithoutCheck();
}
@@ -1868,6 +1900,7 @@ public class ApnSetting implements Parcelable {
private boolean mAlwaysOn;
private int mInfrastructureBitmask = INFRASTRUCTURE_CELLULAR | INFRASTRUCTURE_SATELLITE;
private boolean mEsimBootstrapProvisioning;
+ private @EditStatus int mEditedStatus = Carriers.UNEDITED;
/**
* Default constructor for Builder.
@@ -2310,6 +2343,8 @@ public class ApnSetting implements Parcelable {
*
* @param esimBootstrapProvisioning {@code true} if the APN is used for eSIM bootstrap
* provisioning, {@code false} otherwise.
+ *
+ * @return The builder.
* @hide
*/
@NonNull
@@ -2319,6 +2354,26 @@ public class ApnSetting implements Parcelable {
}
/**
+ * Set the edited status. APN could be added/edited/deleted by a user or carrier.
+ *
+ * @param editedStatus The APN edited status
+ * @return The builder.
+ *
+ * @see Carriers#UNEDITED
+ * @see Carriers#USER_EDITED
+ * @see Carriers#USER_DELETED
+ * @see Carriers#CARRIER_EDITED
+ * @see Carriers#CARRIER_DELETED
+ *
+ * @hide
+ */
+ @NonNull
+ public Builder setEditedStatus(@EditStatus int editedStatus) {
+ this.mEditedStatus = editedStatus;
+ return this;
+ }
+
+ /**
* Builds {@link ApnSetting} from this builder.
*
* @return {@code null} if {@link #setApnName(String)} or {@link #setEntryName(String)}
diff --git a/tests/BootImageProfileTest/AndroidTest.xml b/tests/BootImageProfileTest/AndroidTest.xml
index 7e97fa3a8ff1..9b527dc159ee 100644
--- a/tests/BootImageProfileTest/AndroidTest.xml
+++ b/tests/BootImageProfileTest/AndroidTest.xml
@@ -14,6 +14,7 @@
limitations under the License.
-->
<configuration description="Config for BootImageProfileTest">
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
<!-- do not use DeviceSetup#set-property because it reboots the device b/136200738.
furthermore the changes in /data/local.prop don't actually seem to get picked up.
-->
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
index f628af14a0b9..452c98c65a7f 100644
--- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
@@ -296,6 +296,10 @@ open class PipAppHelper(instrumentation: Instrumentation) :
clickObject(MEDIA_SESSION_START_RADIO_BUTTON_ID)
}
+ fun setSourceRectHint() {
+ clickObject(SOURCE_RECT_HINT)
+ }
+
fun checkWithCustomActionsCheckbox() =
uiDevice
.findObject(By.res(packageName, WITH_CUSTOM_ACTIONS_BUTTON_ID))
@@ -444,6 +448,7 @@ open class PipAppHelper(instrumentation: Instrumentation) :
private const val MEDIA_SESSION_START_RADIO_BUTTON_ID = "media_session_start"
private const val ENTER_PIP_ON_USER_LEAVE_HINT = "enter_pip_on_leave_manual"
private const val ENTER_PIP_AUTOENTER = "enter_pip_on_leave_autoenter"
+ private const val SOURCE_RECT_HINT = "set_source_rect_hint"
// minimum number of steps to take, when animating gestures, needs to be 2
// so that there is at least a single intermediate layer that flicker tests can check
private const val MIN_STEPS_TO_ANIMATE = 2
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_pip.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_pip.xml
index f7ba45b25d48..36cbf1a8fe84 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_pip.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_pip.xml
@@ -27,6 +27,15 @@
where things are arranged differently and to circle back up to the top once we reach the
bottom. -->
+ <!-- View used for testing sourceRectHint. -->
+ <View
+ android:id="@+id/source_rect"
+ android:layout_width="320dp"
+ android:layout_height="180dp"
+ android:visibility="gone"
+ android:background="@android:color/holo_green_light"
+ />
+
<Button
android:id="@+id/enter_pip"
android:layout_width="wrap_content"
@@ -113,6 +122,13 @@
android:onClick="onRatioSelected"/>
</RadioGroup>
+ <Button
+ android:id="@+id/set_source_rect_hint"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Set SourceRectHint"
+ android:onClick="setSourceRectHint"/>
+
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PipActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PipActivity.java
index 12eaad108fc6..1ab8ddbe20e2 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PipActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PipActivity.java
@@ -37,6 +37,7 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.graphics.Rect;
import android.graphics.drawable.Icon;
import android.media.MediaMetadata;
import android.media.session.MediaSession;
@@ -45,6 +46,7 @@ import android.os.Bundle;
import android.util.Log;
import android.util.Rational;
import android.view.View;
+import android.view.ViewTreeObserver;
import android.view.Window;
import android.view.WindowManager;
import android.widget.CheckBox;
@@ -248,6 +250,29 @@ public class PipActivity extends Activity {
}
}
+ /**
+ * Adds a temporary view used for testing sourceRectHint.
+ *
+ */
+ public void setSourceRectHint(View v) {
+ View rectView = findViewById(R.id.source_rect);
+ if (rectView != null) {
+ rectView.setVisibility(View.VISIBLE);
+ rectView.getViewTreeObserver().addOnGlobalLayoutListener(
+ new ViewTreeObserver.OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ Rect boundingRect = new Rect();
+ rectView.getGlobalVisibleRect(boundingRect);
+ mPipParamsBuilder.setSourceRectHint(boundingRect);
+ setPictureInPictureParams(mPipParamsBuilder.build());
+ rectView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+ }
+ });
+ rectView.invalidate(); // changing the visibility, invalidating to redraw the view
+ }
+ }
+
public void onRatioSelected(View v) {
switch (v.getId()) {
case R.id.ratio_default:
diff --git a/tests/InputScreenshotTest/assets/phone/light_landscape_layout-preview.png b/tests/InputScreenshotTest/assets/phone/light_landscape_layout-preview.png
index 70e4a7101c7f..443de8edc2d3 100644
--- a/tests/InputScreenshotTest/assets/phone/light_landscape_layout-preview.png
+++ b/tests/InputScreenshotTest/assets/phone/light_landscape_layout-preview.png
Binary files differ
diff --git a/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview-ansi.png b/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview-ansi.png
index 502c1b4499d4..cb69c0e44a03 100644
--- a/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview-ansi.png
+++ b/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview-ansi.png
Binary files differ
diff --git a/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview-jis.png b/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview-jis.png
index 591b2fa9608e..1c6d1b3a097d 100644
--- a/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview-jis.png
+++ b/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview-jis.png
Binary files differ
diff --git a/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview.png b/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview.png
index 0137a853e538..c51da052f4bf 100644
--- a/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview.png
+++ b/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview.png
Binary files differ
diff --git a/tests/InputScreenshotTest/assets/tablet/dark_portrait_layout-preview.png b/tests/InputScreenshotTest/assets/tablet/dark_portrait_layout-preview.png
index 37a91e1fce53..ab23401bf629 100644
--- a/tests/InputScreenshotTest/assets/tablet/dark_portrait_layout-preview.png
+++ b/tests/InputScreenshotTest/assets/tablet/dark_portrait_layout-preview.png
Binary files differ
diff --git a/tests/Internal/src/com/android/internal/protolog/OWNERS b/tests/Internal/src/com/android/internal/protolog/OWNERS
new file mode 100644
index 000000000000..18cf2be9f7df
--- /dev/null
+++ b/tests/Internal/src/com/android/internal/protolog/OWNERS
@@ -0,0 +1,3 @@
+# ProtoLog owners
+# Bug component: 1157642
+include platform/development:/tools/winscope/OWNERS
diff --git a/tests/vcn/Android.bp b/tests/vcn/Android.bp
index 228520e8545b..ee2e7cfcd480 100644
--- a/tests/vcn/Android.bp
+++ b/tests/vcn/Android.bp
@@ -3,6 +3,7 @@
//########################################################################
package {
+ default_team: "trendy_team_enigma",
// 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"
@@ -30,6 +31,7 @@ android_test {
"platform-test-annotations",
"services.core",
"service-connectivity-tiramisu-pre-jarjar",
+ "flag-junit",
],
libs: [
"android.test.runner",
diff --git a/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java b/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java
index 34f884b94296..887630b03a8c 100644
--- a/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java
+++ b/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java
@@ -38,6 +38,7 @@ import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
@@ -58,6 +59,7 @@ import android.os.HandlerExecutor;
import android.os.ParcelUuid;
import android.os.PersistableBundle;
import android.os.test.TestLooper;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
@@ -71,7 +73,10 @@ import android.util.ArraySet;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.telephony.flags.Flags;
+
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -128,6 +133,9 @@ public class TelephonySubscriptionTrackerTest {
TEST_SUBID_TO_CARRIER_CONFIG_MAP = Collections.unmodifiableMap(subIdToCarrierConfigMap);
}
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+
@NonNull private final Context mContext;
@NonNull private final TestLooper mTestLooper;
@NonNull private final Handler mHandler;
@@ -185,6 +193,7 @@ public class TelephonySubscriptionTrackerTest {
@Before
public void setUp() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_FIX_CRASH_ON_GETTING_CONFIG_WHEN_PHONE_IS_GONE);
doReturn(2).when(mTelephonyManager).getActiveModemCount();
mCallback = mock(TelephonySubscriptionTrackerCallback.class);
@@ -594,4 +603,14 @@ public class TelephonySubscriptionTrackerTest {
new ArraySet<>(Arrays.asList(TEST_SUBSCRIPTION_ID_1, TEST_SUBSCRIPTION_ID_2)),
snapshot.getAllSubIdsInGroup(TEST_PARCEL_UUID));
}
+
+ @Test
+ public void testCarrierConfigChangeWhenPhoneIsGoneShouldNotCrash() throws Exception {
+ doThrow(new IllegalStateException("Carrier config loader is not available."))
+ .when(mCarrierConfigManager)
+ .getConfigForSubId(eq(TEST_SUBSCRIPTION_ID_1), any());
+
+ sendCarrierConfigChange(true /* hasValidSubscription */);
+ mTestLooper.dispatchAll();
+ }
}
diff --git a/wifi/TEST_MAPPING b/wifi/TEST_MAPPING
index 757ecaa8aca9..3ae91bee6f45 100644
--- a/wifi/TEST_MAPPING
+++ b/wifi/TEST_MAPPING
@@ -2,9 +2,7 @@
"presubmit": [
{
"name": "FrameworksWifiNonUpdatableApiTests"
- }
- ],
- "postsubmit": [
+ },
{
"name": "CtsWifiNonUpdatableTestCases"
}