summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AconfigFlags.bp46
-rw-r--r--Android.bp1
-rw-r--r--apct-tests/perftests/contentcapture/src/android/view/contentcapture/AbstractContentCapturePerfTestCase.java5
-rw-r--r--apct-tests/perftests/contentcapture/src/android/view/contentcapture/LoginTest.java60
-rw-r--r--apct-tests/perftests/contentcapture/src/android/view/contentcapture/MyContentCaptureService.java10
-rw-r--r--apex/jobscheduler/framework/aconfig/job.aconfig7
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java11
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java3
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java8
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java14
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java66
-rw-r--r--core/api/Android.bp18
-rw-r--r--core/api/current.txt103
-rw-r--r--core/api/system-current.txt32
-rw-r--r--core/api/test-current.txt1
-rw-r--r--core/java/android/app/ActivityOptions.java63
-rw-r--r--core/java/android/app/ActivityThread.java14
-rw-r--r--core/java/android/app/AppOpsManager.java20
-rw-r--r--core/java/android/app/BackgroundInstallControlManager.java102
-rw-r--r--core/java/android/app/IActivityManager.aidl3
-rw-r--r--core/java/android/app/Notification.java28
-rw-r--r--core/java/android/app/OWNERS4
-rw-r--r--core/java/android/companion/virtual/camera/VirtualCameraConfig.java13
-rw-r--r--core/java/android/content/IntentFilter.java140
-rw-r--r--core/java/android/content/UriRelativeFilter.java260
-rw-r--r--core/java/android/content/UriRelativeFilterGroup.java216
-rw-r--r--core/java/android/content/pm/PackageManager.java6
-rw-r--r--core/java/android/content/pm/UserInfo.java4
-rw-r--r--core/java/android/content/pm/UserProperties.java106
-rw-r--r--core/java/android/content/pm/flags.aconfig22
-rw-r--r--core/java/android/content/pm/multiuser.aconfig16
-rw-r--r--core/java/android/content/res/FontScaleConverterFactory.java10
-rw-r--r--core/java/android/credentials/ui/AuthenticationEntry.java57
-rw-r--r--core/java/android/credentials/ui/BaseDialogResult.java33
-rw-r--r--core/java/android/credentials/ui/CancelUiRequest.java17
-rw-r--r--core/java/android/credentials/ui/Constants.java4
-rw-r--r--core/java/android/credentials/ui/CreateCredentialProviderData.java11
-rw-r--r--core/java/android/credentials/ui/CreateCredentialProviderInfo.java113
-rw-r--r--core/java/android/credentials/ui/DisabledProviderData.java10
-rw-r--r--core/java/android/credentials/ui/DisabledProviderInfo.java50
-rw-r--r--core/java/android/credentials/ui/Entry.java44
-rw-r--r--core/java/android/credentials/ui/FailureDialogResult.java97
-rw-r--r--core/java/android/credentials/ui/FailureResult.java96
-rw-r--r--core/java/android/credentials/ui/GetCredentialProviderData.java48
-rw-r--r--core/java/android/credentials/ui/GetCredentialProviderInfo.java168
-rw-r--r--core/java/android/credentials/ui/IntentFactory.java19
-rw-r--r--core/java/android/credentials/ui/IntentHelper.java97
-rw-r--r--core/java/android/credentials/ui/ProviderDialogResult.java100
-rw-r--r--core/java/android/credentials/ui/ProviderPendingIntentResponse.java28
-rw-r--r--core/java/android/credentials/ui/RequestInfo.java17
-rw-r--r--core/java/android/credentials/ui/ResultHelper.java62
-rw-r--r--core/java/android/credentials/ui/UiResult.java (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorTest.kt)14
-rw-r--r--core/java/android/credentials/ui/UserSelectionResult.java84
-rw-r--r--core/java/android/hardware/HardwareBuffer.java10
-rw-r--r--core/java/android/hardware/biometrics/BiometricPrompt.java20
-rw-r--r--core/java/android/hardware/biometrics/PromptContentItemBulletedText.java10
-rw-r--r--core/java/android/hardware/biometrics/PromptContentItemPlainText.java10
-rw-r--r--core/java/android/hardware/biometrics/PromptInfo.java4
-rw-r--r--core/java/android/hardware/biometrics/PromptVerticalListContentView.java16
-rw-r--r--core/java/android/hardware/input/InputSettings.java87
-rw-r--r--core/java/android/hardware/input/input_framework.aconfig7
-rw-r--r--core/java/android/net/thread/OWNERS3
-rw-r--r--core/java/android/net/thread/flags.aconfig8
-rw-r--r--core/java/android/os/PatternMatcher.java14
-rw-r--r--core/java/android/os/PersistableBundle.java37
-rw-r--r--core/java/android/os/Process.java10
-rw-r--r--core/java/android/os/UserManager.java56
-rw-r--r--core/java/android/provider/Settings.java46
-rw-r--r--core/java/android/service/autofill/AutofillService.java23
-rw-r--r--core/java/android/service/autofill/ConvertCredentialCallback.java65
-rw-r--r--core/java/android/service/autofill/ConvertCredentialRequest.aidl19
-rw-r--r--core/java/android/service/autofill/ConvertCredentialRequest.java158
-rw-r--r--core/java/android/service/autofill/ConvertCredentialResponse.aidl19
-rw-r--r--core/java/android/service/autofill/ConvertCredentialResponse.java157
-rw-r--r--core/java/android/service/autofill/FillEventHistory.java11
-rw-r--r--core/java/android/service/autofill/FillResponse.java37
-rw-r--r--core/java/android/service/autofill/IAutoFillService.aidl5
-rw-r--r--core/java/android/service/autofill/IConvertCredentialCallback.aidl31
-rw-r--r--core/java/android/service/notification/ZenModeConfig.java4
-rw-r--r--core/java/android/service/notification/ZenPolicy.java4
-rw-r--r--core/java/android/util/Singleton.java1
-rw-r--r--core/java/android/view/AttachedSurfaceControl.java19
-rw-r--r--core/java/android/view/Display.java2
-rw-r--r--core/java/android/view/DisplayInfo.java1
-rw-r--r--core/java/android/view/IWindowSession.aidl2
-rw-r--r--core/java/android/view/InsetsSource.java5
-rw-r--r--core/java/android/view/Surface.java7
-rw-r--r--core/java/android/view/WindowManager.java53
-rw-r--r--core/java/android/view/WindowManagerGlobal.java98
-rw-r--r--core/java/android/view/WindowManagerImpl.java20
-rw-r--r--core/java/android/view/WindowlessWindowManager.java3
-rw-r--r--core/java/android/view/accessibility/AccessibilityManager.java46
-rw-r--r--core/java/android/view/autofill/AutofillManager.java9
-rw-r--r--core/java/android/view/autofill/OWNERS1
-rw-r--r--core/java/android/window/ScreenCapture.java11
-rw-r--r--core/java/android/window/WindowOnBackInvokedDispatcher.java22
-rw-r--r--core/java/android/window/flags/large_screen_experiences_app_compat.aconfig8
-rw-r--r--core/java/android/window/flags/window_surfaces.aconfig8
-rw-r--r--core/java/android/window/flags/windowing_frontend.aconfig8
-rw-r--r--core/java/com/android/internal/accessibility/AccessibilityShortcutController.java6
-rw-r--r--core/java/com/android/internal/accessibility/common/ShortcutConstants.java31
-rw-r--r--core/java/com/android/internal/accessibility/dialog/AccessibilityActivityTarget.java8
-rw-r--r--core/java/com/android/internal/accessibility/dialog/AccessibilityButtonChooserActivity.java4
-rw-r--r--core/java/com/android/internal/accessibility/dialog/AccessibilityServiceTarget.java9
-rw-r--r--core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java11
-rw-r--r--core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java20
-rw-r--r--core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java27
-rw-r--r--core/java/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTarget.java10
-rw-r--r--core/java/com/android/internal/accessibility/dialog/InvisibleToggleAllowListingFeatureTarget.java5
-rw-r--r--core/java/com/android/internal/accessibility/dialog/ToggleAccessibilityServiceTarget.java5
-rw-r--r--core/java/com/android/internal/accessibility/dialog/ToggleAllowListingFeatureTarget.java5
-rw-r--r--core/java/com/android/internal/accessibility/dialog/VolumeShortcutToggleAccessibilityServiceTarget.java11
-rw-r--r--core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java24
-rw-r--r--core/java/com/android/internal/accessibility/util/ShortcutUtils.java31
-rw-r--r--core/java/com/android/internal/app/ConfirmUserCreationActivity.java12
-rw-r--r--core/java/com/android/internal/display/BrightnessSynchronizer.java2
-rw-r--r--core/java/com/android/internal/jank/Cuj.java10
-rw-r--r--core/java/com/android/internal/jank/InteractionMonitorDebugOverlay.java14
-rw-r--r--core/java/com/android/internal/pm/pkg/component/ParsedIntentInfoUtils.java200
-rw-r--r--core/java/com/android/internal/policy/SystemBarUtils.java33
-rw-r--r--core/java/com/android/internal/widget/ConversationLayout.java37
-rw-r--r--core/java/com/android/internal/widget/ImageFloatingTextView.java2
-rw-r--r--core/java/com/android/internal/widget/PeopleHelper.java68
-rw-r--r--core/jni/android_util_Process.cpp12
-rw-r--r--core/jni/android_window_ScreenCapture.cpp6
-rw-r--r--core/proto/android/content/intent.proto23
-rw-r--r--core/proto/android/server/windowmanagerservice.proto1
-rw-r--r--core/res/AndroidManifest.xml43
-rw-r--r--core/res/res/color-night/notification_expand_button_state_tint.xml21
-rw-r--r--core/res/res/color/notification_expand_button_state_tint.xml11
-rw-r--r--core/res/res/values/attrs_manifest.xml81
-rw-r--r--core/res/res/values/config.xml2
-rw-r--r--core/res/res/values/public-staging.xml20
-rw-r--r--core/res/res/xml/sms_short_codes.xml19
-rw-r--r--core/tests/InputMethodCoreTests/Android.bp66
-rw-r--r--core/tests/InputMethodCoreTests/AndroidManifest.xml31
-rw-r--r--core/tests/InputMethodCoreTests/AndroidTest.xml38
-rw-r--r--core/tests/InputMethodCoreTests/OWNERS1
-rw-r--r--core/tests/InputMethodCoreTests/res/xml/ime_meta.xml (renamed from core/tests/coretests/res/xml/ime_meta.xml)0
-rw-r--r--core/tests/InputMethodCoreTests/res/xml/ime_meta_inline_suggestions.xml (renamed from core/tests/coretests/res/xml/ime_meta_inline_suggestions.xml)0
-rw-r--r--core/tests/InputMethodCoreTests/res/xml/ime_meta_inline_suggestions_with_touch_exploration.xml (renamed from core/tests/coretests/res/xml/ime_meta_inline_suggestions_with_touch_exploration.xml)0
-rw-r--r--core/tests/InputMethodCoreTests/res/xml/ime_meta_sw_next.xml (renamed from core/tests/coretests/res/xml/ime_meta_sw_next.xml)0
-rw-r--r--core/tests/InputMethodCoreTests/res/xml/ime_meta_virtual_device_only.xml (renamed from core/tests/coretests/res/xml/ime_meta_virtual_device_only.xml)0
-rw-r--r--core/tests/InputMethodCoreTests/res/xml/ime_meta_vr_only.xml (renamed from core/tests/coretests/res/xml/ime_meta_vr_only.xml)0
-rw-r--r--core/tests/InputMethodCoreTests/src/android/view/inputmethod/BaseInputConnectionTest.java (renamed from core/tests/coretests/src/android/view/inputmethod/BaseInputConnectionTest.java)0
-rw-r--r--core/tests/InputMethodCoreTests/src/android/view/inputmethod/CursorAnchorInfoTest.java (renamed from core/tests/coretests/src/android/view/inputmethod/CursorAnchorInfoTest.java)0
-rw-r--r--core/tests/InputMethodCoreTests/src/android/view/inputmethod/DeleteRangeGestureTest.java (renamed from core/tests/coretests/src/android/view/inputmethod/DeleteRangeGestureTest.java)0
-rw-r--r--core/tests/InputMethodCoreTests/src/android/view/inputmethod/EditorInfoTest.java (renamed from core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java)0
-rw-r--r--core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodInfoTest.java (renamed from core/tests/coretests/src/android/view/inputmethod/InputMethodInfoTest.java)2
-rw-r--r--core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodManagerTest.java (renamed from core/tests/coretests/src/android/view/inputmethod/InputMethodManagerTest.java)0
-rw-r--r--core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodSubtypeArrayTest.java (renamed from core/tests/coretests/src/android/view/inputmethod/InputMethodSubtypeArrayTest.java)0
-rw-r--r--core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodSubtypeTest.java (renamed from core/tests/coretests/src/android/view/inputmethod/InputMethodSubtypeTest.java)0
-rw-r--r--core/tests/InputMethodCoreTests/src/android/view/inputmethod/InsertGestureTest.java (renamed from core/tests/coretests/src/android/view/inputmethod/InsertGestureTest.java)0
-rw-r--r--core/tests/InputMethodCoreTests/src/android/view/inputmethod/InsertModeGestureTest.java (renamed from core/tests/coretests/src/android/view/inputmethod/InsertModeGestureTest.java)0
-rw-r--r--core/tests/InputMethodCoreTests/src/android/view/inputmethod/ParcelableHandwritingGestureTest.java (renamed from core/tests/coretests/src/android/view/inputmethod/ParcelableHandwritingGestureTest.java)0
-rw-r--r--core/tests/InputMethodCoreTests/src/android/view/inputmethod/SelectGestureTest.java (renamed from core/tests/coretests/src/android/view/inputmethod/SelectGestureTest.java)0
-rw-r--r--core/tests/InputMethodCoreTests/src/android/view/inputmethod/SelectRangeGestureTest.java (renamed from core/tests/coretests/src/android/view/inputmethod/SelectRangeGestureTest.java)0
-rw-r--r--core/tests/InputMethodCoreTests/src/android/view/inputmethod/SparseRectFArrayTest.java (renamed from core/tests/coretests/src/android/view/inputmethod/SparseRectFArrayTest.java)0
-rw-r--r--core/tests/InputMethodCoreTests/src/android/view/inputmethod/SurroundingTextTest.java (renamed from core/tests/coretests/src/android/view/inputmethod/SurroundingTextTest.java)0
-rw-r--r--core/tests/InputMethodCoreTests/src/android/view/inputmethod/TextAppearanceInfoTest.java (renamed from core/tests/coretests/src/android/view/inputmethod/TextAppearanceInfoTest.java)0
-rw-r--r--core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/CompletableFutureUtilTest.kt (renamed from core/tests/coretests/src/com/android/internal/inputmethod/CompletableFutureUtilTest.kt)0
-rw-r--r--core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputConnectionWrapperTest.java (renamed from core/tests/coretests/src/com/android/internal/inputmethod/InputConnectionWrapperTest.java)0
-rw-r--r--core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputMethodDebugTest.java (renamed from core/tests/coretests/src/com/android/internal/inputmethod/InputMethodDebugTest.java)0
-rw-r--r--core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputMethodSubtypeHandleTest.java (renamed from core/tests/coretests/src/com/android/internal/inputmethod/InputMethodSubtypeHandleTest.java)0
-rw-r--r--core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/SubtypeLocaleUtilsTest.java (renamed from core/tests/coretests/src/com/android/internal/inputmethod/SubtypeLocaleUtilsTest.java)0
-rw-r--r--core/tests/coretests/Android.bp4
-rw-r--r--core/tests/coretests/src/android/content/pm/PackageManagerTest.java49
-rw-r--r--core/tests/coretests/src/android/content/pm/UserInfoTest.java71
-rw-r--r--core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt3
-rw-r--r--core/tests/coretests/src/android/telephony/PinResultTest.java34
-rw-r--r--core/tests/coretests/src/android/util/SingletonTest.java41
-rw-r--r--core/tests/coretests/src/android/view/DisplayInfoTest.java6
-rw-r--r--core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java10
-rw-r--r--core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java10
-rw-r--r--core/tests/coretests/src/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTargetTest.java3
-rw-r--r--data/etc/privapp-permissions-platform.xml3
-rw-r--r--libs/WindowManager/Shell/multivalentTests/OWNERS4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDoubleTapHelper.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDoubleTapHelper.java)14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java53
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java156
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java20
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java6
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java30
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipDoubleTapHelperTest.java11
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java9
-rw-r--r--libs/hwui/Android.bp17
-rw-r--r--libs/hwui/utils/HostColorSpace.cpp417
-rw-r--r--location/java/android/location/altitude/AltitudeConverter.java191
-rw-r--r--location/java/com/android/internal/location/altitude/GeoidMap.java (renamed from location/java/com/android/internal/location/altitude/GeoidHeightMap.java)189
-rw-r--r--media/java/android/media/MediaRouter2.java32
-rw-r--r--media/java/android/media/RoutingSessionInfo.java8
-rw-r--r--media/java/android/media/flags/editing.aconfig8
-rw-r--r--media/java/android/media/metrics/EditingEndedEvent.aidl19
-rw-r--r--media/java/android/media/metrics/EditingEndedEvent.java325
-rw-r--r--media/java/android/media/metrics/EditingSession.java13
-rw-r--r--media/java/android/media/metrics/IMediaMetricsManager.aidl5
-rw-r--r--media/java/android/media/metrics/MediaMetricsManager.java14
-rw-r--r--media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl5
-rw-r--r--media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl2
-rw-r--r--media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl1
-rw-r--r--media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl1
-rw-r--r--media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java14
-rwxr-xr-xmedia/java/android/media/tv/interactive/TvInteractiveAppManager.java46
-rwxr-xr-xmedia/java/android/media/tv/interactive/TvInteractiveAppService.java43
-rwxr-xr-xmedia/java/android/media/tv/interactive/TvInteractiveAppView.java17
-rw-r--r--native/graphics/jni/Android.bp16
-rw-r--r--nfc/jarjar-rules.txt1
-rw-r--r--nfc/java/android/nfc/cardemulation/CardEmulation.java6
-rw-r--r--packages/CrashRecovery/aconfig/flags.aconfig9
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt5
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlow.kt3
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt1
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlowTest.kt2
-rw-r--r--packages/SettingsLib/res/drawable/ic_external_display.xml28
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java115
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java14
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java6
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java24
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt53
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioModeInteractor.kt35
-rw-r--r--packages/SettingsLib/tests/integ/Android.bp1
-rw-r--r--packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioRepository.kt32
-rw-r--r--packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/domain/interactor/AudioModeInteractorTest.kt79
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java273
-rw-r--r--packages/SettingsProvider/res/values/defaults.xml3
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java1
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java1
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java1
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/validators/SettingsValidators.java3
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java5
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java35
-rw-r--r--packages/Shell/AndroidManifest.xml5
-rw-r--r--packages/SystemUI/Android.bp32
-rw-r--r--packages/SystemUI/aconfig/accessibility.aconfig7
-rw-r--r--packages/SystemUI/aconfig/biometrics_framework.aconfig7
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig8
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt15
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt87
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt4
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt2
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/EdgeDetector.kt23
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt6
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt25
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt2
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt194
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt117
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt10
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt110
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt8
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt8
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/FixedSizeEdgeDetectorTest.kt4
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt44
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt47
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt11
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplTest.kt82
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/UserA11yQsShortcutsRepositoryTest.kt66
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt83
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt11
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt27
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt22
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt13
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt134
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt9
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractorTest.kt87
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractorTest.kt77
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/AutoAddSettingsRepositoryTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/CustomTileAddedSharedPreferencesRepositoryTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt35
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/TilesSettingConverterTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/UserAutoAddRepositoryTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepositoryTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/restoreprocessors/WorkTileRestoreProcessorTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableListTest.kt87
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableTest.kt175
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingListTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CallbackControllerAutoAddableTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CastAutoAddableTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DataSaverAutoAddableTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DeviceControlsAutoAddableTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/HotspotAutoAddableTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddableTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/SafetyCenterAutoAddableTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WalletAutoAddableTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractorImplTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractorTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/shared/TileSpecTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/analytics/QSTileAnalyticsTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/airplate/domain/interactor/AirplaneModeTileDataInteractorTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/airplate/domain/interactor/AirplaneModeTileUserActionInteractorTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/interactor/ColorCorrectionTileDataInteractorTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/interactor/ColorCorrectionTileUserActionInteractorTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractorTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/interactor/ColorInversionTileDataInteractorTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/interactor/ColorInversionUserActionInteractorTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/interactor/LocationTileDataInteractorTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/interactor/LocationTileUserActionInteractorTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverDialogDelegateTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileDataInteractorTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileUserActionInteractorTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProviderTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelUserInputTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/util/kotlin/BooleanFlowOperatorsTest.kt104
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModelTest.kt90
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorImplTest.kt122
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/composable/ComponentsFactoryTest.kt63
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManagerTest.kt75
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt117
-rw-r--r--packages/SystemUI/res/values/colors.xml3
-rw-r--r--packages/SystemUI/res/values/ids.xml1
-rw-r--r--packages/SystemUI/res/values/strings.xml4
-rw-r--r--packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/FingerprintSensorType.kt11
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java8
-rw-r--r--packages/SystemUI/src/com/android/keyguard/LockIconViewController.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepository.kt54
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/data/repository/UserA11yQsShortcutsRepository.kt78
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationController.java233
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractView.kt322
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegate.java21
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java56
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java91
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt43
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricCustomizedViewBinder.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt57
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt57
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt61
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivityStarter.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractor.kt55
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractor.kt64
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/BluetoothDialogDelegate.java (renamed from packages/SystemUI/src/com/android/systemui/keyboard/BluetoothDialog.java)20
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/StickyKeysLogger.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt38
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java36
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardWmStateRefactor.kt53
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt77
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java21
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/BaseAutoAddableModule.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt58
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddable.kt94
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableList.kt58
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java48
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifInflater.kt31
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustment.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BindStage.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java99
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridGroupManager.java22
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java102
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterLogger.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java74
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt390
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/SingleLineConversationViewBinder.kt40
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/SingleLineViewBinder.kt34
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/SingleLineViewModel.kt67
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java56
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/kotlin/BooleanFlowOperators.kt52
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/kotlin/Dagger.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt47
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java72
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java54
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java48
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java75
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java41
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/utils/TestUtils.java40
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt14
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsInteractorTest.kt)8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepositoryImplTest.kt98
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt59
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java32
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java17
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/LeftRightArrowPressedListenerTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt94
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt106
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt48
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineConversationViewBinderTest.kt117
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewBinderTest.kt91
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflaterTest.kt463
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt32
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java21
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt16
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java104
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestDependency.kt14
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeAccessibilityQsShortcutsRepository.kt41
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/UdfpsUtilsKosmos.kt22
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryKosmos.kt23
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/PromptRepositoryKosmos.kt22
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractorKosmos.kt29
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorKosmos.kt31
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelKosmos.kt35
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt9
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt13
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractorKosmos.kt29
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt3
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractorKosmos.kt29
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ConfigurationControllerKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/VolumePanelKosmos.kt72
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/dagger/factory/KosmosVolumePanelComponentFactory.kt45
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/domain/TestComponentAvailabilityCriteria.kt25
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/ui/layout/FakeComponentsLayoutManager.kt37
-rw-r--r--ravenwood/framework-minus-apex-ravenwood-policies.txt3
-rw-r--r--ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java2
-rw-r--r--ravenwood/junit-src/android/platform/test/annotations/DisabledOnRavenwood.java (renamed from ravenwood/junit-src/android/platform/test/annotations/IncludeUnderRavenwood.java)13
-rw-r--r--ravenwood/junit-src/android/platform/test/annotations/EnabledOnRavenwood.java (renamed from ravenwood/junit-src/android/platform/test/annotations/ExcludeUnderRavenwood.java)13
-rw-r--r--ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodClassRule.java63
-rw-r--r--ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java98
-rw-r--r--ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java2
-rw-r--r--ravenwood/ravenwood-annotation-allowed-classes.txt21
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java71
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java36
-rw-r--r--services/autofill/java/com/android/server/autofill/RemoteFillService.java95
-rw-r--r--services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java7
-rw-r--r--services/autofill/java/com/android/server/autofill/Session.java26
-rw-r--r--services/backup/flags.aconfig2
-rw-r--r--services/companion/TEST_MAPPING5
-rw-r--r--services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java6
-rw-r--r--services/companion/java/com/android/server/companion/InactiveAssociationsRemovalService.java7
-rw-r--r--services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java7
-rw-r--r--services/companion/java/com/android/server/companion/virtual/InputController.java55
-rw-r--r--services/core/Android.bp2
-rw-r--r--services/core/java/com/android/server/am/ActiveServices.java375
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java42
-rw-r--r--services/core/java/com/android/server/am/AppBatteryExemptionTracker.java2
-rw-r--r--services/core/java/com/android/server/am/CachedAppOptimizer.java17
-rw-r--r--services/core/java/com/android/server/am/ContentProviderHelper.java11
-rw-r--r--services/core/java/com/android/server/am/IntentBindRecord.java12
-rw-r--r--services/core/java/com/android/server/am/OomAdjuster.java279
-rw-r--r--services/core/java/com/android/server/am/OomAdjusterModernImpl.java6
-rw-r--r--services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java13
-rw-r--r--services/core/java/com/android/server/am/ProcessList.java6
-rw-r--r--services/core/java/com/android/server/am/ProcessRecord.java16
-rw-r--r--services/core/java/com/android/server/am/ProcessServiceRecord.java47
-rw-r--r--services/core/java/com/android/server/am/ProcessStateRecord.java39
-rw-r--r--services/core/java/com/android/server/am/flags.aconfig7
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceInventory.java43
-rw-r--r--services/core/java/com/android/server/audio/SoundDoseHelper.java2
-rw-r--r--services/core/java/com/android/server/biometrics/AuthService.java8
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java6
-rw-r--r--services/core/java/com/android/server/input/InputManagerInternal.java6
-rw-r--r--services/core/java/com/android/server/input/InputManagerService.java19
-rw-r--r--services/core/java/com/android/server/input/InputSettingsObserver.java7
-rw-r--r--services/core/java/com/android/server/input/NativeInputManagerService.java17
-rw-r--r--services/core/java/com/android/server/inputmethod/ClientController.java46
-rw-r--r--services/core/java/com/android/server/inputmethod/ClientState.java68
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodManagerService.java37
-rw-r--r--services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java2
-rw-r--r--services/core/java/com/android/server/media/MediaSessionService.java24
-rw-r--r--services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java19
-rw-r--r--services/core/java/com/android/server/notification/ZenModeHelper.java2
-rw-r--r--services/core/java/com/android/server/os/BugreportManagerServiceImpl.java11
-rw-r--r--services/core/java/com/android/server/pm/BACKGROUND_INSTALL_OWNERS2
-rw-r--r--services/core/java/com/android/server/pm/OWNERS2
-rw-r--r--services/core/java/com/android/server/pm/PreferredComponent.java17
-rw-r--r--services/core/java/com/android/server/pm/Settings.java2
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java29
-rw-r--r--services/core/java/com/android/server/pm/UserTypeFactory.java40
-rw-r--r--services/core/java/com/android/server/pm/VerifyingSession.java3
-rw-r--r--services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java5
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java6
-rw-r--r--services/core/java/com/android/server/selinux/QuotaLimiter.java78
-rw-r--r--services/core/java/com/android/server/selinux/RateLimiter.java85
-rw-r--r--services/core/java/com/android/server/selinux/SelinuxAuditLogBuilder.java155
-rw-r--r--services/core/java/com/android/server/selinux/SelinuxAuditLogsCollector.java144
-rw-r--r--services/core/java/com/android/server/selinux/SelinuxAuditLogsService.java132
-rw-r--r--services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java45
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperDisplayHelper.java3
-rw-r--r--services/core/java/com/android/server/wm/AccessibilityController.java48
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java31
-rw-r--r--services/core/java/com/android/server/wm/BackNavigationController.java61
-rw-r--r--services/core/java/com/android/server/wm/BackgroundActivityStartController.java47
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java27
-rw-r--r--services/core/java/com/android/server/wm/LetterboxUiController.java9
-rw-r--r--services/core/java/com/android/server/wm/RecentTasks.java38
-rw-r--r--services/core/java/com/android/server/wm/Session.java5
-rw-r--r--services/core/java/com/android/server/wm/TaskFragment.java39
-rw-r--r--services/core/java/com/android/server/wm/TransitionController.java6
-rw-r--r--services/core/java/com/android/server/wm/WallpaperController.java10
-rw-r--r--services/core/java/com/android/server/wm/WindowAnimator.java7
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java24
-rw-r--r--services/core/java/com/android/server/wm/WindowOrganizerController.java21
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java2
-rw-r--r--services/core/jni/com_android_server_input_InputManagerService.cpp26
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java7
-rw-r--r--services/java/com/android/server/SystemServer.java18
-rw-r--r--services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt7
-rw-r--r--services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ClientControllerTest.java43
-rw-r--r--services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java1
-rw-r--r--services/tests/PackageManagerServiceTests/server/Android.bp1
-rw-r--r--services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java104
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/ServiceBindingOomAdjPolicyTest.java677
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java1
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java67
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/altitude/AltitudeConverterTest.java18
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java19
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java2
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/selinux/RateLimiterTest.java113
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/selinux/SelinuxAuditLogsBuilderTest.java202
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/selinux/SelinuxAuditLogsCollectorTest.java644
-rw-r--r--services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java17
-rw-r--r--services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java5
-rw-r--r--services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java3
-rw-r--r--services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java32
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java13
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java19
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java112
-rw-r--r--services/tests/servicestests/test-apps/PackageParserApp/Android.bp14
-rw-r--r--services/tests/servicestests/test-apps/PackageParserApp/AndroidManifestApp7.xml67
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ZenAdaptersTest.java10
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java4
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java6
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java18
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java8
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java27
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TransitionTests.java27
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowContainerTransactionTests.java126
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java9
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java72
-rw-r--r--telecomm/java/android/telecom/PhoneAccount.java11
-rw-r--r--telecomm/java/android/telecom/TelecomManager.java4
-rw-r--r--telephony/java/android/telephony/CarrierConfigManager.java9
-rw-r--r--telephony/java/android/telephony/SmsManager.java6
-rw-r--r--telephony/java/android/telephony/TelephonyManager.java98
-rw-r--r--telephony/java/android/telephony/satellite/SatelliteManager.java151
-rw-r--r--telephony/java/android/telephony/satellite/stub/INtnSignalStrengthConsumer.aidl2
-rw-r--r--tests/Input/src/com/android/server/input/InputManagerServiceTests.kt16
-rw-r--r--tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/EmbeddedWindowService.java8
-rw-r--r--tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceInputTestActivity.java35
-rw-r--r--tools/aapt2/link/ManifestFixer.cpp2
622 files changed, 17599 insertions, 3494 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index f5bf437a738d..98b62b3e2046 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -26,6 +26,7 @@ aconfig_srcjars = [
":android.content.flags-aconfig-java{.generated_srcjars}",
":android.content.pm.flags-aconfig-java{.generated_srcjars}",
":android.content.res.flags-aconfig-java{.generated_srcjars}",
+ ":android.crashrecovery.flags-aconfig-java{.generated_srcjars}",
":android.credentials.flags-aconfig-java{.generated_srcjars}",
":android.database.sqlite-aconfig-java{.generated_srcjars}",
":android.hardware.biometrics.flags-aconfig-java{.generated_srcjars}",
@@ -64,7 +65,9 @@ aconfig_srcjars = [
":com.android.input.flags-aconfig-java{.generated_srcjars}",
":com.android.internal.foldables.flags-aconfig-java{.generated_srcjars}",
":com.android.media.flags.bettertogether-aconfig-java{.generated_srcjars}",
+ ":com.android.media.flags.editing-aconfig-java{.generated_srcjars}",
":com.android.net.flags-aconfig-java{.generated_srcjars}",
+ ":com.android.net.thread.flags-aconfig-java{.generated_srcjars}",
":com.android.server.flags.services-aconfig-java{.generated_srcjars}",
":com.android.text.flags-aconfig-java{.generated_srcjars}",
":com.android.window.flags.window-aconfig-java{.generated_srcjars}",
@@ -92,6 +95,7 @@ stubs_defaults {
"android.companion.virtual.flags-aconfig",
"android.content.pm.flags-aconfig",
"android.content.res.flags-aconfig",
+ "android.crashrecovery.flags-aconfig",
"android.credentials.flags-aconfig",
"android.database.sqlite-aconfig",
"android.hardware.biometrics.flags-aconfig",
@@ -133,6 +137,7 @@ stubs_defaults {
"com.android.input.flags-aconfig",
"com.android.media.flags.bettertogether-aconfig",
"com.android.net.flags-aconfig",
+ "com.android.net.thread.flags-aconfig",
"com.android.server.flags.services-aconfig",
"com.android.text.flags-aconfig",
"com.android.window.flags.window-aconfig",
@@ -536,6 +541,21 @@ java_aconfig_library {
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+// Media Editing
+aconfig_declarations {
+ name: "com.android.media.flags.editing-aconfig",
+ package: "com.android.media.editing.flags",
+ srcs: [
+ "media/java/android/media/flags/editing.aconfig",
+ ],
+}
+
+java_aconfig_library {
+ name: "com.android.media.flags.editing-aconfig-java",
+ aconfig_declarations: "com.android.media.flags.editing-aconfig",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
// Media TV
aconfig_declarations {
name: "android.media.tv.flags-aconfig",
@@ -760,12 +780,25 @@ aconfig_declarations {
srcs: ["core/java/android/net/flags.aconfig"],
}
+// Thread network
+aconfig_declarations {
+ name: "com.android.net.thread.flags-aconfig",
+ package: "com.android.net.thread.flags",
+ srcs: ["core/java/android/net/thread/flags.aconfig"],
+}
+
java_aconfig_library {
name: "com.android.net.flags-aconfig-java",
aconfig_declarations: "com.android.net.flags-aconfig",
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+java_aconfig_library {
+ name: "com.android.net.thread.flags-aconfig-java",
+ aconfig_declarations: "com.android.net.thread.flags-aconfig",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
// Media
aconfig_declarations {
name: "android.media.playback.flags-aconfig",
@@ -1047,3 +1080,16 @@ java_aconfig_library {
aconfig_declarations: "android.adaptiveauth.flags-aconfig",
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+
+// CrashRecovery Module
+aconfig_declarations {
+ name: "android.crashrecovery.flags-aconfig",
+ package: "android.crashrecovery.flags",
+ srcs: ["packages/CrashRecovery/aconfig/flags.aconfig"],
+}
+
+java_aconfig_library {
+ name: "android.crashrecovery.flags-aconfig-java",
+ aconfig_declarations: "android.crashrecovery.flags-aconfig",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+} \ No newline at end of file
diff --git a/Android.bp b/Android.bp
index 9c56733650cb..e12f74fcd7ca 100644
--- a/Android.bp
+++ b/Android.bp
@@ -95,6 +95,7 @@ filegroup {
":platform-compat-native-aidl",
// AIDL sources from external directories
+ ":android.frameworks.location.altitude-V2-java-source",
":android.hardware.biometrics.common-V4-java-source",
":android.hardware.biometrics.fingerprint-V3-java-source",
":android.hardware.biometrics.face-V4-java-source",
diff --git a/apct-tests/perftests/contentcapture/src/android/view/contentcapture/AbstractContentCapturePerfTestCase.java b/apct-tests/perftests/contentcapture/src/android/view/contentcapture/AbstractContentCapturePerfTestCase.java
index 0ea2dafbb047..bc8470888a5a 100644
--- a/apct-tests/perftests/contentcapture/src/android/view/contentcapture/AbstractContentCapturePerfTestCase.java
+++ b/apct-tests/perftests/contentcapture/src/android/view/contentcapture/AbstractContentCapturePerfTestCase.java
@@ -248,6 +248,11 @@ public abstract class AbstractContentCapturePerfTestCase {
return mServiceWatcher.waitOnCreate();
}
+ /** Wait for session paused. */
+ public void waitForSessionPaused() throws InterruptedException {
+ mServiceWatcher.waitSessionPaused();
+ }
+
@NonNull
protected ActivityWatcher startWatcher() {
return mActivitiesWatcher.watch(CustomTestActivity.class);
diff --git a/apct-tests/perftests/contentcapture/src/android/view/contentcapture/LoginTest.java b/apct-tests/perftests/contentcapture/src/android/view/contentcapture/LoginTest.java
index aa95dfdfdf16..44e8a67e72ef 100644
--- a/apct-tests/perftests/contentcapture/src/android/view/contentcapture/LoginTest.java
+++ b/apct-tests/perftests/contentcapture/src/android/view/contentcapture/LoginTest.java
@@ -80,6 +80,34 @@ public class LoginTest extends AbstractContentCapturePerfTestCase {
testActivityLaunchTime(R.layout.test_container_activity, 500);
}
+ @Test
+ public void testSendEventsLatency() throws Throwable {
+ enableService();
+
+ testSendEventLatency(R.layout.test_container_activity, 0);
+ }
+
+ @Test
+ public void testSendEventsLatency_contains100Views() throws Throwable {
+ enableService();
+
+ testSendEventLatency(R.layout.test_container_activity, 100);
+ }
+
+ @Test
+ public void testSendEventsLatency_contains300Views() throws Throwable {
+ enableService();
+
+ testSendEventLatency(R.layout.test_container_activity, 300);
+ }
+
+ @Test
+ public void testSendEventsLatency_contains500Views() throws Throwable {
+ enableService();
+
+ testSendEventLatency(R.layout.test_container_activity, 500);
+ }
+
private void testActivityLaunchTime(int layoutId, int numViews) throws Throwable {
final Object drawNotifier = new Object();
final Intent intent = getLaunchIntent(layoutId, numViews);
@@ -111,6 +139,38 @@ public class LoginTest extends AbstractContentCapturePerfTestCase {
}
}
+ private void testSendEventLatency(int layoutId, int numViews) throws Throwable {
+ final Object drawNotifier = new Object();
+ final Intent intent = getLaunchIntent(layoutId, numViews);
+ intent.putExtra(CustomTestActivity.INTENT_EXTRA_FINISH_ON_IDLE, true);
+ intent.putExtra(CustomTestActivity.INTENT_EXTRA_DRAW_CALLBACK,
+ new RemoteCallback(result -> {
+ synchronized (drawNotifier) {
+ drawNotifier.notifyAll();
+ }
+ }));
+ final ActivityWatcher watcher = startWatcher();
+
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ mEntryActivity.startActivity(intent);
+ synchronized (drawNotifier) {
+ try {
+ drawNotifier.wait(GENERIC_TIMEOUT_MS);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ waitForSessionPaused();
+
+ // Ignore the time to finish the activity
+ state.pauseTiming();
+ watcher.waitFor(DESTROYED);
+ sInstrumentation.waitForIdleSync();
+ state.resumeTiming();
+ }
+ }
+
@Test
public void testOnVisibilityAggregated_visibleChanged() throws Throwable {
enableService();
diff --git a/apct-tests/perftests/contentcapture/src/android/view/contentcapture/MyContentCaptureService.java b/apct-tests/perftests/contentcapture/src/android/view/contentcapture/MyContentCaptureService.java
index ecc5112ab6dd..0b5345f5e2e1 100644
--- a/apct-tests/perftests/contentcapture/src/android/view/contentcapture/MyContentCaptureService.java
+++ b/apct-tests/perftests/contentcapture/src/android/view/contentcapture/MyContentCaptureService.java
@@ -114,6 +114,10 @@ public class MyContentCaptureService extends ContentCaptureService {
public void onContentCaptureEvent(ContentCaptureSessionId sessionId,
ContentCaptureEvent event) {
Log.i(TAG, "onContentCaptureEventsRequest(session=" + sessionId + "): " + event);
+ if (sServiceWatcher != null
+ && event.getType() == ContentCaptureEvent.TYPE_SESSION_PAUSED) {
+ sServiceWatcher.mSessionPaused.countDown();
+ }
}
@Override
@@ -126,6 +130,7 @@ public class MyContentCaptureService extends ContentCaptureService {
private static final long GENERIC_TIMEOUT_MS = 10_000;
private final CountDownLatch mCreated = new CountDownLatch(1);
private final CountDownLatch mDestroyed = new CountDownLatch(1);
+ private final CountDownLatch mSessionPaused = new CountDownLatch(1);
private boolean mReadyToClear = true;
private Pair<Set<String>, Set<ComponentName>> mAllowList;
@@ -151,6 +156,11 @@ public class MyContentCaptureService extends ContentCaptureService {
await(mDestroyed, "not destroyed");
}
+ /** Wait for session paused. */
+ public void waitSessionPaused() throws InterruptedException {
+ await(mSessionPaused, "no Paused");
+ }
+
/**
* Allow just this package.
*/
diff --git a/apex/jobscheduler/framework/aconfig/job.aconfig b/apex/jobscheduler/framework/aconfig/job.aconfig
index e73b434042af..788e82407926 100644
--- a/apex/jobscheduler/framework/aconfig/job.aconfig
+++ b/apex/jobscheduler/framework/aconfig/job.aconfig
@@ -13,3 +13,10 @@ flag {
description: "Add APIs to let apps attach debug information to jobs"
bug: "293491637"
}
+
+flag {
+ name: "backup_jobs_exemption"
+ namespace: "backstage_power"
+ description: "Introduce a new RUN_BACKUP_JOBS permission and exemption logic allowing for longer running jobs for apps whose primary purpose is to backup or sync content."
+ bug: "318731461"
+}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index 7a92cca74795..fc193d8147b5 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1840,7 +1840,9 @@ public class JobSchedulerService extends com.android.server.SystemService
/* isFlexConstraintSatisfied */ false,
jobStatus.canApplyTransportAffinities(),
jobStatus.getNumAppliedFlexibleConstraints(),
- jobStatus.getNumDroppedFlexibleConstraints());
+ jobStatus.getNumDroppedFlexibleConstraints(),
+ jobStatus.getFilteredTraceTag(),
+ jobStatus.getFilteredDebugTags());
// If the job is immediately ready to run, then we can just immediately
// put it in the pending list and try to schedule it. This is especially
@@ -2288,7 +2290,9 @@ public class JobSchedulerService extends com.android.server.SystemService
cancelled.isConstraintSatisfied(JobStatus.CONSTRAINT_FLEXIBLE),
cancelled.canApplyTransportAffinities(),
cancelled.getNumAppliedFlexibleConstraints(),
- cancelled.getNumDroppedFlexibleConstraints());
+ cancelled.getNumDroppedFlexibleConstraints(),
+ cancelled.getFilteredTraceTag(),
+ cancelled.getFilteredDebugTags());
}
// If this is a replacement, bring in the new version of the job
if (incomingJob != null) {
@@ -5448,6 +5452,9 @@ public class JobSchedulerService extends com.android.server.SystemService
pw.print(Flags.FLAG_THROW_ON_UNSUPPORTED_BIAS_USAGE,
Flags.throwOnUnsupportedBiasUsage());
pw.println();
+ pw.print(android.app.job.Flags.FLAG_BACKUP_JOBS_EXEMPTION,
+ android.app.job.Flags.backupJobsExemption());
+ pw.println();
pw.decreaseIndent();
pw.println();
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
index 6f2393adfc7b..0cf6a7a8a4f6 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
@@ -356,6 +356,9 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler {
case com.android.server.job.Flags.FLAG_THROW_ON_UNSUPPORTED_BIAS_USAGE:
pw.println(com.android.server.job.Flags.throwOnUnsupportedBiasUsage());
break;
+ case android.app.job.Flags.FLAG_BACKUP_JOBS_EXEMPTION:
+ pw.println(android.app.job.Flags.backupJobsExemption());
+ break;
default:
pw.println("Unknown flag: " + flagName);
break;
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index fe55e2702916..8ab7d2fae49f 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -541,7 +541,9 @@ public final class JobServiceContext implements ServiceConnection {
job.isConstraintSatisfied(JobStatus.CONSTRAINT_FLEXIBLE),
job.canApplyTransportAffinities(),
job.getNumAppliedFlexibleConstraints(),
- job.getNumDroppedFlexibleConstraints());
+ job.getNumDroppedFlexibleConstraints(),
+ job.getFilteredTraceTag(),
+ job.getFilteredDebugTags());
sEnqueuedJwiAtJobStart.logSampleWithUid(job.getUid(), job.getWorkCount());
final String sourcePackage = job.getSourcePackageName();
if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
@@ -1630,7 +1632,9 @@ public final class JobServiceContext implements ServiceConnection {
completedJob.isConstraintSatisfied(JobStatus.CONSTRAINT_FLEXIBLE),
completedJob.canApplyTransportAffinities(),
completedJob.getNumAppliedFlexibleConstraints(),
- completedJob.getNumDroppedFlexibleConstraints());
+ completedJob.getNumDroppedFlexibleConstraints(),
+ completedJob.getFilteredTraceTag(),
+ completedJob.getFilteredDebugTags());
if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_SYSTEM_SERVER, "JobScheduler",
getId());
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
index 14cce19ef676..6883d18cd937 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
@@ -972,6 +972,20 @@ public final class FlexibilityController extends StateController {
synchronized (mLock) {
final long earliest = getLifeCycleBeginningElapsedLocked(js);
final long latest = getLifeCycleEndElapsedLocked(js, nowElapsed, earliest);
+ if (latest <= earliest) {
+ // Something has gone horribly wrong. This has only occurred on incorrectly
+ // configured tests, but add a check here for safety.
+ Slog.wtf(TAG, "Got invalid latest when scheduling alarm."
+ + " Prefetch=" + js.getJob().isPrefetch());
+ // Since things have gone wrong, the safest and most reliable thing to do is
+ // stop applying flex policy to the job.
+ mFlexibilityTracker.setNumDroppedFlexibleConstraints(js,
+ js.getNumAppliedFlexibleConstraints());
+ mJobsToCheck.add(js);
+ mHandler.sendEmptyMessage(MSG_CHECK_JOBS);
+ return;
+ }
+
final long nextTimeElapsed =
getNextConstraintDropTimeElapsedLocked(js, earliest, latest);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index d39863c85f33..a4df5d829281 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -48,6 +48,7 @@ import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
import android.util.Pair;
+import android.util.Patterns;
import android.util.Range;
import android.util.Slog;
import android.util.TimeUtils;
@@ -76,6 +77,7 @@ import java.util.Collections;
import java.util.Objects;
import java.util.Random;
import java.util.function.Predicate;
+import java.util.regex.Pattern;
/**
* Uniquely identifies a job internally.
@@ -203,6 +205,17 @@ public final class JobStatus {
// TODO(b/129954980): ensure this doesn't spam statsd, especially at boot
private static final boolean STATS_LOG_ENABLED = false;
+ /**
+ * Simple patterns to match some common forms of PII. This is not intended all-encompassing and
+ * any clients should aim to do additional filtering.
+ */
+ private static final ArrayMap<Pattern, String> BASIC_PII_FILTERS = new ArrayMap<>();
+
+ static {
+ BASIC_PII_FILTERS.put(Patterns.EMAIL_ADDRESS, "[EMAIL]");
+ BASIC_PII_FILTERS.put(Patterns.PHONE, "[PHONE]");
+ }
+
// No override.
public static final int OVERRIDE_NONE = 0;
// Override to improve sorting order. Does not affect constraint evaluation.
@@ -250,6 +263,18 @@ public final class JobStatus {
private final long mLoggingJobId;
/**
+ * List of tags from {@link JobInfo#getDebugTags()}, filtered using {@link #BASIC_PII_FILTERS}.
+ * Lazily loaded in {@link #getFilteredDebugTags()}.
+ */
+ @Nullable
+ private String[] mFilteredDebugTags;
+ /**
+ * Trace tag from {@link JobInfo#getTraceTag()}, filtered using {@link #BASIC_PII_FILTERS}.
+ * Lazily loaded in {@link #getFilteredTraceTag()}.
+ */
+ @Nullable
+ private String mFilteredTraceTag;
+ /**
* Tag to identify the wakelock held for this job. Lazily loaded in
* {@link #getWakelockTag()} since it's not typically needed until the job is about to run.
*/
@@ -1325,6 +1350,47 @@ public final class JobStatus {
return batteryName;
}
+ @VisibleForTesting
+ @NonNull
+ static String applyBasicPiiFilters(@NonNull String val) {
+ for (int i = BASIC_PII_FILTERS.size() - 1; i >= 0; --i) {
+ val = BASIC_PII_FILTERS.keyAt(i).matcher(val).replaceAll(BASIC_PII_FILTERS.valueAt(i));
+ }
+ return val;
+ }
+
+ /**
+ * List of tags from {@link JobInfo#getDebugTags()}, filtered using a basic set of PII filters.
+ */
+ @NonNull
+ public String[] getFilteredDebugTags() {
+ if (mFilteredDebugTags != null) {
+ return mFilteredDebugTags;
+ }
+ final ArraySet<String> debugTags = job.getDebugTagsArraySet();
+ mFilteredDebugTags = new String[debugTags.size()];
+ for (int i = 0; i < mFilteredDebugTags.length; ++i) {
+ mFilteredDebugTags[i] = applyBasicPiiFilters(debugTags.valueAt(i));
+ }
+ return mFilteredDebugTags;
+ }
+
+ /**
+ * Trace tag from {@link JobInfo#getTraceTag()}, filtered using a basic set of PII filters.
+ */
+ @Nullable
+ public String getFilteredTraceTag() {
+ if (mFilteredTraceTag != null) {
+ return mFilteredTraceTag;
+ }
+ final String rawTag = job.getTraceTag();
+ if (rawTag == null) {
+ return null;
+ }
+ mFilteredTraceTag = applyBasicPiiFilters(rawTag);
+ return mFilteredTraceTag;
+ }
+
/** Return the String to be used as the tag for the wakelock held for this job. */
@NonNull
public String getWakelockTag() {
diff --git a/core/api/Android.bp b/core/api/Android.bp
index 907916a125da..8d8a82b69b55 100644
--- a/core/api/Android.bp
+++ b/core/api/Android.bp
@@ -96,21 +96,3 @@ filegroup {
name: "non-updatable-test-lint-baseline.txt",
srcs: ["test-lint-baseline.txt"],
}
-
-java_api_contribution {
- name: "api-stubs-docs-non-updatable-public-stubs",
- api_surface: "public",
- api_file: "current.txt",
- visibility: [
- "//build/orchestrator/apis",
- ],
-}
-
-java_api_contribution {
- name: "frameworks-base-core-api-module-lib-stubs",
- api_surface: "module-lib",
- api_file: "module-lib-current.txt",
- visibility: [
- "//build/orchestrator/apis",
- ],
-}
diff --git a/core/api/current.txt b/core/api/current.txt
index c92b4ffe60a6..66feebc6cc92 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -277,6 +277,7 @@ package android {
field @FlaggedApi("android.companion.flags.device_presence") public static final String REQUEST_OBSERVE_DEVICE_UUID_PRESENCE = "android.permission.REQUEST_OBSERVE_DEVICE_UUID_PRESENCE";
field public static final String REQUEST_PASSWORD_COMPLEXITY = "android.permission.REQUEST_PASSWORD_COMPLEXITY";
field @Deprecated public static final String RESTART_PACKAGES = "android.permission.RESTART_PACKAGES";
+ field @FlaggedApi("android.app.job.backup_jobs_exemption") public static final String RUN_BACKUP_JOBS = "android.permission.RUN_BACKUP_JOBS";
field public static final String RUN_USER_INITIATED_JOBS = "android.permission.RUN_USER_INITIATED_JOBS";
field public static final String SCHEDULE_EXACT_ALARM = "android.permission.SCHEDULE_EXACT_ALARM";
field public static final String SEND_RESPOND_VIA_MESSAGE = "android.permission.SEND_RESPOND_VIA_MESSAGE";
@@ -284,6 +285,7 @@ package android {
field public static final String SET_ALARM = "com.android.alarm.permission.SET_ALARM";
field public static final String SET_ALWAYS_FINISH = "android.permission.SET_ALWAYS_FINISH";
field public static final String SET_ANIMATION_SCALE = "android.permission.SET_ANIMATION_SCALE";
+ field @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") public static final String SET_BIOMETRIC_DIALOG_LOGO = "android.permission.SET_BIOMETRIC_DIALOG_LOGO";
field public static final String SET_DEBUG_APP = "android.permission.SET_DEBUG_APP";
field @Deprecated public static final String SET_PREFERRED_APPLICATIONS = "android.permission.SET_PREFERRED_APPLICATIONS";
field public static final String SET_PROCESS_LIMIT = "android.permission.SET_PROCESS_LIMIT";
@@ -442,6 +444,7 @@ package android {
field public static final int alertDialogTheme = 16843529; // 0x1010309
field public static final int alignmentMode = 16843642; // 0x101037a
field public static final int allContactsName = 16843468; // 0x10102cc
+ field @FlaggedApi("android.content.pm.relative_reference_intent_filters") public static final int allow;
field public static final int allowAudioPlaybackCapture = 16844289; // 0x1010601
field public static final int allowBackup = 16843392; // 0x1010280
field public static final int allowClearUserData = 16842757; // 0x1010005
@@ -845,6 +848,7 @@ package android {
field public static final int format24Hour = 16843723; // 0x10103cb
field public static final int fraction = 16843992; // 0x10104d8
field public static final int fragment = 16843491; // 0x10102e3
+ field @FlaggedApi("android.content.pm.relative_reference_intent_filters") public static final int fragmentAdvancedPattern;
field public static final int fragmentAllowEnterTransitionOverlap = 16843976; // 0x10104c8
field public static final int fragmentAllowReturnTransitionOverlap = 16843977; // 0x10104c9
field public static final int fragmentCloseEnterAnimation = 16843495; // 0x10102e7
@@ -855,10 +859,13 @@ package android {
field public static final int fragmentFadeExitAnimation = 16843498; // 0x10102ea
field public static final int fragmentOpenEnterAnimation = 16843493; // 0x10102e5
field public static final int fragmentOpenExitAnimation = 16843494; // 0x10102e6
+ field @FlaggedApi("android.content.pm.relative_reference_intent_filters") public static final int fragmentPattern;
+ field @FlaggedApi("android.content.pm.relative_reference_intent_filters") public static final int fragmentPrefix;
field public static final int fragmentReenterTransition = 16843975; // 0x10104c7
field public static final int fragmentReturnTransition = 16843973; // 0x10104c5
field public static final int fragmentSharedElementEnterTransition = 16843972; // 0x10104c4
field public static final int fragmentSharedElementReturnTransition = 16843974; // 0x10104c6
+ field @FlaggedApi("android.content.pm.relative_reference_intent_filters") public static final int fragmentSuffix;
field public static final int freezesText = 16843116; // 0x101016c
field public static final int fromAlpha = 16843210; // 0x10101ca
field public static final int fromDegrees = 16843187; // 0x10101b3
@@ -1327,10 +1334,15 @@ package android {
field public static final int propertyYName = 16843893; // 0x1010475
field public static final int protectionLevel = 16842761; // 0x1010009
field public static final int publicKey = 16843686; // 0x10103a6
+ field @FlaggedApi("android.content.pm.relative_reference_intent_filters") public static final int query;
field public static final int queryActionMsg = 16843227; // 0x10101db
+ field @FlaggedApi("android.content.pm.relative_reference_intent_filters") public static final int queryAdvancedPattern;
field public static final int queryAfterZeroResults = 16843394; // 0x1010282
field public static final int queryBackground = 16843911; // 0x1010487
field public static final int queryHint = 16843608; // 0x1010358
+ field @FlaggedApi("android.content.pm.relative_reference_intent_filters") public static final int queryPattern;
+ field @FlaggedApi("android.content.pm.relative_reference_intent_filters") public static final int queryPrefix;
+ field @FlaggedApi("android.content.pm.relative_reference_intent_filters") public static final int querySuffix;
field public static final int quickContactBadgeStyleSmallWindowLarge = 16843443; // 0x10102b3
field public static final int quickContactBadgeStyleSmallWindowMedium = 16843442; // 0x10102b2
field public static final int quickContactBadgeStyleSmallWindowSmall = 16843441; // 0x10102b1
@@ -11385,10 +11397,12 @@ package android.content {
method public final void addDataScheme(String);
method public final void addDataSchemeSpecificPart(String, int);
method public final void addDataType(String) throws android.content.IntentFilter.MalformedMimeTypeException;
+ method @FlaggedApi("android.content.pm.relative_reference_intent_filters") public final void addUriRelativeFilterGroup(@NonNull android.content.UriRelativeFilterGroup);
method @NonNull public java.util.function.Predicate<android.content.Intent> asPredicate();
method @NonNull public java.util.function.Predicate<android.content.Intent> asPredicateWithTypeResolution(@NonNull android.content.ContentResolver);
method public final java.util.Iterator<android.content.IntentFilter.AuthorityEntry> authoritiesIterator();
method public final java.util.Iterator<java.lang.String> categoriesIterator();
+ method @FlaggedApi("android.content.pm.relative_reference_intent_filters") public final void clearUriRelativeFilterGroups();
method public final int countActions();
method public final int countCategories();
method public final int countDataAuthorities();
@@ -11396,6 +11410,7 @@ package android.content {
method public final int countDataSchemeSpecificParts();
method public final int countDataSchemes();
method public final int countDataTypes();
+ method @FlaggedApi("android.content.pm.relative_reference_intent_filters") public final int countUriRelativeFilterGroups();
method public static android.content.IntentFilter create(String, String);
method public final int describeContents();
method public void dump(android.util.Printer, String);
@@ -11407,6 +11422,7 @@ package android.content {
method public final android.os.PatternMatcher getDataSchemeSpecificPart(int);
method public final String getDataType(int);
method public final int getPriority();
+ method @FlaggedApi("android.content.pm.relative_reference_intent_filters") @NonNull public final android.content.UriRelativeFilterGroup getUriRelativeFilterGroup(int);
method public final boolean hasAction(String);
method public final boolean hasCategory(String);
method public final boolean hasDataAuthority(android.net.Uri);
@@ -11828,6 +11844,27 @@ package android.content {
field public static final long INVALID_TIME = -9223372036854775808L; // 0x8000000000000000L
}
+ @FlaggedApi("android.content.pm.relative_reference_intent_filters") public final class UriRelativeFilter {
+ ctor public UriRelativeFilter(int, int, @NonNull String);
+ method @NonNull public String getFilter();
+ method public int getPatternType();
+ method public int getUriPart();
+ method public boolean matchData(@NonNull android.net.Uri);
+ field public static final int FRAGMENT = 2; // 0x2
+ field public static final int PATH = 0; // 0x0
+ field public static final int QUERY = 1; // 0x1
+ }
+
+ @FlaggedApi("android.content.pm.relative_reference_intent_filters") public final class UriRelativeFilterGroup {
+ ctor public UriRelativeFilterGroup(int);
+ method public void addUriRelativeFilter(@NonNull android.content.UriRelativeFilter);
+ method public int getAction();
+ method @NonNull public java.util.Collection<android.content.UriRelativeFilter> getUriRelativeFilters();
+ method public boolean matchData(@NonNull android.net.Uri);
+ field public static final int ACTION_ALLOW = 0; // 0x0
+ field public static final int ACTION_BLOCK = 1; // 0x1
+ }
+
}
package android.content.om {
@@ -18722,8 +18759,8 @@ package android.hardware.biometrics {
method @Nullable public int getAllowedAuthenticators();
method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @Nullable public android.hardware.biometrics.PromptContentView getContentView();
method @Nullable public CharSequence getDescription();
- method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @Nullable @RequiresPermission("android.permission.MANAGE_BIOMETRIC_DIALOG") public android.graphics.Bitmap getLogoBitmap();
- method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @DrawableRes @RequiresPermission("android.permission.MANAGE_BIOMETRIC_DIALOG") public int getLogoRes();
+ method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @Nullable @RequiresPermission(android.Manifest.permission.SET_BIOMETRIC_DIALOG_LOGO) public android.graphics.Bitmap getLogoBitmap();
+ method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @DrawableRes @RequiresPermission(android.Manifest.permission.SET_BIOMETRIC_DIALOG_LOGO) public int getLogoRes();
method @Nullable public CharSequence getNegativeButtonText();
method @Nullable public CharSequence getSubtitle();
method @NonNull public CharSequence getTitle();
@@ -18773,8 +18810,8 @@ package android.hardware.biometrics {
method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setContentView(@NonNull android.hardware.biometrics.PromptContentView);
method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setDescription(@NonNull CharSequence);
method @Deprecated @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setDeviceCredentialAllowed(boolean);
- method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @NonNull @RequiresPermission("android.permission.MANAGE_BIOMETRIC_DIALOG") public android.hardware.biometrics.BiometricPrompt.Builder setLogo(@DrawableRes int);
- method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @NonNull @RequiresPermission("android.permission.MANAGE_BIOMETRIC_DIALOG") public android.hardware.biometrics.BiometricPrompt.Builder setLogo(@NonNull android.graphics.Bitmap);
+ method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @NonNull @RequiresPermission(android.Manifest.permission.SET_BIOMETRIC_DIALOG_LOGO) public android.hardware.biometrics.BiometricPrompt.Builder setLogoBitmap(@NonNull android.graphics.Bitmap);
+ method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @NonNull @RequiresPermission(android.Manifest.permission.SET_BIOMETRIC_DIALOG_LOGO) public android.hardware.biometrics.BiometricPrompt.Builder setLogoRes(@DrawableRes int);
method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setNegativeButton(@NonNull CharSequence, @NonNull java.util.concurrent.Executor, @NonNull android.content.DialogInterface.OnClickListener);
method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setSubtitle(@NonNull CharSequence);
method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setTitle(@NonNull CharSequence);
@@ -18800,14 +18837,14 @@ package android.hardware.biometrics {
}
@FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") public final class PromptContentItemBulletedText implements android.os.Parcelable android.hardware.biometrics.PromptContentItem {
- ctor public PromptContentItemBulletedText(@NonNull CharSequence);
+ ctor public PromptContentItemBulletedText(@NonNull String);
method public int describeContents();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.hardware.biometrics.PromptContentItemBulletedText> CREATOR;
}
@FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") public final class PromptContentItemPlainText implements android.os.Parcelable android.hardware.biometrics.PromptContentItem {
- ctor public PromptContentItemPlainText(@NonNull CharSequence);
+ ctor public PromptContentItemPlainText(@NonNull String);
method public int describeContents();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.hardware.biometrics.PromptContentItemPlainText> CREATOR;
@@ -18818,7 +18855,7 @@ package android.hardware.biometrics {
@FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") public final class PromptVerticalListContentView implements android.os.Parcelable android.hardware.biometrics.PromptContentView {
method public int describeContents();
- method @Nullable public CharSequence getDescription();
+ method @Nullable public String getDescription();
method @NonNull public java.util.List<android.hardware.biometrics.PromptContentItem> getListItems();
method public static int getMaxEachItemCharacterNumber();
method public static int getMaxItemCount();
@@ -18831,7 +18868,7 @@ package android.hardware.biometrics {
method @NonNull public android.hardware.biometrics.PromptVerticalListContentView.Builder addListItem(@NonNull android.hardware.biometrics.PromptContentItem);
method @NonNull public android.hardware.biometrics.PromptVerticalListContentView.Builder addListItem(@NonNull android.hardware.biometrics.PromptContentItem, int);
method @NonNull public android.hardware.biometrics.PromptVerticalListContentView build();
- method @NonNull public android.hardware.biometrics.PromptVerticalListContentView.Builder setDescription(@NonNull CharSequence);
+ method @NonNull public android.hardware.biometrics.PromptVerticalListContentView.Builder setDescription(@NonNull String);
}
}
@@ -24299,7 +24336,7 @@ package android.media {
method @Nullable public android.media.MediaRouter2.RoutingController getController(@NonNull String);
method @NonNull public java.util.List<android.media.MediaRouter2.RoutingController> getControllers();
method @NonNull public static android.media.MediaRouter2 getInstance(@NonNull android.content.Context);
- method @FlaggedApi("com.android.media.flags.enable_cross_user_routing_in_media_router2") @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MEDIA_CONTENT_CONTROL, android.Manifest.permission.MEDIA_ROUTING_CONTROL}) public static android.media.MediaRouter2 getInstance(@NonNull android.content.Context, @NonNull String, @NonNull android.os.UserHandle);
+ method @FlaggedApi("com.android.media.flags.enable_cross_user_routing_in_media_router2") @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MEDIA_CONTENT_CONTROL, android.Manifest.permission.MEDIA_ROUTING_CONTROL}) public static android.media.MediaRouter2 getInstance(@NonNull android.content.Context, @NonNull String);
method @FlaggedApi("com.android.media.flags.enable_rlp_callbacks_in_media_router2") @Nullable public android.media.RouteListingPreference getRouteListingPreference();
method @NonNull public java.util.List<android.media.MediaRoute2Info> getRoutes();
method @NonNull public android.media.MediaRouter2.RoutingController getSystemController();
@@ -25715,9 +25752,48 @@ package android.media.metrics {
field public static final String KEY_STATSD_ATOM = "bundlesession-statsd-atom";
}
+ @FlaggedApi("com.android.media.editing.flags.add_media_metrics_editing") public final class EditingEndedEvent extends android.media.metrics.Event implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getErrorCode();
+ method public int getFinalState();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.media.metrics.EditingEndedEvent> CREATOR;
+ field public static final int ERROR_CODE_AUDIO_PROCESSING_FAILED = 18; // 0x12
+ field public static final int ERROR_CODE_DECODER_INIT_FAILED = 11; // 0xb
+ field public static final int ERROR_CODE_DECODING_FAILED = 12; // 0xc
+ field public static final int ERROR_CODE_DECODING_FORMAT_UNSUPPORTED = 13; // 0xd
+ field public static final int ERROR_CODE_ENCODER_INIT_FAILED = 14; // 0xe
+ field public static final int ERROR_CODE_ENCODING_FAILED = 15; // 0xf
+ field public static final int ERROR_CODE_ENCODING_FORMAT_UNSUPPORTED = 16; // 0x10
+ field public static final int ERROR_CODE_FAILED_RUNTIME_CHECK = 2; // 0x2
+ field public static final int ERROR_CODE_IO_BAD_HTTP_STATUS = 6; // 0x6
+ field public static final int ERROR_CODE_IO_CLEARTEXT_NOT_PERMITTED = 9; // 0x9
+ field public static final int ERROR_CODE_IO_FILE_NOT_FOUND = 7; // 0x7
+ field public static final int ERROR_CODE_IO_NETWORK_CONNECTION_FAILED = 4; // 0x4
+ field public static final int ERROR_CODE_IO_NETWORK_CONNECTION_TIMEOUT = 5; // 0x5
+ field public static final int ERROR_CODE_IO_NO_PERMISSION = 8; // 0x8
+ field public static final int ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE = 10; // 0xa
+ field public static final int ERROR_CODE_IO_UNSPECIFIED = 3; // 0x3
+ field public static final int ERROR_CODE_MUXING_FAILED = 19; // 0x13
+ field public static final int ERROR_CODE_NONE = 1; // 0x1
+ field public static final int ERROR_CODE_VIDEO_FRAME_PROCESSING_FAILED = 17; // 0x11
+ field public static final int FINAL_STATE_CANCELED = 2; // 0x2
+ field public static final int FINAL_STATE_ERROR = 3; // 0x3
+ field public static final int FINAL_STATE_SUCCEEDED = 1; // 0x1
+ }
+
+ @FlaggedApi("com.android.media.editing.flags.add_media_metrics_editing") public static final class EditingEndedEvent.Builder {
+ ctor public EditingEndedEvent.Builder(int);
+ method @NonNull public android.media.metrics.EditingEndedEvent build();
+ method @NonNull public android.media.metrics.EditingEndedEvent.Builder setErrorCode(int);
+ method @NonNull public android.media.metrics.EditingEndedEvent.Builder setMetricsBundle(@NonNull android.os.Bundle);
+ method @NonNull public android.media.metrics.EditingEndedEvent.Builder setTimeSinceCreatedMillis(@IntRange(from=0xffffffff) long);
+ }
+
public final class EditingSession implements java.lang.AutoCloseable {
method public void close();
method @NonNull public android.media.metrics.LogSessionId getSessionId();
+ method @FlaggedApi("com.android.media.editing.flags.add_media_metrics_editing") public void reportEditingEndedEvent(@NonNull android.media.metrics.EditingEndedEvent);
}
public abstract class Event {
@@ -33429,6 +33505,7 @@ package android.os {
field public static final String DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI = "no_sharing_admin_configured_wifi";
field public static final String DISALLOW_SMS = "no_sms";
field public static final String DISALLOW_SYSTEM_ERROR_DIALOGS = "no_system_error_dialogs";
+ field @FlaggedApi("com.android.net.thread.flags.thread_user_restriction_enabled") public static final String DISALLOW_THREAD_NETWORK = "no_thread_network";
field public static final String DISALLOW_ULTRA_WIDEBAND_RADIO = "no_ultra_wideband_radio";
field public static final String DISALLOW_UNIFIED_PASSWORD = "no_unified_password";
field public static final String DISALLOW_UNINSTALL_APPS = "no_uninstall_apps";
@@ -40575,7 +40652,7 @@ package android.service.notification {
method public int getPriorityCategoryReminders();
method public int getPriorityCategoryRepeatCallers();
method public int getPriorityCategorySystem();
- method @FlaggedApi("android.app.modes_api") public int getPriorityChannels();
+ method @FlaggedApi("android.app.modes_api") public int getPriorityChannelsAllowed();
method public int getPriorityConversationSenders();
method public int getPriorityMessageSenders();
method public int getVisualEffectAmbient();
@@ -53625,13 +53702,13 @@ package android.view {
method @Deprecated public android.view.Display getDefaultDisplay();
method @NonNull public default android.view.WindowMetrics getMaximumWindowMetrics();
method public default boolean isCrossWindowBlurEnabled();
- method @FlaggedApi("com.android.window.flags.surface_control_input_receiver") @NonNull public default android.os.IBinder registerBatchedSurfaceControlInputReceiver(int, @NonNull android.os.IBinder, @NonNull android.view.SurfaceControl, @NonNull android.view.Choreographer, @NonNull android.view.SurfaceControlInputReceiver);
+ method @FlaggedApi("com.android.window.flags.surface_control_input_receiver") public default void registerBatchedSurfaceControlInputReceiver(int, @NonNull android.os.IBinder, @NonNull android.view.SurfaceControl, @NonNull android.view.Choreographer, @NonNull android.view.SurfaceControlInputReceiver);
method @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public default void registerTrustedPresentationListener(@NonNull android.os.IBinder, @NonNull android.window.TrustedPresentationThresholds, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
- method @FlaggedApi("com.android.window.flags.surface_control_input_receiver") @NonNull public default android.os.IBinder registerUnbatchedSurfaceControlInputReceiver(int, @NonNull android.os.IBinder, @NonNull android.view.SurfaceControl, @NonNull android.os.Looper, @NonNull android.view.SurfaceControlInputReceiver);
+ method @FlaggedApi("com.android.window.flags.surface_control_input_receiver") public default void registerUnbatchedSurfaceControlInputReceiver(int, @NonNull android.os.IBinder, @NonNull android.view.SurfaceControl, @NonNull android.os.Looper, @NonNull android.view.SurfaceControlInputReceiver);
method public default void removeCrossWindowBlurEnabledListener(@NonNull java.util.function.Consumer<java.lang.Boolean>);
method public default void removeProposedRotationListener(@NonNull java.util.function.IntConsumer);
method public void removeViewImmediate(android.view.View);
- method @FlaggedApi("com.android.window.flags.surface_control_input_receiver") public default void unregisterSurfaceControlInputReceiver(@NonNull android.os.IBinder);
+ method @FlaggedApi("com.android.window.flags.surface_control_input_receiver") public default void unregisterSurfaceControlInputReceiver(@NonNull android.view.SurfaceControl);
method @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public default void unregisterTrustedPresentationListener(@NonNull java.util.function.Consumer<java.lang.Boolean>);
field public static final String PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE = "android.window.PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE";
field public static final String PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED = "android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED";
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index ff2279c68231..017de17b5416 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -129,6 +129,7 @@ package android {
field public static final String DISABLE_SYSTEM_SOUND_EFFECTS = "android.permission.DISABLE_SYSTEM_SOUND_EFFECTS";
field public static final String DISPATCH_PROVISIONING_MESSAGE = "android.permission.DISPATCH_PROVISIONING_MESSAGE";
field public static final String DOMAIN_VERIFICATION_AGENT = "android.permission.DOMAIN_VERIFICATION_AGENT";
+ field @FlaggedApi("android.content.pm.emergency_install_permission") public static final String EMERGENCY_INSTALL_PACKAGES = "android.permission.EMERGENCY_INSTALL_PACKAGES";
field public static final String ENTER_CAR_MODE_PRIORITIZED = "android.permission.ENTER_CAR_MODE_PRIORITIZED";
field public static final String EXEMPT_FROM_AUDIO_RECORD_RESTRICTIONS = "android.permission.EXEMPT_FROM_AUDIO_RECORD_RESTRICTIONS";
field public static final String FORCE_BACK = "android.permission.FORCE_BACK";
@@ -871,6 +872,10 @@ package android.app {
field @NonNull public static final android.os.Parcelable.Creator<android.app.AppOpsManager.PackageOps> CREATOR;
}
+ @FlaggedApi("android.app.bic_client") public final class BackgroundInstallControlManager {
+ method @FlaggedApi("android.app.bic_client") @NonNull @RequiresPermission(android.Manifest.permission.GET_BACKGROUND_INSTALLED_PACKAGES) public java.util.List<android.content.pm.PackageInfo> getBackgroundInstalledPackages(long);
+ }
+
public class BroadcastOptions {
method public void clearRequireCompatChange();
method public int getPendingIntentBackgroundActivityStartMode();
@@ -3388,11 +3393,10 @@ package android.companion.virtual.camera {
}
@FlaggedApi("android.companion.virtual.flags.virtual_camera") public static final class VirtualCameraConfig.Builder {
- ctor public VirtualCameraConfig.Builder();
+ ctor public VirtualCameraConfig.Builder(@NonNull String);
method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder addStreamConfig(@IntRange(from=1) int, @IntRange(from=1) int, int, @IntRange(from=1) int);
method @NonNull public android.companion.virtual.camera.VirtualCameraConfig build();
method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder setLensFacing(int);
- method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder setName(@NonNull String);
method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder setSensorOrientation(int);
method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder setVirtualCameraCallback(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.camera.VirtualCameraCallback);
}
@@ -4228,6 +4232,7 @@ package android.content.pm {
public final class UserProperties implements android.os.Parcelable {
method public int describeContents();
method public int getCrossProfileContentSharingStrategy();
+ method @FlaggedApi("android.multiuser.support_hiding_profiles") @NonNull public int getProfileApiVisibility();
method public int getShowInQuietMode();
method public int getShowInSharingSurfaces();
method public boolean isCredentialShareableWithParent();
@@ -4237,6 +4242,9 @@ package android.content.pm {
field public static final int CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT = 1; // 0x1
field public static final int CROSS_PROFILE_CONTENT_SHARING_NO_DELEGATION = 0; // 0x0
field public static final int CROSS_PROFILE_CONTENT_SHARING_UNKNOWN = -1; // 0xffffffff
+ field @FlaggedApi("android.multiuser.support_hiding_profiles") public static final int PROFILE_API_VISIBILITY_HIDDEN = 1; // 0x1
+ field @FlaggedApi("android.multiuser.support_hiding_profiles") public static final int PROFILE_API_VISIBILITY_UNKNOWN = -1; // 0xffffffff
+ field @FlaggedApi("android.multiuser.support_hiding_profiles") public static final int PROFILE_API_VISIBILITY_VISIBLE = 0; // 0x0
field public static final int SHOW_IN_QUIET_MODE_DEFAULT = 2; // 0x2
field public static final int SHOW_IN_QUIET_MODE_HIDDEN = 1; // 0x1
field public static final int SHOW_IN_QUIET_MODE_PAUSED = 0; // 0x0
@@ -6886,7 +6894,6 @@ package android.media {
public final class MediaRouter2 {
method @NonNull public java.util.List<android.media.MediaRoute2Info> getAllRoutes();
method @Nullable public String getClientPackageName();
- method @Nullable @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public static android.media.MediaRouter2 getInstance(@NonNull android.content.Context, @NonNull String);
method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void setRouteVolume(@NonNull android.media.MediaRoute2Info, int);
method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void startScan();
method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void stopScan();
@@ -14774,6 +14781,7 @@ package android.telephony {
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean matchesCurrentSimOperator(@NonNull String, int, @Nullable String);
method public boolean needsOtaServiceProvisioning();
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyOtaEmergencyNumberDbInstalled();
+ method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @RequiresPermission(android.Manifest.permission.DUMP) public void persistEmergencyCallDiagnosticData(@NonNull String, @NonNull android.telephony.TelephonyManager.EmergencyCallDiagnosticParams);
method @RequiresPermission(android.Manifest.permission.REBOOT) public int prepareForUnattendedReboot();
method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean rebootRadio();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerCarrierPrivilegesCallback(int, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CarrierPrivilegesCallback);
@@ -14952,6 +14960,21 @@ package android.telephony {
method public default void onCarrierServiceChanged(@Nullable String, int);
}
+ @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public static final class TelephonyManager.EmergencyCallDiagnosticParams {
+ method public long getLogcatCollectionStartTimeMillis();
+ method public boolean isLogcatCollectionEnabled();
+ method public boolean isTelecomDumpSysCollectionEnabled();
+ method public boolean isTelephonyDumpSysCollectionEnabled();
+ }
+
+ public static final class TelephonyManager.EmergencyCallDiagnosticParams.Builder {
+ ctor public TelephonyManager.EmergencyCallDiagnosticParams.Builder();
+ method @NonNull public android.telephony.TelephonyManager.EmergencyCallDiagnosticParams build();
+ method @NonNull public android.telephony.TelephonyManager.EmergencyCallDiagnosticParams.Builder setLogcatCollectionStartTimeMillis(long);
+ method @NonNull public android.telephony.TelephonyManager.EmergencyCallDiagnosticParams.Builder setTelecomDumpSysCollectionEnabled(boolean);
+ method @NonNull public android.telephony.TelephonyManager.EmergencyCallDiagnosticParams.Builder setTelephonyDumpSysCollectionEnabled(boolean);
+ }
+
public static class TelephonyManager.ModemActivityInfoException extends java.lang.Exception {
ctor public TelephonyManager.ModemActivityInfoException(int);
method public int getErrorCode();
@@ -17159,7 +17182,7 @@ package android.telephony.satellite {
method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @NonNull @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public java.util.Set<java.lang.Integer> getSatelliteAttachRestrictionReasonsForCarrier(int);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void pollPendingSatelliteDatagrams(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void provisionSatelliteService(@NonNull String, @NonNull byte[], @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
- method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void registerForNtnSignalStrengthChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.NtnSignalStrengthCallback) throws android.telephony.satellite.SatelliteManager.SatelliteException;
+ method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void registerForNtnSignalStrengthChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.NtnSignalStrengthCallback);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteCapabilitiesChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteCapabilitiesCallback);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteDatagram(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteDatagramCallback);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteModemStateChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteModemStateCallback);
@@ -17226,6 +17249,7 @@ package android.telephony.satellite {
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_UNKNOWN = -1; // 0xffffffff
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_ACCESS_BARRED = 16; // 0x10
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_ERROR = 1; // 0x1
+ field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_ILLEGAL_STATE = 23; // 0x17
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_INVALID_ARGUMENTS = 8; // 0x8
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_INVALID_MODEM_STATE = 7; // 0x7
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_INVALID_TELEPHONY_STATE = 6; // 0x6
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index b8b98a3cb3ba..7a3d320dddab 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -3646,6 +3646,7 @@ package android.view {
public interface WindowManager extends android.view.ViewManager {
method public default int getDisplayImePolicy(int);
+ method @FlaggedApi("com.android.window.flags.surface_control_input_receiver") @Nullable public default android.os.IBinder getSurfaceControlInputClientToken(@NonNull android.view.SurfaceControl);
method public static boolean hasWindowExtensionsEnabled();
method public default void holdLock(android.os.IBinder, int);
method public default boolean isGlobalKey(int);
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 57fca74e7e59..4a566db3afb3 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -184,6 +184,12 @@ public class ActivityOptions extends ComponentOptions {
public static final String KEY_ANIM_START_LISTENER = "android:activity.animStartListener";
/**
+ * Callback for when animation is aborted.
+ * @hide
+ */
+ private static final String KEY_ANIM_ABORT_LISTENER = "android:activity.animAbortListener";
+
+ /**
* Specific a theme for a splash screen window.
* @hide
*/
@@ -459,6 +465,7 @@ public class ActivityOptions extends ComponentOptions {
private int mHeight;
private IRemoteCallback mAnimationStartedListener;
private IRemoteCallback mAnimationFinishedListener;
+ private IRemoteCallback mAnimationAbortListener;
private SceneTransitionInfo mSceneTransitionInfo;
private PendingIntent mUsageTimeReport;
private int mLaunchDisplayId = INVALID_DISPLAY;
@@ -716,6 +723,14 @@ public class ActivityOptions extends ComponentOptions {
}
/**
+ * Callback for finding out when the given animation is finished
+ * @hide
+ */
+ public void setOnAnimationFinishedListener(IRemoteCallback listener) {
+ mAnimationFinishedListener = listener;
+ }
+
+ /**
* Callback for finding out when the given animation has drawn its last frame.
* @hide
*/
@@ -728,6 +743,14 @@ public class ActivityOptions extends ComponentOptions {
}
/**
+ * Callback for finding out when the given animation is aborted
+ * @hide
+ */
+ public void setOnAnimationAbortListener(IRemoteCallback listener) {
+ mAnimationAbortListener = listener;
+ }
+
+ /**
* Create an ActivityOptions specifying an animation where the new
* activity is scaled from a small originating area of the screen to
* its final full representation.
@@ -1327,6 +1350,8 @@ public class ActivityOptions extends ComponentOptions {
KEY_PENDING_INTENT_CREATOR_BACKGROUND_ACTIVITY_START_MODE,
MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED);
mDisableStartingWindow = opts.getBoolean(KEY_DISABLE_STARTING_WINDOW);
+ mAnimationAbortListener = IRemoteCallback.Stub.asInterface(
+ opts.getBinder(KEY_ANIM_ABORT_LISTENER));
}
/**
@@ -1427,11 +1452,15 @@ public class ActivityOptions extends ComponentOptions {
/** @hide */
public void abort() {
- if (mAnimationStartedListener != null) {
+ sendResultIgnoreErrors(mAnimationStartedListener, null);
+ sendResultIgnoreErrors(mAnimationAbortListener, null);
+ }
+
+ private void sendResultIgnoreErrors(IRemoteCallback callback, Bundle data) {
+ if (callback != null) {
try {
- mAnimationStartedListener.sendResult(null);
- } catch (RemoteException e) {
- }
+ callback.sendResult(data);
+ } catch (RemoteException e) { }
}
}
@@ -2110,12 +2139,7 @@ public class ActivityOptions extends ComponentOptions {
mCustomExitResId = otherOptions.mCustomExitResId;
mCustomBackgroundColor = otherOptions.mCustomBackgroundColor;
mThumbnail = null;
- if (mAnimationStartedListener != null) {
- try {
- mAnimationStartedListener.sendResult(null);
- } catch (RemoteException e) {
- }
- }
+ sendResultIgnoreErrors(mAnimationStartedListener, null);
mAnimationStartedListener = otherOptions.mAnimationStartedListener;
break;
case ANIM_CUSTOM_IN_PLACE:
@@ -2126,12 +2150,7 @@ public class ActivityOptions extends ComponentOptions {
mStartY = otherOptions.mStartY;
mWidth = otherOptions.mWidth;
mHeight = otherOptions.mHeight;
- if (mAnimationStartedListener != null) {
- try {
- mAnimationStartedListener.sendResult(null);
- } catch (RemoteException e) {
- }
- }
+ sendResultIgnoreErrors(mAnimationStartedListener, null);
mAnimationStartedListener = null;
break;
case ANIM_THUMBNAIL_SCALE_UP:
@@ -2143,12 +2162,7 @@ public class ActivityOptions extends ComponentOptions {
mStartY = otherOptions.mStartY;
mWidth = otherOptions.mWidth;
mHeight = otherOptions.mHeight;
- if (mAnimationStartedListener != null) {
- try {
- mAnimationStartedListener.sendResult(null);
- } catch (RemoteException e) {
- }
- }
+ sendResultIgnoreErrors(mAnimationStartedListener, null);
mAnimationStartedListener = otherOptions.mAnimationStartedListener;
break;
case ANIM_SCENE_TRANSITION:
@@ -2165,6 +2179,9 @@ public class ActivityOptions extends ComponentOptions {
mRemoteAnimationAdapter = otherOptions.mRemoteAnimationAdapter;
mLaunchIntoPipParams = otherOptions.mLaunchIntoPipParams;
mIsEligibleForLegacyPermissionPrompt = otherOptions.mIsEligibleForLegacyPermissionPrompt;
+
+ sendResultIgnoreErrors(mAnimationAbortListener, null);
+ mAnimationAbortListener = otherOptions.mAnimationAbortListener;
}
/**
@@ -2363,6 +2380,8 @@ public class ActivityOptions extends ComponentOptions {
if (mDisableStartingWindow) {
b.putBoolean(KEY_DISABLE_STARTING_WINDOW, mDisableStartingWindow);
}
+ b.putBinder(KEY_ANIM_ABORT_LISTENER,
+ mAnimationAbortListener != null ? mAnimationAbortListener.asBinder() : null);
return b;
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 1bdbd4c50634..5d2a26e13ee7 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -319,6 +319,10 @@ public final class ActivityThread extends ClientTransactionHandler
public static final int SERVICE_DONE_EXECUTING_START = 1;
/** Type for IActivityManager.serviceDoneExecuting: done stopping (destroying) service */
public static final int SERVICE_DONE_EXECUTING_STOP = 2;
+ /** Type for IActivityManager.serviceDoneExecuting: done with an onRebind call */
+ public static final int SERVICE_DONE_EXECUTING_REBIND = 3;
+ /** Type for IActivityManager.serviceDoneExecuting: done with an onUnbind call */
+ public static final int SERVICE_DONE_EXECUTING_UNBIND = 4;
/** Use foreground GC policy (less pause time) and higher JIT weight. */
private static final int VM_PROCESS_STATE_JANK_PERCEPTIBLE = 0;
@@ -4882,7 +4886,7 @@ public final class ActivityThread extends ClientTransactionHandler
mServices.put(data.token, service);
try {
ActivityManager.getService().serviceDoneExecuting(
- data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
+ data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0, null);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -4913,7 +4917,7 @@ public final class ActivityThread extends ClientTransactionHandler
} else {
s.onRebind(data.intent);
ActivityManager.getService().serviceDoneExecuting(
- data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
+ data.token, SERVICE_DONE_EXECUTING_REBIND, 0, 0, data.intent);
}
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
@@ -4943,7 +4947,7 @@ public final class ActivityThread extends ClientTransactionHandler
data.token, data.intent, doRebind);
} else {
ActivityManager.getService().serviceDoneExecuting(
- data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
+ data.token, SERVICE_DONE_EXECUTING_UNBIND, 0, 0, data.intent);
}
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
@@ -5057,7 +5061,7 @@ public final class ActivityThread extends ClientTransactionHandler
try {
ActivityManager.getService().serviceDoneExecuting(
- data.token, SERVICE_DONE_EXECUTING_START, data.startId, res);
+ data.token, SERVICE_DONE_EXECUTING_START, data.startId, res, null);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -5089,7 +5093,7 @@ public final class ActivityThread extends ClientTransactionHandler
try {
ActivityManager.getService().serviceDoneExecuting(
- token, SERVICE_DONE_EXECUTING_STOP, 0, 0);
+ token, SERVICE_DONE_EXECUTING_STOP, 0, 0, null);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index ccd8456129eb..00c4b0f6515f 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1548,9 +1548,16 @@ public class AppOpsManager {
public static final int OP_READ_SYSTEM_GRAMMATICAL_GENDER =
AppProtoEnums.APP_OP_READ_SYSTEM_GRAMMATICAL_GENDER;
+ /**
+ * Allows an app whose primary use case is to backup or sync content to run longer jobs.
+ *
+ * @hide
+ */
+ public static final int OP_RUN_BACKUP_JOBS = AppProtoEnums.APP_OP_RUN_BACKUP_JOBS;
+
/** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public static final int _NUM_OP = 144;
+ public static final int _NUM_OP = 145;
/**
* All app ops represented as strings.
@@ -1700,6 +1707,7 @@ public class AppOpsManager {
OPSTR_ENABLE_MOBILE_DATA_BY_USER,
OPSTR_RESERVED_FOR_TESTING,
OPSTR_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER,
+ OPSTR_RUN_BACKUP_JOBS,
})
public @interface AppOpString {}
@@ -2392,6 +2400,13 @@ public class AppOpsManager {
public static final String OPSTR_READ_SYSTEM_GRAMMATICAL_GENDER =
"android:read_system_grammatical_gender";
+ /**
+ * Allows an app whose primary use case is to backup or sync content to run longer jobs.
+ *
+ * @hide
+ */
+ public static final String OPSTR_RUN_BACKUP_JOBS = "android:run_backup_jobs";
+
/** {@link #sAppOpsToNote} not initialized yet for this op */
private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0;
/** Should not collect noting of this app-op in {@link #sAppOpsToNote} */
@@ -2504,6 +2519,7 @@ public class AppOpsManager {
OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA,
OP_MEDIA_ROUTING_CONTROL,
OP_READ_SYSTEM_GRAMMATICAL_GENDER,
+ OP_RUN_BACKUP_JOBS,
};
static final AppOpInfo[] sAppOpInfos = new AppOpInfo[]{
@@ -2961,6 +2977,8 @@ public class AppOpsManager {
// will make it an app-op permission in the future.
// .setPermission(Manifest.permission.READ_SYSTEM_GRAMMATICAL_GENDER)
.build(),
+ new AppOpInfo.Builder(OP_RUN_BACKUP_JOBS, OPSTR_RUN_BACKUP_JOBS, "RUN_BACKUP_JOBS")
+ .setPermission(Manifest.permission.RUN_BACKUP_JOBS).build(),
};
// The number of longs needed to form a full bitmask of app ops
diff --git a/core/java/android/app/BackgroundInstallControlManager.java b/core/java/android/app/BackgroundInstallControlManager.java
new file mode 100644
index 000000000000..664fcebcfc05
--- /dev/null
+++ b/core/java/android/app/BackgroundInstallControlManager.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import static android.Manifest.permission.GET_BACKGROUND_INSTALLED_PACKAGES;
+import static android.annotation.SystemApi.Client.PRIVILEGED_APPS;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.content.pm.IBackgroundInstallControlService;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+
+import java.util.List;
+
+/**
+ * BackgroundInstallControlManager client allows apps to query apps installed in background.
+ *
+ * <p>Any applications that was installed without an accompanying installer UI activity paired
+ * with recorded user interaction event is considered background installed. This is determined by
+ * analysis of user-activity logs.
+ *
+ * <p>Warning: BackgroundInstallControl should not be considered a definitive
+ * authority of identifying background installed applications. Consumers can use this as a
+ * supplementary signal, but must perform additional due diligence to confirm the install nature
+ * of the package.
+ *
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_BIC_CLIENT)
+@SystemApi(client = PRIVILEGED_APPS)
+@SystemService(Context.BACKGROUND_INSTALL_CONTROL_SERVICE)
+public final class BackgroundInstallControlManager {
+
+ private static final String TAG = "BackgroundInstallControlManager";
+ private static IBackgroundInstallControlService sService;
+ private final Context mContext;
+
+ BackgroundInstallControlManager(Context context) {
+ mContext = context;
+ }
+
+ private static IBackgroundInstallControlService getService() {
+ if (sService == null) {
+ sService =
+ IBackgroundInstallControlService.Stub.asInterface(
+ ServiceManager.getService(Context.BACKGROUND_INSTALL_CONTROL_SERVICE));
+ }
+ return sService;
+ }
+
+ /**
+ * Returns a full list of {@link PackageInfo} of apps currently installed for the current user
+ * that are considered installed in the background.
+ *
+ * <p>Refer to top level doc {@link BackgroundInstallControlManager} for more details on
+ * background-installed applications.
+ * <p>
+ *
+ * @param flags - Flags will be used to call
+ * {@link PackageManager#getInstalledPackages(PackageInfoFlags)} to retrieve installed packages.
+ * @return A list of packages retrieved from {@link PackageManager} with non-background
+ * installed app filter applied.
+ *
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_BIC_CLIENT)
+ @SystemApi
+ @RequiresPermission(GET_BACKGROUND_INSTALLED_PACKAGES)
+ public @NonNull List<PackageInfo> getBackgroundInstalledPackages(
+ @PackageManager.PackageInfoFlagsBits long flags) {
+ List<PackageInfo> backgroundInstalledPackages;
+ try {
+ return getService()
+ .getBackgroundInstalledPackages(flags, mContext.getUserId())
+ .getList();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+}
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 47403d26c907..b063d04655f5 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -294,7 +294,8 @@ interface IActivityManager {
@UnsupportedAppUsage
ParceledListSlice getRecentTasks(int maxNum, int flags, int userId);
@UnsupportedAppUsage
- oneway void serviceDoneExecuting(in IBinder token, int type, int startId, int res);
+ oneway void serviceDoneExecuting(in IBinder token, int type, int startId, int res,
+ in Intent intent);
/** @deprecated Use {@link #getIntentSenderWithFeature} instead */
@UnsupportedAppUsage(maxTargetSdk=29, publicAlternatives="Use {@link PendingIntent#getIntentSender()} instead")
IIntentSender getIntentSender(int type, in String packageName, in IBinder token,
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index ed0cfbe3d9c3..a81ad3c429ea 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -5487,6 +5487,15 @@ public class Notification implements Parcelable
return mColors;
}
+ /**
+ * @param isHeader If the notification is a notification header
+ * @return An instance of mColors after resolving the palette
+ */
+ private Colors getColors(boolean isHeader) {
+ mColors.resolvePalette(mContext, mN.color, !isHeader && mN.isColorized(), mInNightMode);
+ return mColors;
+ }
+
private void updateBackgroundColor(RemoteViews contentView,
StandardTemplateParams p) {
if (isBackgroundColorized(p)) {
@@ -6618,6 +6627,23 @@ public class Notification implements Parcelable
return getColors(p).getContrastColor();
}
+ /**
+ * Gets the foreground color of the small icon. If the notification is colorized, this
+ * is the primary text color, otherwise it's the contrast-adjusted app-provided color.
+ * @hide
+ */
+ public @ColorInt int getSmallIconColor(boolean isHeader) {
+ return getColors(/* isHeader = */ isHeader).getContrastColor();
+ }
+
+ /**
+ * Gets the background color of the notification.
+ * @hide
+ */
+ public @ColorInt int getBackgroundColor(boolean isHeader) {
+ return getColors(/* isHeader = */ isHeader).getBackgroundColor();
+ }
+
/** @return the theme's accent color for colored UI elements. */
private @ColorInt int getPrimaryAccentColor(StandardTemplateParams p) {
return getColors(p).getPrimaryAccentColor();
@@ -8532,6 +8558,8 @@ public class Notification implements Parcelable
boolean isImportantConversation = mConversationType == CONVERSATION_TYPE_IMPORTANT;
boolean isHeaderless = !isConversationLayout && isCollapsed;
+ //TODO (b/217799515): ensure mConversationTitle always returns the correct
+ // conversationTitle, probably set mConversationTitle = conversationTitle after this
CharSequence conversationTitle = !TextUtils.isEmpty(super.mBigContentTitle)
? super.mBigContentTitle
: mConversationTitle;
diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS
index 0760d4db9169..3b5bba20a10b 100644
--- a/core/java/android/app/OWNERS
+++ b/core/java/android/app/OWNERS
@@ -90,8 +90,8 @@ per-file InstantAppResolveInfo.aidl = file:/services/core/java/com/android/serve
per-file pinner-client.aconfig = file:/core/java/android/app/pinner/OWNERS
# BackgroundInstallControlManager
-per-file BackgroundInstallControlManager.java = file:/services/core/java/com/android/server/pm/OWNERS
-per-file background_install_control_manager.aconfig = file:/services/core/java/com/android/server/pm/OWNERS
+per-file BackgroundInstallControlManager.java = file:/services/core/java/com/android/server/pm/BACKGROUND_INSTALL_OWNERS
+per-file background_install_control_manager.aconfig = file:/services/core/java/com/android/server/pm/BACKGROUND_INSTALL_OWNERS
# ResourcesManager
per-file ResourcesManager.java = file:RESOURCES_OWNERS
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraConfig.java b/core/java/android/companion/virtual/camera/VirtualCameraConfig.java
index 350cf3d832d6..06a0f5c09e18 100644
--- a/core/java/android/companion/virtual/camera/VirtualCameraConfig.java
+++ b/core/java/android/companion/virtual/camera/VirtualCameraConfig.java
@@ -196,13 +196,12 @@ public final class VirtualCameraConfig implements Parcelable {
* <li>At least one stream must be added with {@link #addStreamConfig(int, int, int, int)}.
* <li>A callback must be set with {@link #setVirtualCameraCallback(Executor,
* VirtualCameraCallback)}
- * <li>A camera name must be set with {@link #setName(String)}
* <li>A lens facing must be set with {@link #setLensFacing(int)}
*/
@FlaggedApi(Flags.FLAG_VIRTUAL_CAMERA)
public static final class Builder {
- private String mName;
+ private final String mName;
private final ArraySet<VirtualCameraStreamConfig> mStreamConfigurations = new ArraySet<>();
private Executor mCallbackExecutor;
private VirtualCameraCallback mCallback;
@@ -210,12 +209,12 @@ public final class VirtualCameraConfig implements Parcelable {
private int mLensFacing = LENS_FACING_UNKNOWN;
/**
- * Sets the name of the virtual camera instance.
+ * Creates a new instance of {@link Builder}.
+ *
+ * @param name The name of the {@link VirtualCamera}.
*/
- @NonNull
- public Builder setName(@NonNull String name) {
- mName = requireNonNull(name, "Display name cannot be null");
- return this;
+ public Builder(@NonNull String name) {
+ mName = requireNonNull(name, "Name cannot be null");
}
/**
diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java
index ad3acd713c6b..79af65a3a3e5 100644
--- a/core/java/android/content/IntentFilter.java
+++ b/core/java/android/content/IntentFilter.java
@@ -16,11 +16,13 @@
package android.content;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.compat.annotation.UnsupportedAppUsage;
+import android.content.pm.Flags;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
@@ -175,6 +177,7 @@ public class IntentFilter implements Parcelable {
private static final String ACTION_STR = "action";
private static final String AUTO_VERIFY_STR = "autoVerify";
private static final String EXTRAS_STR = "extras";
+ private static final String URI_RELATIVE_FILTER_GROUP_STR = "uriRelativeFilterGroup";
private static final int[] EMPTY_INT_ARRAY = new int[0];
private static final long[] EMPTY_LONG_ARRAY = new long[0];
@@ -324,6 +327,7 @@ public class IntentFilter implements Parcelable {
private ArrayList<PatternMatcher> mDataSchemeSpecificParts = null;
private ArrayList<AuthorityEntry> mDataAuthorities = null;
private ArrayList<PatternMatcher> mDataPaths = null;
+ private ArrayList<UriRelativeFilterGroup> mUriRelativeFilterGroups = null;
private ArrayList<String> mStaticDataTypes = null;
private ArrayList<String> mDataTypes = null;
private ArrayList<String> mMimeGroups = null;
@@ -520,6 +524,10 @@ public class IntentFilter implements Parcelable {
if (o.mDataPaths != null) {
mDataPaths = new ArrayList<PatternMatcher>(o.mDataPaths);
}
+ if (o.mUriRelativeFilterGroups != null) {
+ mUriRelativeFilterGroups =
+ new ArrayList<UriRelativeFilterGroup>(o.mUriRelativeFilterGroups);
+ }
if (o.mMimeGroups != null) {
mMimeGroups = new ArrayList<String>(o.mMimeGroups);
}
@@ -1563,6 +1571,63 @@ public class IntentFilter implements Parcelable {
}
/**
+ * Add a new URI relative filter group to match against the Intent data. The
+ * intent filter must include one or more schemes (via {@link #addDataScheme})
+ * <em>and</em> one or more authorities (via {@link #addDataAuthority}) for
+ * the group to be considered.
+ *
+ * <p>Groups will be matched in the order they were added and matching will only
+ * be done if no data paths match or if none are included. If both data paths and
+ * groups are not included, then only the scheme/authority must match.</p>
+ *
+ * @param group A {@link UriRelativeFilterGroup} to match the URI.
+ *
+ * @see UriRelativeFilterGroup
+ * @see #matchData
+ * @see #addDataScheme
+ * @see #addDataAuthority
+ */
+ @FlaggedApi(Flags.FLAG_RELATIVE_REFERENCE_INTENT_FILTERS)
+ public final void addUriRelativeFilterGroup(@NonNull UriRelativeFilterGroup group) {
+ Objects.requireNonNull(group);
+ if (mUriRelativeFilterGroups == null) {
+ mUriRelativeFilterGroups = new ArrayList<>();
+ }
+ mUriRelativeFilterGroups.add(group);
+ }
+
+ /**
+ * Return the number of URI relative filter groups in the intent filter.
+ */
+ @FlaggedApi(Flags.FLAG_RELATIVE_REFERENCE_INTENT_FILTERS)
+ public final int countUriRelativeFilterGroups() {
+ return mUriRelativeFilterGroups == null ? 0 : mUriRelativeFilterGroups.size();
+ }
+
+ /**
+ * Return a URI relative filter group in the intent filter.
+ *
+ * <p>Note: use of this method will result in a NullPointerException
+ * if no groups exists for this intent filter.</p>
+ *
+ * @param index index of the element to return
+ * @throws IndexOutOfBoundsException if index is out of range
+ */
+ @FlaggedApi(Flags.FLAG_RELATIVE_REFERENCE_INTENT_FILTERS)
+ @NonNull
+ public final UriRelativeFilterGroup getUriRelativeFilterGroup(int index) {
+ return mUriRelativeFilterGroups.get(index);
+ }
+
+ /**
+ * Removes all existing URI relative filter groups in the intent filter.
+ */
+ @FlaggedApi(Flags.FLAG_RELATIVE_REFERENCE_INTENT_FILTERS)
+ public final void clearUriRelativeFilterGroups() {
+ mUriRelativeFilterGroups = null;
+ }
+
+ /**
* Match this intent filter against the given Intent data. This ignores
* the data scheme -- unlike {@link #matchData}, the authority will match
* regardless of whether there is a matching scheme.
@@ -1677,12 +1742,24 @@ public class IntentFilter implements Parcelable {
int authMatch = matchDataAuthority(data, wildcardSupported);
if (authMatch >= 0) {
final ArrayList<PatternMatcher> paths = mDataPaths;
- if (paths == null) {
- match = authMatch;
- } else if (hasDataPath(data.getPath(), wildcardSupported)) {
- match = MATCH_CATEGORY_PATH;
+ final ArrayList<UriRelativeFilterGroup> groups = mUriRelativeFilterGroups;
+ if (Flags.relativeReferenceIntentFilters()) {
+ if (paths == null && groups == null) {
+ match = authMatch;
+ } else if (hasDataPath(data.getPath(), wildcardSupported)
+ || matchRelRefGroups(data)) {
+ match = MATCH_CATEGORY_PATH;
+ } else {
+ return NO_MATCH_DATA;
+ }
} else {
- return NO_MATCH_DATA;
+ if (paths == null) {
+ match = authMatch;
+ } else if (hasDataPath(data.getPath(), wildcardSupported)) {
+ match = MATCH_CATEGORY_PATH;
+ } else {
+ return NO_MATCH_DATA;
+ }
}
} else {
return NO_MATCH_DATA;
@@ -1726,6 +1803,19 @@ public class IntentFilter implements Parcelable {
return match + MATCH_ADJUSTMENT_NORMAL;
}
+ private boolean matchRelRefGroups(Uri data) {
+ if (mUriRelativeFilterGroups == null) {
+ return false;
+ }
+ for (int i = 0; i < mUriRelativeFilterGroups.size(); i++) {
+ UriRelativeFilterGroup group = mUriRelativeFilterGroups.get(i);
+ if (group.matchData(data)) {
+ return group.getAction() == UriRelativeFilterGroup.ACTION_ALLOW;
+ }
+ }
+ return false;
+ }
+
/**
* Add a new Intent category to match against. The semantics of
* categories is the opposite of actions -- an Intent includes the
@@ -2486,6 +2576,12 @@ public class IntentFilter implements Parcelable {
}
serializer.endTag(null, EXTRAS_STR);
}
+ if (Flags.relativeReferenceIntentFilters()) {
+ N = countUriRelativeFilterGroups();
+ for (int i = 0; i < N; i++) {
+ mUriRelativeFilterGroups.get(i).writeToXml(serializer);
+ }
+ }
}
/**
@@ -2614,6 +2710,9 @@ public class IntentFilter implements Parcelable {
}
} else if (tagName.equals(EXTRAS_STR)) {
mExtras = PersistableBundle.restoreFromXml(parser);
+ } else if (Flags.relativeReferenceIntentFilters()
+ && URI_RELATIVE_FILTER_GROUP_STR.equals(tagName)) {
+ addUriRelativeFilterGroup(new UriRelativeFilterGroup(parser));
} else {
Log.w("IntentFilter", "Unknown tag parsing IntentFilter: " + tagName);
}
@@ -2680,6 +2779,12 @@ public class IntentFilter implements Parcelable {
if (mExtras != null) {
mExtras.dumpDebug(proto, IntentFilterProto.EXTRAS);
}
+ if (Flags.relativeReferenceIntentFilters() && mUriRelativeFilterGroups != null) {
+ Iterator<UriRelativeFilterGroup> it = mUriRelativeFilterGroups.iterator();
+ while (it.hasNext()) {
+ it.next().dumpDebug(proto, IntentFilterProto.URI_RELATIVE_FILTER_GROUPS);
+ }
+ }
proto.end(token);
}
@@ -2744,6 +2849,15 @@ public class IntentFilter implements Parcelable {
du.println(sb.toString());
}
}
+ if (mUriRelativeFilterGroups != null) {
+ Iterator<UriRelativeFilterGroup> it = mUriRelativeFilterGroups.iterator();
+ while (it.hasNext()) {
+ sb.setLength(0);
+ sb.append(prefix); sb.append("UriRelativeFilterGroup: \"");
+ sb.append(it.next()); sb.append("\"");
+ du.println(sb.toString());
+ }
+ }
if (mStaticDataTypes != null) {
Iterator<String> it = mStaticDataTypes.iterator();
while (it.hasNext()) {
@@ -2883,6 +2997,15 @@ public class IntentFilter implements Parcelable {
} else {
dest.writeInt(0);
}
+ if (Flags.relativeReferenceIntentFilters() && mUriRelativeFilterGroups != null) {
+ final int N = mUriRelativeFilterGroups.size();
+ dest.writeInt(N);
+ for (int i = 0; i < N; i++) {
+ mUriRelativeFilterGroups.get(i).writeToParcel(dest, flags);
+ }
+ } else {
+ dest.writeInt(0);
+ }
}
/**
@@ -2989,6 +3112,13 @@ public class IntentFilter implements Parcelable {
if (source.readInt() != 0) {
mExtras = PersistableBundle.CREATOR.createFromParcel(source);
}
+ N = source.readInt();
+ if (Flags.relativeReferenceIntentFilters() && N > 0) {
+ mUriRelativeFilterGroups = new ArrayList<UriRelativeFilterGroup>(N);
+ for (int i = 0; i < N; i++) {
+ mUriRelativeFilterGroups.add(new UriRelativeFilterGroup(source));
+ }
+ }
}
private boolean hasPartialTypes() {
diff --git a/core/java/android/content/UriRelativeFilter.java b/core/java/android/content/UriRelativeFilter.java
new file mode 100644
index 000000000000..9866cd0e992a
--- /dev/null
+++ b/core/java/android/content/UriRelativeFilter.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.content;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.Flags;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.PatternMatcher;
+import android.util.proto.ProtoOutputStream;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * A filter for matching Intent URI Data as part of a
+ * {@link UriRelativeFilterGroup}. A single filter can only be
+ * matched against either a URI path, query or fragment
+ */
+@FlaggedApi(Flags.FLAG_RELATIVE_REFERENCE_INTENT_FILTERS)
+public final class UriRelativeFilter {
+ private static final String FILTER_STR = "filter";
+ private static final String PART_STR = "part";
+ private static final String PATTERN_STR = "pattern";
+ static final String URI_RELATIVE_FILTER_STR = "uriRelativeFilter";
+
+ /**
+ * Value to indicate that the filter is to be applied to a URI path.
+ */
+ public static final int PATH = 0;
+ /**
+ * Value to indicate that the filter is to be applied to a URI query.
+ */
+ public static final int QUERY = 1;
+ /**
+ * Value to indicate that the filter is to be applied to a URI fragment.
+ */
+ public static final int FRAGMENT = 2;
+
+ /** @hide */
+ @IntDef(value = {
+ PATH,
+ QUERY,
+ FRAGMENT
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface UriPart {}
+
+ private final @UriPart int mUriPart;
+ private final @PatternMatcher.PatternType int mPatternType;
+ private final String mFilter;
+
+ /**
+ * Creates a new UriRelativeFilter.
+ *
+ * @param uriPart The URI part this filter operates on. Can be either a
+ * {@link UriRelativeFilter#PATH}, {@link UriRelativeFilter#QUERY},
+ * or {@link UriRelativeFilter#FRAGMENT}.
+ * @param patternType The pattern type of the filter. Can be either a
+ * {@link PatternMatcher#PATTERN_LITERAL},
+ * {@link PatternMatcher#PATTERN_PREFIX},
+* {@link PatternMatcher#PATTERN_SUFFIX},
+ * {@link PatternMatcher#PATTERN_SIMPLE_GLOB},
+ * or {@link PatternMatcher#PATTERN_ADVANCED_GLOB}.
+ * @param filter A literal or pattern string depedning on patterType
+ * used to match a uriPart .
+ */
+ public UriRelativeFilter(
+ @UriPart int uriPart,
+ @PatternMatcher.PatternType int patternType,
+ @NonNull String filter) {
+ mUriPart = uriPart;
+ com.android.internal.util.AnnotationValidations.validate(
+ UriPart.class, null, mUriPart);
+ mPatternType = patternType;
+ com.android.internal.util.AnnotationValidations.validate(
+ PatternMatcher.PatternType.class, null, mPatternType);
+ mFilter = filter;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mFilter);
+ }
+
+ /**
+ * The URI part this filter operates on.
+ */
+ public @UriPart int getUriPart() {
+ return mUriPart;
+ }
+
+ /**
+ * The pattern type of the filter.
+ */
+ public @PatternMatcher.PatternType int getPatternType() {
+ return mPatternType;
+ }
+
+ /**
+ * The string used to filter the URI.
+ */
+ public @NonNull String getFilter() {
+ return mFilter;
+ }
+
+ /**
+ * Match this URI filter against an Intent's data. QUERY filters can
+ * match against any key value pair in the query string. PATH and
+ * FRAGMENT filters must match the entire string.
+ *
+ * @param data The full data string to match against, as supplied in
+ * Intent.data.
+ *
+ * @return true if there is a match.
+ */
+ public boolean matchData(@NonNull Uri data) {
+ PatternMatcher pe = new PatternMatcher(mFilter, mPatternType);
+ switch (getUriPart()) {
+ case PATH:
+ return pe.match(data.getPath());
+ case QUERY:
+ return matchQuery(pe, data.getQuery());
+ case FRAGMENT:
+ return pe.match(data.getFragment());
+ default:
+ return false;
+ }
+ }
+
+ private boolean matchQuery(PatternMatcher pe, String query) {
+ if (query != null) {
+ String[] params = query.split("&");
+ if (params.length == 1) {
+ params = query.split(";");
+ }
+ for (int i = 0; i < params.length; i++) {
+ if (pe.match(params[i])) return true;
+ }
+ }
+ return false;
+ }
+
+ /** @hide */
+ public void dumpDebug(ProtoOutputStream proto, long fieldId) {
+ long token = proto.start(fieldId);
+ proto.write(UriRelativeFilterProto.URI_PART, mUriPart);
+ proto.write(UriRelativeFilterProto.PATTERN_TYPE, mPatternType);
+ proto.write(UriRelativeFilterProto.FILTER, mFilter);
+ proto.end(token);
+ }
+
+ /** @hide */
+ public void writeToXml(XmlSerializer serializer) throws IOException {
+ serializer.startTag(null, URI_RELATIVE_FILTER_STR);
+ serializer.attribute(null, PATTERN_STR, Integer.toString(mPatternType));
+ serializer.attribute(null, PART_STR, Integer.toString(mUriPart));
+ serializer.attribute(null, FILTER_STR, mFilter);
+ serializer.endTag(null, URI_RELATIVE_FILTER_STR);
+ }
+
+ private String uriPartToString() {
+ switch (mUriPart) {
+ case PATH:
+ return "PATH";
+ case QUERY:
+ return "QUERY";
+ case FRAGMENT:
+ return "FRAGMENT";
+ default:
+ return "UNKNOWN";
+ }
+ }
+
+ private String patternTypeToString() {
+ switch (mPatternType) {
+ case PatternMatcher.PATTERN_LITERAL:
+ return "LITERAL";
+ case PatternMatcher.PATTERN_PREFIX:
+ return "PREFIX";
+ case PatternMatcher.PATTERN_SIMPLE_GLOB:
+ return "GLOB";
+ case PatternMatcher.PATTERN_ADVANCED_GLOB:
+ return "ADVANCED_GLOB";
+ case PatternMatcher.PATTERN_SUFFIX:
+ return "SUFFIX";
+ default:
+ return "UNKNOWN";
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "UriRelativeFilter { "
+ + "uriPart = " + uriPartToString() + ", "
+ + "patternType = " + patternTypeToString() + ", "
+ + "filter = " + mFilter
+ + " }";
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ UriRelativeFilter that = (UriRelativeFilter) o;
+ return mUriPart == that.mUriPart
+ && mPatternType == that.mPatternType
+ && java.util.Objects.equals(mFilter, that.mFilter);
+ }
+
+ @Override
+ public int hashCode() {
+ int _hash = 1;
+ _hash = 31 * _hash + mUriPart;
+ _hash = 31 * _hash + mPatternType;
+ _hash = 31 * _hash + java.util.Objects.hashCode(mFilter);
+ return _hash;
+ }
+
+ /** @hide */
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mUriPart);
+ dest.writeInt(mPatternType);
+ dest.writeString(mFilter);
+ }
+
+ /** @hide */
+ UriRelativeFilter(@NonNull android.os.Parcel in) {
+ mUriPart = in.readInt();
+ mPatternType = in.readInt();
+ mFilter = in.readString();
+ }
+
+ /** @hide */
+ public UriRelativeFilter(XmlPullParser parser) throws XmlPullParserException, IOException {
+ mUriPart = Integer.parseInt(parser.getAttributeValue(null, PART_STR));
+ mPatternType = Integer.parseInt(parser.getAttributeValue(null, PATTERN_STR));
+ mFilter = parser.getAttributeValue(null, FILTER_STR);
+ }
+}
diff --git a/core/java/android/content/UriRelativeFilterGroup.java b/core/java/android/content/UriRelativeFilterGroup.java
new file mode 100644
index 000000000000..72c396a73ec8
--- /dev/null
+++ b/core/java/android/content/UriRelativeFilterGroup.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.content.pm.Flags;
+import android.net.Uri;
+import android.os.Parcel;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.util.CollectionUtils;
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.Objects;
+
+/**
+ * An intent data matching group based on a URI's relative reference which
+ * includes the path, query and fragment. The group is only considered as
+ * matching if <em>all</em> UriRelativeFilters in the group match. Each
+ * UriRelativeFilter defines a matching rule for a URI path, query or fragment.
+ * A group must contain one or more UriRelativeFilters to match but does not need to
+ * contain UriRelativeFilters for all existing parts of a URI to match.
+ *
+ * <p>For example, given a URI that contains path, query and fragment parts,
+ * a group containing only a path filter will match the URI if the path
+ * filter matches the URI path. If the group contains a path and query
+ * filter, then the group will only match if both path and query filters
+ * match. If a URI contains only a path with no query or fragment then a
+ * group can only match if it contains only a matching path filter. If the
+ * group also contained additional query or fragment filters then it will
+ * not match.</p>
+ */
+@FlaggedApi(Flags.FLAG_RELATIVE_REFERENCE_INTENT_FILTERS)
+public final class UriRelativeFilterGroup {
+ private static final String ALLOW_STR = "allow";
+ private static final String URI_RELATIVE_FILTER_GROUP_STR = "uriRelativeFilterGroup";
+
+ /**
+ * Value to indicate that the group match is allowed.
+ */
+ public static final int ACTION_ALLOW = 0;
+ /**
+ * Value to indicate that the group match is blocked.
+ */
+ public static final int ACTION_BLOCK = 1;
+
+ /** @hide */
+ @IntDef(value = {
+ ACTION_ALLOW,
+ ACTION_BLOCK
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Action {}
+
+ private final @Action int mAction;
+ private final ArraySet<UriRelativeFilter> mUriRelativeFilters = new ArraySet<>();
+
+ /**
+ * New UriRelativeFilterGroup that matches a Intent data.
+ *
+ * @param action Whether this matching group should be allowed or disallowed.
+ */
+ public UriRelativeFilterGroup(@Action int action) {
+ mAction = action;
+ }
+
+ /** @hide */
+ public UriRelativeFilterGroup(XmlPullParser parser) throws XmlPullParserException, IOException {
+ mAction = Integer.parseInt(parser.getAttributeValue(null, ALLOW_STR));
+
+ int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG
+ || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG
+ || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ String tagName = parser.getName();
+ if (tagName.equals(UriRelativeFilter.URI_RELATIVE_FILTER_STR)) {
+ addUriRelativeFilter(new UriRelativeFilter(parser));
+ } else {
+ Log.w("IntentFilter", "Unknown tag parsing IntentFilter: " + tagName);
+ }
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+
+ /**
+ * Return {@link UriRelativeFilterGroup#ACTION_ALLOW} if a URI is allowed when matched
+ * and {@link UriRelativeFilterGroup#ACTION_BLOCK} if a URI is blacked when matched.
+ */
+ public @Action int getAction() {
+ return mAction;
+ }
+
+ /**
+ * Add a filter to the group.
+ */
+ public void addUriRelativeFilter(@NonNull UriRelativeFilter uriRelativeFilter) {
+ Objects.requireNonNull(uriRelativeFilter);
+ if (!CollectionUtils.contains(mUriRelativeFilters, uriRelativeFilter)) {
+ mUriRelativeFilters.add(uriRelativeFilter);
+ }
+ }
+
+ /**
+ * Returns a unmodifiable view of the UriRelativeFilters list in this group.
+ */
+ @NonNull
+ public Collection<UriRelativeFilter> getUriRelativeFilters() {
+ return Collections.unmodifiableCollection(mUriRelativeFilters);
+ }
+
+ /**
+ * Match all URI filter in this group against {@link Intent#getData()}.
+ *
+ * @param data The full data string to match against, as supplied in
+ * Intent.data.
+ * @return true if all filters match.
+ */
+ public boolean matchData(@NonNull Uri data) {
+ if (mUriRelativeFilters.size() == 0) {
+ return false;
+ }
+ for (UriRelativeFilter filter : mUriRelativeFilters) {
+ if (!filter.matchData(data)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /** @hide */
+ public void dumpDebug(ProtoOutputStream proto, long fieldId) {
+ long token = proto.start(fieldId);
+ proto.write(UriRelativeFilterGroupProto.ACTION, mAction);
+ Iterator<UriRelativeFilter> it = mUriRelativeFilters.iterator();
+ while (it.hasNext()) {
+ it.next().dumpDebug(proto, UriRelativeFilterGroupProto.URI_RELATIVE_FILTERS);
+ }
+ proto.end(token);
+ }
+
+ /** @hide */
+ public void writeToXml(XmlSerializer serializer) throws IOException {
+ serializer.startTag(null, URI_RELATIVE_FILTER_GROUP_STR);
+ serializer.attribute(null, ALLOW_STR, Integer.toString(mAction));
+ Iterator<UriRelativeFilter> it = mUriRelativeFilters.iterator();
+ while (it.hasNext()) {
+ UriRelativeFilter filter = it.next();
+ filter.writeToXml(serializer);
+ }
+ serializer.endTag(null, URI_RELATIVE_FILTER_GROUP_STR);
+ }
+
+ @Override
+ public String toString() {
+ return "UriRelativeFilterGroup { allow = " + mAction
+ + ", uri_filters = " + mUriRelativeFilters + ", }";
+ }
+
+ /** @hide */
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mAction);
+ final int n = mUriRelativeFilters.size();
+ if (n > 0) {
+ dest.writeInt(n);
+ Iterator<UriRelativeFilter> it = mUriRelativeFilters.iterator();
+ while (it.hasNext()) {
+ it.next().writeToParcel(dest, flags);
+ }
+ } else {
+ dest.writeInt(0);
+ }
+ }
+
+ /** @hide */
+ UriRelativeFilterGroup(@NonNull Parcel src) {
+ mAction = src.readInt();
+ final int n = src.readInt();
+ for (int i = 0; i < n; i++) {
+ mUriRelativeFilters.add(new UriRelativeFilter(src));
+ }
+ }
+}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 4724e866f094..8744eaee4341 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -128,6 +128,7 @@ import java.util.function.Function;
* <a href="/training/basics/intents/package-visibility">manage package visibility</a>.
* </p>
*/
+@android.ravenwood.annotation.RavenwoodKeepPartialClass
public abstract class PackageManager {
private static final String TAG = "PackageManager";
@@ -5492,6 +5493,7 @@ public abstract class PackageManager {
* application info.
* @hide
*/
+ @android.ravenwood.annotation.RavenwoodKeepWholeClass
public static class Flags {
final long mValue;
protected Flags(long value) {
@@ -5506,6 +5508,7 @@ public abstract class PackageManager {
* Specific flags used for retrieving package info. Example:
* {@code PackageManager.getPackageInfo(packageName, PackageInfoFlags.of(0)}
*/
+ @android.ravenwood.annotation.RavenwoodKeepWholeClass
public final static class PackageInfoFlags extends Flags {
private PackageInfoFlags(@PackageInfoFlagsBits long value) {
super(value);
@@ -5519,6 +5522,7 @@ public abstract class PackageManager {
/**
* Specific flags used for retrieving application info.
*/
+ @android.ravenwood.annotation.RavenwoodKeepWholeClass
public final static class ApplicationInfoFlags extends Flags {
private ApplicationInfoFlags(@ApplicationInfoFlagsBits long value) {
super(value);
@@ -5532,6 +5536,7 @@ public abstract class PackageManager {
/**
* Specific flags used for retrieving component info.
*/
+ @android.ravenwood.annotation.RavenwoodKeepWholeClass
public final static class ComponentInfoFlags extends Flags {
private ComponentInfoFlags(@ComponentInfoFlagsBits long value) {
super(value);
@@ -5545,6 +5550,7 @@ public abstract class PackageManager {
/**
* Specific flags used for retrieving resolve info.
*/
+ @android.ravenwood.annotation.RavenwoodKeepWholeClass
public final static class ResolveInfoFlags extends Flags {
private ResolveInfoFlags(@ResolveInfoFlagsBits long value) {
super(value);
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java
index 8fd78bd4276c..3e9f260566bd 100644
--- a/core/java/android/content/pm/UserInfo.java
+++ b/core/java/android/content/pm/UserInfo.java
@@ -52,6 +52,7 @@ import java.lang.annotation.RetentionPolicy;
* @hide
*/
@TestApi
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public class UserInfo implements Parcelable {
/**
@@ -438,6 +439,7 @@ public class UserInfo implements Parcelable {
/**
* @return true if this user can be switched to.
**/
+ @android.ravenwood.annotation.RavenwoodThrow
public boolean supportsSwitchTo() {
if (partial || !isEnabled()) {
// Don't support switching to disabled or partial users, which includes users with
@@ -455,6 +457,7 @@ public class UserInfo implements Parcelable {
* @return true if user is of type {@link UserManager#USER_TYPE_SYSTEM_HEADLESS} and
* {@link com.android.internal.R.bool.config_canSwitchToHeadlessSystemUser} is true.
*/
+ @android.ravenwood.annotation.RavenwoodThrow
private boolean canSwitchToHeadlessSystemUser() {
return UserManager.USER_TYPE_SYSTEM_HEADLESS.equals(userType) && Resources.getSystem()
.getBoolean(com.android.internal.R.bool.config_canSwitchToHeadlessSystemUser);
@@ -465,6 +468,7 @@ public class UserInfo implements Parcelable {
* @deprecated Use {@link UserInfo#supportsSwitchTo} instead.
*/
@Deprecated
+ @android.ravenwood.annotation.RavenwoodThrow
public boolean supportsSwitchToByUser() {
return supportsSwitchTo();
}
diff --git a/core/java/android/content/pm/UserProperties.java b/core/java/android/content/pm/UserProperties.java
index 269c6c27821c..1d0e2db78bcb 100644
--- a/core/java/android/content/pm/UserProperties.java
+++ b/core/java/android/content/pm/UserProperties.java
@@ -16,6 +16,9 @@
package android.content.pm;
+import static android.multiuser.Flags.FLAG_SUPPORT_HIDING_PROFILES;
+
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -73,7 +76,7 @@ public final class UserProperties implements Parcelable {
private static final String ATTR_CROSS_PROFILE_CONTENT_SHARING_STRATEGY =
"crossProfileContentSharingStrategy";
-
+ private static final String ATTR_PROFILE_API_VISIBILITY = "profileApiVisibility";
/** Index values of each property (to indicate whether they are present in this object). */
@IntDef(prefix = "INDEX_", value = {
INDEX_SHOW_IN_LAUNCHER,
@@ -93,6 +96,7 @@ public final class UserProperties implements Parcelable {
INDEX_AUTH_ALWAYS_REQUIRED_TO_DISABLE_QUIET_MODE,
INDEX_CROSS_PROFILE_CONTENT_SHARING_STRATEGY,
INDEX_ALLOW_STOPPING_USER_WITH_DELAYED_LOCKING,
+ INDEX_PROFILE_API_VISIBILITY
})
@Retention(RetentionPolicy.SOURCE)
private @interface PropertyIndex {
@@ -114,6 +118,7 @@ public final class UserProperties implements Parcelable {
private static final int INDEX_SHOW_IN_SHARING_SURFACES = 14;
private static final int INDEX_CROSS_PROFILE_CONTENT_SHARING_STRATEGY = 15;
private static final int INDEX_ALLOW_STOPPING_USER_WITH_DELAYED_LOCKING = 16;
+ private static final int INDEX_PROFILE_API_VISIBILITY = 17;
/** A bit set, mapping each PropertyIndex to whether it is present (1) or absent (0). */
private long mPropertiesPresent = 0;
@@ -450,6 +455,41 @@ public final class UserProperties implements Parcelable {
@SuppressLint("UnflaggedApi") // b/306636213
public static final int CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT = 1;
+ /**
+ * Possible values for the profile visibility in public API surfaces. This indicates whether or
+ * not the information linked to the profile (userId, package names) should not be returned in
+ * API surfaces if a user is marked as hidden.
+ *
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = "PROFILE_API_VISIBILITY_",
+ value = {
+ PROFILE_API_VISIBILITY_UNKNOWN,
+ PROFILE_API_VISIBILITY_VISIBLE,
+ PROFILE_API_VISIBILITY_HIDDEN,
+ }
+ )
+ public @interface ProfileApiVisibility {
+ }
+ /*
+ * The api visibility value for this profile user is undefined or unknown.
+ */
+ @FlaggedApi(FLAG_SUPPORT_HIDING_PROFILES)
+ public static final int PROFILE_API_VISIBILITY_UNKNOWN = -1;
+
+ /**
+ * Indicates that information about this profile user should be shown in API surfaces.
+ */
+ @FlaggedApi(FLAG_SUPPORT_HIDING_PROFILES)
+ public static final int PROFILE_API_VISIBILITY_VISIBLE = 0;
+
+ /**
+ * Indicates that information about this profile should be not be visible in API surfaces.
+ */
+ @FlaggedApi(FLAG_SUPPORT_HIDING_PROFILES)
+ public static final int PROFILE_API_VISIBILITY_HIDDEN = 1;
+
/**
* Creates a UserProperties (intended for the SystemServer) that stores a reference to the given
@@ -510,6 +550,9 @@ public final class UserProperties implements Parcelable {
setShowInQuietMode(orig.getShowInQuietMode());
setShowInSharingSurfaces(orig.getShowInSharingSurfaces());
setCrossProfileContentSharingStrategy(orig.getCrossProfileContentSharingStrategy());
+ if (android.multiuser.Flags.supportHidingProfiles()) {
+ setProfileApiVisibility(orig.getProfileApiVisibility());
+ }
}
/**
@@ -951,9 +994,31 @@ public final class UserProperties implements Parcelable {
}
private @CrossProfileContentSharingStrategy int mCrossProfileContentSharingStrategy;
+ /**
+ * Returns the visibility of the profile user in API surfaces. Any information linked to the
+ * profile (userId, package names) should be hidden API surfaces if a user is marked as hidden.
+ */
+ @NonNull
+ @FlaggedApi(FLAG_SUPPORT_HIDING_PROFILES)
+ public @ProfileApiVisibility int getProfileApiVisibility() {
+ if (isPresent(INDEX_PROFILE_API_VISIBILITY)) return mProfileApiVisibility;
+ if (mDefaultProperties != null) return mDefaultProperties.mProfileApiVisibility;
+ throw new SecurityException("You don't have permission to query profileApiVisibility");
+ }
+ /** @hide */
+ @NonNull
+ @FlaggedApi(FLAG_SUPPORT_HIDING_PROFILES)
+ public void setProfileApiVisibility(@ProfileApiVisibility int profileApiVisibility) {
+ this.mProfileApiVisibility = profileApiVisibility;
+ setPresent(INDEX_PROFILE_API_VISIBILITY);
+ }
+ private @ProfileApiVisibility int mProfileApiVisibility;
@Override
public String toString() {
+ String profileApiVisibility =
+ android.multiuser.Flags.supportHidingProfiles() ? ", mProfileApiVisibility="
+ + getProfileApiVisibility() : "";
// Please print in increasing order of PropertyIndex.
return "UserProperties{"
+ "mPropertiesPresent=" + Long.toBinaryString(mPropertiesPresent)
@@ -977,6 +1042,7 @@ public final class UserProperties implements Parcelable {
+ ", mDeleteAppWithParent=" + getDeleteAppWithParent()
+ ", mAlwaysVisible=" + getAlwaysVisible()
+ ", mCrossProfileContentSharingStrategy=" + getCrossProfileContentSharingStrategy()
+ + profileApiVisibility
+ "}";
}
@@ -1010,6 +1076,9 @@ public final class UserProperties implements Parcelable {
pw.println(prefix + " mAlwaysVisible=" + getAlwaysVisible());
pw.println(prefix + " mCrossProfileContentSharingStrategy="
+ getCrossProfileContentSharingStrategy());
+ if (android.multiuser.Flags.supportHidingProfiles()) {
+ pw.println(prefix + " mProfileApiVisibility=" + getProfileApiVisibility());
+ }
}
/**
@@ -1093,6 +1162,12 @@ public final class UserProperties implements Parcelable {
break;
case ATTR_CROSS_PROFILE_CONTENT_SHARING_STRATEGY:
setCrossProfileContentSharingStrategy(parser.getAttributeInt(i));
+ break;
+ case ATTR_PROFILE_API_VISIBILITY:
+ if (android.multiuser.Flags.supportHidingProfiles()) {
+ setProfileApiVisibility(parser.getAttributeInt(i));
+ }
+ break;
default:
Slog.w(LOG_TAG, "Skipping unknown property " + attributeName);
}
@@ -1175,6 +1250,12 @@ public final class UserProperties implements Parcelable {
serializer.attributeInt(null, ATTR_CROSS_PROFILE_CONTENT_SHARING_STRATEGY,
mCrossProfileContentSharingStrategy);
}
+ if (isPresent(INDEX_PROFILE_API_VISIBILITY)) {
+ if (android.multiuser.Flags.supportHidingProfiles()) {
+ serializer.attributeInt(null, ATTR_PROFILE_API_VISIBILITY,
+ mProfileApiVisibility);
+ }
+ }
}
// For use only with an object that has already had any permission-lacking fields stripped out.
@@ -1198,6 +1279,7 @@ public final class UserProperties implements Parcelable {
dest.writeBoolean(mDeleteAppWithParent);
dest.writeBoolean(mAlwaysVisible);
dest.writeInt(mCrossProfileContentSharingStrategy);
+ dest.writeInt(mProfileApiVisibility);
}
/**
@@ -1225,6 +1307,7 @@ public final class UserProperties implements Parcelable {
mDeleteAppWithParent = source.readBoolean();
mAlwaysVisible = source.readBoolean();
mCrossProfileContentSharingStrategy = source.readInt();
+ mProfileApiVisibility = source.readInt();
}
@Override
@@ -1274,6 +1357,7 @@ public final class UserProperties implements Parcelable {
private boolean mAlwaysVisible = false;
private @CrossProfileContentSharingStrategy int mCrossProfileContentSharingStrategy =
CROSS_PROFILE_CONTENT_SHARING_NO_DELEGATION;
+ private @ProfileApiVisibility int mProfileApiVisibility = 0;
/**
* @hide
@@ -1428,6 +1512,17 @@ public final class UserProperties implements Parcelable {
return this;
}
+ /**
+ * Sets the value for {@link #mProfileApiVisibility}
+ * @hide
+ */
+ @NonNull
+ @FlaggedApi(FLAG_SUPPORT_HIDING_PROFILES)
+ public Builder setProfileApiVisibility(@ProfileApiVisibility int profileApiVisibility){
+ mProfileApiVisibility = profileApiVisibility;
+ return this;
+ }
+
/** Builds a UserProperties object with *all* values populated.
* @hide
*/
@@ -1452,7 +1547,8 @@ public final class UserProperties implements Parcelable {
mAllowStoppingUserWithDelayedLocking,
mDeleteAppWithParent,
mAlwaysVisible,
- mCrossProfileContentSharingStrategy);
+ mCrossProfileContentSharingStrategy,
+ mProfileApiVisibility);
}
} // end Builder
@@ -1473,7 +1569,8 @@ public final class UserProperties implements Parcelable {
boolean allowStoppingUserWithDelayedLocking,
boolean deleteAppWithParent,
boolean alwaysVisible,
- @CrossProfileContentSharingStrategy int crossProfileContentSharingStrategy) {
+ @CrossProfileContentSharingStrategy int crossProfileContentSharingStrategy,
+ @ProfileApiVisibility int profileApiVisibility) {
mDefaultProperties = null;
setShowInLauncher(showInLauncher);
setStartWithParent(startWithParent);
@@ -1493,5 +1590,8 @@ public final class UserProperties implements Parcelable {
setDeleteAppWithParent(deleteAppWithParent);
setAlwaysVisible(alwaysVisible);
setCrossProfileContentSharingStrategy(crossProfileContentSharingStrategy);
+ if (android.multiuser.Flags.supportHidingProfiles()) {
+ setProfileApiVisibility(profileApiVisibility);
+ }
}
}
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index 903875bef1fc..caff4576c9c3 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -110,6 +110,14 @@ flag {
}
flag {
+ name: "relative_reference_intent_filters"
+ namespace: "package_manager_service"
+ description: "Feature flag to enable relative reference intent filters"
+ bug: "307556883"
+ is_fixed_read_only: true
+}
+
+flag {
name: "fix_duplicated_flags"
namespace: "package_manager_service"
description: "Feature flag to fix duplicated PackageManager flag values"
@@ -161,3 +169,17 @@ flag {
bug: "321258605"
is_fixed_read_only: true
}
+
+flag {
+ name: "allow_sdk_sandbox_query_intent_activities"
+ namespace: "package_manager_service"
+ description: "Feature flag to allow the sandbox SDK to query intent activities of the client app."
+ bug: "295842134"
+}
+
+flag {
+ name: "emergency_install_permission"
+ namespace: "permissions"
+ description: "Feature flag to enable permission EMERGENCY_INSTALL_PACKAGES"
+ bug: "321080601"
+}
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index 9644d8095a4d..efb8607f75f7 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -85,6 +85,7 @@ flag {
description: "Enable auto-locking private space on device restarts"
bug: "296993385"
}
+
flag {
name: "enable_system_user_only_for_services_and_providers"
namespace: "multiuser"
@@ -92,3 +93,18 @@ flag {
bug: "302354856"
is_fixed_read_only: true
}
+
+flag {
+ name: "allow_private_profile_apis"
+ namespace: "profile_experiences"
+ description: "Enable only the API changes to support private space"
+ bug: "299069460"
+}
+
+flag {
+ name: "support_hiding_profiles"
+ namespace: "profile_experiences"
+ description: "Allow the use of a hide_profile property to hide some profiles behind a permission"
+ bug: "316362775"
+ is_fixed_read_only: true
+}
diff --git a/core/java/android/content/res/FontScaleConverterFactory.java b/core/java/android/content/res/FontScaleConverterFactory.java
index cbe4c62d7069..625d7cb66900 100644
--- a/core/java/android/content/res/FontScaleConverterFactory.java
+++ b/core/java/android/content/res/FontScaleConverterFactory.java
@@ -58,6 +58,16 @@ public class FontScaleConverterFactory {
synchronized (LOOKUP_TABLES_WRITE_LOCK) {
putInto(
sLookupTables,
+ /* scaleKey= */ 1.1f,
+ new FontScaleConverterImpl(
+ /* fromSp= */
+ new float[] { 8f, 10f, 12f, 14f, 18f, 20f, 24f, 30f, 100},
+ /* toDp= */
+ new float[] { 8.8f, 11f, 13.2f, 15.6f, 19.2f, 21.2f, 24.8f, 30f, 100})
+ );
+
+ putInto(
+ sLookupTables,
/* scaleKey= */ 1.15f,
new FontScaleConverterImpl(
/* fromSp= */
diff --git a/core/java/android/credentials/ui/AuthenticationEntry.java b/core/java/android/credentials/ui/AuthenticationEntry.java
index b1a382c8ea96..9bd0871b3d3b 100644
--- a/core/java/android/credentials/ui/AuthenticationEntry.java
+++ b/core/java/android/credentials/ui/AuthenticationEntry.java
@@ -34,15 +34,24 @@ import java.lang.annotation.RetentionPolicy;
/**
* An authentication entry.
*
+ * Applicable only for credential retrieval flow, authentication entries are a special type of
+ * entries that require the user to unlock the given provider before its credential options can
+ * be fully rendered.
+ *
* @hide
*/
@TestApi
public final class AuthenticationEntry implements Parcelable {
- @NonNull private final String mKey;
- @NonNull private final String mSubkey;
- @NonNull private final @Status int mStatus;
- @Nullable private Intent mFrameworkExtrasIntent;
- @NonNull private final Slice mSlice;
+ @NonNull
+ private final String mKey;
+ @NonNull
+ private final String mSubkey;
+ @NonNull
+ private final @Status int mStatus;
+ @Nullable
+ private Intent mFrameworkExtrasIntent;
+ @NonNull
+ private final Slice mSlice;
/** @hide **/
@IntDef(prefix = {"STATUS_"}, value = {
@@ -51,15 +60,21 @@ public final class AuthenticationEntry implements Parcelable {
STATUS_UNLOCKED_BUT_EMPTY_MOST_RECENT,
})
@Retention(RetentionPolicy.SOURCE)
- public @interface Status {}
+ public @interface Status {
+ }
/** This entry is still locked, as initially supplied by the provider. */
public static final int STATUS_LOCKED = 0;
- /** This entry was unlocked but didn't contain any credential. Meanwhile, "less recent" means
- * there is another such entry that was unlocked more recently. */
+ /**
+ * This entry was unlocked but didn't contain any credential. Meanwhile, "less recent" means
+ * there is another such entry that was unlocked more recently.
+ */
public static final int STATUS_UNLOCKED_BUT_EMPTY_LESS_RECENT = 1;
- /** This is the most recent entry that was unlocked but didn't contain any credential.
- * There should be at most one authentication entry with this status. */
+ /**
+ * This is the most recent entry that was unlocked but didn't contain any credential.
+ *
+ * There will be at most one authentication entry with this status.
+ */
public static final int STATUS_UNLOCKED_BUT_EMPTY_MOST_RECENT = 2;
private AuthenticationEntry(@NonNull Parcel in) {
@@ -74,9 +89,11 @@ public final class AuthenticationEntry implements Parcelable {
AnnotationValidations.validate(NonNull.class, null, mSlice);
}
- /** Constructor to be used for an entry that does not require further activities
+ /**
+ * Constructor to be used for an entry that does not require further activities
* to be invoked when selected.
*/
+ // TODO(b/322065508): remove this constructor.
public AuthenticationEntry(@NonNull String key, @NonNull String subkey, @NonNull Slice slice,
@Status int status) {
mKey = key;
@@ -95,9 +112,9 @@ public final class AuthenticationEntry implements Parcelable {
}
/**
- * Returns the identifier of this entry that's unique within the context of the CredentialManager
- * request.
- */
+ * Returns the identifier of this entry that's unique within the context of the
+ * CredentialManager request.
+ */
@NonNull
public String getKey() {
return mKey;
@@ -111,23 +128,23 @@ public final class AuthenticationEntry implements Parcelable {
return mSubkey;
}
- /**
- * Returns the Slice to be rendered.
- */
+ /** Returns the Slice to be rendered. */
@NonNull
public Slice getSlice() {
return mSlice;
}
- /**
- * Returns the entry status.
- */
+ /** Returns the entry status, depending on which the entry will be rendered differently. */
@NonNull
@Status
public int getStatus() {
return mStatus;
}
+ /**
+ * Returns the framework intent to be filled in when launching this entry's provider
+ * PendingIntent.
+ */
@Nullable
@SuppressLint("IntentBuilderName") // Not building a new intent.
public Intent getFrameworkExtrasIntent() {
diff --git a/core/java/android/credentials/ui/BaseDialogResult.java b/core/java/android/credentials/ui/BaseDialogResult.java
index e8cf5abd5239..e985a4666d31 100644
--- a/core/java/android/credentials/ui/BaseDialogResult.java
+++ b/core/java/android/credentials/ui/BaseDialogResult.java
@@ -24,8 +24,6 @@ import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
-import com.android.internal.util.AnnotationValidations;
-
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -46,7 +44,7 @@ public class BaseDialogResult implements Parcelable {
/**
* Used for the UX to construct the {@code resultData Bundle} to send via the {@code
- * ResultReceiver}.
+ * ResultReceiver}.
*/
public static void addToBundle(@NonNull BaseDialogResult result, @NonNull Bundle bundle) {
bundle.putParcelable(EXTRA_BASE_RESULT, result);
@@ -66,13 +64,14 @@ public class BaseDialogResult implements Parcelable {
RESULT_CODE_DATA_PARSING_FAILURE,
})
@Retention(RetentionPolicy.SOURCE)
- public @interface ResultCode {}
+ public @interface ResultCode {
+ }
/** User intentionally canceled the dialog. */
public static final int RESULT_CODE_DIALOG_USER_CANCELED = 0;
/**
- * The user has consented to switching to a new default provider. The provider info is in the
- * {@code resultData}.
+ * The UI was stopped since the user has chosen to navigate to the Settings UI to reconfigure
+ * their providers.
*/
public static final int RESULT_CODE_CANCELED_AND_LAUNCHED_SETTINGS = 1;
/**
@@ -86,6 +85,7 @@ public class BaseDialogResult implements Parcelable {
public static final int RESULT_CODE_DATA_PARSING_FAILURE = 3;
@Nullable
+ @Deprecated
private final IBinder mRequestToken;
public BaseDialogResult(@Nullable IBinder requestToken) {
@@ -94,6 +94,7 @@ public class BaseDialogResult implements Parcelable {
/** Returns the unique identifier for the request that launched the operation. */
@Nullable
+ @Deprecated
public IBinder getRequestToken() {
return mRequestToken;
}
@@ -115,14 +116,14 @@ public class BaseDialogResult implements Parcelable {
public static final @NonNull Creator<BaseDialogResult> CREATOR =
new Creator<BaseDialogResult>() {
- @Override
- public BaseDialogResult createFromParcel(@NonNull Parcel in) {
- return new BaseDialogResult(in);
- }
-
- @Override
- public BaseDialogResult[] newArray(int size) {
- return new BaseDialogResult[size];
- }
- };
+ @Override
+ public BaseDialogResult createFromParcel(@NonNull Parcel in) {
+ return new BaseDialogResult(in);
+ }
+
+ @Override
+ public BaseDialogResult[] newArray(int size) {
+ return new BaseDialogResult[size];
+ }
+ };
}
diff --git a/core/java/android/credentials/ui/CancelUiRequest.java b/core/java/android/credentials/ui/CancelUiRequest.java
index d4c249e58c8a..712424ced41b 100644
--- a/core/java/android/credentials/ui/CancelUiRequest.java
+++ b/core/java/android/credentials/ui/CancelUiRequest.java
@@ -24,7 +24,7 @@ import android.os.Parcelable;
import com.android.internal.util.AnnotationValidations;
/**
- * A request to cancel any ongoing UI matching this request.
+ * A request to cancel the ongoing UI matching the identifier token in this request.
*
* @hide
*/
@@ -33,9 +33,12 @@ public final class CancelUiRequest implements Parcelable {
/**
* The intent extra key for the {@code CancelUiRequest} object when launching the UX
* activities.
+ *
+ * @hide
*/
- @NonNull public static final String EXTRA_CANCEL_UI_REQUEST =
- "android.credentials.ui.extra.EXTRA_CANCEL_UI_REQUEST";
+ @NonNull
+ public static final String EXTRA_CANCEL_UI_REQUEST =
+ "android.credentials.ui.extra.CANCEL_UI_REQUEST";
@NonNull
private final IBinder mToken;
@@ -51,6 +54,10 @@ public final class CancelUiRequest implements Parcelable {
return mToken;
}
+ /**
+ * Returns the app package name invoking this request, that can be used to derive display
+ * metadata (e.g. "Cancelled by `App Name`").
+ */
@NonNull
public String getAppPackageName() {
return mAppPackageName;
@@ -64,6 +71,7 @@ public final class CancelUiRequest implements Parcelable {
return mShouldShowCancellationUi;
}
+ /** Constructs a {@link CancelUiRequest}. */
public CancelUiRequest(@NonNull IBinder token, boolean shouldShowCancellationUi,
@NonNull String appPackageName) {
mToken = token;
@@ -91,7 +99,8 @@ public final class CancelUiRequest implements Parcelable {
return 0;
}
- @NonNull public static final Creator<CancelUiRequest> CREATOR = new Creator<>() {
+ @NonNull
+ public static final Creator<CancelUiRequest> CREATOR = new Creator<>() {
@Override
public CancelUiRequest createFromParcel(@NonNull Parcel in) {
return new CancelUiRequest(in);
diff --git a/core/java/android/credentials/ui/Constants.java b/core/java/android/credentials/ui/Constants.java
index 37f850bc46c5..68f28e74dad0 100644
--- a/core/java/android/credentials/ui/Constants.java
+++ b/core/java/android/credentials/ui/Constants.java
@@ -36,7 +36,5 @@ public class Constants {
public static final String EXTRA_REQ_FOR_ALL_OPTIONS =
"android.credentials.ui.extra.REQ_FOR_ALL_OPTIONS";
- /** The intent action for when the enabled Credential Manager providers has been updated. */
- public static final String CREDMAN_ENABLED_PROVIDERS_UPDATED =
- "android.credentials.ui.action.CREDMAN_ENABLED_PROVIDERS_UPDATED";
+ private Constants() {}
}
diff --git a/core/java/android/credentials/ui/CreateCredentialProviderData.java b/core/java/android/credentials/ui/CreateCredentialProviderData.java
index 2508d8eb20ab..d7a4f5bdbfca 100644
--- a/core/java/android/credentials/ui/CreateCredentialProviderData.java
+++ b/core/java/android/credentials/ui/CreateCredentialProviderData.java
@@ -47,6 +47,17 @@ public final class CreateCredentialProviderData extends ProviderData implements
mRemoteEntry = remoteEntry;
}
+ /**
+ * Converts the instance to a {@link CreateCredentialProviderInfo}.
+ *
+ * @hide
+ */
+ @NonNull
+ public CreateCredentialProviderInfo toCreateCredentialProviderInfo() {
+ return new CreateCredentialProviderInfo(
+ getProviderFlattenedComponentName(), mSaveEntries, mRemoteEntry);
+ }
+
@NonNull
public List<Entry> getSaveEntries() {
return mSaveEntries;
diff --git a/core/java/android/credentials/ui/CreateCredentialProviderInfo.java b/core/java/android/credentials/ui/CreateCredentialProviderInfo.java
new file mode 100644
index 000000000000..41ca852c2351
--- /dev/null
+++ b/core/java/android/credentials/ui/CreateCredentialProviderInfo.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials.ui;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Information pertaining to a specific provider during the given create-credential flow.
+ *
+ * This includes provider metadata and its credential creation options for display purposes.
+ *
+ * @hide
+ */
+public final class CreateCredentialProviderInfo {
+
+ @NonNull
+ private final String mProviderName;
+
+ @NonNull
+ private final List<Entry> mSaveEntries;
+ @Nullable
+ private final Entry mRemoteEntry;
+
+ CreateCredentialProviderInfo(
+ @NonNull String providerName, @NonNull List<Entry> saveEntries,
+ @Nullable Entry remoteEntry) {
+ mProviderName = Preconditions.checkStringNotEmpty(providerName);
+ mSaveEntries = new ArrayList<>(saveEntries);
+ mRemoteEntry = remoteEntry;
+ }
+
+ /** Returns the fully-qualified provider (component or package) name. */
+ @NonNull
+ public String getProviderName() {
+ return mProviderName;
+ }
+
+ /** Returns all the options this provider has, to which the credential can be saved. */
+ @NonNull
+ public List<Entry> getSaveEntries() {
+ return mSaveEntries;
+ }
+
+ /**
+ * Returns the remote credential saving option, if any.
+ *
+ * Notice that only one system configured provider can set this option, and when set, it means
+ * that the system service has already validated the provider's eligibility.
+ */
+ @Nullable
+ public Entry getRemoteEntry() {
+ return mRemoteEntry;
+ }
+
+ /**
+ * Builder for {@link CreateCredentialProviderInfo}.
+ *
+ * @hide
+ */
+ public static final class Builder {
+ @NonNull
+ private String mProviderName;
+ @NonNull
+ private List<Entry> mSaveEntries = new ArrayList<>();
+ @Nullable
+ private Entry mRemoteEntry = null;
+
+ /** Constructor with required properties. */
+ public Builder(@NonNull String providerName) {
+ mProviderName = Preconditions.checkStringNotEmpty(providerName);
+ }
+
+ /** Sets the list of options for credential saving to be displayed to the user. */
+ @NonNull
+ public Builder setSaveEntries(@NonNull List<Entry> credentialEntries) {
+ mSaveEntries = credentialEntries;
+ return this;
+ }
+
+ /** Sets the remote entry of the provider. */
+ @NonNull
+ public Builder setRemoteEntry(@Nullable Entry remoteEntry) {
+ mRemoteEntry = remoteEntry;
+ return this;
+ }
+
+ /** Builds a {@link CreateCredentialProviderInfo}. */
+ @NonNull
+ public CreateCredentialProviderInfo build() {
+ return new CreateCredentialProviderInfo(mProviderName, mSaveEntries, mRemoteEntry);
+ }
+ }
+}
diff --git a/core/java/android/credentials/ui/DisabledProviderData.java b/core/java/android/credentials/ui/DisabledProviderData.java
index c266fd56acef..8bccdc9a199f 100644
--- a/core/java/android/credentials/ui/DisabledProviderData.java
+++ b/core/java/android/credentials/ui/DisabledProviderData.java
@@ -34,6 +34,16 @@ public final class DisabledProviderData extends ProviderData implements Parcelab
super(providerFlattenedComponentName);
}
+ /**
+ * Converts the instance to a {@link DisabledProviderInfo}.
+ *
+ * @hide
+ */
+ @NonNull
+ public DisabledProviderInfo toDisabledProviderInfo() {
+ return new DisabledProviderInfo(getProviderFlattenedComponentName());
+ }
+
private DisabledProviderData(@NonNull Parcel in) {
super(in);
}
diff --git a/core/java/android/credentials/ui/DisabledProviderInfo.java b/core/java/android/credentials/ui/DisabledProviderInfo.java
new file mode 100644
index 000000000000..7ce63681cf1c
--- /dev/null
+++ b/core/java/android/credentials/ui/DisabledProviderInfo.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials.ui;
+
+import android.annotation.NonNull;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Information pertaining to a specific provider that is disabled from the user settings.
+ *
+ * Currently, disabled provider data is only propagated in the create-credential flow.
+ *
+ * @hide
+ */
+public final class DisabledProviderInfo {
+
+ @NonNull
+ private final String mProviderName;
+
+ /**
+ * Constructs a {@link DisabledProviderInfo}.
+ *
+ * @throws IllegalArgumentException if {@code providerName} is empty
+ */
+ public DisabledProviderInfo(
+ @NonNull String providerName) {
+ mProviderName = Preconditions.checkStringNotEmpty(providerName);
+ }
+
+ /** Returns the fully-qualified provider (component or package) name. */
+ @NonNull
+ public String getProviderName() {
+ return mProviderName;
+ }
+}
diff --git a/core/java/android/credentials/ui/Entry.java b/core/java/android/credentials/ui/Entry.java
index 55f2a3eb490b..84694471ce70 100644
--- a/core/java/android/credentials/ui/Entry.java
+++ b/core/java/android/credentials/ui/Entry.java
@@ -35,10 +35,14 @@ import com.android.internal.util.AnnotationValidations;
*/
@TestApi
public final class Entry implements Parcelable {
- @NonNull private final String mKey;
- @NonNull private final String mSubkey;
- @Nullable private PendingIntent mPendingIntent;
- @Nullable private Intent mFrameworkExtrasIntent;
+ @NonNull
+ private final String mKey;
+ @NonNull
+ private final String mSubkey;
+ @Nullable
+ private PendingIntent mPendingIntent;
+ @Nullable
+ private Intent mFrameworkExtrasIntent;
@NonNull
private final Slice mSlice;
@@ -58,16 +62,19 @@ public final class Entry implements Parcelable {
mFrameworkExtrasIntent = in.readTypedObject(Intent.CREATOR);
}
- /** Constructor to be used for an entry that does not require further activities
+ /**
+ * Constructor to be used for an entry that does not require further activities
* to be invoked when selected.
*/
+ // TODO(b/322065508): deprecate this constructor.
public Entry(@NonNull String key, @NonNull String subkey, @NonNull Slice slice) {
mKey = key;
mSubkey = subkey;
mSlice = slice;
}
- /** Constructor to be used for an entry that requires a pending intent to be invoked
+ /**
+ * Constructor to be used for an entry that requires a pending intent to be invoked
* when clicked.
*/
public Entry(@NonNull String key, @NonNull String subkey, @NonNull Slice slice,
@@ -77,9 +84,12 @@ public final class Entry implements Parcelable {
}
/**
- * Returns the identifier of this entry that's unique within the context of the CredentialManager
- * request.
- */
+ * Returns the identifier of this entry that's unique within the context of the
+ * CredentialManager
+ * request.
+ *
+ * Generally used when sending the user selection result back to the system service.
+ */
@NonNull
public String getKey() {
return mKey;
@@ -87,25 +97,33 @@ public final class Entry implements Parcelable {
/**
* Returns the sub-identifier of this entry that's unique within the context of the {@code key}.
+ *
+ * Generally used when sending the user selection result back to the system service.
*/
@NonNull
public String getSubkey() {
return mSubkey;
}
- /**
- * Returns the Slice to be rendered.
- */
+ /** Returns the Slice to be rendered. */
@NonNull
public Slice getSlice() {
return mSlice;
}
+ /**
+ * Returns the provider PendingIntent to launch once this entry is selected.
+ */
+ // TODO(b/322065508): deprecate this bit.
@Nullable
public PendingIntent getPendingIntent() {
return mPendingIntent;
}
+ /**
+ * Returns the framework fill in intent to add to the provider PendingIntent to launch, once
+ * this entry is selected.
+ */
@Nullable
@SuppressLint("IntentBuilderName") // Not building a new intent.
public Intent getFrameworkExtrasIntent() {
@@ -126,7 +144,7 @@ public final class Entry implements Parcelable {
return 0;
}
- public static final @NonNull Creator<Entry> CREATOR = new Creator<Entry>() {
+ public static final @NonNull Creator<Entry> CREATOR = new Creator<>() {
@Override
public Entry createFromParcel(@NonNull Parcel in) {
return new Entry(in);
diff --git a/core/java/android/credentials/ui/FailureDialogResult.java b/core/java/android/credentials/ui/FailureDialogResult.java
new file mode 100644
index 000000000000..abd5a92415d8
--- /dev/null
+++ b/core/java/android/credentials/ui/FailureDialogResult.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials.ui;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Result data when the selector UI has encountered a failure.
+ *
+ * @hide
+ */
+public final class FailureDialogResult extends BaseDialogResult implements Parcelable {
+ /** Parses and returns a UserSelectionDialogResult from the given resultData. */
+ @Nullable
+ public static FailureDialogResult fromResultData(@NonNull Bundle resultData) {
+ return resultData.getParcelable(
+ EXTRA_FAILURE_RESULT, FailureDialogResult.class);
+ }
+
+ /**
+ * Used for the UX to construct the {@code resultData Bundle} to send via the {@code
+ * ResultReceiver}.
+ */
+ public static void addToBundle(
+ @NonNull FailureDialogResult result, @NonNull Bundle bundle) {
+ bundle.putParcelable(EXTRA_FAILURE_RESULT, result);
+ }
+
+ /**
+ * The intent extra key for the {@code UserSelectionDialogResult} object when the credential
+ * selector activity finishes.
+ */
+ private static final String EXTRA_FAILURE_RESULT =
+ "android.credentials.ui.extra.FAILURE_RESULT";
+
+ @Nullable
+ private final String mErrorMessage;
+
+ public FailureDialogResult(@Nullable IBinder requestToken, @Nullable String errorMessage) {
+ super(requestToken);
+ mErrorMessage = errorMessage;
+ }
+
+ /** Returns provider package name whose entry was selected by the user. */
+ @Nullable
+ public String getErrorMessage() {
+ return mErrorMessage;
+ }
+
+ protected FailureDialogResult(@NonNull Parcel in) {
+ super(in);
+ mErrorMessage = in.readString8();
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeString8(mErrorMessage);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final @NonNull Creator<FailureDialogResult> CREATOR =
+ new Creator<>() {
+ @Override
+ public FailureDialogResult createFromParcel(@NonNull Parcel in) {
+ return new FailureDialogResult(in);
+ }
+
+ @Override
+ public FailureDialogResult[] newArray(int size) {
+ return new FailureDialogResult[size];
+ }
+ };
+}
diff --git a/core/java/android/credentials/ui/FailureResult.java b/core/java/android/credentials/ui/FailureResult.java
new file mode 100644
index 000000000000..ec584170fba2
--- /dev/null
+++ b/core/java/android/credentials/ui/FailureResult.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials.ui;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Failure or cancellation result encountered during a UI flow.
+ *
+ * @hide
+ */
+public final class FailureResult implements UiResult {
+ @Nullable
+ private final String mErrorMessage;
+ @NonNull
+ private final int mErrorCode;
+
+ /** @hide **/
+ @IntDef(prefix = {"ERROR_CODE_"}, value = {
+ ERROR_CODE_DIALOG_CANCELED_BY_USER,
+ ERROR_CODE_CANCELED_AND_LAUNCHED_SETTINGS,
+ ERROR_CODE_UI_FAILURE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ErrorCode {
+ }
+
+ /**
+ * The UI was stopped due to a failure, e.g. because it failed to parse the incoming data,
+ * or it encountered an irrecoverable internal issue.
+ */
+ public static final int ERROR_CODE_UI_FAILURE = 0;
+ /** The user intentionally canceled the dialog. */
+ public static final int ERROR_CODE_DIALOG_CANCELED_BY_USER = 1;
+ /**
+ * The UI was stopped since the user has chosen to navigate to the Settings UI to reconfigure
+ * their providers.
+ */
+ public static final int ERROR_CODE_CANCELED_AND_LAUNCHED_SETTINGS = 2;
+
+ /**
+ * Constructs a {@link FailureResult}.
+ *
+ * @throws IllegalArgumentException if {@code providerId} is empty
+ */
+ public FailureResult(@ErrorCode int errorCode, @Nullable String errorMessage) {
+ mErrorCode = errorCode;
+ mErrorMessage = errorMessage;
+ }
+
+ /** Returns the error code. */
+ @ErrorCode
+ public int getErrorCode() {
+ return mErrorCode;
+ }
+
+ /** Returns the error message. */
+ @Nullable
+ public String getErrorMessage() {
+ return mErrorMessage;
+ }
+
+ FailureDialogResult toFailureDialogResult() {
+ return new FailureDialogResult(/*requestToken=*/null, mErrorMessage);
+ }
+
+ int errorCodeToResultCode() {
+ switch (mErrorCode) {
+ case ERROR_CODE_DIALOG_CANCELED_BY_USER:
+ return BaseDialogResult.RESULT_CODE_DIALOG_USER_CANCELED;
+ case ERROR_CODE_CANCELED_AND_LAUNCHED_SETTINGS:
+ return BaseDialogResult.RESULT_CODE_CANCELED_AND_LAUNCHED_SETTINGS;
+ default:
+ return BaseDialogResult.RESULT_CODE_DATA_PARSING_FAILURE;
+ }
+ }
+}
diff --git a/core/java/android/credentials/ui/GetCredentialProviderData.java b/core/java/android/credentials/ui/GetCredentialProviderData.java
index 181475c7ce5a..481419b4f732 100644
--- a/core/java/android/credentials/ui/GetCredentialProviderData.java
+++ b/core/java/android/credentials/ui/GetCredentialProviderData.java
@@ -55,6 +55,17 @@ public final class GetCredentialProviderData extends ProviderData implements Par
mRemoteEntry = remoteEntry;
}
+ /**
+ * Converts the instance to a {@link GetCredentialProviderInfo}.
+ *
+ * @hide
+ */
+ @NonNull
+ public GetCredentialProviderInfo toGetCredentialProviderInfo() {
+ return new GetCredentialProviderInfo(getProviderFlattenedComponentName(),
+ mCredentialEntries, mActionChips, mAuthenticationEntries, mRemoteEntry);
+ }
+
@NonNull
public List<Entry> getCredentialEntries() {
return mCredentialEntries;
@@ -83,12 +94,12 @@ public final class GetCredentialProviderData extends ProviderData implements Par
mCredentialEntries = credentialEntries;
AnnotationValidations.validate(NonNull.class, null, mCredentialEntries);
- List<Entry> actionChips = new ArrayList<>();
+ List<Entry> actionChips = new ArrayList<>();
in.readTypedList(actionChips, Entry.CREATOR);
mActionChips = actionChips;
AnnotationValidations.validate(NonNull.class, null, mActionChips);
- List<AuthenticationEntry> authenticationEntries = new ArrayList<>();
+ List<AuthenticationEntry> authenticationEntries = new ArrayList<>();
in.readTypedList(authenticationEntries, AuthenticationEntry.CREATOR);
mAuthenticationEntries = authenticationEntries;
AnnotationValidations.validate(NonNull.class, null, mAuthenticationEntries);
@@ -113,16 +124,16 @@ public final class GetCredentialProviderData extends ProviderData implements Par
public static final @NonNull Creator<GetCredentialProviderData> CREATOR =
new Creator<GetCredentialProviderData>() {
- @Override
- public GetCredentialProviderData createFromParcel(@NonNull Parcel in) {
- return new GetCredentialProviderData(in);
- }
+ @Override
+ public GetCredentialProviderData createFromParcel(@NonNull Parcel in) {
+ return new GetCredentialProviderData(in);
+ }
- @Override
- public GetCredentialProviderData[] newArray(int size) {
- return new GetCredentialProviderData[size];
- }
- };
+ @Override
+ public GetCredentialProviderData[] newArray(int size) {
+ return new GetCredentialProviderData[size];
+ }
+ };
/**
* Builder for {@link GetCredentialProviderData}.
@@ -131,11 +142,16 @@ public final class GetCredentialProviderData extends ProviderData implements Par
*/
@TestApi
public static final class Builder {
- @NonNull private String mProviderFlattenedComponentName;
- @NonNull private List<Entry> mCredentialEntries = new ArrayList<>();
- @NonNull private List<Entry> mActionChips = new ArrayList<>();
- @NonNull private List<AuthenticationEntry> mAuthenticationEntries = new ArrayList<>();
- @Nullable private Entry mRemoteEntry = null;
+ @NonNull
+ private String mProviderFlattenedComponentName;
+ @NonNull
+ private List<Entry> mCredentialEntries = new ArrayList<>();
+ @NonNull
+ private List<Entry> mActionChips = new ArrayList<>();
+ @NonNull
+ private List<AuthenticationEntry> mAuthenticationEntries = new ArrayList<>();
+ @Nullable
+ private Entry mRemoteEntry = null;
/** Constructor with required properties. */
public Builder(@NonNull String providerFlattenedComponentName) {
diff --git a/core/java/android/credentials/ui/GetCredentialProviderInfo.java b/core/java/android/credentials/ui/GetCredentialProviderInfo.java
new file mode 100644
index 000000000000..bac71472acd1
--- /dev/null
+++ b/core/java/android/credentials/ui/GetCredentialProviderInfo.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials.ui;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Information pertaining to a specific provider during the given create-credential flow.
+ *
+ * This includes provider metadata and its credential creation options for display purposes.
+ *
+ * @hide
+ */
+public final class GetCredentialProviderInfo {
+
+ @NonNull
+ private final String mProviderName;
+
+ @NonNull
+ private final List<Entry> mCredentialEntries;
+ @NonNull
+ private final List<Entry> mActionChips;
+ @NonNull
+ private final List<AuthenticationEntry> mAuthenticationEntries;
+ @Nullable
+ private final Entry mRemoteEntry;
+
+ GetCredentialProviderInfo(
+ @NonNull String providerName, @NonNull List<Entry> credentialEntries,
+ @NonNull List<Entry> actionChips,
+ @NonNull List<AuthenticationEntry> authenticationEntries,
+ @Nullable Entry remoteEntry) {
+ mProviderName = Preconditions.checkStringNotEmpty(providerName);
+ mCredentialEntries = new ArrayList<>(credentialEntries);
+ mActionChips = new ArrayList<>(actionChips);
+ mAuthenticationEntries = new ArrayList<>(authenticationEntries);
+ mRemoteEntry = remoteEntry;
+ }
+
+ /** Returns the fully-qualified provider (component or package) name. */
+ @NonNull
+ public String getProviderName() {
+ return mProviderName;
+ }
+
+ /** Returns the display information for all the candidate credentials this provider has. */
+ @NonNull
+ public List<Entry> getCredentialEntries() {
+ return mCredentialEntries;
+ }
+
+ /**
+ * Returns a list of actions defined by the provider that intent into the provider's app for
+ * specific user actions, each of which should eventually lead to an actual credential.
+ */
+ @NonNull
+ public List<Entry> getActionChips() {
+ return mActionChips;
+ }
+
+ /**
+ * Returns a list of authentication actions that each intents into a provider authentication
+ * activity.
+ *
+ * When the authentication activity succeeds, the provider will return a list of actual
+ * credential candidates to render. However, the UI should not attempt to parse the result
+ * itself, but rather send the result back to the system service, which will then process the
+ * new candidates and relaunch the UI with updated display data.
+ */
+ @NonNull
+ public List<AuthenticationEntry> getAuthenticationEntries() {
+ return mAuthenticationEntries;
+ }
+
+ /**
+ * Returns the remote credential retrieval option, if any.
+ *
+ * Notice that only one system configured provider can set this option, and when set, it means
+ * that the system service has already validated the provider's eligibility.
+ */
+ @Nullable
+ public Entry getRemoteEntry() {
+ return mRemoteEntry;
+ }
+
+ /**
+ * Builder for {@link GetCredentialProviderInfo}.
+ *
+ * @hide
+ */
+ public static final class Builder {
+ @NonNull
+ private String mProviderName;
+ @NonNull
+ private List<Entry> mCredentialEntries = new ArrayList<>();
+ @NonNull
+ private List<Entry> mActionChips = new ArrayList<>();
+ @NonNull
+ private List<AuthenticationEntry> mAuthenticationEntries = new ArrayList<>();
+ @Nullable
+ private Entry mRemoteEntry = null;
+
+ /**
+ * Constructs a {@link GetCredentialProviderInfo.Builder}.
+ *
+ * @throws IllegalArgumentException if {@code providerName} is null or empty
+ */
+ public Builder(@NonNull String providerName) {
+ mProviderName = Preconditions.checkStringNotEmpty(providerName);
+ }
+
+ /** Sets the list of credential candidates to be displayed to the user. */
+ @NonNull
+ public Builder setCredentialEntries(@NonNull List<Entry> credentialEntries) {
+ mCredentialEntries = credentialEntries;
+ return this;
+ }
+
+ /** Sets the list of action chips to be displayed to the user. */
+ @NonNull
+ public Builder setActionChips(@NonNull List<Entry> actionChips) {
+ mActionChips = actionChips;
+ return this;
+ }
+
+ /** Sets the authentication entry to be displayed to the user. */
+ @NonNull
+ public Builder setAuthenticationEntries(
+ @NonNull List<AuthenticationEntry> authenticationEntry) {
+ mAuthenticationEntries = authenticationEntry;
+ return this;
+ }
+
+ /** Sets the remote entry to be displayed to the user. */
+ @NonNull
+ public Builder setRemoteEntry(@Nullable Entry remoteEntry) {
+ mRemoteEntry = remoteEntry;
+ return this;
+ }
+
+ /** Builds a {@link GetCredentialProviderInfo}. */
+ @NonNull
+ public GetCredentialProviderInfo build() {
+ return new GetCredentialProviderInfo(mProviderName,
+ mCredentialEntries, mActionChips, mAuthenticationEntries, mRemoteEntry);
+ }
+ }
+}
diff --git a/core/java/android/credentials/ui/IntentFactory.java b/core/java/android/credentials/ui/IntentFactory.java
index 49321d514128..5e1e0efe39c4 100644
--- a/core/java/android/credentials/ui/IntentFactory.java
+++ b/core/java/android/credentials/ui/IntentFactory.java
@@ -113,25 +113,6 @@ public class IntentFactory {
}
/**
- * Notify the UI that providers have been enabled/disabled.
- *
- * @hide
- */
- @NonNull
- public static Intent createProviderUpdateIntent() {
- Intent intent = new Intent();
- ComponentName componentName =
- ComponentName.unflattenFromString(
- Resources.getSystem()
- .getString(
- com.android.internal.R.string
- .config_credentialManagerReceiverComponent));
- intent.setComponent(componentName);
- intent.setAction(Constants.CREDMAN_ENABLED_PROVIDERS_UPDATED);
- return intent;
- }
-
- /**
* Convert an instance of a "locally-defined" ResultReceiver to an instance of {@link
* android.os.ResultReceiver} itself, which the receiving process will be able to unmarshall.
*/
diff --git a/core/java/android/credentials/ui/IntentHelper.java b/core/java/android/credentials/ui/IntentHelper.java
new file mode 100644
index 000000000000..c5f34c1440a7
--- /dev/null
+++ b/core/java/android/credentials/ui/IntentHelper.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials.ui;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.content.Intent;
+import android.os.ResultReceiver;
+
+import java.util.List;
+
+/**
+ * Utilities for parsing the intent data used to launch the UI activity.
+ *
+ * @hide
+ */
+public final class IntentHelper {
+ /**
+ * Attempts to extract a {@link CancelUiRequest} from the given intent; returns null
+ * if not found.
+ */
+ @Nullable
+ public static CancelUiRequest extractCancelUiRequest(@NonNull Intent intent) {
+ return intent.getParcelableExtra(CancelUiRequest.EXTRA_CANCEL_UI_REQUEST,
+ CancelUiRequest.class);
+ }
+
+ /**
+ * Attempts to extract a {@link RequestInfo} from the given intent; returns null
+ * if not found.
+ */
+ @Nullable
+ public static RequestInfo extractRequestInfo(@NonNull Intent intent) {
+ return intent.getParcelableExtra(RequestInfo.EXTRA_REQUEST_INFO,
+ RequestInfo.class);
+ }
+
+ /**
+ * Attempts to extract the list of {@link GetCredentialProviderInfo} from the given intent;
+ * returns null if not found.
+ */
+ @Nullable
+ @SuppressLint("NullableCollection") // To be consistent with the nullable Intent extra APIs
+ // and the other APIs in this class.
+ public static List<GetCredentialProviderInfo> extractGetCredentialProviderDataList(
+ @NonNull Intent intent) {
+ List<GetCredentialProviderData> providerList = intent.getParcelableArrayListExtra(
+ ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST,
+ GetCredentialProviderData.class);
+ return providerList == null ? null : providerList.stream().map(
+ GetCredentialProviderData::toGetCredentialProviderInfo).toList();
+ }
+
+ /**
+ * Attempts to extract the list of {@link CreateCredentialProviderInfo} from the given intent;
+ * returns null if not found.
+ */
+ @Nullable
+ @SuppressLint("NullableCollection") // To be consistent with the nullable Intent extra APIs
+ // and the other APIs in this class.
+ public static List<CreateCredentialProviderInfo> extractCreateCredentialProviderDataList(
+ @NonNull Intent intent) {
+ List<CreateCredentialProviderData> providerList = intent.getParcelableArrayListExtra(
+ ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST,
+ CreateCredentialProviderData.class);
+ return providerList == null ? null : providerList.stream().map(
+ CreateCredentialProviderData::toCreateCredentialProviderInfo).toList();
+ }
+
+ /**
+ * Attempts to extract a {@link android.os.ResultReceiver} from the given intent, which should
+ * be used to send back UI results; returns null if not found.
+ */
+ @Nullable
+ public static ResultReceiver extractResultReceiver(@NonNull Intent intent) {
+ return intent.getParcelableExtra(Constants.EXTRA_RESULT_RECEIVER,
+ ResultReceiver.class);
+ }
+
+ private IntentHelper() {
+ }
+}
diff --git a/core/java/android/credentials/ui/ProviderDialogResult.java b/core/java/android/credentials/ui/ProviderDialogResult.java
deleted file mode 100644
index 53f1864f7bc6..000000000000
--- a/core/java/android/credentials/ui/ProviderDialogResult.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.credentials.ui;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import com.android.internal.util.AnnotationValidations;
-
-/**
- * Result data matching {@link BaseDialogResult#RESULT_CODE_PROVIDER_ENABLED}, or {@link
- * BaseDialogResult#RESULT_CODE_DEFAULT_PROVIDER_CHANGED}.
- *
- * @hide
- */
-public final class ProviderDialogResult extends BaseDialogResult implements Parcelable {
- /** Parses and returns a ProviderDialogResult from the given resultData. */
- @Nullable
- public static ProviderDialogResult fromResultData(@NonNull Bundle resultData) {
- return resultData.getParcelable(EXTRA_PROVIDER_RESULT, ProviderDialogResult.class);
- }
-
- /**
- * Used for the UX to construct the {@code resultData Bundle} to send via the {@code
- * ResultReceiver}.
- */
- public static void addToBundle(
- @NonNull ProviderDialogResult result, @NonNull Bundle bundle) {
- bundle.putParcelable(EXTRA_PROVIDER_RESULT, result);
- }
-
- /**
- * The intent extra key for the {@code ProviderDialogResult} object when the credential
- * selector activity finishes.
- */
- private static final String EXTRA_PROVIDER_RESULT =
- "android.credentials.ui.extra.PROVIDER_RESULT";
-
- @NonNull
- private final String mProviderId;
-
- public ProviderDialogResult(@NonNull IBinder requestToken, @NonNull String providerId) {
- super(requestToken);
- mProviderId = providerId;
- }
-
- @NonNull
- public String getProviderId() {
- return mProviderId;
- }
-
- protected ProviderDialogResult(@NonNull Parcel in) {
- super(in);
- String providerId = in.readString8();
- mProviderId = providerId;
- AnnotationValidations.validate(NonNull.class, null, mProviderId);
- }
-
- @Override
- public void writeToParcel(@NonNull Parcel dest, int flags) {
- super.writeToParcel(dest, flags);
- dest.writeString8(mProviderId);
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- public static final @NonNull Creator<ProviderDialogResult> CREATOR =
- new Creator<ProviderDialogResult>() {
- @Override
- public ProviderDialogResult createFromParcel(@NonNull Parcel in) {
- return new ProviderDialogResult(in);
- }
-
- @Override
- public ProviderDialogResult[] newArray(int size) {
- return new ProviderDialogResult[size];
- }
- };
-}
diff --git a/core/java/android/credentials/ui/ProviderPendingIntentResponse.java b/core/java/android/credentials/ui/ProviderPendingIntentResponse.java
index 47936c48f927..11cc21f9d2db 100644
--- a/core/java/android/credentials/ui/ProviderPendingIntentResponse.java
+++ b/core/java/android/credentials/ui/ProviderPendingIntentResponse.java
@@ -16,15 +16,20 @@
package android.credentials.ui;
+import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.content.Intent;
import android.os.Parcel;
import android.os.Parcelable;
-import androidx.annotation.NonNull;
-
/**
- * Response from a provider's pending intent
+ * Result of launching a provider's PendingIntent associated with an {@link Entry} after it is
+ * selected by the user.
+ *
+ * The provider sets the credential creation / retrieval result through
+ * {@link android.app.Activity#setResult(int, Intent)}, which is then directly propagated back
+ * through this data structure.
*
* @hide
*/
@@ -33,20 +38,21 @@ public final class ProviderPendingIntentResponse implements Parcelable {
@Nullable
private final Intent mResultData;
+ /** Constructs a {@link ProviderPendingIntentResponse}. */
public ProviderPendingIntentResponse(int resultCode, @Nullable Intent resultData) {
mResultCode = resultCode;
mResultData = resultData;
}
- protected ProviderPendingIntentResponse(Parcel in) {
+ private ProviderPendingIntentResponse(@NonNull Parcel in) {
mResultCode = in.readInt();
mResultData = in.readTypedObject(Intent.CREATOR);
}
- public static final Creator<ProviderPendingIntentResponse> CREATOR =
- new Creator<ProviderPendingIntentResponse>() {
+ public static final @NonNull Creator<ProviderPendingIntentResponse> CREATOR =
+ new Creator<>() {
@Override
- public ProviderPendingIntentResponse createFromParcel(Parcel in) {
+ public ProviderPendingIntentResponse createFromParcel(@NonNull Parcel in) {
return new ProviderPendingIntentResponse(in);
}
@@ -67,13 +73,15 @@ public final class ProviderPendingIntentResponse implements Parcelable {
dest.writeTypedObject(mResultData, flags);
}
- /** Returns the result code associated with this pending intent activity result. */
+ /** Returns the result code associated with this provider PendingIntent activity result. */
public int getResultCode() {
return mResultCode;
}
- /** Returns the result data associated with this pending intent activity result. */
- @NonNull public Intent getResultData() {
+ /** Returns the result data associated with this provider PendingIntent activity result. */
+ @SuppressLint("IntentBuilderName") // Not building a new intent.
+ @NonNull
+ public Intent getResultData() {
return mResultData;
}
}
diff --git a/core/java/android/credentials/ui/RequestInfo.java b/core/java/android/credentials/ui/RequestInfo.java
index 4fedc8353bf7..f65158444e48 100644
--- a/core/java/android/credentials/ui/RequestInfo.java
+++ b/core/java/android/credentials/ui/RequestInfo.java
@@ -48,17 +48,24 @@ public final class RequestInfo implements Parcelable {
@NonNull public static final String EXTRA_REQUEST_INFO =
"android.credentials.ui.extra.REQUEST_INFO";
- /** Type value for any request that does not require UI. */
+ /**
+ * Type value for any request that does not require UI.
+ */
@NonNull public static final String TYPE_UNDEFINED = "android.credentials.ui.TYPE_UNDEFINED";
- /** Type value for a getCredential request. */
+ /**
+ * Type value for a getCredential request.
+ */
@NonNull public static final String TYPE_GET = "android.credentials.ui.TYPE_GET";
- /** Type value for a getCredential request that utilizes the credential registry.
+ /**
+ * Type value for a getCredential request that utilizes the credential registry.
*
* @hide
- **/
+ */
@NonNull public static final String TYPE_GET_VIA_REGISTRY =
"android.credentials.ui.TYPE_GET_VIA_REGISTRY";
- /** Type value for a createCredential request. */
+ /**
+ * Type value for a createCredential request.
+ */
@NonNull public static final String TYPE_CREATE = "android.credentials.ui.TYPE_CREATE";
/** @hide */
diff --git a/core/java/android/credentials/ui/ResultHelper.java b/core/java/android/credentials/ui/ResultHelper.java
new file mode 100644
index 000000000000..7b9d5e87d666
--- /dev/null
+++ b/core/java/android/credentials/ui/ResultHelper.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials.ui;
+
+import android.annotation.NonNull;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.ResultReceiver;
+
+/**
+ * Utilities for sending the UI results back to the system service.
+ *
+ * @hide
+ */
+public final class ResultHelper {
+ /**
+ * Sends the {@code failureResult} that caused the UI to stop back to the CredentialManager
+ * service.
+ *
+ * The {code resultReceiver} for a UI flow can be extracted from the UI launch intent via
+ * {@link IntentHelper#extractResultReceiver(Intent)}.
+ */
+ public static void sendFailureResult(@NonNull ResultReceiver resultReceiver,
+ @NonNull FailureResult failureResult) {
+ FailureDialogResult result = failureResult.toFailureDialogResult();
+ Bundle resultData = new Bundle();
+ FailureDialogResult.addToBundle(result, resultData);
+ resultReceiver.send(failureResult.errorCodeToResultCode(),
+ resultData);
+ }
+
+ /**
+ * Sends the completed {@code userSelectionResult} back to the CredentialManager service.
+ *
+ * The {code resultReceiver} for a UI flow can be extracted from the UI launch intent via
+ * {@link IntentHelper#extractResultReceiver(Intent)}.
+ */
+ public static void sendUserSelectionResult(@NonNull ResultReceiver resultReceiver,
+ @NonNull UserSelectionResult userSelectionResult) {
+ UserSelectionDialogResult result = userSelectionResult.toUserSelectionDialogResult();
+ Bundle resultData = new Bundle();
+ UserSelectionDialogResult.addToBundle(result, resultData);
+ resultReceiver.send(BaseDialogResult.RESULT_CODE_DIALOG_COMPLETE_WITH_SELECTION,
+ resultData);
+ }
+
+ private ResultHelper() {}
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorTest.kt b/core/java/android/credentials/ui/UiResult.java
index dbff63f355c8..692584d1a561 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorTest.kt
+++ b/core/java/android/credentials/ui/UiResult.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,9 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.volume.panel.domain.interactor
+package android.credentials.ui;
-class ComponentsInteractorTest {
-
- // TODO(b/318080198) Write tests
-}
+/**
+ * Base class for different types of ui results.
+ *
+ * @hide
+ */
+public interface UiResult {}
diff --git a/core/java/android/credentials/ui/UserSelectionResult.java b/core/java/android/credentials/ui/UserSelectionResult.java
new file mode 100644
index 000000000000..431dc631f3f6
--- /dev/null
+++ b/core/java/android/credentials/ui/UserSelectionResult.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials.ui;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Result sent back from the UI after the user chose an option and completed the following
+ * transaction launched through the provider PendingIntent associated with that option.
+ *
+ * @hide
+ */
+public final class UserSelectionResult implements UiResult {
+ @NonNull
+ private final String mProviderId;
+ @NonNull
+ private final String mEntryKey;
+ @NonNull
+ private final String mEntrySubkey;
+ @Nullable
+ private ProviderPendingIntentResponse mProviderPendingIntentResponse;
+
+ /**
+ * Constructs a {@link UserSelectionResult}.
+ *
+ * @throws IllegalArgumentException if {@code providerId} is empty
+ */
+
+ public UserSelectionResult(@NonNull String providerId,
+ @NonNull String entryKey, @NonNull String entrySubkey,
+ @Nullable ProviderPendingIntentResponse providerPendingIntentResponse) {
+ mProviderId = Preconditions.checkStringNotEmpty(providerId);
+ mEntryKey = Preconditions.checkNotNull(entryKey);
+ mEntrySubkey = Preconditions.checkNotNull(entrySubkey);
+ mProviderPendingIntentResponse = providerPendingIntentResponse;
+ }
+
+ /** Returns provider package name whose entry was selected by the user. */
+ @NonNull
+ public String getProviderId() {
+ return mProviderId;
+ }
+
+ /** Returns the key of the visual entry that the user selected. */
+ @NonNull
+ public String getEntryKey() {
+ return mEntryKey;
+ }
+
+ /** Returns the subkey of the visual entry that the user selected. */
+ @NonNull
+ public String getEntrySubkey() {
+ return mEntrySubkey;
+ }
+
+ /** Returns the pending intent response from the provider. */
+ @Nullable
+ public ProviderPendingIntentResponse getPendingIntentProviderResponse() {
+ return mProviderPendingIntentResponse;
+ }
+
+ @NonNull
+ UserSelectionDialogResult toUserSelectionDialogResult() {
+ return new UserSelectionDialogResult(/*requestToken=*/null, mProviderId, mEntryKey,
+ mEntrySubkey, mProviderPendingIntentResponse);
+ }
+}
diff --git a/core/java/android/hardware/HardwareBuffer.java b/core/java/android/hardware/HardwareBuffer.java
index 0047b7d69282..ce0f9f598897 100644
--- a/core/java/android/hardware/HardwareBuffer.java
+++ b/core/java/android/hardware/HardwareBuffer.java
@@ -115,14 +115,16 @@ public final class HardwareBuffer implements Parcelable, AutoCloseable {
@FlaggedApi(com.android.graphics.hwui.flags.Flags.FLAG_REQUESTED_FORMATS_V)
public static final int R_8 = 0x38;
/**
- * Format: 16 bits red. Bits should be represented in unsigned integer, instead of the
- * implicit unsigned normalized.
+ * Format: 16 bits red. When sampled on the GPU this is represented as an
+ * unsigned integer instead of implicit unsigned normalize.
+ * For more information see https://www.khronos.org/opengl/wiki/Normalized_Integer
*/
@FlaggedApi(com.android.graphics.hwui.flags.Flags.FLAG_REQUESTED_FORMATS_V)
public static final int R_16 = 0x39;
/**
- * Format: 16 bits each red, green. Bits should be represented in unsigned integer,
- * instead of the implicit unsigned normalized.
+ * Format: 16 bits each red, green. When sampled on the GPU this is represented
+ * as an unsigned integer instead of implicit unsigned normalize.
+ * For more information see https://www.khronos.org/opengl/wiki/Normalized_Integer
*/
@FlaggedApi(com.android.graphics.hwui.flags.Flags.FLAG_REQUESTED_FORMATS_V)
public static final int RG_1616 = 0x3a;
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index c0424dbeb813..bdaf9d789960 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -16,7 +16,7 @@
package android.hardware.biometrics;
-import static android.Manifest.permission.MANAGE_BIOMETRIC_DIALOG;
+import static android.Manifest.permission.SET_BIOMETRIC_DIALOG_LOGO;
import static android.Manifest.permission.TEST_BIOMETRIC;
import static android.Manifest.permission.USE_BIOMETRIC;
import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
@@ -174,9 +174,9 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
* @return This builder.
*/
@FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
- @RequiresPermission(MANAGE_BIOMETRIC_DIALOG)
+ @RequiresPermission(SET_BIOMETRIC_DIALOG_LOGO)
@NonNull
- public BiometricPrompt.Builder setLogo(@DrawableRes int logoRes) {
+ public BiometricPrompt.Builder setLogoRes(@DrawableRes int logoRes) {
mPromptInfo.setLogoRes(logoRes);
return this;
}
@@ -193,9 +193,9 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
* @return This builder.
*/
@FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
- @RequiresPermission(MANAGE_BIOMETRIC_DIALOG)
+ @RequiresPermission(SET_BIOMETRIC_DIALOG_LOGO)
@NonNull
- public BiometricPrompt.Builder setLogo(@NonNull Bitmap logoBitmap) {
+ public BiometricPrompt.Builder setLogoBitmap(@NonNull Bitmap logoBitmap) {
mPromptInfo.setLogoBitmap(logoBitmap);
return this;
}
@@ -719,25 +719,25 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
/**
* Gets the drawable resource of the logo for the prompt, as set by
- * {@link Builder#setLogo(int)}. Currently for system applications use only.
+ * {@link Builder#setLogoRes(int)}. Currently for system applications use only.
*
* @return The drawable resource of the logo, or -1 if the prompt has no logo resource set.
*/
@FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
- @RequiresPermission(MANAGE_BIOMETRIC_DIALOG)
+ @RequiresPermission(SET_BIOMETRIC_DIALOG_LOGO)
@DrawableRes
public int getLogoRes() {
return mPromptInfo.getLogoRes();
}
/**
- * Gets the logo bitmap for the prompt, as set by {@link Builder#setLogo(Bitmap)}. Currently for
- * system applications use only.
+ * Gets the logo bitmap for the prompt, as set by {@link Builder#setLogoBitmap(Bitmap)}.
+ * Currently for system applications use only.
*
* @return The logo bitmap of the prompt, or null if the prompt has no logo bitmap set.
*/
@FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
- @RequiresPermission(MANAGE_BIOMETRIC_DIALOG)
+ @RequiresPermission(SET_BIOMETRIC_DIALOG_LOGO)
@Nullable
public Bitmap getLogoBitmap() {
return mPromptInfo.getLogoBitmap();
diff --git a/core/java/android/hardware/biometrics/PromptContentItemBulletedText.java b/core/java/android/hardware/biometrics/PromptContentItemBulletedText.java
index c5e5a8076747..25e5cca485d2 100644
--- a/core/java/android/hardware/biometrics/PromptContentItemBulletedText.java
+++ b/core/java/android/hardware/biometrics/PromptContentItemBulletedText.java
@@ -28,14 +28,14 @@ import android.os.Parcelable;
*/
@FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
public final class PromptContentItemBulletedText implements PromptContentItemParcelable {
- private final CharSequence mText;
+ private final String mText;
/**
* A list item with bulleted text shown on {@link PromptVerticalListContentView}.
*
* @param text The text of this list item.
*/
- public PromptContentItemBulletedText(@NonNull CharSequence text) {
+ public PromptContentItemBulletedText(@NonNull String text) {
mText = text;
}
@@ -43,7 +43,7 @@ public final class PromptContentItemBulletedText implements PromptContentItemPar
* @hide
*/
@NonNull
- public CharSequence getText() {
+ public String getText() {
return mText;
}
@@ -60,7 +60,7 @@ public final class PromptContentItemBulletedText implements PromptContentItemPar
*/
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeCharSequence(mText);
+ dest.writeString(mText);
}
/**
@@ -70,7 +70,7 @@ public final class PromptContentItemBulletedText implements PromptContentItemPar
public static final Creator<PromptContentItemBulletedText> CREATOR = new Creator<>() {
@Override
public PromptContentItemBulletedText createFromParcel(Parcel in) {
- return new PromptContentItemBulletedText(in.readCharSequence());
+ return new PromptContentItemBulletedText(in.readString());
}
@Override
diff --git a/core/java/android/hardware/biometrics/PromptContentItemPlainText.java b/core/java/android/hardware/biometrics/PromptContentItemPlainText.java
index 6434c5975c12..7919256f9c6d 100644
--- a/core/java/android/hardware/biometrics/PromptContentItemPlainText.java
+++ b/core/java/android/hardware/biometrics/PromptContentItemPlainText.java
@@ -28,14 +28,14 @@ import android.os.Parcelable;
*/
@FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
public final class PromptContentItemPlainText implements PromptContentItemParcelable {
- private final CharSequence mText;
+ private final String mText;
/**
* A list item with plain text shown on {@link PromptVerticalListContentView}.
*
* @param text The text of this list item.
*/
- public PromptContentItemPlainText(@NonNull CharSequence text) {
+ public PromptContentItemPlainText(@NonNull String text) {
mText = text;
}
@@ -43,7 +43,7 @@ public final class PromptContentItemPlainText implements PromptContentItemParcel
* @hide
*/
@NonNull
- public CharSequence getText() {
+ public String getText() {
return mText;
}
@@ -60,7 +60,7 @@ public final class PromptContentItemPlainText implements PromptContentItemParcel
*/
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeCharSequence(mText);
+ dest.writeString(mText);
}
/**
@@ -70,7 +70,7 @@ public final class PromptContentItemPlainText implements PromptContentItemParcel
public static final Creator<PromptContentItemPlainText> CREATOR = new Creator<>() {
@Override
public PromptContentItemPlainText createFromParcel(Parcel in) {
- return new PromptContentItemPlainText(in.readCharSequence());
+ return new PromptContentItemPlainText(in.readString());
}
@Override
diff --git a/core/java/android/hardware/biometrics/PromptInfo.java b/core/java/android/hardware/biometrics/PromptInfo.java
index d788b37c781d..0f9cadc52608 100644
--- a/core/java/android/hardware/biometrics/PromptInfo.java
+++ b/core/java/android/hardware/biometrics/PromptInfo.java
@@ -166,9 +166,9 @@ public class PromptInfo implements Parcelable {
}
/**
- * Returns whether MANAGE_BIOMETRIC_DIALOG is contained.
+ * Returns whether SET_BIOMETRIC_DIALOG_LOGO is contained.
*/
- public boolean containsManageBioApiConfigurations() {
+ public boolean containsSetLogoApiConfigurations() {
if (mLogoRes != -1) {
return true;
} else if (mLogoBitmap != null) {
diff --git a/core/java/android/hardware/biometrics/PromptVerticalListContentView.java b/core/java/android/hardware/biometrics/PromptVerticalListContentView.java
index f3e62907d845..38d32dc73ccb 100644
--- a/core/java/android/hardware/biometrics/PromptVerticalListContentView.java
+++ b/core/java/android/hardware/biometrics/PromptVerticalListContentView.java
@@ -52,11 +52,11 @@ public final class PromptVerticalListContentView implements PromptContentViewPar
private static final int MAX_ITEM_NUMBER = 20;
private static final int MAX_EACH_ITEM_CHARACTER_NUMBER = 640;
private final List<PromptContentItemParcelable> mContentList;
- private final CharSequence mDescription;
+ private final String mDescription;
private PromptVerticalListContentView(
@NonNull List<PromptContentItemParcelable> contentList,
- @NonNull CharSequence description) {
+ @NonNull String description) {
mContentList = contentList;
mDescription = description;
}
@@ -65,7 +65,7 @@ public final class PromptVerticalListContentView implements PromptContentViewPar
mContentList = in.readArrayList(
PromptContentItemParcelable.class.getClassLoader(),
PromptContentItemParcelable.class);
- mDescription = in.readCharSequence();
+ mDescription = in.readString();
}
/**
@@ -84,12 +84,12 @@ public final class PromptVerticalListContentView implements PromptContentViewPar
/**
* Gets the description for the content view, as set by
- * {@link PromptVerticalListContentView.Builder#setDescription(CharSequence)}.
+ * {@link PromptVerticalListContentView.Builder#setDescription(String)}.
*
* @return The description for the content view, or null if the content view has no description.
*/
@Nullable
- public CharSequence getDescription() {
+ public String getDescription() {
return mDescription;
}
@@ -118,7 +118,7 @@ public final class PromptVerticalListContentView implements PromptContentViewPar
@Override
public void writeToParcel(@androidx.annotation.NonNull Parcel dest, int flags) {
dest.writeList(mContentList);
- dest.writeCharSequence(mDescription);
+ dest.writeString(mDescription);
}
/**
@@ -143,7 +143,7 @@ public final class PromptVerticalListContentView implements PromptContentViewPar
*/
public static final class Builder {
private final List<PromptContentItemParcelable> mContentList = new ArrayList<>();
- private CharSequence mDescription;
+ private String mDescription;
/**
* Optional: Sets a description that will be shown on the content view.
@@ -152,7 +152,7 @@ public final class PromptVerticalListContentView implements PromptContentViewPar
* @return This builder.
*/
@NonNull
- public Builder setDescription(@NonNull CharSequence description) {
+ public Builder setDescription(@NonNull String description) {
mDescription = description;
return this;
}
diff --git a/core/java/android/hardware/input/InputSettings.java b/core/java/android/hardware/input/InputSettings.java
index d93953231eaf..fdbd3197fb79 100644
--- a/core/java/android/hardware/input/InputSettings.java
+++ b/core/java/android/hardware/input/InputSettings.java
@@ -17,6 +17,7 @@
package android.hardware.input;
import static com.android.hardware.input.Flags.keyboardA11yBounceKeysFlag;
+import static com.android.hardware.input.Flags.keyboardA11ySlowKeysFlag;
import static com.android.hardware.input.Flags.keyboardA11yStickyKeysFlag;
import static com.android.input.flags.Flags.enableInputFilterRustImpl;
@@ -68,6 +69,12 @@ public class InputSettings {
*/
public static final int MAX_ACCESSIBILITY_BOUNCE_KEYS_THRESHOLD_MILLIS = 5000;
+ /**
+ * The maximum allowed Accessibility slow keys threshold.
+ * @hide
+ */
+ public static final int MAX_ACCESSIBILITY_SLOW_KEYS_THRESHOLD_MILLIS = 5000;
+
private InputSettings() {
}
@@ -419,6 +426,86 @@ public class InputSettings {
}
/**
+ * Whether Accessibility slow keys feature flags is enabled.
+ *
+ * <p>
+ * 'Slow keys' is an accessibility feature to aid users who have physical disabilities, that
+ * allows the user to specify the duration for which one must press-and-hold a key before the
+ * system accepts the keypress.
+ * </p>
+ *
+ * @hide
+ */
+ public static boolean isAccessibilitySlowKeysFeatureFlagEnabled() {
+ return keyboardA11ySlowKeysFlag() && enableInputFilterRustImpl();
+ }
+
+ /**
+ * Whether Accessibility slow keys is enabled.
+ *
+ * <p>
+ * 'Slow keys' is an accessibility feature to aid users who have physical disabilities, that
+ * allows the user to specify the duration for which one must press-and-hold a key before the
+ * system accepts the keypress.
+ * </p>
+ *
+ * @hide
+ */
+ public static boolean isAccessibilitySlowKeysEnabled(@NonNull Context context) {
+ return getAccessibilitySlowKeysThreshold(context) != 0;
+ }
+
+ /**
+ * Get Accessibility slow keys threshold duration in milliseconds.
+ *
+ * <p>
+ * 'Slow keys' is an accessibility feature to aid users who have physical disabilities, that
+ * allows the user to specify the duration for which one must press-and-hold a key before the
+ * system accepts the keypress.
+ * </p>
+ *
+ * @hide
+ */
+ public static int getAccessibilitySlowKeysThreshold(@NonNull Context context) {
+ if (!isAccessibilitySlowKeysFeatureFlagEnabled()) {
+ return 0;
+ }
+ return Settings.Secure.getIntForUser(context.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_SLOW_KEYS, 0, UserHandle.USER_CURRENT);
+ }
+
+ /**
+ * Set Accessibility slow keys threshold duration in milliseconds.
+ * @param thresholdTimeMillis time duration for which a key should be pressed to be registered
+ * in the system. The threshold must be between 0 and
+ * {@link MAX_ACCESSIBILITY_SLOW_KEYS_THRESHOLD_MILLIS}
+ *
+ * <p>
+ * 'Slow keys' is an accessibility feature to aid users who have physical disabilities, that
+ * allows the user to specify the duration for which one must press-and-hold a key before the
+ * system accepts the keypress.
+ * </p>
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.WRITE_SETTINGS)
+ public static void setAccessibilitySlowKeysThreshold(@NonNull Context context,
+ int thresholdTimeMillis) {
+ if (!isAccessibilitySlowKeysFeatureFlagEnabled()) {
+ return;
+ }
+ if (thresholdTimeMillis < 0
+ || thresholdTimeMillis > MAX_ACCESSIBILITY_SLOW_KEYS_THRESHOLD_MILLIS) {
+ throw new IllegalArgumentException(
+ "Provided Slow keys threshold should be in range [0, "
+ + MAX_ACCESSIBILITY_SLOW_KEYS_THRESHOLD_MILLIS + "]");
+ }
+ Settings.Secure.putIntForUser(context.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_SLOW_KEYS, thresholdTimeMillis,
+ UserHandle.USER_CURRENT);
+ }
+
+ /**
* Whether Accessibility sticky keys feature is enabled.
*
* <p>
diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig
index 362fe78b14b8..0ed6569afd2a 100644
--- a/core/java/android/hardware/input/input_framework.aconfig
+++ b/core/java/android/hardware/input/input_framework.aconfig
@@ -29,4 +29,11 @@ flag {
name: "pointer_coords_is_resampled_api"
description: "Makes MotionEvent.PointerCoords#isResampled() a public API"
bug: "298197511"
+}
+
+flag {
+ namespace: "input_native"
+ name: "keyboard_a11y_slow_keys_flag"
+ description: "Controls if the slow keys accessibility feature for physical keyboard is available to the user"
+ bug: "294546335"
} \ No newline at end of file
diff --git a/core/java/android/net/thread/OWNERS b/core/java/android/net/thread/OWNERS
new file mode 100644
index 000000000000..55c307b5eb62
--- /dev/null
+++ b/core/java/android/net/thread/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 1203089
+
+include platform/packages/modules/ThreadNetwork:/OWNERS
diff --git a/core/java/android/net/thread/flags.aconfig b/core/java/android/net/thread/flags.aconfig
new file mode 100644
index 000000000000..6e72f8ebd8d1
--- /dev/null
+++ b/core/java/android/net/thread/flags.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.net.thread.flags"
+
+flag {
+ name: "thread_user_restriction_enabled"
+ namespace: "thread_network"
+ description: "Controls whether user restriction on thread networks is enabled"
+ bug: "307679182"
+}
diff --git a/core/java/android/os/PatternMatcher.java b/core/java/android/os/PatternMatcher.java
index b5425b43fe7e..79a2c59d6a03 100644
--- a/core/java/android/os/PatternMatcher.java
+++ b/core/java/android/os/PatternMatcher.java
@@ -16,9 +16,12 @@
package android.os;
+import android.annotation.IntDef;
import android.util.Log;
import android.util.proto.ProtoOutputStream;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
/**
@@ -68,6 +71,17 @@ public class PatternMatcher implements Parcelable {
*/
public static final int PATTERN_SUFFIX = 4;
+ /** @hide */
+ @IntDef(value = {
+ PATTERN_LITERAL,
+ PATTERN_PREFIX,
+ PATTERN_SIMPLE_GLOB,
+ PATTERN_ADVANCED_GLOB,
+ PATTERN_SUFFIX,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PatternType {}
+
// token types for advanced matching
private static final int TOKEN_TYPE_LITERAL = 0;
private static final int TOKEN_TYPE_ANY = 1;
diff --git a/core/java/android/os/PersistableBundle.java b/core/java/android/os/PersistableBundle.java
index 02704f5b346b..236194d16ad8 100644
--- a/core/java/android/os/PersistableBundle.java
+++ b/core/java/android/os/PersistableBundle.java
@@ -294,6 +294,43 @@ public final class PersistableBundle extends BaseBundle implements Cloneable, Pa
XmlUtils.writeMapXml(mMap, out, this);
}
+ /**
+ * Checks whether all keys and values are within the given character limit.
+ * Note: Maximum character limit of String that can be saved to XML as part of bundle is 65535.
+ * Otherwise IOException is thrown.
+ * @param limit length of String keys and values in the PersistableBundle, including nested
+ * PersistableBundles to check against.
+ *
+ * @hide
+ */
+ public boolean isBundleContentsWithinLengthLimit(int limit) {
+ unparcel();
+ if (mMap == null) {
+ return true;
+ }
+ for (int i = 0; i < mMap.size(); i++) {
+ if (mMap.keyAt(i) != null && mMap.keyAt(i).length() > limit) {
+ return false;
+ }
+ final Object value = mMap.valueAt(i);
+ if (value instanceof String && ((String) value).length() > limit) {
+ return false;
+ } else if (value instanceof String[]) {
+ String[] stringArray = (String[]) value;
+ for (int j = 0; j < stringArray.length; j++) {
+ if (stringArray[j] != null
+ && stringArray[j].length() > limit) {
+ return false;
+ }
+ }
+ } else if (value instanceof PersistableBundle
+ && !((PersistableBundle) value).isBundleContentsWithinLengthLimit(limit)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
/** @hide */
static class MyReadMapCallback implements XmlUtils.ReadMapCallback {
@Override
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index dd0436cbb2f2..1f3a1620a9f2 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -1589,7 +1589,15 @@ public class Process {
@UnsupportedAppUsage
public static final native long getPss(int pid);
- /** @hide */
+ /**
+ * Gets the total Rss value for a given process, in bytes.
+ *
+ * @param pid the process to the Rss for
+ * @return an ordered array containing multiple values, they are:
+ * [total_rss, file, anon, swap, shmem].
+ * or NULL if the value cannot be determined
+ * @hide
+ */
public static final native long[] getRss(int pid);
/**
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index c280d132ae38..d6df8d940904 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -88,6 +88,7 @@ import java.util.Set;
* See {@link DevicePolicyManager#ACTION_PROVISION_MANAGED_PROFILE} for more on managed profiles.
*/
@SystemService(Context.USER_SERVICE)
+@android.ravenwood.annotation.RavenwoodKeepPartialClass
public class UserManager {
private static final String TAG = "UserManager";
@@ -106,6 +107,21 @@ public class UserManager {
/** Whether the device is in headless system user mode; null until cached. */
private static Boolean sIsHeadlessSystemUser = null;
+ /** Maximum length of username.
+ * @hide
+ */
+ public static final int MAX_USER_NAME_LENGTH = 100;
+
+ /** Maximum length of user property String value.
+ * @hide
+ */
+ public static final int MAX_ACCOUNT_STRING_LENGTH = 500;
+
+ /** Maximum length of account options String values.
+ * @hide
+ */
+ public static final int MAX_ACCOUNT_OPTIONS_LENGTH = 1000;
+
/**
* User type representing a {@link UserHandle#USER_SYSTEM system} user that is a human user.
* This type of user cannot be created; it can only pre-exist on first boot.
@@ -1882,6 +1898,30 @@ public class UserManager {
"no_near_field_communication_radio";
/**
+ * This user restriction specifies if Thread network is disallowed on the device. If Thread
+ * network is disallowed it cannot be turned on via Settings.
+ *
+ * <p>This restriction can only be set by a device owner or a profile owner of an
+ * organization-owned managed profile on the parent profile.
+ * In both cases, the restriction applies globally on the device and will turn off the
+ * Thread network radio if it's currently on and prevent the radio from being turned
+ * on in the future.
+ *
+ * <p> <a href="https://www.threadgroup.org">Thread</a> is a low-power and low-latency wireless
+ * mesh networking protocol built on IPv6.
+ *
+ * <p>Default is <code>false</code>.
+ *
+ * <p>Key for user restrictions.
+ * <p>Type: Boolean
+ * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+ * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+ * @see #getUserRestrictions()
+ */
+ @FlaggedApi("com.android.net.thread.flags.thread_user_restriction_enabled")
+ public static final String DISALLOW_THREAD_NETWORK = "no_thread_network";
+
+ /**
* List of key values that can be passed into the various user restriction related methods
* in {@link UserManager} & {@link DevicePolicyManager}.
* Note: This is slightly different from the real set of user restrictions listed in {@link
@@ -1967,6 +2007,7 @@ public class UserManager {
DISALLOW_ULTRA_WIDEBAND_RADIO,
DISALLOW_GRANT_ADMIN,
DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO,
+ DISALLOW_THREAD_NETWORK,
})
@Retention(RetentionPolicy.SOURCE)
public @interface UserRestrictionKey {}
@@ -2906,6 +2947,7 @@ public class UserManager {
* {@link UserManager#USER_TYPE_PROFILE_MANAGED managed profile}.
* @hide
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public static boolean isUserTypeManagedProfile(@Nullable String userType) {
return USER_TYPE_PROFILE_MANAGED.equals(userType);
}
@@ -2914,6 +2956,7 @@ public class UserManager {
* Returns whether the user type is a {@link UserManager#USER_TYPE_FULL_GUEST guest user}.
* @hide
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public static boolean isUserTypeGuest(@Nullable String userType) {
return USER_TYPE_FULL_GUEST.equals(userType);
}
@@ -2923,6 +2966,7 @@ public class UserManager {
* {@link UserManager#USER_TYPE_FULL_RESTRICTED restricted user}.
* @hide
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public static boolean isUserTypeRestricted(@Nullable String userType) {
return USER_TYPE_FULL_RESTRICTED.equals(userType);
}
@@ -2931,6 +2975,7 @@ public class UserManager {
* Returns whether the user type is a {@link UserManager#USER_TYPE_FULL_DEMO demo user}.
* @hide
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public static boolean isUserTypeDemo(@Nullable String userType) {
return USER_TYPE_FULL_DEMO.equals(userType);
}
@@ -2939,6 +2984,7 @@ public class UserManager {
* Returns whether the user type is a {@link UserManager#USER_TYPE_PROFILE_CLONE clone user}.
* @hide
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public static boolean isUserTypeCloneProfile(@Nullable String userType) {
return USER_TYPE_PROFILE_CLONE.equals(userType);
}
@@ -2948,6 +2994,7 @@ public class UserManager {
* {@link UserManager#USER_TYPE_PROFILE_COMMUNAL communal profile}.
* @hide
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public static boolean isUserTypeCommunalProfile(@Nullable String userType) {
return USER_TYPE_PROFILE_COMMUNAL.equals(userType);
}
@@ -2958,6 +3005,7 @@ public class UserManager {
*
* @hide
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public static boolean isUserTypePrivateProfile(@Nullable String userType) {
return USER_TYPE_PROFILE_PRIVATE.equals(userType);
}
@@ -4423,15 +4471,15 @@ public class UserManager {
* This API should only be called if the current user is an {@link #isAdminUser() admin} user,
* as otherwise the returned intent will not be able to create a user.
*
- * @param userName Optional name to assign to the user.
+ * @param userName Optional name to assign to the user. Character limit is 100.
* @param accountName Optional account name that will be used by the setup wizard to initialize
- * the user.
+ * the user. Character limit is 500.
* @param accountType Optional account type for the account to be created. This is required
- * if the account name is specified.
+ * if the account name is specified. Character limit is 500.
* @param accountOptions Optional bundle of data to be passed in during account creation in the
* new user via {@link AccountManager#addAccount(String, String, String[],
* Bundle, android.app.Activity, android.accounts.AccountManagerCallback,
- * Handler)}.
+ * Handler)}. Character limit is 1000.
* @return An Intent that can be launched from an Activity.
* @see #USER_CREATION_FAILED_NOT_PERMITTED
* @see #USER_CREATION_FAILED_NO_MORE_USERS
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index ecd6f22607b6..11edcafecdee 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -444,6 +444,18 @@ public final class Settings {
"android.settings.ACCESSIBILITY_DETAILS_SETTINGS";
/**
+ * Activity Action: Show settings to allow configuration of an accessibility
+ * shortcut belonging to an accessibility feature or features.
+ * <p>
+ * Input: ":settings:show_fragment_args" must contain "targets" denoting the services to edit.
+ * <p>
+ * Output: Nothing.
+ * @hide
+ **/
+ public static final String ACTION_ACCESSIBILITY_SHORTCUT_SETTINGS =
+ "android.settings.ACCESSIBILITY_SHORTCUT_SETTINGS";
+
+ /**
* Activity Action: Show settings to allow configuration of accessibility color and motion.
* <p>
* In some cases, a matching Activity may not exist, so ensure you
@@ -4513,10 +4525,11 @@ public final class Settings {
/** @hide */
public static void adjustConfigurationForUser(ContentResolver cr, Configuration outConfig,
int userHandle, boolean updateSettingsIfEmpty) {
+ final float defaultFontScale = getDefaultFontScale(cr, userHandle);
outConfig.fontScale = Settings.System.getFloatForUser(
- cr, FONT_SCALE, DEFAULT_FONT_SCALE, userHandle);
+ cr, FONT_SCALE, defaultFontScale, userHandle);
if (outConfig.fontScale < 0) {
- outConfig.fontScale = DEFAULT_FONT_SCALE;
+ outConfig.fontScale = defaultFontScale;
}
outConfig.fontWeightAdjustment = Settings.Secure.getIntForUser(
cr, Settings.Secure.FONT_WEIGHT_ADJUSTMENT, DEFAULT_FONT_WEIGHT, userHandle);
@@ -4541,6 +4554,12 @@ public final class Settings {
}
}
+ private static float getDefaultFontScale(ContentResolver cr, int userHandle) {
+ return com.android.window.flags.Flags.configurableFontScaleDefault()
+ ? Settings.System.getFloatForUser(cr, DEFAULT_DEVICE_FONT_SCALE,
+ DEFAULT_FONT_SCALE, userHandle) : DEFAULT_FONT_SCALE;
+ }
+
/**
* @hide Erase the fields in the Configuration that should be applied
* by the settings.
@@ -4907,6 +4926,15 @@ public final class Settings {
public static final String FONT_SCALE = "font_scale";
/**
+ * Default scaling factor for fonts for the specific device, float.
+ * The value is read from the {@link R.dimen.def_device_font_scale}
+ * configuration property.
+ *
+ * @hide
+ */
+ public static final String DEFAULT_DEVICE_FONT_SCALE = "device_font_scale";
+
+ /**
* The serialized system locale value.
*
* Do not use this value directory.
@@ -6245,6 +6273,7 @@ public final class Settings {
PRIVATE_SETTINGS.add(CAMERA_FLASH_NOTIFICATION);
PRIVATE_SETTINGS.add(SCREEN_FLASH_NOTIFICATION);
PRIVATE_SETTINGS.add(SCREEN_FLASH_NOTIFICATION_COLOR);
+ PRIVATE_SETTINGS.add(DEFAULT_DEVICE_FONT_SCALE);
}
/**
@@ -7881,6 +7910,17 @@ public final class Settings {
public static final String ACCESSIBILITY_BOUNCE_KEYS = "accessibility_bounce_keys";
/**
+ * Whether to enable slow keys for Physical Keyboard accessibility.
+ *
+ * If set to non-zero value, any key press on physical keyboard needs to be pressed and
+ * held for the provided threshold duration (in milliseconds) to be registered in the
+ * system.
+ *
+ * @hide
+ */
+ public static final String ACCESSIBILITY_SLOW_KEYS = "accessibility_slow_keys";
+
+ /**
* Whether to enable sticky keys for Physical Keyboard accessibility.
*
* This is a boolean value that determines if Sticky keys feature is enabled.
@@ -12276,6 +12316,8 @@ public final class Settings {
CLONE_TO_MANAGED_PROFILE.add(LOCATION_MODE);
CLONE_TO_MANAGED_PROFILE.add(SHOW_IME_WITH_HARD_KEYBOARD);
CLONE_TO_MANAGED_PROFILE.add(ACCESSIBILITY_BOUNCE_KEYS);
+ CLONE_TO_MANAGED_PROFILE.add(ACCESSIBILITY_SLOW_KEYS);
+ CLONE_TO_MANAGED_PROFILE.add(ACCESSIBILITY_STICKY_KEYS);
CLONE_TO_MANAGED_PROFILE.add(NOTIFICATION_BUBBLES);
CLONE_TO_MANAGED_PROFILE.add(NOTIFICATION_HISTORY_ENABLED);
}
diff --git a/core/java/android/service/autofill/AutofillService.java b/core/java/android/service/autofill/AutofillService.java
index 5ad2502d1546..298bdb881e9f 100644
--- a/core/java/android/service/autofill/AutofillService.java
+++ b/core/java/android/service/autofill/AutofillService.java
@@ -622,6 +622,15 @@ public abstract class AutofillService extends Service {
new FillCallback(callback, request.getId())));
}
+ @Override
+ public void onConvertCredentialRequest(
+ @NonNull ConvertCredentialRequest convertCredentialRequest,
+ @NonNull IConvertCredentialCallback convertCredentialCallback) {
+ mHandler.sendMessage(obtainMessage(
+ AutofillService::onConvertCredentialRequest,
+ AutofillService.this, convertCredentialRequest,
+ new ConvertCredentialCallback(convertCredentialCallback)));
+ }
@Override
public void onFillCredentialRequest(FillRequest request, IFillCallback callback,
@@ -707,7 +716,19 @@ public abstract class AutofillService extends Service {
*/
public void onFillCredentialRequest(@NonNull FillRequest request,
@NonNull CancellationSignal cancellationSignal, @NonNull FillCallback callback,
- IAutoFillManagerClient autofillClientCallback) {}
+ @NonNull IAutoFillManagerClient autofillClientCallback) {}
+
+ /**
+ * Called by the Android system to convert a credential manager response to a dataset
+ *
+ * @param convertCredentialRequest the request that has the original credential manager response
+ * @param convertCredentialCallback callback used to notify the result of the request.
+ *
+ * @hide
+ */
+ public void onConvertCredentialRequest(
+ @NonNull ConvertCredentialRequest convertCredentialRequest,
+ @NonNull ConvertCredentialCallback convertCredentialCallback){}
/**
* Called when the user requests the service to save the contents of a screen.
diff --git a/core/java/android/service/autofill/ConvertCredentialCallback.java b/core/java/android/service/autofill/ConvertCredentialCallback.java
new file mode 100644
index 000000000000..a39f01115338
--- /dev/null
+++ b/core/java/android/service/autofill/ConvertCredentialCallback.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.autofill;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.RemoteException;
+
+/**
+ * <p><code>ConvertCredentialCallback</code> handles convertCredentialResponse from Autofill
+ * Service.
+ *
+ * @hide
+ */
+public final class ConvertCredentialCallback {
+
+ private static final String TAG = "ConvertCredentialCallback";
+
+ private final IConvertCredentialCallback mCallback;
+
+ /** @hide */
+ public ConvertCredentialCallback(IConvertCredentialCallback callback) {
+ mCallback = callback;
+ }
+
+ /**
+ * Notifies the Android System that a convertCredentialRequest was fulfilled by the service.
+ *
+ * @param convertCredentialResponse the result
+ */
+ public void onSuccess(@NonNull ConvertCredentialResponse convertCredentialResponse) {
+ try {
+ mCallback.onSuccess(convertCredentialResponse);
+ } catch (RemoteException e) {
+ e.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
+ * Notifies the Android System that a convert credential request has failed
+ *
+ * @param message the error message
+ */
+ public void onFailure(@Nullable CharSequence message) {
+ try {
+ mCallback.onFailure(message);
+ } catch (RemoteException e) {
+ e.rethrowAsRuntimeException();
+ }
+ }
+}
diff --git a/core/java/android/service/autofill/ConvertCredentialRequest.aidl b/core/java/android/service/autofill/ConvertCredentialRequest.aidl
new file mode 100644
index 000000000000..79681e2dbe84
--- /dev/null
+++ b/core/java/android/service/autofill/ConvertCredentialRequest.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2024, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.autofill;
+
+parcelable ConvertCredentialRequest; \ No newline at end of file
diff --git a/core/java/android/service/autofill/ConvertCredentialRequest.java b/core/java/android/service/autofill/ConvertCredentialRequest.java
new file mode 100644
index 000000000000..d2d7556f40b7
--- /dev/null
+++ b/core/java/android/service/autofill/ConvertCredentialRequest.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.autofill;
+
+import android.annotation.NonNull;
+import android.credentials.GetCredentialResponse;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+
+/**
+ * This class represents a request to an autofill service to convert the credential manager response
+ * to a dataset.
+ *
+ * @hide
+ */
+@DataClass(
+ genToString = true,
+ genHiddenConstructor = true,
+ genHiddenConstDefs = true)
+public final class ConvertCredentialRequest implements Parcelable {
+ private final @NonNull GetCredentialResponse mGetCredentialResponse;
+ private final @NonNull Bundle mClientState;
+
+
+
+ // Code below generated by codegen v1.0.23.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/service/autofill/ConvertCredentialRequest.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /**
+ * Creates a new ConvertCredentialRequest.
+ *
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public ConvertCredentialRequest(
+ @NonNull GetCredentialResponse getCredentialResponse,
+ @NonNull Bundle clientState) {
+ this.mGetCredentialResponse = getCredentialResponse;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mGetCredentialResponse);
+ this.mClientState = clientState;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mClientState);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull GetCredentialResponse getGetCredentialResponse() {
+ return mGetCredentialResponse;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull Bundle getClientState() {
+ return mClientState;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "ConvertCredentialRequest { " +
+ "getCredentialResponse = " + mGetCredentialResponse + ", " +
+ "clientState = " + mClientState +
+ " }";
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ dest.writeTypedObject(mGetCredentialResponse, flags);
+ dest.writeBundle(mClientState);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ ConvertCredentialRequest(@NonNull Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ GetCredentialResponse getCredentialResponse = (GetCredentialResponse) in.readTypedObject(GetCredentialResponse.CREATOR);
+ Bundle clientState = in.readBundle();
+
+ this.mGetCredentialResponse = getCredentialResponse;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mGetCredentialResponse);
+ this.mClientState = clientState;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mClientState);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<ConvertCredentialRequest> CREATOR
+ = new Parcelable.Creator<ConvertCredentialRequest>() {
+ @Override
+ public ConvertCredentialRequest[] newArray(int size) {
+ return new ConvertCredentialRequest[size];
+ }
+
+ @Override
+ public ConvertCredentialRequest createFromParcel(@NonNull Parcel in) {
+ return new ConvertCredentialRequest(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1706132305002L,
+ codegenVersion = "1.0.23",
+ sourceFile = "frameworks/base/core/java/android/service/autofill/ConvertCredentialRequest.java",
+ inputSignatures = "private final @android.annotation.NonNull android.credentials.GetCredentialResponse mGetCredentialResponse\nprivate final @android.annotation.NonNull android.os.Bundle mClientState\nclass ConvertCredentialRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/core/java/android/service/autofill/ConvertCredentialResponse.aidl b/core/java/android/service/autofill/ConvertCredentialResponse.aidl
new file mode 100644
index 000000000000..98ac6f67c521
--- /dev/null
+++ b/core/java/android/service/autofill/ConvertCredentialResponse.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2024, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.autofill;
+
+parcelable ConvertCredentialResponse; \ No newline at end of file
diff --git a/core/java/android/service/autofill/ConvertCredentialResponse.java b/core/java/android/service/autofill/ConvertCredentialResponse.java
new file mode 100644
index 000000000000..5da4f63f31f9
--- /dev/null
+++ b/core/java/android/service/autofill/ConvertCredentialResponse.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.autofill;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+/**
+ * Response for a {@Link ConvertCredentialRequest}
+ *
+ * @hide
+ */
+@DataClass(
+ genToString = true,
+ genHiddenConstructor = true,
+ genHiddenConstDefs = true)
+public final class ConvertCredentialResponse implements Parcelable {
+ private final @NonNull Dataset mDataset;
+ private final @Nullable Bundle mClientState;
+
+
+
+
+ // Code below generated by codegen v1.0.23.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/service/autofill/ConvertCredentialResponse.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /**
+ * Creates a new ConvertCredentialResponse.
+ *
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public ConvertCredentialResponse(
+ @NonNull Dataset dataset,
+ @Nullable Bundle clientState) {
+ this.mDataset = dataset;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mDataset);
+ this.mClientState = clientState;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull Dataset getDataset() {
+ return mDataset;
+ }
+
+ @DataClass.Generated.Member
+ public @Nullable Bundle getClientState() {
+ return mClientState;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "ConvertCredentialResponse { " +
+ "dataset = " + mDataset + ", " +
+ "clientState = " + mClientState +
+ " }";
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ byte flg = 0;
+ if (mClientState != null) flg |= 0x2;
+ dest.writeByte(flg);
+ dest.writeTypedObject(mDataset, flags);
+ if (mClientState != null) dest.writeBundle(mClientState);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ ConvertCredentialResponse(@NonNull Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ byte flg = in.readByte();
+ Dataset dataset = (Dataset) in.readTypedObject(Dataset.CREATOR);
+ Bundle clientState = (flg & 0x2) == 0 ? null : in.readBundle();
+
+ this.mDataset = dataset;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mDataset);
+ this.mClientState = clientState;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<ConvertCredentialResponse> CREATOR
+ = new Parcelable.Creator<ConvertCredentialResponse>() {
+ @Override
+ public ConvertCredentialResponse[] newArray(int size) {
+ return new ConvertCredentialResponse[size];
+ }
+
+ @Override
+ public ConvertCredentialResponse createFromParcel(@NonNull Parcel in) {
+ return new ConvertCredentialResponse(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1706132669373L,
+ codegenVersion = "1.0.23",
+ sourceFile = "frameworks/base/core/java/android/service/autofill/ConvertCredentialResponse.java",
+ inputSignatures = "private final @android.annotation.NonNull android.service.autofill.Dataset mDataset\nprivate final @android.annotation.Nullable android.os.Bundle mClientState\nclass ConvertCredentialResponse extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/core/java/android/service/autofill/FillEventHistory.java b/core/java/android/service/autofill/FillEventHistory.java
index 5d58120ef5bb..98dda1031eff 100644
--- a/core/java/android/service/autofill/FillEventHistory.java
+++ b/core/java/android/service/autofill/FillEventHistory.java
@@ -309,12 +309,19 @@ public final class FillEventHistory implements Parcelable {
/** The autofill suggestion is shown as a dialog presentation. */
public static final int UI_TYPE_DIALOG = 3;
+ /**
+ * The autofill suggestion is shown os a credman bottom sheet
+ * @hide
+ */
+ public static final int UI_TYPE_CREDMAN_BOTTOM_SHEET = 4;
+
/** @hide */
@IntDef(prefix = { "UI_TYPE_" }, value = {
UI_TYPE_UNKNOWN,
UI_TYPE_MENU,
UI_TYPE_INLINE,
- UI_TYPE_DIALOG
+ UI_TYPE_DIALOG,
+ UI_TYPE_CREDMAN_BOTTOM_SHEET
})
@Retention(RetentionPolicy.SOURCE)
public @interface UiType {}
@@ -755,6 +762,8 @@ public final class FillEventHistory implements Parcelable {
return "UI_TYPE_INLINE";
case UI_TYPE_DIALOG:
return "UI_TYPE_FILL_DIALOG";
+ case UI_TYPE_CREDMAN_BOTTOM_SHEET:
+ return "UI_TYPE_CREDMAN_BOTTOM_SHEET";
default:
return "UI_TYPE_UNKNOWN";
}
diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java
index 7ea74d375ffb..09ec933880d4 100644
--- a/core/java/android/service/autofill/FillResponse.java
+++ b/core/java/android/service/autofill/FillResponse.java
@@ -28,6 +28,7 @@ import android.annotation.StringRes;
import android.annotation.SuppressLint;
import android.annotation.TestApi;
import android.app.Activity;
+import android.app.PendingIntent;
import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.ParceledListSlice;
@@ -116,6 +117,7 @@ public final class FillResponse implements Parcelable {
private final boolean mShowFillDialogIcon;
private final boolean mShowSaveDialogIcon;
private final @Nullable FieldClassification[] mDetectedFieldTypes;
+ private final @Nullable PendingIntent mDialogPendingIntent;
/**
* Creates a shollow copy of the provided FillResponse.
@@ -150,7 +152,8 @@ public final class FillResponse implements Parcelable {
r.mServiceDisplayNameResourceId,
r.mShowFillDialogIcon,
r.mShowSaveDialogIcon,
- r.mDetectedFieldTypes);
+ r.mDetectedFieldTypes,
+ r.mDialogPendingIntent);
}
private FillResponse(ParceledListSlice<Dataset> datasets, SaveInfo saveInfo, Bundle clientState,
@@ -163,7 +166,7 @@ public final class FillResponse implements Parcelable {
int[] cancelIds, boolean supportsInlineSuggestions, int iconResourceId,
int serviceDisplayNameResourceId, boolean showFillDialogIcon,
boolean showSaveDialogIcon,
- FieldClassification[] detectedFieldTypes) {
+ FieldClassification[] detectedFieldTypes, PendingIntent dialogPendingIntent) {
mDatasets = datasets;
mSaveInfo = saveInfo;
mClientState = clientState;
@@ -190,6 +193,7 @@ public final class FillResponse implements Parcelable {
mShowFillDialogIcon = showFillDialogIcon;
mShowSaveDialogIcon = showSaveDialogIcon;
mDetectedFieldTypes = detectedFieldTypes;
+ mDialogPendingIntent = dialogPendingIntent;
}
private FillResponse(@NonNull Builder builder) {
@@ -219,6 +223,7 @@ public final class FillResponse implements Parcelable {
mShowFillDialogIcon = builder.mShowFillDialogIcon;
mShowSaveDialogIcon = builder.mShowSaveDialogIcon;
mDetectedFieldTypes = builder.mDetectedFieldTypes;
+ mDialogPendingIntent = builder.mDialogPendingIntent;
}
/** @hide */
@@ -399,6 +404,7 @@ public final class FillResponse implements Parcelable {
private boolean mShowFillDialogIcon = true;
private boolean mShowSaveDialogIcon = true;
private FieldClassification[] mDetectedFieldTypes;
+ private PendingIntent mDialogPendingIntent;
/**
* Adds a new {@link FieldClassification} to this response, to
@@ -1079,6 +1085,24 @@ public final class FillResponse implements Parcelable {
}
/**
+ * Sets credential dialog pending intent. Framework will use the intent to launch the
+ * selector UI. A replacement for previous fill bottom sheet.
+ *
+ * @throws IllegalStateException if {@link #build()} was already called.
+ * @throws NullPointerException if {@code pendingIntent} is {@code null}.
+ *
+ * @hide
+ */
+ @NonNull
+ public Builder setDialogPendingIntent(@NonNull PendingIntent pendingIntent) {
+ throwIfDestroyed();
+ Preconditions.checkNotNull(pendingIntent,
+ "can't pass a null object to setDialogPendingIntent");
+ mDialogPendingIntent = pendingIntent;
+ return this;
+ }
+
+ /**
* Builds a new {@link FillResponse} instance.
*
* @throws IllegalStateException if any of the following conditions occur:
@@ -1187,6 +1211,9 @@ public final class FillResponse implements Parcelable {
if (mAuthentication != null) {
builder.append(", hasAuthentication");
}
+ if (mDialogPendingIntent != null) {
+ builder.append(", hasDialogPendingIntent");
+ }
if (mAuthenticationIds != null) {
builder.append(", authenticationIds=").append(Arrays.toString(mAuthenticationIds));
}
@@ -1232,6 +1259,7 @@ public final class FillResponse implements Parcelable {
parcel.writeParcelable(mInlineTooltipPresentation, flags);
parcel.writeParcelable(mDialogPresentation, flags);
parcel.writeParcelable(mDialogHeader, flags);
+ parcel.writeParcelable(mDialogPendingIntent, flags);
parcel.writeParcelableArray(mFillDialogTriggerIds, flags);
parcel.writeParcelable(mHeader, flags);
parcel.writeParcelable(mFooter, flags);
@@ -1282,6 +1310,11 @@ public final class FillResponse implements Parcelable {
if (dialogHeader != null) {
builder.setDialogHeader(dialogHeader);
}
+ final PendingIntent dialogPendingIntent = parcel.readParcelable(null,
+ PendingIntent.class);
+ if (dialogPendingIntent != null) {
+ builder.setDialogPendingIntent(dialogPendingIntent);
+ }
final AutofillId[] triggerIds = parcel.readParcelableArray(null, AutofillId.class);
if (triggerIds != null) {
builder.setFillDialogTriggerIds(triggerIds);
diff --git a/core/java/android/service/autofill/IAutoFillService.aidl b/core/java/android/service/autofill/IAutoFillService.aidl
index 03ead3266521..2c2feae7aeea 100644
--- a/core/java/android/service/autofill/IAutoFillService.aidl
+++ b/core/java/android/service/autofill/IAutoFillService.aidl
@@ -16,6 +16,8 @@
package android.service.autofill;
+import android.service.autofill.ConvertCredentialRequest;
+import android.service.autofill.IConvertCredentialCallback;
import android.service.autofill.FillRequest;
import android.service.autofill.IFillCallback;
import android.service.autofill.ISaveCallback;
@@ -32,7 +34,8 @@ oneway interface IAutoFillService {
void onConnectedStateChanged(boolean connected);
void onFillRequest(in FillRequest request, in IFillCallback callback);
void onFillCredentialRequest(in FillRequest request, in IFillCallback callback,
- in IAutoFillManagerClient client);
+ in IAutoFillManagerClient client);
void onSaveRequest(in SaveRequest request, in ISaveCallback callback);
void onSavedPasswordCountRequest(in IResultReceiver receiver);
+ void onConvertCredentialRequest(in ConvertCredentialRequest convertCredentialRequest, in IConvertCredentialCallback convertCredentialCallback);
}
diff --git a/core/java/android/service/autofill/IConvertCredentialCallback.aidl b/core/java/android/service/autofill/IConvertCredentialCallback.aidl
new file mode 100644
index 000000000000..9dfc29429876
--- /dev/null
+++ b/core/java/android/service/autofill/IConvertCredentialCallback.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.autofill;
+
+import android.os.ICancellationSignal;
+
+import android.service.autofill.ConvertCredentialResponse;
+
+/**
+ * Interface to receive the result of a convert credential request
+ *
+ * @hide
+ */
+oneway interface IConvertCredentialCallback {
+ void onSuccess(in ConvertCredentialResponse convertCredentialResponse);
+ void onFailure(CharSequence message);
+}
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 9895551a8672..9d19ef6bdc64 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -1053,7 +1053,7 @@ public class ZenModeConfig implements Parcelable {
out);
if (Flags.modesApi()) {
- writeZenPolicyState(ALLOW_ATT_CHANNELS, policy.getPriorityChannels(), out);
+ writeZenPolicyState(ALLOW_ATT_CHANNELS, policy.getPriorityChannelsAllowed(), out);
}
}
@@ -1381,7 +1381,7 @@ public class ZenModeConfig implements Parcelable {
int state = defaultPolicy.state;
if (Flags.modesApi()) {
state = Policy.policyState(defaultPolicy.hasPriorityChannels(),
- ZenPolicy.stateToBoolean(zenPolicy.getPriorityChannels(),
+ ZenPolicy.stateToBoolean(zenPolicy.getPriorityChannelsAllowed(),
DEFAULT_ALLOW_PRIORITY_CHANNELS));
}
diff --git a/core/java/android/service/notification/ZenPolicy.java b/core/java/android/service/notification/ZenPolicy.java
index d8318a6bee7c..786d768bc55b 100644
--- a/core/java/android/service/notification/ZenPolicy.java
+++ b/core/java/android/service/notification/ZenPolicy.java
@@ -570,7 +570,7 @@ public final class ZenPolicy implements Parcelable {
* with {@link NotificationChannel#canBypassDnd()} will be intercepted.
*/
@FlaggedApi(Flags.FLAG_MODES_API)
- public @State int getPriorityChannels() {
+ public @State int getPriorityChannelsAllowed() {
switch (mAllowChannels) {
case CHANNEL_POLICY_PRIORITY:
return STATE_ALLOW;
@@ -1529,7 +1529,7 @@ public final class ZenPolicy implements Parcelable {
proto.write(DNDPolicyProto.ALLOW_CONVERSATIONS_FROM, getPriorityConversationSenders());
if (Flags.modesApi()) {
- proto.write(DNDPolicyProto.ALLOW_CHANNELS, getPriorityChannels());
+ proto.write(DNDPolicyProto.ALLOW_CHANNELS, getPriorityChannelsAllowed());
}
proto.flush();
diff --git a/core/java/android/util/Singleton.java b/core/java/android/util/Singleton.java
index 92646b47cef2..d27bef9e9adc 100644
--- a/core/java/android/util/Singleton.java
+++ b/core/java/android/util/Singleton.java
@@ -25,6 +25,7 @@ import android.compat.annotation.UnsupportedAppUsage;
*
* @hide
*/
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public abstract class Singleton<T> {
@UnsupportedAppUsage
diff --git a/core/java/android/view/AttachedSurfaceControl.java b/core/java/android/view/AttachedSurfaceControl.java
index f28574ecb3b2..27c509a8605f 100644
--- a/core/java/android/view/AttachedSurfaceControl.java
+++ b/core/java/android/view/AttachedSurfaceControl.java
@@ -197,9 +197,28 @@ public interface AttachedSurfaceControl {
* Transfer the currently in progress touch gesture from the host to the requested
* {@link SurfaceControlViewHost.SurfacePackage}. This requires that the
* SurfaceControlViewHost was created with the current host's inputToken.
+ * <p>
+ * When the touch is transferred, the window currently receiving touch gets an ACTION_CANCEL
+ * and does not receive any further input events for this gesture.
+ * <p>
+ * The transferred-to window receives an ACTION_DOWN event and then the remainder of the
+ * input events for this gesture. It does not receive any of the previous events of this gesture
+ * that the originating window received.
+ * <p>
+ * The "transferTouch" API only works for the current gesture. When a new gesture arrives,
+ * input dispatcher will do a new round of hit testing. So, if the "host" window is still the
+ * first thing that's being touched, then it will receive the new gesture again. It will
+ * again be up to the host to transfer this new gesture to the embedded.
+ * <p>
+ * Once the transferred-to window receives the gesture, it can choose to give up this gesture
+ * and send it to another window that it's linked to (it can't be an arbitrary window for
+ * security reasons) using the same transferTouch API. Only the window currently receiving
+ * touch is allowed to transfer the gesture.
*
* @param surfacePackage The SurfacePackage to transfer the gesture to.
* @return Whether the touch stream was transferred.
+ * @see SurfaceControlViewHost#transferTouchGestureToHost() for the reverse to transfer touch
+ * gesture from the embedded to the host.
*/
@FlaggedApi(Flags.FLAG_TRANSFER_GESTURE_TO_EMBEDDED)
default boolean transferHostTouchGestureToEmbedded(
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 1908c64ce42d..fbadef3d19ef 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -2079,6 +2079,7 @@ public final class Display {
*
* @see Display#getSupportedModes()
*/
+ @android.ravenwood.annotation.RavenwoodKeepWholeClass
public static final class Mode implements Parcelable {
/**
* @hide
@@ -2467,6 +2468,7 @@ public final class Display {
* <p>You can get an instance for a given {@link Display} object with
* {@link Display#getHdrCapabilities getHdrCapabilities()}.
*/
+ @android.ravenwood.annotation.RavenwoodKeepWholeClass
public static final class HdrCapabilities implements Parcelable {
/**
* Invalid luminance value.
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index 981911ec8880..5654bc159568 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -51,6 +51,7 @@ import java.util.Objects;
* Describes the characteristics of a particular logical display.
* @hide
*/
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public final class DisplayInfo implements Parcelable {
/**
* The surface flinger layer stack associated with this logical display.
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 1d81be17f580..d2c25cde35d6 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -235,7 +235,7 @@ interface IWindowSession {
*/
oneway void setWallpaperDisplayOffset(IBinder windowToken, int x, int y);
- Bundle sendWallpaperCommand(IBinder window, String action, int x, int y,
+ oneway void sendWallpaperCommand(IBinder window, String action, int x, int y,
int z, in Bundle extras, boolean sync);
@UnsupportedAppUsage
diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java
index 86ab21348e4f..bc33d5e2f6b1 100644
--- a/core/java/android/view/InsetsSource.java
+++ b/core/java/android/view/InsetsSource.java
@@ -17,6 +17,7 @@
package android.view;
import static android.view.InsetsSourceProto.FRAME;
+import static android.view.InsetsSourceProto.TYPE;
import static android.view.InsetsSourceProto.TYPE_NUMBER;
import static android.view.InsetsSourceProto.VISIBLE;
import static android.view.InsetsSourceProto.VISIBLE_FRAME;
@@ -442,6 +443,10 @@ public class InsetsSource implements Parcelable {
*/
public void dumpDebug(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
+ if (!android.os.Flags.androidOsBuildVanillaIceCream()) {
+ // Deprecated since V.
+ proto.write(TYPE, WindowInsets.Type.toString(mType));
+ }
mFrame.dumpDebug(proto, FRAME);
if (mVisibleFrame != null) {
mVisibleFrame.dumpDebug(proto, VISIBLE_FRAME);
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index ba7874eb2d21..6c6e8b247886 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -1073,8 +1073,7 @@ public class Surface implements Parcelable {
if (error == -EINVAL) {
throw new IllegalArgumentException("Invalid argument to Surface.setFrameRate()");
} else if (error != 0) {
- throw new RuntimeException("Failed to set frame rate on Surface. Native error: "
- + error);
+ Log.e(TAG, "Failed to set frame rate on Surface. Native error: " + error);
}
}
}
@@ -1256,13 +1255,13 @@ public class Surface implements Parcelable {
}
private static void registerNativeMemoryUsage() {
- if (Flags.enableSurfaceNativeAllocRegistration()) {
+ if (Flags.enableSurfaceNativeAllocRegistrationRo()) {
VMRuntime.getRuntime().registerNativeAllocation(SURFACE_NATIVE_ALLOCATION_SIZE_BYTES);
}
}
private static void freeNativeMemoryUsage() {
- if (Flags.enableSurfaceNativeAllocRegistration()) {
+ if (Flags.enableSurfaceNativeAllocRegistrationRo()) {
VMRuntime.getRuntime().registerNativeFree(SURFACE_NATIVE_ALLOCATION_SIZE_BYTES);
}
}
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 42355bb17c2d..427d053f754e 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -6022,8 +6022,8 @@ public interface WindowManager extends ViewManager {
* This is different from
* {@link #registerUnbatchedSurfaceControlInputReceiver(int, IBinder, SurfaceControl, Looper,
* SurfaceControlInputReceiver)} in that the input events are received batched. The caller must
- * invoke {@link #unregisterSurfaceControlInputReceiver(IBinder)} to clean up the resources when
- * no longer needing to use the {@link SurfaceControlInputReceiver}
+ * invoke {@link #unregisterSurfaceControlInputReceiver(SurfaceControl)} to clean up the
+ * resources when no longer needing to use the {@link SurfaceControlInputReceiver}
*
* @param displayId The display that the SurfaceControl will be placed on. Input will
* only work
@@ -6035,14 +6035,9 @@ public interface WindowManager extends ViewManager {
* @param choreographer The Choreographer used for batching. This should match the rendering
* Choreographer.
* @param receiver The SurfaceControlInputReceiver that will receive the input events
- * @return an {@link IBinder} token that is used to unregister the input receiver via
- * {@link #unregisterSurfaceControlInputReceiver(IBinder)}.
- * @see #registerUnbatchedSurfaceControlInputReceiver(int, IBinder, SurfaceControl, Looper,
- * SurfaceControlInputReceiver)
*/
@FlaggedApi(Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER)
- @NonNull
- default IBinder registerBatchedSurfaceControlInputReceiver(int displayId,
+ default void registerBatchedSurfaceControlInputReceiver(int displayId,
@NonNull IBinder hostToken, @NonNull SurfaceControl surfaceControl,
@NonNull Choreographer choreographer, @NonNull SurfaceControlInputReceiver receiver) {
throw new UnsupportedOperationException(
@@ -6054,8 +6049,8 @@ public interface WindowManager extends ViewManager {
* receive every input event. This is different than calling @link
* #registerBatchedSurfaceControlInputReceiver(int, IBinder, SurfaceControl, Choreographer,
* SurfaceControlInputReceiver)} in that the input events are received unbatched. The caller
- * must invoke {@link #unregisterSurfaceControlInputReceiver(IBinder)} to clean up the resources
- * when no longer needing to use the {@link SurfaceControlInputReceiver}
+ * must invoke {@link #unregisterSurfaceControlInputReceiver(SurfaceControl)} to clean up the
+ * resources when no longer needing to use the {@link SurfaceControlInputReceiver}
*
* @param displayId The display that the SurfaceControl will be placed on. Input will only
* work if SurfaceControl is on that display and that display was
@@ -6066,14 +6061,9 @@ public interface WindowManager extends ViewManager {
* @param surfaceControl The SurfaceControl to register the InputChannel for
* @param looper The looper to use when invoking callbacks.
* @param receiver The SurfaceControlInputReceiver that will receive the input events
- * @return an {@link IBinder} token that is used to unregister the input receiver via
- * {@link #unregisterSurfaceControlInputReceiver(IBinder)}.
- * @see #registerBatchedSurfaceControlInputReceiver(int, IBinder, SurfaceControl, Choreographer,
- * SurfaceControlInputReceiver)
**/
@FlaggedApi(Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER)
- @NonNull
- default IBinder registerUnbatchedSurfaceControlInputReceiver(int displayId,
+ default void registerUnbatchedSurfaceControlInputReceiver(int displayId,
@NonNull IBinder hostToken, @NonNull SurfaceControl surfaceControl,
@NonNull Looper looper, @NonNull SurfaceControlInputReceiver receiver) {
throw new UnsupportedOperationException(
@@ -6091,17 +6081,32 @@ public interface WindowManager extends ViewManager {
* {@link #registerUnbatchedSurfaceControlInputReceiver(int, IBinder, SurfaceControl, Looper,
* SurfaceControlInputReceiver)}
*
- * @param token The token that was returned via
- * {@link #registerBatchedSurfaceControlInputReceiver(int, IBinder,
- * SurfaceControl,
- * Choreographer, SurfaceControlInputReceiver)} or
- * {@link #registerUnbatchedSurfaceControlInputReceiver(int, IBinder,
- * SurfaceControl,
- * Looper, SurfaceControlInputReceiver)}
+ * @param surfaceControl The SurfaceControl to remove and unregister the input channel for.
*/
@FlaggedApi(Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER)
- default void unregisterSurfaceControlInputReceiver(@NonNull IBinder token) {
+ default void unregisterSurfaceControlInputReceiver(@NonNull SurfaceControl surfaceControl) {
throw new UnsupportedOperationException(
"unregisterSurfaceControlInputReceiver is not implemented");
}
+
+ /**
+ * Returns the input client token for the {@link SurfaceControl}. This will only return non null
+ * if the SurfaceControl was registered for input via
+ * { #registerBatchedSurfaceControlInputReceiver(int, IBinder, SurfaceControl, Choreographer,
+ * SurfaceControlInputReceiver)} or
+ * {@link #registerUnbatchedSurfaceControlInputReceiver(int, IBinder, SurfaceControl, Looper,
+ * SurfaceControlInputReceiver)}.
+ * <p>
+ * This is helpful for testing to ensure the test waits for the layer to be registered with
+ * SurfaceFlinger and Input before proceeding with the test.
+ *
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER)
+ @TestApi
+ @Nullable
+ default IBinder getSurfaceControlInputClientToken(@NonNull SurfaceControl surfaceControl) {
+ throw new UnsupportedOperationException(
+ "getSurfaceControlInputClientToken is not implemented");
+ }
}
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index 8d40f9a4f7b1..c49fce5b558f 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -38,10 +38,12 @@ import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
+import android.util.SparseArray;
import android.view.inputmethod.InputMethodManager;
import android.window.ITrustedPresentationListener;
import android.window.TrustedPresentationThresholds;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.FastPrintWriter;
import java.io.FileDescriptor;
@@ -50,7 +52,6 @@ import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.WeakHashMap;
-import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import java.util.function.IntConsumer;
@@ -156,8 +157,9 @@ public final class WindowManagerGlobal {
private final TrustedPresentationListener mTrustedPresentationListener =
new TrustedPresentationListener();
- private final ConcurrentHashMap<IBinder, InputEventReceiver> mSurfaceControlInputReceivers =
- new ConcurrentHashMap<>();
+ @GuardedBy("mSurfaceControlInputReceivers")
+ private final SparseArray<SurfaceControlInputReceiverInfo>
+ mSurfaceControlInputReceivers = new SparseArray<>();
private WindowManagerGlobal() {
}
@@ -816,7 +818,7 @@ public final class WindowManagerGlobal {
mTrustedPresentationListener.removeListener(listener);
}
- IBinder registerBatchedSurfaceControlInputReceiver(int displayId,
+ void registerBatchedSurfaceControlInputReceiver(int displayId,
@NonNull IBinder hostToken, @NonNull SurfaceControl surfaceControl,
@NonNull Choreographer choreographer, @NonNull SurfaceControlInputReceiver receiver) {
IBinder clientToken = new Binder();
@@ -830,19 +832,21 @@ public final class WindowManagerGlobal {
e.rethrowAsRuntimeException();
}
- mSurfaceControlInputReceivers.put(clientToken,
- new BatchedInputEventReceiver(inputChannel, choreographer.getLooper(),
- choreographer) {
- @Override
- public void onInputEvent(InputEvent event) {
- boolean handled = receiver.onInputEvent(event);
- finishInputEvent(event, handled);
- }
- });
- return clientToken;
+ synchronized (mSurfaceControlInputReceivers) {
+ mSurfaceControlInputReceivers.put(surfaceControl.getLayerId(),
+ new SurfaceControlInputReceiverInfo(clientToken,
+ new BatchedInputEventReceiver(inputChannel, choreographer.getLooper(),
+ choreographer) {
+ @Override
+ public void onInputEvent(InputEvent event) {
+ boolean handled = receiver.onInputEvent(event);
+ finishInputEvent(event, handled);
+ }
+ }));
+ }
}
- IBinder registerUnbatchedSurfaceControlInputReceiver(
+ void registerUnbatchedSurfaceControlInputReceiver(
int displayId, @NonNull IBinder hostToken, @NonNull SurfaceControl surfaceControl,
@NonNull Looper looper, @NonNull SurfaceControlInputReceiver receiver) {
IBinder clientToken = new Binder();
@@ -856,32 +860,53 @@ public final class WindowManagerGlobal {
e.rethrowAsRuntimeException();
}
- mSurfaceControlInputReceivers.put(clientToken,
- new InputEventReceiver(inputChannel, looper) {
- @Override
- public void onInputEvent(InputEvent event) {
- boolean handled = receiver.onInputEvent(event);
- finishInputEvent(event, handled);
- }
- });
-
- return clientToken;
+ synchronized (mSurfaceControlInputReceivers) {
+ mSurfaceControlInputReceivers.put(surfaceControl.getLayerId(),
+ new SurfaceControlInputReceiverInfo(clientToken,
+ new InputEventReceiver(inputChannel, looper) {
+ @Override
+ public void onInputEvent(InputEvent event) {
+ boolean handled = receiver.onInputEvent(event);
+ finishInputEvent(event, handled);
+ }
+ }));
+ }
}
- void unregisterSurfaceControlInputReceiver(IBinder token) {
- InputEventReceiver inputEventReceiver = mSurfaceControlInputReceivers.get(token);
- if (inputEventReceiver == null) {
- Log.w(TAG, "No registered input event receiver with token: " + token);
+ void unregisterSurfaceControlInputReceiver(SurfaceControl surfaceControl) {
+ SurfaceControlInputReceiverInfo surfaceControlInputReceiverInfo;
+ synchronized (mSurfaceControlInputReceivers) {
+ surfaceControlInputReceiverInfo = mSurfaceControlInputReceivers.removeReturnOld(
+ surfaceControl.getLayerId());
+ }
+
+ if (surfaceControlInputReceiverInfo == null) {
+ Log.w(TAG, "No registered input event receiver with sc: " + surfaceControl);
return;
}
try {
- WindowManagerGlobal.getWindowSession().remove(token);
+ WindowManagerGlobal.getWindowSession().remove(
+ surfaceControlInputReceiverInfo.mClientToken);
} catch (RemoteException e) {
Log.e(TAG, "Failed to remove input channel", e);
e.rethrowAsRuntimeException();
}
- inputEventReceiver.dispose();
+ surfaceControlInputReceiverInfo.mInputEventReceiver.dispose();
+ }
+
+ IBinder getSurfaceControlInputClientToken(SurfaceControl surfaceControl) {
+ SurfaceControlInputReceiverInfo surfaceControlInputReceiverInfo;
+ synchronized (mSurfaceControlInputReceivers) {
+ surfaceControlInputReceiverInfo = mSurfaceControlInputReceivers.get(
+ surfaceControl.getLayerId());
+ }
+
+ if (surfaceControlInputReceiverInfo == null) {
+ Log.w(TAG, "No registered input event receiver with sc: " + surfaceControl);
+ return null;
+ }
+ return surfaceControlInputReceiverInfo.mClientToken;
}
private final class TrustedPresentationListener extends
@@ -976,6 +1001,17 @@ public final class WindowManagerGlobal {
throw e.rethrowFromSystemServer();
}
}
+
+ private static class SurfaceControlInputReceiverInfo {
+ final IBinder mClientToken;
+ final InputEventReceiver mInputEventReceiver;
+
+ private SurfaceControlInputReceiverInfo(IBinder clientToken,
+ InputEventReceiver inputEventReceiver) {
+ mClientToken = clientToken;
+ mInputEventReceiver = inputEventReceiver;
+ }
+ }
}
final class WindowLeaked extends AndroidRuntimeException {
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index aaf5fcc6f095..41d181c1b10c 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -523,26 +523,30 @@ public final class WindowManagerImpl implements WindowManager {
mGlobal.unregisterTrustedPresentationListener(listener);
}
- @NonNull
@Override
- public IBinder registerBatchedSurfaceControlInputReceiver(int displayId,
+ public void registerBatchedSurfaceControlInputReceiver(int displayId,
@NonNull IBinder hostToken, @NonNull SurfaceControl surfaceControl,
@NonNull Choreographer choreographer, @NonNull SurfaceControlInputReceiver receiver) {
- return mGlobal.registerBatchedSurfaceControlInputReceiver(displayId, hostToken,
+ mGlobal.registerBatchedSurfaceControlInputReceiver(displayId, hostToken,
surfaceControl, choreographer, receiver);
}
- @NonNull
@Override
- public IBinder registerUnbatchedSurfaceControlInputReceiver(
+ public void registerUnbatchedSurfaceControlInputReceiver(
int displayId, @NonNull IBinder hostToken, @NonNull SurfaceControl surfaceControl,
@NonNull Looper looper, @NonNull SurfaceControlInputReceiver receiver) {
- return mGlobal.registerUnbatchedSurfaceControlInputReceiver(displayId, hostToken,
+ mGlobal.registerUnbatchedSurfaceControlInputReceiver(displayId, hostToken,
surfaceControl, looper, receiver);
}
@Override
- public void unregisterSurfaceControlInputReceiver(@NonNull IBinder token) {
- mGlobal.unregisterSurfaceControlInputReceiver(token);
+ public void unregisterSurfaceControlInputReceiver(@NonNull SurfaceControl surfaceControl) {
+ mGlobal.unregisterSurfaceControlInputReceiver(surfaceControl);
+ }
+
+ @Override
+ @Nullable
+ public IBinder getSurfaceControlInputClientToken(@NonNull SurfaceControl surfaceControl) {
+ return mGlobal.getSurfaceControlInputClientToken(surfaceControl);
}
}
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index b95e4595d6b9..c4d18c6a8da5 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -534,9 +534,8 @@ public class WindowlessWindowManager implements IWindowSession {
}
@Override
- public android.os.Bundle sendWallpaperCommand(android.os.IBinder window,
+ public void sendWallpaperCommand(android.os.IBinder window,
java.lang.String action, int x, int y, int z, android.os.Bundle extras, boolean sync) {
- return null;
}
@Override
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index 49d2ceb8fecf..7782fd7e6c2a 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -66,6 +66,7 @@ import android.view.View;
import android.view.accessibility.AccessibilityEvent.EventType;
import com.android.internal.R;
+import com.android.internal.accessibility.common.ShortcutConstants;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IntPair;
@@ -155,22 +156,6 @@ public final class AccessibilityManager {
public static final String ACTION_CHOOSE_ACCESSIBILITY_BUTTON =
"com.android.internal.intent.action.CHOOSE_ACCESSIBILITY_BUTTON";
- /**
- * Used as an int value for accessibility chooser activity to represent the accessibility button
- * shortcut type.
- *
- * @hide
- */
- public static final int ACCESSIBILITY_BUTTON = 0;
-
- /**
- * Used as an int value for accessibility chooser activity to represent hardware key shortcut,
- * such as volume key button.
- *
- * @hide
- */
- public static final int ACCESSIBILITY_SHORTCUT_KEY = 1;
-
/** @hide */
public static final int FLASH_REASON_CALL = 1;
@@ -184,32 +169,6 @@ public final class AccessibilityManager {
public static final int FLASH_REASON_PREVIEW = 4;
/**
- * Annotations for the shortcut type.
- * <p>Note: Keep in sync with {@link #SHORTCUT_TYPES}.</p>
- * @hide
- */
- @Retention(RetentionPolicy.SOURCE)
- @IntDef(value = {
- // LINT.IfChange(shortcut_type_intdef)
- ACCESSIBILITY_BUTTON,
- ACCESSIBILITY_SHORTCUT_KEY
- // LINT.ThenChange(:shortcut_type_array)
- })
- public @interface ShortcutType {}
-
- /**
- * Used for iterating through {@link ShortcutType}.
- * <p>Note: Keep in sync with {@link ShortcutType}.</p>
- * @hide
- */
- public static final int[] SHORTCUT_TYPES = {
- // LINT.IfChange(shortcut_type_array)
- ACCESSIBILITY_BUTTON,
- ACCESSIBILITY_SHORTCUT_KEY,
- // LINT.ThenChange(:shortcut_type_intdef)
- };
-
- /**
* Annotations for content flag of UI.
* @hide
*/
@@ -1785,7 +1744,8 @@ public final class AccessibilityManager {
@TestApi
@RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
@NonNull
- public List<String> getAccessibilityShortcutTargets(@ShortcutType int shortcutType) {
+ public List<String> getAccessibilityShortcutTargets(
+ @ShortcutConstants.UserShortcutType int shortcutType) {
final IAccessibilityManager service;
synchronized (mLock) {
service = getServiceLocked();
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index dbeffc89fa09..559ccfea72c2 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -298,6 +298,15 @@ public final class AutofillManager {
"android.view.autofill.extra.AUGMENTED_AUTOFILL_CLIENT";
/**
+ * Internal extra used to pass the fill request id in client state of
+ * {@link ConvertCredentialResponse}
+ *
+ * @hide
+ */
+ public static final String EXTRA_AUTOFILL_REQUEST_ID =
+ "android.view.autofill.extra.AUTOFILL_REQUEST_ID";
+
+ /**
* Autofill Hint to indicate that it can match any field.
*
* @hide
diff --git a/core/java/android/view/autofill/OWNERS b/core/java/android/view/autofill/OWNERS
index 37c6f5bf3425..898947adcd1b 100644
--- a/core/java/android/view/autofill/OWNERS
+++ b/core/java/android/view/autofill/OWNERS
@@ -4,6 +4,7 @@ simranjit@google.com
haoranzhang@google.com
skxu@google.com
yunicorn@google.com
+reemabajwa@google.com
# Bug component: 543785 = per-file *Augmented*
per-file *Augmented* = wangqi@google.com
diff --git a/core/java/android/window/ScreenCapture.java b/core/java/android/window/ScreenCapture.java
index befb0023ebe6..544642811a39 100644
--- a/core/java/android/window/ScreenCapture.java
+++ b/core/java/android/window/ScreenCapture.java
@@ -30,6 +30,8 @@ import android.os.Parcelable;
import android.util.Log;
import android.view.SurfaceControl;
+import com.android.window.flags.Flags;
+
import libcore.util.NativeAllocationRegistry;
import java.util.concurrent.CountDownLatch;
@@ -48,7 +50,7 @@ public class ScreenCapture {
private static native int nativeCaptureDisplay(DisplayCaptureArgs captureArgs,
long captureListener);
private static native int nativeCaptureLayers(LayerCaptureArgs captureArgs,
- long captureListener);
+ long captureListener, boolean sync);
private static native long nativeCreateScreenCaptureListener(
ObjIntConsumer<ScreenshotHardwareBuffer> consumer);
private static native void nativeWriteListenerToParcel(long nativeObject, Parcel out);
@@ -134,7 +136,8 @@ public class ScreenCapture {
*/
public static ScreenshotHardwareBuffer captureLayers(LayerCaptureArgs captureArgs) {
SynchronousScreenCaptureListener syncScreenCapture = createSyncCaptureListener();
- int status = captureLayers(captureArgs, syncScreenCapture);
+ int status = nativeCaptureLayers(captureArgs, syncScreenCapture.mNativeObject,
+ Flags.syncScreenCapture());
if (status != 0) {
return null;
}
@@ -171,7 +174,7 @@ public class ScreenCapture {
*/
public static int captureLayers(@NonNull LayerCaptureArgs captureArgs,
@NonNull ScreenCaptureListener captureListener) {
- return nativeCaptureLayers(captureArgs, captureListener.mNativeObject);
+ return nativeCaptureLayers(captureArgs, captureListener.mNativeObject, false /* sync */);
}
/**
@@ -674,7 +677,7 @@ public class ScreenCapture {
* This listener can only be used for a single call to capture content call.
*/
public static class ScreenCaptureListener implements Parcelable {
- private final long mNativeObject;
+ final long mNativeObject;
private static final NativeAllocationRegistry sRegistry =
NativeAllocationRegistry.createMalloced(
ScreenCaptureListener.class.getClassLoader(), getNativeListenerFinalizer());
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index 86804c6117c7..65075aea7b27 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -226,9 +226,6 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
setTopOnBackInvokedCallback(null);
}
- // We should also stop running animations since all callbacks have been removed.
- // note: mSpring.skipToEnd(), in ProgressAnimator.reset(), requires the main handler.
- Handler.getMain().post(mProgressAnimator::reset);
mAllCallbacks.clear();
mOnBackInvokedCallbacks.clear();
}
@@ -442,8 +439,7 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
return WindowOnBackInvokedDispatcher
.isOnBackInvokedCallbackEnabled(activityInfo, applicationInfo,
- () -> originalContext.obtainStyledAttributes(
- new int[] {android.R.attr.windowSwipeToDismiss}), true);
+ () -> originalContext);
}
@Override
@@ -501,7 +497,7 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
*/
public static boolean isOnBackInvokedCallbackEnabled(@Nullable ActivityInfo activityInfo,
@NonNull ApplicationInfo applicationInfo,
- @NonNull Supplier<TypedArray> windowAttrSupplier, boolean recycleTypedArray) {
+ @NonNull Supplier<Context> contextSupplier) {
// new back is enabled if the feature flag is enabled AND the app does not explicitly
// request legacy back.
if (!ENABLE_PREDICTIVE_BACK) {
@@ -547,15 +543,15 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
// setTrigger(true)
// Use the original context to resolve the styled attribute so that they stay
// true to the window.
- TypedArray windowAttr = windowAttrSupplier.get();
+ final Context context = contextSupplier.get();
boolean windowSwipeToDismiss = true;
- if (windowAttr != null) {
- if (windowAttr.getIndexCount() > 0) {
- windowSwipeToDismiss = windowAttr.getBoolean(0, true);
- }
- if (recycleTypedArray) {
- windowAttr.recycle();
+ if (context != null) {
+ final TypedArray array = context.obtainStyledAttributes(
+ new int[]{android.R.attr.windowSwipeToDismiss});
+ if (array.getIndexCount() > 0) {
+ windowSwipeToDismiss = array.getBoolean(0, true);
}
+ array.recycle();
}
if (DEBUG) {
diff --git a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
index edfbea4f51a4..1de77f6d29e7 100644
--- a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
+++ b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
@@ -58,3 +58,11 @@ flag {
bug: "319808237"
is_fixed_read_only: true
}
+
+flag {
+ name: "camera_compat_for_freeform"
+ namespace: "large_screen_experiences_app_compat"
+ description: "Whether to apply Camera Compat treatment to fixed-orientation apps in freeform windowing mode"
+ bug: "314952133"
+ is_fixed_read_only: true
+}
diff --git a/core/java/android/window/flags/window_surfaces.aconfig b/core/java/android/window/flags/window_surfaces.aconfig
index 751c1a8dd0ff..069affb4c06c 100644
--- a/core/java/android/window/flags/window_surfaces.aconfig
+++ b/core/java/android/window/flags/window_surfaces.aconfig
@@ -88,3 +88,11 @@ flag {
is_fixed_read_only: true
bug: "304574518"
}
+
+flag {
+ namespace: "window_surfaces"
+ name: "sync_screen_capture"
+ description: "Create a screen capture API that blocks in SurfaceFlinger"
+ is_fixed_read_only: true
+ bug: "321263247"
+}
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 2c5fbd7f3725..f234637a7d82 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -38,14 +38,6 @@ flag {
}
flag {
- name: "draw_magnifier_border_outside_wmlock"
- namespace: "windowing_frontend"
- description: "Avoid holding WM locks for a long time when executing lockCanvas"
- bug: "316075123"
- is_fixed_read_only: true
-}
-
-flag {
name: "introduce_smoother_dimmer"
namespace: "windowing_frontend"
description: "Refactor dim to fix flickers"
diff --git a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
index de0f070b01a3..b4395a77c24b 100644
--- a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
+++ b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
@@ -18,7 +18,6 @@ package com.android.internal.accessibility;
import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_ALL_MASK;
import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG;
-import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
import static com.android.internal.accessibility.dialog.AccessibilityTargetHelper.getTargets;
import static com.android.internal.os.RoSystemProperties.SUPPORT_ONE_HANDED_MODE;
@@ -329,7 +328,8 @@ public class AccessibilityShortcutController {
}
private AlertDialog createShortcutWarningDialog(int userId) {
- List<AccessibilityTarget> targets = getTargets(mContext, ACCESSIBILITY_SHORTCUT_KEY);
+ List<AccessibilityTarget> targets = getTargets(mContext,
+ ShortcutConstants.UserShortcutType.HARDWARE);
if (targets.size() == 0) {
return null;
}
@@ -541,7 +541,7 @@ public class AccessibilityShortcutController {
private ComponentName getShortcutTargetComponentName() {
final List<String> shortcutTargets = mFrameworkObjectProvider
.getAccessibilityManagerInstance(mContext)
- .getAccessibilityShortcutTargets(ACCESSIBILITY_SHORTCUT_KEY);
+ .getAccessibilityShortcutTargets(ShortcutConstants.UserShortcutType.HARDWARE);
if (shortcutTargets.size() != 1) {
return null;
}
diff --git a/core/java/com/android/internal/accessibility/common/ShortcutConstants.java b/core/java/com/android/internal/accessibility/common/ShortcutConstants.java
index 7ec8838699b3..353e1826a27a 100644
--- a/core/java/com/android/internal/accessibility/common/ShortcutConstants.java
+++ b/core/java/com/android/internal/accessibility/common/ShortcutConstants.java
@@ -44,19 +44,27 @@ public final class ShortcutConstants {
* choose accessibility shortcut as preferred shortcut.
* {@code TRIPLETAP} for displaying specifying magnification to be toggled via quickly
* tapping screen 3 times as preferred shortcut.
+ * {@code TWO_FINGERS_TRIPLE_TAP} for displaying specifying magnification to be toggled via
+ * quickly tapping screen 3 times with two fingers as preferred shortcut.
*/
@Retention(RetentionPolicy.SOURCE)
- @IntDef({
- UserShortcutType.DEFAULT,
- UserShortcutType.SOFTWARE,
- UserShortcutType.HARDWARE,
- UserShortcutType.TRIPLETAP,
- })
+ @IntDef(
+ flag = true,
+ value = {
+ UserShortcutType.DEFAULT,
+ UserShortcutType.SOFTWARE,
+ UserShortcutType.HARDWARE,
+ UserShortcutType.TRIPLETAP,
+ UserShortcutType.TWO_FINGERS_TRIPLE_TAP,
+ })
public @interface UserShortcutType {
int DEFAULT = 0;
- int SOFTWARE = 1; // 1 << 0
- int HARDWARE = 2; // 1 << 1
- int TRIPLETAP = 4; // 1 << 2
+ // LINT.IfChange(shortcut_type_intdef)
+ int SOFTWARE = 1;
+ int HARDWARE = 1 << 1;
+ int TRIPLETAP = 1 << 2;
+ int TWO_FINGERS_TRIPLE_TAP = 1 << 3;
+ // LINT.ThenChange(:shortcut_type_array)
}
/**
@@ -64,9 +72,12 @@ public final class ShortcutConstants {
* non-default IntDef types.
*/
public static final int[] USER_SHORTCUT_TYPES = {
+ // LINT.IfChange(shortcut_type_array)
UserShortcutType.SOFTWARE,
UserShortcutType.HARDWARE,
- UserShortcutType.TRIPLETAP
+ UserShortcutType.TRIPLETAP,
+ UserShortcutType.TWO_FINGERS_TRIPLE_TAP,
+ // LINT.ThenChange(:shortcut_type_intdef)
};
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityActivityTarget.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityActivityTarget.java
index 063154d9a6d6..33048dca12b0 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityActivityTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityActivityTarget.java
@@ -17,14 +17,13 @@
package com.android.internal.accessibility.dialog;
import static com.android.internal.accessibility.util.ShortcutUtils.convertToKey;
-import static com.android.internal.accessibility.util.ShortcutUtils.convertToUserType;
import static com.android.internal.accessibility.util.ShortcutUtils.isShortcutContained;
import android.accessibilityservice.AccessibilityShortcutInfo;
import android.annotation.NonNull;
import android.content.Context;
-import android.view.accessibility.AccessibilityManager.ShortcutType;
+import com.android.internal.accessibility.common.ShortcutConstants;
import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
import com.android.internal.accessibility.common.ShortcutConstants.ShortcutMenuMode;
@@ -33,7 +32,8 @@ import com.android.internal.accessibility.common.ShortcutConstants.ShortcutMenuM
*/
class AccessibilityActivityTarget extends AccessibilityTarget {
- AccessibilityActivityTarget(Context context, @ShortcutType int shortcutType,
+ AccessibilityActivityTarget(Context context,
+ @ShortcutConstants.UserShortcutType int shortcutType,
@NonNull AccessibilityShortcutInfo shortcutInfo) {
super(context,
shortcutType,
@@ -44,7 +44,7 @@ class AccessibilityActivityTarget extends AccessibilityTarget {
shortcutInfo.getActivityInfo().applicationInfo.uid,
shortcutInfo.getActivityInfo().loadLabel(context.getPackageManager()),
shortcutInfo.getActivityInfo().loadIcon(context.getPackageManager()),
- convertToKey(convertToUserType(shortcutType)));
+ convertToKey(shortcutType));
}
@Override
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityButtonChooserActivity.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityButtonChooserActivity.java
index 7eb09e59601b..e084ebdaf3a8 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityButtonChooserActivity.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityButtonChooserActivity.java
@@ -17,7 +17,6 @@
package com.android.internal.accessibility.dialog;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
-import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_COMPONENT_NAME;
import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
@@ -36,6 +35,7 @@ import android.widget.GridView;
import android.widget.TextView;
import com.android.internal.R;
+import com.android.internal.accessibility.common.ShortcutConstants;
import com.android.internal.widget.ResolverDrawerLayout;
import java.util.ArrayList;
@@ -85,7 +85,7 @@ public class AccessibilityButtonChooserActivity extends Activity {
prompt.setVisibility(View.VISIBLE);
}
- mTargets.addAll(getTargets(this, ACCESSIBILITY_BUTTON));
+ mTargets.addAll(getTargets(this, ShortcutConstants.UserShortcutType.SOFTWARE));
final GridView gridview = findViewById(R.id.accessibility_button_chooser_grid);
gridview.setAdapter(new ButtonTargetAdapter(mTargets));
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityServiceTarget.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityServiceTarget.java
index 2b6913ca5e5a..7406da42507f 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityServiceTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityServiceTarget.java
@@ -17,14 +17,13 @@
package com.android.internal.accessibility.dialog;
import static com.android.internal.accessibility.util.ShortcutUtils.convertToKey;
-import static com.android.internal.accessibility.util.ShortcutUtils.convertToUserType;
import static com.android.internal.accessibility.util.ShortcutUtils.isShortcutContained;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.annotation.NonNull;
import android.content.Context;
-import android.view.accessibility.AccessibilityManager.ShortcutType;
+import com.android.internal.accessibility.common.ShortcutConstants;
import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
import com.android.internal.accessibility.common.ShortcutConstants.ShortcutMenuMode;
@@ -36,7 +35,9 @@ class AccessibilityServiceTarget extends AccessibilityTarget {
private final AccessibilityServiceInfo mAccessibilityServiceInfo;
- AccessibilityServiceTarget(Context context, @ShortcutType int shortcutType,
+ AccessibilityServiceTarget(
+ Context context,
+ @ShortcutConstants.UserShortcutType int shortcutType,
@AccessibilityFragmentType int fragmentType,
@NonNull AccessibilityServiceInfo serviceInfo) {
super(context,
@@ -48,7 +49,7 @@ class AccessibilityServiceTarget extends AccessibilityTarget {
serviceInfo.getResolveInfo().serviceInfo.applicationInfo.uid,
serviceInfo.getResolveInfo().loadLabel(context.getPackageManager()),
serviceInfo.getResolveInfo().loadIcon(context.getPackageManager()),
- convertToKey(convertToUserType(shortcutType)));
+ convertToKey(shortcutType));
mAccessibilityServiceInfo = serviceInfo;
}
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java
index 2e80b7e19516..8e2ec1bfd53d 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java
@@ -15,10 +15,6 @@
*/
package com.android.internal.accessibility.dialog;
-import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
-import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
-import static android.view.accessibility.AccessibilityManager.ShortcutType;
-
import static com.android.internal.accessibility.common.ShortcutConstants.ShortcutMenuMode;
import static com.android.internal.accessibility.dialog.AccessibilityTargetHelper.createEnableDialogContentView;
import static com.android.internal.accessibility.dialog.AccessibilityTargetHelper.getInstalledTargets;
@@ -42,6 +38,7 @@ import android.view.accessibility.Flags;
import android.widget.AdapterView;
import com.android.internal.R;
+import com.android.internal.accessibility.common.ShortcutConstants;
import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
@@ -52,8 +49,8 @@ import java.util.List;
* activity or allowlisting feature for volume key shortcut.
*/
public class AccessibilityShortcutChooserActivity extends Activity {
- @ShortcutType
- private final int mShortcutType = ACCESSIBILITY_SHORTCUT_KEY;
+ @ShortcutConstants.UserShortcutType
+ private final int mShortcutType = ShortcutConstants.UserShortcutType.HARDWARE;
private static final String KEY_ACCESSIBILITY_SHORTCUT_MENU_MODE =
"accessibility_shortcut_menu_mode";
private final List<AccessibilityTarget> mTargets = new ArrayList<>();
@@ -246,7 +243,7 @@ public class AccessibilityShortcutChooserActivity extends Activity {
mTargetAdapter.getShortcutMenuMode() == ShortcutMenuMode.EDIT;
final int selectDialogTitleId = R.string.accessibility_select_shortcut_menu_title;
final int editDialogTitleId =
- mShortcutType == ACCESSIBILITY_BUTTON
+ mShortcutType == ShortcutConstants.UserShortcutType.SOFTWARE
? R.string.accessibility_edit_shortcut_menu_button_title
: R.string.accessibility_edit_shortcut_menu_volume_title;
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java
index 652cb5233461..4ab1ee9d6b46 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityTarget.java
@@ -16,10 +16,6 @@
package com.android.internal.accessibility.dialog;
-import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
-import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
-
-import static com.android.internal.accessibility.util.ShortcutUtils.convertToUserType;
import static com.android.internal.accessibility.util.ShortcutUtils.optInValueToSettings;
import static com.android.internal.accessibility.util.ShortcutUtils.optOutValueFromSettings;
@@ -30,7 +26,6 @@ import android.content.Context;
import android.graphics.drawable.Drawable;
import android.view.View;
import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.AccessibilityManager.ShortcutType;
import com.android.internal.accessibility.common.ShortcutConstants;
import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
@@ -47,7 +42,7 @@ import com.android.internal.annotations.VisibleForTesting;
public abstract class AccessibilityTarget implements TargetOperations, OnTargetSelectedListener,
OnTargetCheckedChangeListener {
private Context mContext;
- @ShortcutType
+ @ShortcutConstants.UserShortcutType
private int mShortcutType;
@AccessibilityFragmentType
private int mFragmentType;
@@ -61,7 +56,8 @@ public abstract class AccessibilityTarget implements TargetOperations, OnTargetS
private CharSequence mStateDescription;
@VisibleForTesting
- public AccessibilityTarget(Context context, @ShortcutType int shortcutType,
+ public AccessibilityTarget(
+ Context context, @ShortcutConstants.UserShortcutType int shortcutType,
@AccessibilityFragmentType int fragmentType, boolean isShortcutSwitched, String id,
int uid, CharSequence label, Drawable icon, String key) {
mContext = context;
@@ -99,10 +95,10 @@ public abstract class AccessibilityTarget implements TargetOperations, OnTargetS
final AccessibilityManager am =
getContext().getSystemService(AccessibilityManager.class);
switch (getShortcutType()) {
- case ACCESSIBILITY_BUTTON:
+ case ShortcutConstants.UserShortcutType.SOFTWARE:
am.notifyAccessibilityButtonClicked(getContext().getDisplayId(), getId());
return;
- case ACCESSIBILITY_SHORTCUT_KEY:
+ case ShortcutConstants.UserShortcutType.HARDWARE:
am.performAccessibilityShortcut(getId());
return;
default:
@@ -114,9 +110,9 @@ public abstract class AccessibilityTarget implements TargetOperations, OnTargetS
public void onCheckedChanged(boolean isChecked) {
setShortcutEnabled(isChecked);
if (isChecked) {
- optInValueToSettings(getContext(), convertToUserType(getShortcutType()), getId());
+ optInValueToSettings(getContext(), getShortcutType(), getId());
} else {
- optOutValueFromSettings(getContext(), convertToUserType(getShortcutType()), getId());
+ optOutValueFromSettings(getContext(), getShortcutType(), getId());
}
}
@@ -142,7 +138,7 @@ public abstract class AccessibilityTarget implements TargetOperations, OnTargetS
return mContext;
}
- public @ShortcutType int getShortcutType() {
+ public @ShortcutConstants.UserShortcutType int getShortcutType() {
return mShortcutType;
}
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
index 51a5ddfa8dd6..bd63e23a066f 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
@@ -16,8 +16,6 @@
package com.android.internal.accessibility.dialog;
-import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
-
import static com.android.internal.accessibility.AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME;
import static com.android.internal.accessibility.AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME;
import static com.android.internal.accessibility.AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME;
@@ -41,12 +39,12 @@ import android.text.BidiFormatter;
import android.view.LayoutInflater;
import android.view.View;
import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.AccessibilityManager.ShortcutType;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import com.android.internal.R;
+import com.android.internal.accessibility.common.ShortcutConstants;
import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
import java.util.ArrayList;
@@ -70,8 +68,9 @@ public final class AccessibilityTargetHelper {
* @return The list of {@link AccessibilityTarget}.
* @hide
*/
- public static List<AccessibilityTarget> getTargets(Context context,
- @ShortcutType int shortcutType) {
+ public static List<AccessibilityTarget> getTargets(
+ Context context,
+ @ShortcutConstants.UserShortcutType int shortcutType) {
// List all accessibility target
final List<AccessibilityTarget> installedTargets = getInstalledTargets(context,
shortcutType);
@@ -113,7 +112,7 @@ public final class AccessibilityTargetHelper {
* @return The list of {@link AccessibilityTarget}.
*/
static List<AccessibilityTarget> getInstalledTargets(Context context,
- @ShortcutType int shortcutType) {
+ @ShortcutConstants.UserShortcutType int shortcutType) {
final List<AccessibilityTarget> targets = new ArrayList<>();
targets.addAll(getAccessibilityFilteredTargets(context, shortcutType));
targets.addAll(getAllowListingFeatureTargets(context, shortcutType));
@@ -122,7 +121,7 @@ public final class AccessibilityTargetHelper {
}
private static List<AccessibilityTarget> getAccessibilityFilteredTargets(Context context,
- @ShortcutType int shortcutType) {
+ @ShortcutConstants.UserShortcutType int shortcutType) {
final List<AccessibilityTarget> serviceTargets =
getAccessibilityServiceTargets(context, shortcutType);
final List<AccessibilityTarget> activityTargets =
@@ -155,7 +154,7 @@ public final class AccessibilityTargetHelper {
}
private static List<AccessibilityTarget> getAccessibilityServiceTargets(Context context,
- @ShortcutType int shortcutType) {
+ @ShortcutConstants.UserShortcutType int shortcutType) {
final AccessibilityManager am = (AccessibilityManager) context.getSystemService(
Context.ACCESSIBILITY_SERVICE);
final List<AccessibilityServiceInfo> installedServices =
@@ -171,7 +170,7 @@ public final class AccessibilityTargetHelper {
final boolean hasRequestAccessibilityButtonFlag =
(info.flags & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0;
if ((targetSdk <= Build.VERSION_CODES.Q) && !hasRequestAccessibilityButtonFlag
- && (shortcutType == ACCESSIBILITY_BUTTON)) {
+ && (shortcutType == ShortcutConstants.UserShortcutType.SOFTWARE)) {
continue;
}
@@ -182,7 +181,7 @@ public final class AccessibilityTargetHelper {
}
private static List<AccessibilityTarget> getAccessibilityActivityTargets(Context context,
- @ShortcutType int shortcutType) {
+ @ShortcutConstants.UserShortcutType int shortcutType) {
final AccessibilityManager am = (AccessibilityManager) context.getSystemService(
Context.ACCESSIBILITY_SERVICE);
final List<AccessibilityShortcutInfo> installedServices =
@@ -201,7 +200,7 @@ public final class AccessibilityTargetHelper {
}
private static List<AccessibilityTarget> getAllowListingFeatureTargets(Context context,
- @ShortcutType int shortcutType) {
+ @ShortcutConstants.UserShortcutType int shortcutType) {
final List<AccessibilityTarget> targets = new ArrayList<>();
final int uid = context.getApplicationInfo().uid;
@@ -281,8 +280,10 @@ public final class AccessibilityTargetHelper {
return targets;
}
- private static AccessibilityTarget createAccessibilityServiceTarget(Context context,
- @ShortcutType int shortcutType, @NonNull AccessibilityServiceInfo info) {
+ private static AccessibilityTarget createAccessibilityServiceTarget(
+ Context context,
+ @ShortcutConstants.UserShortcutType int shortcutType,
+ @NonNull AccessibilityServiceInfo info) {
switch (getAccessibilityServiceFragmentType(info)) {
case AccessibilityFragmentType.VOLUME_SHORTCUT_TOGGLE:
return new VolumeShortcutToggleAccessibilityServiceTarget(context, shortcutType,
diff --git a/core/java/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTarget.java b/core/java/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTarget.java
index 1bc8b84e6869..641a9f18e3d6 100644
--- a/core/java/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTarget.java
@@ -16,9 +16,6 @@
package com.android.internal.accessibility.dialog;
-import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
-import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
-
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType;
import static com.android.internal.accessibility.util.AccessibilityUtils.setAccessibilityServiceState;
import static com.android.internal.accessibility.util.ShortcutUtils.isComponentIdExistingInSettings;
@@ -28,7 +25,6 @@ import android.annotation.NonNull;
import android.content.ComponentName;
import android.content.Context;
import android.os.UserHandle;
-import android.view.accessibility.AccessibilityManager.ShortcutType;
import android.view.accessibility.Flags;
import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
@@ -45,7 +41,7 @@ import java.util.Set;
public class InvisibleToggleAccessibilityServiceTarget extends AccessibilityServiceTarget {
public InvisibleToggleAccessibilityServiceTarget(
- Context context, @ShortcutType int shortcutType,
+ Context context, @UserShortcutType int shortcutType,
@NonNull AccessibilityServiceInfo serviceInfo) {
super(context,
shortcutType,
@@ -72,10 +68,10 @@ public class InvisibleToggleAccessibilityServiceTarget extends AccessibilityServ
private boolean isComponentIdExistingInOtherShortcut() {
switch (getShortcutType()) {
- case ACCESSIBILITY_BUTTON:
+ case UserShortcutType.SOFTWARE:
return isComponentIdExistingInSettings(getContext(), UserShortcutType.HARDWARE,
getId());
- case ACCESSIBILITY_SHORTCUT_KEY:
+ case UserShortcutType.HARDWARE:
return isComponentIdExistingInSettings(getContext(), UserShortcutType.SOFTWARE,
getId());
default:
diff --git a/core/java/com/android/internal/accessibility/dialog/InvisibleToggleAllowListingFeatureTarget.java b/core/java/com/android/internal/accessibility/dialog/InvisibleToggleAllowListingFeatureTarget.java
index c22f17dfa967..2204c0beb4fd 100644
--- a/core/java/com/android/internal/accessibility/dialog/InvisibleToggleAllowListingFeatureTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/InvisibleToggleAllowListingFeatureTarget.java
@@ -18,8 +18,8 @@ package com.android.internal.accessibility.dialog;
import android.content.Context;
import android.graphics.drawable.Drawable;
-import android.view.accessibility.AccessibilityManager.ShortcutType;
+import com.android.internal.accessibility.common.ShortcutConstants;
import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
/**
@@ -28,7 +28,8 @@ import com.android.internal.accessibility.common.ShortcutConstants.Accessibility
*/
class InvisibleToggleAllowListingFeatureTarget extends AccessibilityTarget {
- InvisibleToggleAllowListingFeatureTarget(Context context, @ShortcutType int shortcutType,
+ InvisibleToggleAllowListingFeatureTarget(Context context,
+ @ShortcutConstants.UserShortcutType int shortcutType,
boolean isShortcutSwitched, String id, int uid, CharSequence label, Drawable icon,
String key) {
super(context, shortcutType, AccessibilityFragmentType.INVISIBLE_TOGGLE, isShortcutSwitched,
diff --git a/core/java/com/android/internal/accessibility/dialog/ToggleAccessibilityServiceTarget.java b/core/java/com/android/internal/accessibility/dialog/ToggleAccessibilityServiceTarget.java
index a4ffef6bfbc2..a6ef73e90772 100644
--- a/core/java/com/android/internal/accessibility/dialog/ToggleAccessibilityServiceTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/ToggleAccessibilityServiceTarget.java
@@ -22,9 +22,9 @@ import android.accessibilityservice.AccessibilityServiceInfo;
import android.annotation.NonNull;
import android.content.Context;
import android.view.View;
-import android.view.accessibility.AccessibilityManager.ShortcutType;
import com.android.internal.R;
+import com.android.internal.accessibility.common.ShortcutConstants;
import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
import com.android.internal.accessibility.common.ShortcutConstants.ShortcutMenuMode;
import com.android.internal.accessibility.dialog.TargetAdapter.ViewHolder;
@@ -45,7 +45,8 @@ class ToggleAccessibilityServiceTarget extends AccessibilityServiceTarget {
float DISABLED = 0.5f;
}
- ToggleAccessibilityServiceTarget(Context context, @ShortcutType int shortcutType,
+ ToggleAccessibilityServiceTarget(Context context,
+ @ShortcutConstants.UserShortcutType int shortcutType,
@NonNull AccessibilityServiceInfo serviceInfo) {
super(context,
shortcutType,
diff --git a/core/java/com/android/internal/accessibility/dialog/ToggleAllowListingFeatureTarget.java b/core/java/com/android/internal/accessibility/dialog/ToggleAllowListingFeatureTarget.java
index 11e668f51774..2a9c555efce8 100644
--- a/core/java/com/android/internal/accessibility/dialog/ToggleAllowListingFeatureTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/ToggleAllowListingFeatureTarget.java
@@ -21,9 +21,9 @@ import android.content.Context;
import android.graphics.drawable.Drawable;
import android.provider.Settings;
import android.view.View;
-import android.view.accessibility.AccessibilityManager.ShortcutType;
import com.android.internal.R;
+import com.android.internal.accessibility.common.ShortcutConstants;
import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
import com.android.internal.accessibility.common.ShortcutConstants.ShortcutMenuMode;
import com.android.internal.accessibility.dialog.TargetAdapter.ViewHolder;
@@ -34,7 +34,8 @@ import com.android.internal.accessibility.dialog.TargetAdapter.ViewHolder;
*/
class ToggleAllowListingFeatureTarget extends AccessibilityTarget {
- ToggleAllowListingFeatureTarget(Context context, @ShortcutType int shortcutType,
+ ToggleAllowListingFeatureTarget(Context context,
+ @ShortcutConstants.UserShortcutType int shortcutType,
boolean isShortcutSwitched, String id, int uid, CharSequence label, Drawable icon,
String key) {
super(context, shortcutType, AccessibilityFragmentType.TOGGLE, isShortcutSwitched, id,
diff --git a/core/java/com/android/internal/accessibility/dialog/VolumeShortcutToggleAccessibilityServiceTarget.java b/core/java/com/android/internal/accessibility/dialog/VolumeShortcutToggleAccessibilityServiceTarget.java
index 04f5061fbd8e..4926e7218530 100644
--- a/core/java/com/android/internal/accessibility/dialog/VolumeShortcutToggleAccessibilityServiceTarget.java
+++ b/core/java/com/android/internal/accessibility/dialog/VolumeShortcutToggleAccessibilityServiceTarget.java
@@ -16,9 +16,6 @@
package com.android.internal.accessibility.dialog;
-import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
-import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
-
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType;
import static com.android.internal.accessibility.util.AccessibilityUtils.setAccessibilityServiceState;
import static com.android.internal.accessibility.util.ShortcutUtils.optOutValueFromSettings;
@@ -27,7 +24,6 @@ import android.accessibilityservice.AccessibilityServiceInfo;
import android.annotation.NonNull;
import android.content.ComponentName;
import android.content.Context;
-import android.view.accessibility.AccessibilityManager.ShortcutType;
import android.widget.Toast;
import com.android.internal.R;
@@ -39,7 +35,8 @@ import com.android.internal.accessibility.common.ShortcutConstants.Accessibility
*/
class VolumeShortcutToggleAccessibilityServiceTarget extends AccessibilityServiceTarget {
- VolumeShortcutToggleAccessibilityServiceTarget(Context context, @ShortcutType int shortcutType,
+ VolumeShortcutToggleAccessibilityServiceTarget(Context context,
+ @UserShortcutType int shortcutType,
@NonNull AccessibilityServiceInfo serviceInfo) {
super(context,
shortcutType,
@@ -50,10 +47,10 @@ class VolumeShortcutToggleAccessibilityServiceTarget extends AccessibilityServic
@Override
public void onCheckedChanged(boolean isChecked) {
switch (getShortcutType()) {
- case ACCESSIBILITY_BUTTON:
+ case UserShortcutType.SOFTWARE:
onCheckedFromAccessibilityButton(isChecked);
return;
- case ACCESSIBILITY_SHORTCUT_KEY:
+ case UserShortcutType.HARDWARE:
super.onCheckedChanged(isChecked);
return;
default:
diff --git a/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java b/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java
index 6b074a610818..1e4bcf21c635 100644
--- a/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java
+++ b/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java
@@ -21,8 +21,6 @@ import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_GESTURE
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL;
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
-import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
-import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_COMPONENT_NAME;
import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SERVICE_STATUS__DISABLED;
@@ -47,9 +45,8 @@ import static com.android.internal.util.FrameworkStatsLog.NON_A11Y_TOOL_SERVICE_
import android.content.ComponentName;
import android.content.Context;
import android.provider.Settings;
-import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.AccessibilityManager.ShortcutType;
+import com.android.internal.accessibility.common.ShortcutConstants;
import com.android.internal.util.FrameworkStatsLog;
/** Methods for logging accessibility states. */
@@ -71,15 +68,15 @@ public final class AccessibilityStatsLogUtils {
/**
* Logs accessibility feature name that is assigned to the given {@code shortcutType}.
- * Calls this when clicking the shortcut {@link AccessibilityManager#ACCESSIBILITY_BUTTON} or
- * {@link AccessibilityManager#ACCESSIBILITY_SHORTCUT_KEY}.
+ * Calls this when clicking the shortcut {@link ShortcutConstants.UserShortcutType.SOFTWARE} or
+ * {@link ShortcutConstants.UserShortcutType.HARDWARE}.
*
* @param context context used to retrieve the {@link Settings} provider
* @param componentName component name of the accessibility feature
* @param shortcutType accessibility shortcut type
*/
public static void logAccessibilityShortcutActivated(Context context,
- ComponentName componentName, @ShortcutType int shortcutType) {
+ ComponentName componentName, @ShortcutConstants.UserShortcutType int shortcutType) {
logAccessibilityShortcutActivatedInternal(componentName,
convertToLoggingShortcutType(context, shortcutType), UNKNOWN_STATUS);
}
@@ -87,8 +84,8 @@ public final class AccessibilityStatsLogUtils {
/**
* Logs accessibility feature name that is assigned to the given {@code shortcutType} and the
* {@code serviceEnabled} status.
- * Calls this when clicking the shortcut {@link AccessibilityManager#ACCESSIBILITY_BUTTON}
- * or {@link AccessibilityManager#ACCESSIBILITY_SHORTCUT_KEY}.
+ * Calls this when clicking the shortcut {@link ShortcutConstants.UserShortcutType.SOFTWARE}
+ * or {@link ShortcutConstants.UserShortcutType.HARDWARE}.
*
* @param context context used to retrieve the {@link Settings} provider
* @param componentName component name of the accessibility feature
@@ -96,7 +93,8 @@ public final class AccessibilityStatsLogUtils {
* @param serviceEnabled {@code true} if the service is enabled
*/
public static void logAccessibilityShortcutActivated(Context context,
- ComponentName componentName, @ShortcutType int shortcutType, boolean serviceEnabled) {
+ ComponentName componentName, @ShortcutConstants.UserShortcutType int shortcutType,
+ boolean serviceEnabled) {
logAccessibilityShortcutActivatedInternal(componentName,
convertToLoggingShortcutType(context, shortcutType),
convertToLoggingServiceStatus(serviceEnabled));
@@ -235,9 +233,9 @@ public final class AccessibilityStatsLogUtils {
}
private static int convertToLoggingShortcutType(Context context,
- @ShortcutType int shortcutType) {
+ @ShortcutConstants.UserShortcutType int shortcutType) {
switch (shortcutType) {
- case ACCESSIBILITY_BUTTON:
+ case ShortcutConstants.UserShortcutType.SOFTWARE:
if (isAccessibilityFloatingMenuEnabled(context)) {
return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_FLOATING_MENU;
} else if (isAccessibilityGestureEnabled(context)) {
@@ -245,7 +243,7 @@ public final class AccessibilityStatsLogUtils {
} else {
return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_BUTTON;
}
- case ACCESSIBILITY_SHORTCUT_KEY:
+ case ShortcutConstants.UserShortcutType.HARDWARE:
return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__VOLUME_KEY;
}
return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__UNKNOWN_TYPE;
diff --git a/core/java/com/android/internal/accessibility/util/ShortcutUtils.java b/core/java/com/android/internal/accessibility/util/ShortcutUtils.java
index 3fd303038c57..276c5c4ffdea 100644
--- a/core/java/com/android/internal/accessibility/util/ShortcutUtils.java
+++ b/core/java/com/android/internal/accessibility/util/ShortcutUtils.java
@@ -16,9 +16,6 @@
package com.android.internal.accessibility.util;
-import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
-import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
-
import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
import static com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType.INVISIBLE_TOGGLE;
import static com.android.internal.accessibility.common.ShortcutConstants.SERVICES_SEPARATOR;
@@ -33,7 +30,6 @@ import android.provider.Settings;
import android.text.TextUtils;
import android.util.ArraySet;
import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.AccessibilityManager.ShortcutType;
import java.util.Collections;
import java.util.List;
@@ -144,7 +140,7 @@ public final class ShortcutUtils {
* @param componentId The component id that need to be checked.
* @return {@code true} if a component id is contained.
*/
- public static boolean isShortcutContained(Context context, @ShortcutType int shortcutType,
+ public static boolean isShortcutContained(Context context, @UserShortcutType int shortcutType,
@NonNull String componentId) {
final AccessibilityManager am = (AccessibilityManager) context.getSystemService(
Context.ACCESSIBILITY_SERVICE);
@@ -166,6 +162,8 @@ public final class ShortcutUtils {
return Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE;
case UserShortcutType.TRIPLETAP:
return Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED;
+ case UserShortcutType.TWO_FINGERS_TRIPLE_TAP:
+ return Settings.Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED;
default:
throw new IllegalArgumentException(
"Unsupported user shortcut type: " + type);
@@ -173,24 +171,6 @@ public final class ShortcutUtils {
}
/**
- * Converts {@link ShortcutType} to {@link UserShortcutType}.
- *
- * @param type The shortcut type.
- * @return Mapping type from {@link UserShortcutType}.
- */
- public static @UserShortcutType int convertToUserType(@ShortcutType int type) {
- switch (type) {
- case ACCESSIBILITY_BUTTON:
- return UserShortcutType.SOFTWARE;
- case ACCESSIBILITY_SHORTCUT_KEY:
- return UserShortcutType.HARDWARE;
- default:
- throw new IllegalArgumentException(
- "Unsupported shortcut type:" + type);
- }
- }
-
- /**
* Updates an accessibility state if the accessibility service is a Always-On a11y service,
* a.k.a. AccessibilityServices that has FLAG_REQUEST_ACCESSIBILITY_BUTTON
* <p>
@@ -255,12 +235,13 @@ public final class ShortcutUtils {
public static Set<String> getShortcutTargetsFromSettings(
Context context, @UserShortcutType int shortcutType, int userId) {
final String targetKey = convertToKey(shortcutType);
- if (Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED.equals(targetKey)) {
+ if (Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED.equals(targetKey)
+ || Settings.Secure.ACCESSIBILITY_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP_ENABLED
+ .equals(targetKey)) {
boolean magnificationEnabled = Settings.Secure.getIntForUser(
context.getContentResolver(), targetKey, /* def= */ 0, userId) == 1;
return magnificationEnabled ? Set.of(MAGNIFICATION_CONTROLLER_NAME)
: Collections.emptySet();
-
} else {
final String targetString = Settings.Secure.getStringForUser(
context.getContentResolver(), targetKey, userId);
diff --git a/core/java/com/android/internal/app/ConfirmUserCreationActivity.java b/core/java/com/android/internal/app/ConfirmUserCreationActivity.java
index 0a28997885ff..b4e87498f09b 100644
--- a/core/java/com/android/internal/app/ConfirmUserCreationActivity.java
+++ b/core/java/com/android/internal/app/ConfirmUserCreationActivity.java
@@ -116,6 +116,14 @@ public class ConfirmUserCreationActivity extends AlertActivity
if (cantCreateUser) {
setResult(UserManager.USER_CREATION_FAILED_NOT_PERMITTED);
return null;
+ } else if (!(isUserPropertyWithinLimit(mUserName, UserManager.MAX_USER_NAME_LENGTH)
+ && isUserPropertyWithinLimit(mAccountName, UserManager.MAX_ACCOUNT_STRING_LENGTH)
+ && isUserPropertyWithinLimit(mAccountType, UserManager.MAX_ACCOUNT_STRING_LENGTH))
+ || (mAccountOptions != null && !mAccountOptions.isBundleContentsWithinLengthLimit(
+ UserManager.MAX_ACCOUNT_OPTIONS_LENGTH))) {
+ setResult(UserManager.USER_CREATION_FAILED_NOT_PERMITTED);
+ Log.i(TAG, "User properties must not exceed their character limits");
+ return null;
} else if (cantCreateAnyMoreUsers) {
setResult(UserManager.USER_CREATION_FAILED_NO_MORE_USERS);
return null;
@@ -144,4 +152,8 @@ public class ConfirmUserCreationActivity extends AlertActivity
}
finish();
}
+
+ private boolean isUserPropertyWithinLimit(String property, int limit) {
+ return property == null || property.length() <= limit;
+ }
}
diff --git a/core/java/com/android/internal/display/BrightnessSynchronizer.java b/core/java/com/android/internal/display/BrightnessSynchronizer.java
index 37aaa72cb7a0..006849034fbd 100644
--- a/core/java/com/android/internal/display/BrightnessSynchronizer.java
+++ b/core/java/com/android/internal/display/BrightnessSynchronizer.java
@@ -47,6 +47,7 @@ import java.io.PrintWriter;
* (new) system for storing the brightness. It has methods to convert between the two and also
* observes for when one of the settings is changed and syncs this with the other.
*/
+@android.ravenwood.annotation.RavenwoodKeepPartialClass
public class BrightnessSynchronizer {
private static final String TAG = "BrightnessSynchronizer";
@@ -282,6 +283,7 @@ public class BrightnessSynchronizer {
* @param b second float to compare
* @return whether the two values are within a small enough tolerance value
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public static boolean floatEquals(float a, float b) {
if (a == b) {
return true;
diff --git a/core/java/com/android/internal/jank/Cuj.java b/core/java/com/android/internal/jank/Cuj.java
index 96740c59ec06..7b3565bff533 100644
--- a/core/java/com/android/internal/jank/Cuj.java
+++ b/core/java/com/android/internal/jank/Cuj.java
@@ -121,10 +121,11 @@ public class Cuj {
public static final int CUJ_PREDICTIVE_BACK_CROSS_TASK = 85;
public static final int CUJ_PREDICTIVE_BACK_HOME = 86;
public static final int CUJ_LAUNCHER_SEARCH_QSB_OPEN = 87;
+ public static final int CUJ_BACK_PANEL_ARROW = 88;
// When adding a CUJ, update this and make sure to also update CUJ_TO_STATSD_INTERACTION_TYPE.
@VisibleForTesting
- static final int LAST_CUJ = CUJ_LAUNCHER_SEARCH_QSB_OPEN;
+ static final int LAST_CUJ = CUJ_BACK_PANEL_ARROW;
/** @hide */
@IntDef({
@@ -207,6 +208,7 @@ public class Cuj {
CUJ_PREDICTIVE_BACK_CROSS_TASK,
CUJ_PREDICTIVE_BACK_HOME,
CUJ_LAUNCHER_SEARCH_QSB_OPEN,
+ CUJ_BACK_PANEL_ARROW,
})
@Retention(RetentionPolicy.SOURCE)
public @interface CujType {
@@ -298,8 +300,8 @@ public class Cuj {
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_CROSS_ACTIVITY;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PREDICTIVE_BACK_CROSS_TASK] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_CROSS_TASK;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PREDICTIVE_BACK_HOME] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_HOME;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_SEARCH_QSB_OPEN] =
- FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_SEARCH_QSB_OPEN;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_SEARCH_QSB_OPEN] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_SEARCH_QSB_OPEN;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_BACK_PANEL_ARROW] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__BACK_PANEL_ARROW;
}
private Cuj() {
@@ -474,6 +476,8 @@ public class Cuj {
return "PREDICTIVE_BACK_HOME";
case CUJ_LAUNCHER_SEARCH_QSB_OPEN:
return "LAUNCHER_SEARCH_QSB_OPEN";
+ case CUJ_BACK_PANEL_ARROW:
+ return "BACK_PANEL_ARROW";
}
return "UNKNOWN";
}
diff --git a/core/java/com/android/internal/jank/InteractionMonitorDebugOverlay.java b/core/java/com/android/internal/jank/InteractionMonitorDebugOverlay.java
index f3f16a0c662d..d9cac12c3372 100644
--- a/core/java/com/android/internal/jank/InteractionMonitorDebugOverlay.java
+++ b/core/java/com/android/internal/jank/InteractionMonitorDebugOverlay.java
@@ -28,6 +28,7 @@ import android.graphics.RecordingCanvas;
import android.graphics.Rect;
import android.os.Handler;
import android.os.Trace;
+import android.util.Log;
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.view.WindowCallbacks;
@@ -52,6 +53,7 @@ import com.android.internal.jank.FrameTracker.Reasons;
* @hide
*/
class InteractionMonitorDebugOverlay implements WindowCallbacks {
+ private static final String TAG = "InteractionMonitorDebug";
private static final int REASON_STILL_RUNNING = -1000;
private final Object mLock;
// Sparse array where the key in the CUJ and the value is the session status, or null if
@@ -77,7 +79,7 @@ class InteractionMonitorDebugOverlay implements WindowCallbacks {
mDebugPaint.setAntiAlias(false);
mDebugFontMetrics = new Paint.FontMetrics();
final Context context = ActivityThread.currentApplication();
- mPackageName = context.getPackageName();
+ mPackageName = context == null ? "null" : context.getPackageName();
}
@UiThread
@@ -153,8 +155,14 @@ class InteractionMonitorDebugOverlay implements WindowCallbacks {
SparseArray<InteractionJankMonitor.RunningTracker> runningTrackers) {
synchronized (mLock) {
mRunningCujs.put(removedCuj, reason);
+ boolean isLoggable = Log.isLoggable(TAG, Log.DEBUG);
+ if (isLoggable) {
+ String cujName = Cuj.getNameOfCuj(removedCuj);
+ Log.d(TAG, cujName + (reason == REASON_END_NORMAL ? " ended" : " cancelled"));
+ }
// If REASON_STILL_RUNNING is not in mRunningCujs, then all CUJs have ended
if (mRunningCujs.indexOfValue(REASON_STILL_RUNNING) < 0) {
+ if (isLoggable) Log.d(TAG, "All CUJs ended");
mRunningCujs.clear();
dispose();
} else {
@@ -186,6 +194,10 @@ class InteractionMonitorDebugOverlay implements WindowCallbacks {
@UiThread
void onTrackerAdded(@Cuj.CujType int addedCuj, InteractionJankMonitor.RunningTracker tracker) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ String cujName = Cuj.getNameOfCuj(addedCuj);
+ Log.d(TAG, cujName + " started");
+ }
synchronized (mLock) {
// Use REASON_STILL_RUNNING (not technically one of the '@Reasons') to indicate the CUJ
// is still running
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedIntentInfoUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedIntentInfoUtils.java
index c6683cfc8331..05728eee174f 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedIntentInfoUtils.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedIntentInfoUtils.java
@@ -18,9 +18,13 @@ package com.android.internal.pm.pkg.component;
import static com.android.internal.pm.pkg.parsing.ParsingUtils.ANDROID_RES_NAMESPACE;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.UriRelativeFilter;
+import android.content.UriRelativeFilterGroup;
+import android.content.pm.Flags;
import android.content.pm.parsing.result.ParseInput;
import android.content.pm.parsing.result.ParseResult;
import android.content.res.Resources;
@@ -132,6 +136,11 @@ public class ParsedIntentInfoUtils {
case "data":
result = parseData(intentInfo, res, parser, allowGlobs, input);
break;
+ case "uri-relative-filter-group":
+ if (Flags.relativeReferenceIntentFilters()) {
+ result = parseRelRefGroup(intentInfo, pkg, res, parser, allowGlobs, input);
+ break;
+ }
default:
result = ParsingUtils.unknownTag("<intent-filter>", pkg, parser, input);
break;
@@ -163,6 +172,197 @@ public class ParsedIntentInfoUtils {
}
@NonNull
+ @FlaggedApi(Flags.FLAG_RELATIVE_REFERENCE_INTENT_FILTERS)
+ private static ParseResult<ParsedIntentInfo> parseRelRefGroup(ParsedIntentInfo intentInfo,
+ ParsingPackage pkg, Resources res, XmlResourceParser parser, boolean allowGlobs,
+ ParseInput input) throws XmlPullParserException, IOException {
+ IntentFilter intentFilter = intentInfo.getIntentFilter();
+ TypedArray sa = res.obtainAttributes(parser,
+ R.styleable.AndroidManifestUriRelativeFilterGroup);
+ UriRelativeFilterGroup group;
+ try {
+ int action = UriRelativeFilterGroup.ACTION_ALLOW;
+ if (!sa.getBoolean(R.styleable.AndroidManifestUriRelativeFilterGroup_allow, true)) {
+ action = UriRelativeFilterGroup.ACTION_BLOCK;
+ }
+ group = new UriRelativeFilterGroup(action);
+ } finally {
+ sa.recycle();
+ }
+ final int depth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG
+ || parser.getDepth() > depth)) {
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+
+ final ParseResult result;
+ String nodeName = parser.getName();
+ switch (nodeName) {
+ case "data":
+ result = parseRelRefGroupData(group, res, parser, allowGlobs, input);
+ break;
+ default:
+ result = ParsingUtils.unknownTag("<uri-relative-filter-group>",
+ pkg, parser, input);
+ break;
+ }
+
+ if (result.isError()) {
+ return input.error(result);
+ }
+ }
+
+ if (group.getUriRelativeFilters().size() > 0) {
+ intentFilter.addUriRelativeFilterGroup(group);
+ }
+ return input.success(null);
+ }
+
+ @NonNull
+ @FlaggedApi(Flags.FLAG_RELATIVE_REFERENCE_INTENT_FILTERS)
+ private static ParseResult<ParsedIntentInfo> parseRelRefGroupData(UriRelativeFilterGroup group,
+ Resources res, XmlResourceParser parser, boolean allowGlobs, ParseInput input) {
+ TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestData);
+ try {
+ String str = sa.getNonConfigurationString(
+ R.styleable.AndroidManifestData_path, 0);
+ if (str != null) {
+ group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.PATH,
+ PatternMatcher.PATTERN_LITERAL, str));
+ }
+
+ str = sa.getNonConfigurationString(
+ R.styleable.AndroidManifestData_pathPrefix, 0);
+ if (str != null) {
+ group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.PATH,
+ PatternMatcher.PATTERN_PREFIX, str));
+ }
+
+ str = sa.getNonConfigurationString(
+ R.styleable.AndroidManifestData_pathPattern, 0);
+ if (str != null) {
+ if (!allowGlobs) {
+ return input.error(
+ "pathPattern not allowed here; path must be literal");
+ }
+ group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.PATH,
+ PatternMatcher.PATTERN_SIMPLE_GLOB, str));
+ }
+
+ str = sa.getNonConfigurationString(
+ R.styleable.AndroidManifestData_pathAdvancedPattern, 0);
+ if (str != null) {
+ if (!allowGlobs) {
+ return input.error(
+ "pathAdvancedPattern not allowed here; path must be literal");
+ }
+ group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.PATH,
+ PatternMatcher.PATTERN_ADVANCED_GLOB, str));
+ }
+
+ str = sa.getNonConfigurationString(
+ R.styleable.AndroidManifestData_pathSuffix, 0);
+ if (str != null) {
+ group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.PATH,
+ PatternMatcher.PATTERN_SUFFIX, str));
+ }
+
+ str = sa.getNonConfigurationString(
+ R.styleable.AndroidManifestData_fragment, 0);
+ if (str != null) {
+ group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.FRAGMENT,
+ PatternMatcher.PATTERN_LITERAL, str));
+ }
+
+ str = sa.getNonConfigurationString(
+ R.styleable.AndroidManifestData_fragmentPrefix, 0);
+ if (str != null) {
+ group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.FRAGMENT,
+ PatternMatcher.PATTERN_PREFIX, str));
+ }
+
+ str = sa.getNonConfigurationString(
+ R.styleable.AndroidManifestData_fragmentPattern, 0);
+ if (str != null) {
+ if (!allowGlobs) {
+ return input.error(
+ "fragmentPattern not allowed here; fragment must be literal");
+ }
+ group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.FRAGMENT,
+ PatternMatcher.PATTERN_SIMPLE_GLOB, str));
+ }
+
+ str = sa.getNonConfigurationString(
+ R.styleable.AndroidManifestData_fragmentAdvancedPattern, 0);
+ if (str != null) {
+ if (!allowGlobs) {
+ return input.error(
+ "fragmentAdvancedPattern not allowed here; fragment must be literal");
+ }
+ group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.FRAGMENT,
+ PatternMatcher.PATTERN_ADVANCED_GLOB, str));
+ }
+
+ str = sa.getNonConfigurationString(
+ R.styleable.AndroidManifestData_fragmentSuffix, 0);
+ if (str != null) {
+ group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.FRAGMENT,
+ PatternMatcher.PATTERN_SUFFIX, str));
+ }
+
+ str = sa.getNonConfigurationString(
+ R.styleable.AndroidManifestData_query, 0);
+ if (str != null) {
+ group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.QUERY,
+ PatternMatcher.PATTERN_LITERAL, str));
+ }
+
+ str = sa.getNonConfigurationString(
+ R.styleable.AndroidManifestData_queryPrefix, 0);
+ if (str != null) {
+ group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.QUERY,
+ PatternMatcher.PATTERN_PREFIX, str));
+ }
+
+ str = sa.getNonConfigurationString(
+ R.styleable.AndroidManifestData_queryPattern, 0);
+ if (str != null) {
+ if (!allowGlobs) {
+ return input.error(
+ "queryPattern not allowed here; query must be literal");
+ }
+ group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.QUERY,
+ PatternMatcher.PATTERN_SIMPLE_GLOB, str));
+ }
+
+ str = sa.getNonConfigurationString(
+ R.styleable.AndroidManifestData_queryAdvancedPattern, 0);
+ if (str != null) {
+ if (!allowGlobs) {
+ return input.error(
+ "queryAdvancedPattern not allowed here; query must be literal");
+ }
+ group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.QUERY,
+ PatternMatcher.PATTERN_ADVANCED_GLOB, str));
+ }
+
+ str = sa.getNonConfigurationString(
+ R.styleable.AndroidManifestData_querySuffix, 0);
+ if (str != null) {
+ group.addUriRelativeFilter(new UriRelativeFilter(UriRelativeFilter.QUERY,
+ PatternMatcher.PATTERN_SUFFIX, str));
+ }
+
+ return input.success(null);
+ } finally {
+ sa.recycle();
+ }
+ }
+
+ @NonNull
private static ParseResult<ParsedIntentInfo> parseData(ParsedIntentInfo intentInfo,
Resources resources, XmlResourceParser parser, boolean allowGlobs, ParseInput input) {
IntentFilter intentFilter = intentInfo.getIntentFilter();
diff --git a/core/java/com/android/internal/policy/SystemBarUtils.java b/core/java/com/android/internal/policy/SystemBarUtils.java
index 7a1ac071a625..efa369715373 100644
--- a/core/java/com/android/internal/policy/SystemBarUtils.java
+++ b/core/java/com/android/internal/policy/SystemBarUtils.java
@@ -19,8 +19,9 @@ package com.android.internal.policy;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Insets;
-import android.util.RotationUtils;
+import android.view.Display;
import android.view.DisplayCutout;
+import android.view.DisplayInfo;
import android.view.Surface;
import com.android.internal.R;
@@ -56,21 +57,21 @@ public final class SystemBarUtils {
*/
public static int getStatusBarHeightForRotation(
Context context, @Surface.Rotation int targetRot) {
- final int rotation = context.getDisplay().getRotation();
- final DisplayCutout cutout = context.getDisplay().getCutout();
-
- Insets insets = cutout == null ? Insets.NONE : Insets.of(cutout.getSafeInsets());
- Insets waterfallInsets = cutout == null ? Insets.NONE : cutout.getWaterfallInsets();
- // rotate insets to target rotation if needed.
- if (rotation != targetRot) {
- if (!insets.equals(Insets.NONE)) {
- insets = RotationUtils.rotateInsets(
- insets, RotationUtils.deltaRotation(rotation, targetRot));
- }
- if (!waterfallInsets.equals(Insets.NONE)) {
- waterfallInsets = RotationUtils.rotateInsets(
- waterfallInsets, RotationUtils.deltaRotation(rotation, targetRot));
- }
+ final Display display = context.getDisplay();
+ final int rotation = display.getRotation();
+ final DisplayCutout cutout = display.getCutout();
+ DisplayInfo info = new DisplayInfo();
+ display.getDisplayInfo(info);
+ Insets insets;
+ Insets waterfallInsets;
+ if (cutout == null) {
+ insets = Insets.NONE;
+ waterfallInsets = Insets.NONE;
+ } else {
+ DisplayCutout rotated =
+ cutout.getRotated(info.logicalWidth, info.logicalHeight, rotation, targetRot);
+ insets = Insets.of(rotated.getSafeInsets());
+ waterfallInsets = rotated.getWaterfallInsets();
}
final int defaultSize =
context.getResources().getDimensionPixelSize(R.dimen.status_bar_height_default);
diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java
index 42be784d8baa..a8d0d37f78bd 100644
--- a/core/java/com/android/internal/widget/ConversationLayout.java
+++ b/core/java/com/android/internal/widget/ConversationLayout.java
@@ -105,6 +105,9 @@ public class ConversationLayout extends FrameLayout
private int mConversationIconTopPaddingExpandedGroup;
private int mConversationIconTopPadding;
private int mExpandedGroupMessagePadding;
+ // TODO (b/217799515) Currently, mConversationText shows the conversation title, the actual
+ // conversation text is inside of mMessagingLinearLayout, which is misleading, we should rename
+ // this to mConversationTitleView
private TextView mConversationText;
private View mConversationIconBadge;
private CachingIconView mConversationIconBadgeBg;
@@ -125,6 +128,11 @@ public class ConversationLayout extends FrameLayout
private int mNotificationBackgroundColor;
private CharSequence mFallbackChatName;
private CharSequence mFallbackGroupChatName;
+ //TODO (b/217799515) Currently, Notification.MessagingStyle, ConversationLayout, and
+ // HybridConversationNotificationView, each has their own definition of "ConversationTitle".
+ // What make things worse is that the term of "ConversationTitle" often confuses with
+ // "ConversationText".
+ // We need to unify them or differentiate the namings.
private CharSequence mConversationTitle;
private int mMessageSpacingStandard;
private int mMessageSpacingGroup;
@@ -160,12 +168,12 @@ public class ConversationLayout extends FrameLayout
}
public ConversationLayout(@NonNull Context context, @Nullable AttributeSet attrs,
- @AttrRes int defStyleAttr) {
+ @AttrRes int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public ConversationLayout(@NonNull Context context, @Nullable AttributeSet attrs,
- @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
+ @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@@ -297,13 +305,17 @@ public class ConversationLayout extends FrameLayout
mNameReplacement = nameReplacement;
}
- /** Sets this conversation as "important", adding some additional UI treatment. */
+ /**
+ * Sets this conversation as "important", adding some additional UI treatment.
+ */
@RemotableViewMethod
public void setIsImportantConversation(boolean isImportantConversation) {
setIsImportantConversation(isImportantConversation, false);
}
- /** @hide **/
+ /**
+ * @hide
+ **/
public void setIsImportantConversation(boolean isImportantConversation, boolean animate) {
mImportantConversation = isImportantConversation;
mImportanceRingView.setVisibility(isImportantConversation && mIcon.getVisibility() != GONE
@@ -386,6 +398,7 @@ public class ConversationLayout extends FrameLayout
/**
* Set conversation data
+ *
* @param extras Bundle contains conversation data
*/
@RemotableViewMethod(asyncImpl = "setDataAsync")
@@ -427,6 +440,7 @@ public class ConversationLayout extends FrameLayout
* RemotableViewMethod's asyncImpl of {@link #setData(Bundle)}.
* This should be called on a background thread, and returns a Runnable which is then must be
* called on the main thread to complete the operation and set text.
+ *
* @param extras Bundle contains conversation data
* @hide
*/
@@ -449,6 +463,7 @@ public class ConversationLayout extends FrameLayout
/**
* enable/disable precomputed text usage
+ *
* @hide
*/
public void setPrecomputedTextEnabled(boolean precomputedTextEnabled) {
@@ -466,7 +481,9 @@ public class ConversationLayout extends FrameLayout
mImageResolver = resolver;
}
- /** @hide */
+ /**
+ * @hide
+ */
public void setUnreadCount(int unreadCount) {
mExpandButton.setNumber(unreadCount);
}
@@ -795,6 +812,10 @@ public class ConversationLayout extends FrameLayout
mConversationTitle = conversationTitle != null ? conversationTitle.toString() : null;
}
+ // TODO (b/217799515) getConversationTitle is not consistent with setConversationTitle
+ // if you call getConversationTitle() immediately after setConversationTitle(), the result
+ // will not correctly reflect the new change without calling updateConversationLayout, for
+ // example.
public CharSequence getConversationTitle() {
return mConversationText.getText();
}
@@ -914,7 +935,7 @@ public class ConversationLayout extends FrameLayout
}
private void createGroupViews(List<List<MessagingMessage>> groups,
- List<Person> senders, boolean showSpinner) {
+ List<Person> senders, boolean showSpinner) {
mGroups.clear();
for (int groupIndex = 0; groupIndex < groups.size(); groupIndex++) {
List<MessagingMessage> group = groups.get(groupIndex);
@@ -963,8 +984,8 @@ public class ConversationLayout extends FrameLayout
}
private void findGroups(List<MessagingMessage> historicMessages,
- List<MessagingMessage> messages, List<List<MessagingMessage>> groups,
- List<Person> senders) {
+ List<MessagingMessage> messages, List<List<MessagingMessage>> groups,
+ List<Person> senders) {
CharSequence currentSenderKey = null;
List<MessagingMessage> currentGroup = null;
int histSize = historicMessages.size();
diff --git a/core/java/com/android/internal/widget/ImageFloatingTextView.java b/core/java/com/android/internal/widget/ImageFloatingTextView.java
index ae8f0255a192..5da64350619c 100644
--- a/core/java/com/android/internal/widget/ImageFloatingTextView.java
+++ b/core/java/com/android/internal/widget/ImageFloatingTextView.java
@@ -251,7 +251,7 @@ public class ImageFloatingTextView extends TextView {
*/
private int isTextAPrecomputedText() {
final CharSequence text = getText();
- if (text == null || text.isEmpty()) {
+ if (text == null) {
return 0;
}
diff --git a/core/java/com/android/internal/widget/PeopleHelper.java b/core/java/com/android/internal/widget/PeopleHelper.java
index 85cedc362b99..3f5b4a0d61fe 100644
--- a/core/java/com/android/internal/widget/PeopleHelper.java
+++ b/core/java/com/android/internal/widget/PeopleHelper.java
@@ -22,6 +22,8 @@ import static com.android.internal.widget.MessagingPropertyAnimator.ALPHA_OUT;
import android.annotation.ColorInt;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.Notification;
+import android.app.Person;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
@@ -222,6 +224,72 @@ public class PeopleHelper {
}
/**
+ * A class that represents a map from unique sender names in the groups to the string 1- or
+ * 2-character prefix strings for the names. This class uses the String value of the
+ * CharSequence Names as the key.
+ */
+ public class NameToPrefixMap {
+ Map<String, String> mMap;
+ NameToPrefixMap(Map<String, String> map) {
+ this.mMap = map;
+ }
+
+ /**
+ * @param name the name
+ * @return the prefix of the given name
+ */
+ public String getPrefix(CharSequence name) {
+ return mMap.get(name.toString());
+ }
+ }
+
+ /**
+ * Same functionality as mapUniqueNamesToPrefix, but takes list-represented message groups as
+ * the input. This method is better when inflating MessagingGroup from the UI thread is not
+ * an option.
+ * @param groups message groups represented by lists. A message group is some consecutive
+ * messages (>=3) from the same sender in a conversation.
+ */
+ public NameToPrefixMap mapUniqueNamesToPrefixWithGroupList(
+ List<List<Notification.MessagingStyle.Message>> groups) {
+ // Map of unique names to their prefix
+ ArrayMap<String, String> uniqueNames = new ArrayMap<>();
+ // Map of single-character string prefix to the only name which uses it, or null if multiple
+ ArrayMap<String, CharSequence> uniqueCharacters = new ArrayMap<>();
+ for (int i = 0; i < groups.size(); i++) {
+ List<Notification.MessagingStyle.Message> group = groups.get(i);
+ if (group.isEmpty()) continue;
+ Person sender = group.get(0).getSenderPerson();
+ if (sender == null) continue;
+ CharSequence senderName = sender.getName();
+ if (sender.getIcon() != null || TextUtils.isEmpty(senderName)) {
+ continue;
+ }
+ String senderNameString = senderName.toString();
+ if (!uniqueNames.containsKey(senderNameString)) {
+ String charPrefix = findNamePrefix(senderName, null);
+ if (charPrefix == null) {
+ continue;
+ }
+ if (uniqueCharacters.containsKey(charPrefix)) {
+ // this character was already used, lets make it more unique. We first need to
+ // resolve the existing character if it exists
+ CharSequence existingName = uniqueCharacters.get(charPrefix);
+ if (existingName != null) {
+ uniqueNames.put(existingName.toString(), findNameSplit(existingName));
+ uniqueCharacters.put(charPrefix, null);
+ }
+ uniqueNames.put(senderNameString, findNameSplit(senderName));
+ } else {
+ uniqueNames.put(senderNameString, charPrefix);
+ uniqueCharacters.put(charPrefix, senderName);
+ }
+ }
+ }
+ return new NameToPrefixMap(uniqueNames);
+ }
+
+ /**
* Update whether the groups can hide the sender if they are first
* (happens only for 1:1 conversations where the given title matches the sender's name)
*/
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index 6a640a5ab23b..d2e58bb62c46 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -1165,12 +1165,11 @@ static jlong android_os_Process_getPss(JNIEnv* env, jobject clazz, jint pid)
static jlongArray android_os_Process_getRss(JNIEnv* env, jobject clazz, jint pid)
{
- // total, file, anon, swap
- jlong rss[4] = {0, 0, 0, 0};
+ // total, file, anon, swap, shmem
+ jlong rss[5] = {0, 0, 0, 0, 0};
std::string status_path =
android::base::StringPrintf("/proc/%d/status", pid);
UniqueFile file = MakeUniqueFile(status_path.c_str(), "re");
-
char line[256];
while (file != nullptr && fgets(line, sizeof(line), file.get())) {
jlong v;
@@ -1182,17 +1181,18 @@ static jlongArray android_os_Process_getRss(JNIEnv* env, jobject clazz, jint pid
rss[2] = v;
} else if ( sscanf(line, "VmSwap: %" SCNd64 " kB", &v) == 1) {
rss[3] = v;
+ } else if ( sscanf(line, "RssShmem: %" SCNd64 " kB", &v) == 1) {
+ rss[4] = v;
}
}
- jlongArray rssArray = env->NewLongArray(4);
+ jlongArray rssArray = env->NewLongArray(5);
if (rssArray == NULL) {
jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
return NULL;
}
- env->SetLongArrayRegion(rssArray, 0, 4, rss);
-
+ env->SetLongArrayRegion(rssArray, 0, 5, rss);
return rssArray;
}
diff --git a/core/jni/android_window_ScreenCapture.cpp b/core/jni/android_window_ScreenCapture.cpp
index 6e903b3ab56d..1031542eb2e6 100644
--- a/core/jni/android_window_ScreenCapture.cpp
+++ b/core/jni/android_window_ScreenCapture.cpp
@@ -211,7 +211,7 @@ static jint nativeCaptureDisplay(JNIEnv* env, jclass clazz, jobject displayCaptu
}
static jint nativeCaptureLayers(JNIEnv* env, jclass clazz, jobject layerCaptureArgsObject,
- jlong screenCaptureListenerObject) {
+ jlong screenCaptureListenerObject, jboolean sync) {
LayerCaptureArgs captureArgs;
getCaptureArgs(env, layerCaptureArgsObject, captureArgs);
@@ -227,7 +227,7 @@ static jint nativeCaptureLayers(JNIEnv* env, jclass clazz, jobject layerCaptureA
sp<gui::IScreenCaptureListener> captureListener =
reinterpret_cast<gui::IScreenCaptureListener*>(screenCaptureListenerObject);
- return ScreenshotClient::captureLayers(captureArgs, captureListener);
+ return ScreenshotClient::captureLayers(captureArgs, captureListener, sync);
}
static jlong nativeCreateScreenCaptureListener(JNIEnv* env, jclass clazz, jobject consumerObj) {
@@ -281,7 +281,7 @@ static const JNINativeMethod sScreenCaptureMethods[] = {
// clang-format off
{"nativeCaptureDisplay", "(Landroid/window/ScreenCapture$DisplayCaptureArgs;J)I",
(void*)nativeCaptureDisplay },
- {"nativeCaptureLayers", "(Landroid/window/ScreenCapture$LayerCaptureArgs;J)I",
+ {"nativeCaptureLayers", "(Landroid/window/ScreenCapture$LayerCaptureArgs;JZ)I",
(void*)nativeCaptureLayers },
{"nativeCreateScreenCaptureListener", "(Ljava/util/function/ObjIntConsumer;)J",
(void*)nativeCreateScreenCaptureListener },
diff --git a/core/proto/android/content/intent.proto b/core/proto/android/content/intent.proto
index 75e29082508b..1d1f88b01838 100644
--- a/core/proto/android/content/intent.proto
+++ b/core/proto/android/content/intent.proto
@@ -66,7 +66,7 @@ message IntentProto {
optional string identifier = 13 [ (.android.privacy).dest = DEST_EXPLICIT ];
}
-// Next Tag: 12
+// Next Tag: 14
message IntentFilterProto {
option (.android.msg_privacy).dest = DEST_AUTOMATIC;
@@ -89,6 +89,7 @@ message IntentFilterProto {
optional bool get_auto_verify = 10;
repeated string mime_groups = 11;
optional android.os.PersistableBundleProto extras = 12;
+ repeated UriRelativeFilterGroupProto uri_relative_filter_groups = 13;
}
message AuthorityEntryProto {
@@ -98,3 +99,23 @@ message AuthorityEntryProto {
optional bool wild = 2;
optional int32 port = 3;
}
+
+message UriRelativeFilterGroupProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ enum Action {
+ ACTION_ALLOW = 0;
+ ACTION_BLOCK = 1;
+ }
+
+ optional Action action = 1;
+ repeated UriRelativeFilterProto uri_relative_filters = 2;
+}
+
+message UriRelativeFilterProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ required int32 uri_part = 1;
+ required int32 pattern_type = 2;
+ required string filter = 3;
+}
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index b63021d52f1c..c92435f61ab1 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -464,6 +464,7 @@ message WindowStateProto {
repeated .android.graphics.RectProto keep_clear_areas = 45;
repeated .android.graphics.RectProto unrestricted_keep_clear_areas = 46;
repeated .android.view.InsetsSourceProto mergedLocalInsetsSources = 47;
+ optional int32 requested_visible_types = 48;
}
message IdentifierProto {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 070e2cb0dc5e..d972556df015 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -6654,7 +6654,14 @@
<!-- Allows the system to control the BiometricDialog (SystemUI). Reserved for the system. @hide -->
<permission android:name="android.permission.MANAGE_BIOMETRIC_DIALOG"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature" />
+
+ <!-- Allows an application to set the BiometricDialog (SystemUI) logo .
+ <p>Not for use by third-party applications.
+ @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt")
+ -->
+ <permission android:name="android.permission.SET_BIOMETRIC_DIALOG_LOGO"
+ android:protectionLevel="signature" />
<!-- Allows an application to control keyguard. Only allowed for system processes.
@hide -->
@@ -7801,6 +7808,16 @@
<permission android:name="android.permission.RUN_USER_INITIATED_JOBS"
android:protectionLevel="normal"/>
+ <!-- @FlaggedApi("android.app.job.backup_jobs_exemption")
+ Gives applications whose <b>primary use case</b> is to backup or sync content increased
+ job execution allowance in order to complete the related work. The jobs must have a valid
+ content URI trigger and network constraint set.
+ <p>This is a special access permission that can be revoked by the system or the user.
+ <p>Protection level: signature|privileged|appop
+ -->
+ <permission android:name="android.permission.RUN_BACKUP_JOBS"
+ android:protectionLevel="signature|privileged|appop"/>
+
<!-- Allows an app access to the installer provided app metadata.
@SystemApi
@hide
@@ -7968,6 +7985,16 @@
<permission android:name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"
android:protectionLevel="signature|privileged"/>
+ <!-- @SystemApi
+ @FlaggedApi("android.content.pm.emergency_install_permission")
+ Allows each app store in the system image to designate another app in the system image to
+ update the app store
+ <p>Protection level: signature|privileged
+ @hide
+ -->
+ <permission android:name="android.permission.EMERGENCY_INSTALL_PACKAGES"
+ android:protectionLevel="signature|privileged"/>
+
<!-- Attribution for Geofencing service. -->
<attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
<!-- Attribution for Country Detector. -->
@@ -8374,6 +8401,16 @@
</intent-filter>
</receiver>
+ <!-- Broadcast Receiver listens to sufficient verifier broadcast from Package Manager
+ when installing new SDK. Verification of SDK code during installation time is run
+ to determine compatibility with privacy sandbox restrictions. -->
+ <receiver android:name="com.android.server.sdksandbox.SdkSandboxVerifierReceiver"
+ android:exported="false">
+ <intent-filter>
+ <action android:name="android.intent.action.PACKAGE_NEEDS_VERIFICATION"/>
+ </intent-filter>
+ </receiver>
+
<service android:name="android.hardware.location.GeofenceHardwareService"
android:permission="android.permission.LOCATION_HARDWARE"
android:exported="false" />
@@ -8415,6 +8452,10 @@
android:permission="android.permission.BIND_JOB_SERVICE">
</service>
+ <service android:name="com.android.server.selinux.SelinuxAuditLogsService"
+ android:permission="android.permission.BIND_JOB_SERVICE">
+ </service>
+
<service android:name="com.android.server.compos.IsolatedCompilationJobService"
android:permission="android.permission.BIND_JOB_SERVICE">
</service>
diff --git a/core/res/res/color-night/notification_expand_button_state_tint.xml b/core/res/res/color-night/notification_expand_button_state_tint.xml
deleted file mode 100644
index a794d53c7e71..000000000000
--- a/core/res/res/color-night/notification_expand_button_state_tint.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<!--
- ~ Copyright (C) 2023 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_pressed="true" android:color="@android:color/system_on_surface_dark" android:alpha="0.06"/>
- <item android:state_hovered="true" android:color="@android:color/system_on_surface_dark" android:alpha="0.03"/>
- <item android:color="@android:color/system_on_surface_dark" android:alpha="0.00"/>
-</selector> \ No newline at end of file
diff --git a/core/res/res/color/notification_expand_button_state_tint.xml b/core/res/res/color/notification_expand_button_state_tint.xml
index 67b2c2568bb1..5a8594f0e461 100644
--- a/core/res/res/color/notification_expand_button_state_tint.xml
+++ b/core/res/res/color/notification_expand_button_state_tint.xml
@@ -14,8 +14,11 @@
~ limitations under the License.
-->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_pressed="true" android:color="@android:color/system_on_surface_light" android:alpha="0.12"/>
- <item android:state_hovered="true" android:color="@android:color/system_on_surface_light" android:alpha="0.08"/>
- <item android:color="@android:color/system_on_surface_light" android:alpha="0.00"/>
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+ <item android:state_pressed="true" android:color="?androidprv:attr/materialColorOnPrimaryFixed"
+ android:alpha="0.15"/>
+ <item android:state_hovered="true" android:color="?androidprv:attr/materialColorOnPrimaryFixed"
+ android:alpha="0.11"/>
+ <item android:color="@color/transparent" />
</selector> \ No newline at end of file
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 35276bf8ead2..6884fc0057d9 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -3438,6 +3438,20 @@
<!-- Attributes that can be supplied in an AndroidManifest.xml
<code>data</code> tag, a child of the
{@link #AndroidManifestIntentFilter intent-filter} tag, describing
+ a group matching rule consisting of one or more
+ {@link #AndroidManifestData data} tags that must all match. This
+ tag can be specified multiple times to create multiple groups that
+ will be matched in the order they are defined. -->
+ <declare-styleable name="AndroidManifestUriRelativeFilterGroup"
+ parent="AndroidManifestIntentFilter">
+ <!-- Specify if this group is allow rule or disallow rule. If this
+ attribute is not specified then it is assumed to be true -->
+ <attr name="allow" format="boolean"/>
+ </declare-styleable>
+
+ <!-- Attributes that can be supplied in an AndroidManifest.xml
+ <code>data</code> tag, a child of the
+ {@link #AndroidManifestIntentFilter intent-filter} tag, describing
the types of data that match. This tag can be specified multiple
times to supply multiple data options, as described in the
{@link android.content.IntentFilter} class. Note that all such
@@ -3445,7 +3459,8 @@
<code>&lt;data android:scheme="myscheme" android:host="me.com" /&gt;</code>
is equivalent to <code>&lt;data android:scheme="myscheme" /&gt;
&lt;data android:host="me.com" /&gt;</code>. -->
- <declare-styleable name="AndroidManifestData" parent="AndroidManifestIntentFilter">
+ <declare-styleable name="AndroidManifestData"
+ parent="AndroidManifestIntentFilter AndroidManifestUriRelativeFilterGroup">
<!-- Specify a MIME type that is handled, as per
{@link android.content.IntentFilter#addDataType
IntentFilter.addDataType()}.
@@ -3549,6 +3564,70 @@
IntentFilter.addDataPath()} with
{@link android.os.PatternMatcher#PATTERN_SUFFIX}. -->
<attr name="pathSuffix" />
+ <!-- Specify a URI query that must exactly match, as a
+ {@link android.content.UriRelativeFilter UriRelativeFilter} with
+ {@link android.os.PatternMatcher#PATTERN_LITERAL}. -->
+ <attr name="query" format="string" />
+ <!-- Specify a URI query that must be a prefix to match, as a
+ {@link android.content.UriRelativeFilter UriRelativeFilter} with
+ {@link android.os.PatternMatcher#PATTERN_PREFIX}. -->
+ <attr name="queryPrefix" format="string" />
+ <!-- Specify a URI query that matches a simple pattern, as a
+ {@link android.content.UriRelativeFilter UriRelativeFilter} with
+ {@link android.os.PatternMatcher#PATTERN_SIMPLE_GLOB}.
+ Note that because '\' is used as an escape character when
+ reading the string from XML (before it is parsed as a pattern),
+ you will need to double-escape: for example a literal "*" would
+ be written as "\\*" and a literal "\" would be written as
+ "\\\\". This is basically the same as what you would need to
+ write if constructing the string in Java code. -->
+ <attr name="queryPattern" format="string" />
+ <!-- Specify a URI query that matches an advanced pattern, as a
+ {@link android.content.UriRelativeFilter UriRelativeFilter} with
+ {@link android.os.PatternMatcher#PATTERN_ADVANCED_GLOB}.
+ Note that because '\' is used as an escape character when
+ reading the string from XML (before it is parsed as a pattern),
+ you will need to double-escape: for example a literal "*" would
+ be written as "\\*" and a literal "\" would be written as
+ "\\\\". This is basically the same as what you would need to
+ write if constructing the string in Java code. -->
+ <attr name="queryAdvancedPattern" format="string" />
+ <!-- Specify a URI query that must be a suffix to match, as a
+ {@link android.content.UriRelativeFilter UriRelativeFilter} with
+ {@link android.os.PatternMatcher#PATTERN_SUFFIX}. -->
+ <attr name="querySuffix" format="string" />
+ <!-- Specify a URI fragment that must exactly match, as a
+ {@link android.content.UriRelativeFilter UriRelativeFilter} with
+ {@link android.os.PatternMatcher#PATTERN_LITERAL}. -->
+ <attr name="fragment" format="string" />
+ <!-- Specify a URI fragment that must be a prefix to match, as a
+ {@link android.content.UriRelativeFilter UriRelativeFilter} with
+ {@link android.os.PatternMatcher#PATTERN_PREFIX}. -->
+ <attr name="fragmentPrefix" format="string" />
+ <!-- Specify a URI fragment that matches a simple pattern, as a
+ {@link android.content.UriRelativeFilter UriRelativeFilter} with
+ {@link android.os.PatternMatcher#PATTERN_SIMPLE_GLOB}.
+ Note that because '\' is used as an escape character when
+ reading the string from XML (before it is parsed as a pattern),
+ you will need to double-escape: for example a literal "*" would
+ be written as "\\*" and a literal "\" would be written as
+ "\\\\". This is basically the same as what you would need to
+ write if constructing the string in Java code. -->
+ <attr name="fragmentPattern" format="string" />
+ <!-- Specify a URI fragment that matches an advanced pattern, as a
+ {@link android.content.UriRelativeFilter UriRelativeFilter} with
+ {@link android.os.PatternMatcher#PATTERN_ADVANCED_GLOB}.
+ Note that because '\' is used as an escape character when
+ reading the string from XML (before it is parsed as a pattern),
+ you will need to double-escape: for example a literal "*" would
+ be written as "\\*" and a literal "\" would be written as
+ "\\\\". This is basically the same as what you would need to
+ write if constructing the string in Java code. -->
+ <attr name="fragmentAdvancedPattern" format="string" />
+ <!-- Specify a URI fragment that must be a suffix to match, as a
+ {@link android.content.UriRelativeFilter UriRelativeFilter} with
+ {@link android.os.PatternMatcher#PATTERN_SUFFIX}. -->
+ <attr name="fragmentSuffix" format="string" />
</declare-styleable>
<!-- Attributes that can be supplied in an AndroidManifest.xml
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 0d1a98785695..23c78fdf108e 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -6611,7 +6611,7 @@
</string-array>
<!-- Whether or not the monitoring on the apps' background battery drain is enabled -->
- <bool name="config_bg_current_drain_monitor_enabled">true</bool>
+ <bool name="config_bg_current_drain_monitor_enabled">false</bool>
<!-- The threshold of the background current drain (in percentage) to the restricted
standby bucket.
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index b8fc052a2fa9..830e99ca907b 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -123,6 +123,26 @@
<public name="featureFlag"/>
<!-- @FlaggedApi("android.multiuser.enable_system_user_only_for_services_and_providers") -->
<public name="systemUserOnly"/>
+ <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") -->
+ <public name="allow"/>
+ <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") -->
+ <public name="query"/>
+ <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") -->
+ <public name="queryPrefix"/>
+ <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") -->
+ <public name="queryPattern"/>
+ <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") -->
+ <public name="queryAdvancedPattern"/>
+ <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") -->
+ <public name="querySuffix"/>
+ <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") -->
+ <public name="fragmentPrefix"/>
+ <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") -->
+ <public name="fragmentPattern"/>
+ <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") -->
+ <public name="fragmentAdvancedPattern"/>
+ <!-- @FlaggedApi("android.content.pm.relative_reference_intent_filters") -->
+ <public name="fragmentSuffix"/>
</staging-public-group>
<staging-public-group type="id" first-id="0x01bc0000">
diff --git a/core/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml
index 3a2e50aa06e8..9bb249999d99 100644
--- a/core/res/res/xml/sms_short_codes.xml
+++ b/core/res/res/xml/sms_short_codes.xml
@@ -34,7 +34,7 @@
http://smscoin.net/software/engine/WordPress/Paid+SMS-registration/ -->
<!-- Arab Emirates -->
- <shortcode country="ae" pattern="\\d{1,5}" free="1017|1355|3214" />
+ <shortcode country="ae" pattern="\\d{1,5}" free="1017|1355|3214|6253" />
<!-- Albania: 5 digits, known short codes listed -->
<shortcode country="al" pattern="\\d{5}" premium="15191|55[56]00" />
@@ -86,7 +86,7 @@
<shortcode country="cn" premium="1066.*" free="1065.*" />
<!-- Colombia: 1-6 digits (not confirmed) -->
- <shortcode country="co" pattern="\\d{1,6}" free="890350|908160|892255|898002|898880|899960|899948|87739|85517" />
+ <shortcode country="co" pattern="\\d{1,6}" free="890350|908160|892255|898002|898880|899960|899948|87739|85517|491289" />
<!-- Cyprus: 4-6 digits (not confirmed), known premium codes listed, plus EU -->
<shortcode country="cy" pattern="\\d{4,6}" premium="7510" free="116\\d{3}" />
@@ -104,6 +104,12 @@
<!-- Denmark: see http://iprs.webspacecommerce.com/Denmark-Premium-Rate-Numbers -->
<shortcode country="dk" pattern="\\d{4,5}" premium="1\\d{3}" free="116\\d{3}|4665" />
+ <!-- Dominican Republic: 1-6 digits (standard system default, not country specific) -->
+ <shortcode country="do" pattern="\\d{1,6}" free="912892" />
+
+ <!-- Ecuador: 1-6 digits (standard system default, not country specific) -->
+ <shortcode country="ec" pattern="\\d{1,6}" free="466453" />
+
<!-- Estonia: short codes 3-5 digits starting with 1, plus premium 7 digit numbers starting with 90, plus EU.
http://www.tja.ee/public/documents/Elektrooniline_side/Oigusaktid/ENG/Estonian_Numbering_Plan_annex_06_09_2010.mht -->
<shortcode country="ee" pattern="1\\d{2,4}" premium="90\\d{5}|15330|1701[0-3]" free="116\\d{3}|95034" />
@@ -154,8 +160,8 @@
http://www.comreg.ie/_fileupload/publications/ComReg1117.pdf -->
<shortcode country="ie" pattern="\\d{5}" premium="5[3-9]\\d{3}" free="50\\d{3}|116\\d{3}" standard="5[12]\\d{3}" />
- <!-- Israel: 4 digits, known premium codes listed -->
- <shortcode country="il" pattern="\\d{4}" premium="4422|4545" />
+ <!-- Israel: 1-5 digits, known premium codes listed -->
+ <shortcode country="il" pattern="\\d{1,5}" premium="4422|4545" free="37477|6681" />
<!-- Italy: 5 digits (premium=41xxx,42xxx), plus EU:
https://www.itu.int/dms_pub/itu-t/oth/02/02/T020200006B0001PDFE.pdf -->
@@ -193,11 +199,14 @@
<shortcode country="mk" pattern="\\d{1,6}" free="129005|122" />
<!-- Mexico: 4-5 digits (not confirmed), known premium codes listed -->
- <shortcode country="mx" pattern="\\d{4,5}" premium="53035|7766" free="26259|46645|50025|50052|5050|76551|88778|9963|91101|45453" />
+ <shortcode country="mx" pattern="\\d{4,6}" premium="53035|7766" free="26259|46645|50025|50052|5050|76551|88778|9963|91101|45453|550346" />
<!-- Malaysia: 5 digits: http://www.skmm.gov.my/attachment/Consumer_Regulation/Mobile_Content_Services_FAQs.pdf -->
<shortcode country="my" pattern="\\d{5}" premium="32298|33776" free="22099|28288|66668" />
+ <!-- Namibia: 1-5 digits (standard system default, not country specific) -->
+ <shortcode country="na" pattern="\\d{1,5}" free="40005" />
+
<!-- The Netherlands, 4 digits, known premium codes listed, plus EU -->
<shortcode country="nl" pattern="\\d{4}" premium="4466|5040" free="116\\d{3}|2223|6225|2223|1662" />
diff --git a/core/tests/InputMethodCoreTests/Android.bp b/core/tests/InputMethodCoreTests/Android.bp
new file mode 100644
index 000000000000..ac6462589e16
--- /dev/null
+++ b/core/tests/InputMethodCoreTests/Android.bp
@@ -0,0 +1,66 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+ name: "InputMethodCoreTests",
+
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ "src/**/I*.aidl",
+ ],
+
+ dxflags: ["--core-library"],
+
+ static_libs: [
+ "collector-device-lib-platform",
+ "android-common",
+ "frameworks-core-util-lib",
+ "androidx.core_core",
+ "androidx.core_core-ktx",
+ "androidx.test.ext.junit",
+ "androidx.test.runner",
+ "androidx.test.rules",
+ "flag-junit",
+ "junit-params",
+ "kotlin-test",
+ "mockito-target-minus-junit4",
+ "platform-test-annotations",
+ "platform-compat-test-rules",
+ "truth",
+ "print-test-util-lib",
+ "testng",
+ "device-time-shell-utils",
+ "testables",
+ "flag-junit",
+ ],
+
+ libs: [
+ "android.test.runner",
+ "android.test.base",
+ "android.test.mock",
+ "framework",
+ "ext",
+ "framework-res",
+ ],
+
+ sdk_version: "core_platform",
+ test_suites: [
+ "device-tests",
+ "automotive-tests",
+ ],
+
+ certificate: "platform",
+
+ resource_dirs: ["res"],
+
+ data: [
+ ":com.android.cts.helpers.aosp",
+ ],
+}
diff --git a/core/tests/InputMethodCoreTests/AndroidManifest.xml b/core/tests/InputMethodCoreTests/AndroidManifest.xml
new file mode 100644
index 000000000000..8d00d0f755bf
--- /dev/null
+++ b/core/tests/InputMethodCoreTests/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ android:installLocation="internalOnly"
+ package="com.android.frameworks.inputmethodcoretests"
+ android:sharedUserId="com.android.uid.test">
+
+ <application
+ android:supportsRtl="true"
+ android:enableOnBackInvokedCallback="true">
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.frameworks.inputmethodcoretests"
+ android:label="InputMethod Core Tests" />
+</manifest>
diff --git a/core/tests/InputMethodCoreTests/AndroidTest.xml b/core/tests/InputMethodCoreTests/AndroidTest.xml
new file mode 100644
index 000000000000..fa585d8d1075
--- /dev/null
+++ b/core/tests/InputMethodCoreTests/AndroidTest.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Runs InputMethod Core Tests.">
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="apct-instrumentation" />
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="InputMethodCoreTests.apk" />
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <!-- TODO(b/254155965): Design a mechanism to finally remove this command. -->
+ <option name="run-command" value="settings put global device_config_sync_disabled 0" />
+ </target_preparer>
+
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.DeviceInteractionHelperInstaller" />
+
+ <option name="test-tag" value="InputMethodCoreTests" />
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.frameworks.inputmethodcoretests" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ <option name="hidden-api-checks" value="false"/>
+ </test>
+</configuration>
diff --git a/core/tests/InputMethodCoreTests/OWNERS b/core/tests/InputMethodCoreTests/OWNERS
new file mode 100644
index 000000000000..5deb2ce8f24b
--- /dev/null
+++ b/core/tests/InputMethodCoreTests/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/view/inputmethod/OWNERS
diff --git a/core/tests/coretests/res/xml/ime_meta.xml b/core/tests/InputMethodCoreTests/res/xml/ime_meta.xml
index a975718c50e9..a975718c50e9 100644
--- a/core/tests/coretests/res/xml/ime_meta.xml
+++ b/core/tests/InputMethodCoreTests/res/xml/ime_meta.xml
diff --git a/core/tests/coretests/res/xml/ime_meta_inline_suggestions.xml b/core/tests/InputMethodCoreTests/res/xml/ime_meta_inline_suggestions.xml
index e67bf6331bfe..e67bf6331bfe 100644
--- a/core/tests/coretests/res/xml/ime_meta_inline_suggestions.xml
+++ b/core/tests/InputMethodCoreTests/res/xml/ime_meta_inline_suggestions.xml
diff --git a/core/tests/coretests/res/xml/ime_meta_inline_suggestions_with_touch_exploration.xml b/core/tests/InputMethodCoreTests/res/xml/ime_meta_inline_suggestions_with_touch_exploration.xml
index 34402089b47d..34402089b47d 100644
--- a/core/tests/coretests/res/xml/ime_meta_inline_suggestions_with_touch_exploration.xml
+++ b/core/tests/InputMethodCoreTests/res/xml/ime_meta_inline_suggestions_with_touch_exploration.xml
diff --git a/core/tests/coretests/res/xml/ime_meta_sw_next.xml b/core/tests/InputMethodCoreTests/res/xml/ime_meta_sw_next.xml
index 2e2ee33e9933..2e2ee33e9933 100644
--- a/core/tests/coretests/res/xml/ime_meta_sw_next.xml
+++ b/core/tests/InputMethodCoreTests/res/xml/ime_meta_sw_next.xml
diff --git a/core/tests/coretests/res/xml/ime_meta_virtual_device_only.xml b/core/tests/InputMethodCoreTests/res/xml/ime_meta_virtual_device_only.xml
index 1905365808bc..1905365808bc 100644
--- a/core/tests/coretests/res/xml/ime_meta_virtual_device_only.xml
+++ b/core/tests/InputMethodCoreTests/res/xml/ime_meta_virtual_device_only.xml
diff --git a/core/tests/coretests/res/xml/ime_meta_vr_only.xml b/core/tests/InputMethodCoreTests/res/xml/ime_meta_vr_only.xml
index 653a8ffcb944..653a8ffcb944 100644
--- a/core/tests/coretests/res/xml/ime_meta_vr_only.xml
+++ b/core/tests/InputMethodCoreTests/res/xml/ime_meta_vr_only.xml
diff --git a/core/tests/coretests/src/android/view/inputmethod/BaseInputConnectionTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/BaseInputConnectionTest.java
index f04f603564f6..f04f603564f6 100644
--- a/core/tests/coretests/src/android/view/inputmethod/BaseInputConnectionTest.java
+++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/BaseInputConnectionTest.java
diff --git a/core/tests/coretests/src/android/view/inputmethod/CursorAnchorInfoTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/CursorAnchorInfoTest.java
index 9d7d71d8d539..9d7d71d8d539 100644
--- a/core/tests/coretests/src/android/view/inputmethod/CursorAnchorInfoTest.java
+++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/CursorAnchorInfoTest.java
diff --git a/core/tests/coretests/src/android/view/inputmethod/DeleteRangeGestureTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/DeleteRangeGestureTest.java
index d7b911dda672..d7b911dda672 100644
--- a/core/tests/coretests/src/android/view/inputmethod/DeleteRangeGestureTest.java
+++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/DeleteRangeGestureTest.java
diff --git a/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/EditorInfoTest.java
index 4839dd27b283..4839dd27b283 100644
--- a/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java
+++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/EditorInfoTest.java
diff --git a/core/tests/coretests/src/android/view/inputmethod/InputMethodInfoTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodInfoTest.java
index 909af7b4c5fb..a3f537ef5f1c 100644
--- a/core/tests/coretests/src/android/view/inputmethod/InputMethodInfoTest.java
+++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodInfoTest.java
@@ -32,7 +32,7 @@ import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.frameworks.coretests.R;
+import com.android.frameworks.inputmethodcoretests.R;
import org.junit.Rule;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/view/inputmethod/InputMethodManagerTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodManagerTest.java
index d70572444128..d70572444128 100644
--- a/core/tests/coretests/src/android/view/inputmethod/InputMethodManagerTest.java
+++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodManagerTest.java
diff --git a/core/tests/coretests/src/android/view/inputmethod/InputMethodSubtypeArrayTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodSubtypeArrayTest.java
index e7b1110f898a..e7b1110f898a 100644
--- a/core/tests/coretests/src/android/view/inputmethod/InputMethodSubtypeArrayTest.java
+++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodSubtypeArrayTest.java
diff --git a/core/tests/coretests/src/android/view/inputmethod/InputMethodSubtypeTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodSubtypeTest.java
index 5095cad1b607..5095cad1b607 100644
--- a/core/tests/coretests/src/android/view/inputmethod/InputMethodSubtypeTest.java
+++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodSubtypeTest.java
diff --git a/core/tests/coretests/src/android/view/inputmethod/InsertGestureTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InsertGestureTest.java
index 47a724d36038..47a724d36038 100644
--- a/core/tests/coretests/src/android/view/inputmethod/InsertGestureTest.java
+++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InsertGestureTest.java
diff --git a/core/tests/coretests/src/android/view/inputmethod/InsertModeGestureTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InsertModeGestureTest.java
index a94f8772fcd0..a94f8772fcd0 100644
--- a/core/tests/coretests/src/android/view/inputmethod/InsertModeGestureTest.java
+++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InsertModeGestureTest.java
diff --git a/core/tests/coretests/src/android/view/inputmethod/ParcelableHandwritingGestureTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/ParcelableHandwritingGestureTest.java
index 90f7d06857c7..90f7d06857c7 100644
--- a/core/tests/coretests/src/android/view/inputmethod/ParcelableHandwritingGestureTest.java
+++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/ParcelableHandwritingGestureTest.java
diff --git a/core/tests/coretests/src/android/view/inputmethod/SelectGestureTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SelectGestureTest.java
index b2eb07c0a9e7..b2eb07c0a9e7 100644
--- a/core/tests/coretests/src/android/view/inputmethod/SelectGestureTest.java
+++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SelectGestureTest.java
diff --git a/core/tests/coretests/src/android/view/inputmethod/SelectRangeGestureTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SelectRangeGestureTest.java
index df63a4aaaefe..df63a4aaaefe 100644
--- a/core/tests/coretests/src/android/view/inputmethod/SelectRangeGestureTest.java
+++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SelectRangeGestureTest.java
diff --git a/core/tests/coretests/src/android/view/inputmethod/SparseRectFArrayTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SparseRectFArrayTest.java
index f264cc630dc5..f264cc630dc5 100644
--- a/core/tests/coretests/src/android/view/inputmethod/SparseRectFArrayTest.java
+++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SparseRectFArrayTest.java
diff --git a/core/tests/coretests/src/android/view/inputmethod/SurroundingTextTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SurroundingTextTest.java
index 047f33074460..047f33074460 100644
--- a/core/tests/coretests/src/android/view/inputmethod/SurroundingTextTest.java
+++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SurroundingTextTest.java
diff --git a/core/tests/coretests/src/android/view/inputmethod/TextAppearanceInfoTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/TextAppearanceInfoTest.java
index 0750cf1a64ab..0750cf1a64ab 100644
--- a/core/tests/coretests/src/android/view/inputmethod/TextAppearanceInfoTest.java
+++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/TextAppearanceInfoTest.java
diff --git a/core/tests/coretests/src/com/android/internal/inputmethod/CompletableFutureUtilTest.kt b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/CompletableFutureUtilTest.kt
index 07471f08e0f5..07471f08e0f5 100644
--- a/core/tests/coretests/src/com/android/internal/inputmethod/CompletableFutureUtilTest.kt
+++ b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/CompletableFutureUtilTest.kt
diff --git a/core/tests/coretests/src/com/android/internal/inputmethod/InputConnectionWrapperTest.java b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputConnectionWrapperTest.java
index a6262944e8b0..a6262944e8b0 100644
--- a/core/tests/coretests/src/com/android/internal/inputmethod/InputConnectionWrapperTest.java
+++ b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputConnectionWrapperTest.java
diff --git a/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodDebugTest.java b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputMethodDebugTest.java
index 32bfdcb7b217..32bfdcb7b217 100644
--- a/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodDebugTest.java
+++ b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputMethodDebugTest.java
diff --git a/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodSubtypeHandleTest.java b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputMethodSubtypeHandleTest.java
index f111bf6fcd64..f111bf6fcd64 100644
--- a/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodSubtypeHandleTest.java
+++ b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputMethodSubtypeHandleTest.java
diff --git a/core/tests/coretests/src/com/android/internal/inputmethod/SubtypeLocaleUtilsTest.java b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/SubtypeLocaleUtilsTest.java
index ba6390808151..ba6390808151 100644
--- a/core/tests/coretests/src/com/android/internal/inputmethod/SubtypeLocaleUtilsTest.java
+++ b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/SubtypeLocaleUtilsTest.java
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index 440630287b7c..f47679991418 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -209,9 +209,13 @@ android_ravenwood_test {
"testng",
],
srcs: [
+ "src/android/content/pm/PackageManagerTest.java",
+ "src/android/content/pm/UserInfoTest.java",
"src/android/database/CursorWindowTest.java",
"src/android/os/**/*.java",
+ "src/android/telephony/PinResultTest.java",
"src/android/util/**/*.java",
+ "src/android/view/DisplayInfoTest.java",
"src/com/android/internal/logging/**/*.java",
"src/com/android/internal/os/**/*.java",
"src/com/android/internal/util/**/*.java",
diff --git a/core/tests/coretests/src/android/content/pm/PackageManagerTest.java b/core/tests/coretests/src/android/content/pm/PackageManagerTest.java
new file mode 100644
index 000000000000..20421d105db8
--- /dev/null
+++ b/core/tests/coretests/src/android/content/pm/PackageManagerTest.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class PackageManagerTest {
+ @Test
+ public void testPackageInfoFlags() throws Exception {
+ assertThat(PackageManager.PackageInfoFlags.of(42L).getValue()).isEqualTo(42L);
+ }
+
+ @Test
+ public void testApplicationInfoFlags() throws Exception {
+ assertThat(PackageManager.ApplicationInfoFlags.of(42L).getValue()).isEqualTo(42L);
+ }
+
+ @Test
+ public void testComponentInfoFlags() throws Exception {
+ assertThat(PackageManager.ComponentInfoFlags.of(42L).getValue()).isEqualTo(42L);
+ }
+
+ @Test
+ public void testResolveInfoFlags() throws Exception {
+ assertThat(PackageManager.ResolveInfoFlags.of(42L).getValue()).isEqualTo(42L);
+ }
+}
diff --git a/core/tests/coretests/src/android/content/pm/UserInfoTest.java b/core/tests/coretests/src/android/content/pm/UserInfoTest.java
new file mode 100644
index 000000000000..af36dbb36379
--- /dev/null
+++ b/core/tests/coretests/src/android/content/pm/UserInfoTest.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.UserHandle;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class UserInfoTest {
+ @Test
+ public void testSimple() throws Exception {
+ final UserInfo ui = new UserInfo(10, "Test", UserInfo.FLAG_GUEST);
+
+ assertThat(ui.getUserHandle()).isEqualTo(UserHandle.of(10));
+ assertThat(ui.name).isEqualTo("Test");
+
+ // Derived based on userType field
+ assertThat(ui.isManagedProfile()).isEqualTo(false);
+ assertThat(ui.isGuest()).isEqualTo(true);
+ assertThat(ui.isRestricted()).isEqualTo(false);
+ assertThat(ui.isDemo()).isEqualTo(false);
+ assertThat(ui.isCloneProfile()).isEqualTo(false);
+ assertThat(ui.isCommunalProfile()).isEqualTo(false);
+ assertThat(ui.isPrivateProfile()).isEqualTo(false);
+
+ // Derived based on flags field
+ assertThat(ui.isPrimary()).isEqualTo(false);
+ assertThat(ui.isAdmin()).isEqualTo(false);
+ assertThat(ui.isProfile()).isEqualTo(false);
+ assertThat(ui.isEnabled()).isEqualTo(true);
+ assertThat(ui.isQuietModeEnabled()).isEqualTo(false);
+ assertThat(ui.isEphemeral()).isEqualTo(false);
+ assertThat(ui.isForTesting()).isEqualTo(false);
+ assertThat(ui.isInitialized()).isEqualTo(false);
+ assertThat(ui.isFull()).isEqualTo(false);
+ assertThat(ui.isMain()).isEqualTo(false);
+
+ // Derived dynamically
+ assertThat(ui.canHaveProfile()).isEqualTo(false);
+ }
+
+ @Test
+ public void testDebug() throws Exception {
+ final UserInfo ui = new UserInfo(10, "Test", UserInfo.FLAG_GUEST);
+
+ assertThat(ui.toString()).isNotEmpty();
+ assertThat(ui.toFullString()).isNotEmpty();
+ }
+}
diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
index e32a57b1aefe..a2a5433eca24 100644
--- a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
+++ b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
@@ -145,7 +145,6 @@ class FontScaleConverterFactoryTest {
fun unnecessaryFontScalesReturnsNull() {
assertThat(FontScaleConverterFactory.forScale(0F)).isNull()
assertThat(FontScaleConverterFactory.forScale(1F)).isNull()
- assertThat(FontScaleConverterFactory.forScale(1.1F)).isNull()
assertThat(FontScaleConverterFactory.forScale(0.85F)).isNull()
}
@@ -176,7 +175,7 @@ class FontScaleConverterFactoryTest {
assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(-1f)).isFalse()
assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(0.85f)).isFalse()
assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(1.02f)).isFalse()
- assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(1.10f)).isFalse()
+ assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(1.10f)).isTrue()
assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(1.15f)).isTrue()
assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(1.1499999f))
.isTrue()
diff --git a/core/tests/coretests/src/android/telephony/PinResultTest.java b/core/tests/coretests/src/android/telephony/PinResultTest.java
new file mode 100644
index 000000000000..c260807e5cbc
--- /dev/null
+++ b/core/tests/coretests/src/android/telephony/PinResultTest.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class PinResultTest {
+ @Test
+ public void testSimple() throws Exception {
+ final PinResult res = new PinResult(PinResult.PIN_RESULT_TYPE_SUCCESS, 5);
+ assertThat(res.getResult()).isEqualTo(PinResult.PIN_RESULT_TYPE_SUCCESS);
+ assertThat(res.getAttemptsRemaining()).isEqualTo(5);
+ }
+}
diff --git a/core/tests/coretests/src/android/util/SingletonTest.java b/core/tests/coretests/src/android/util/SingletonTest.java
new file mode 100644
index 000000000000..8c5a9639c23a
--- /dev/null
+++ b/core/tests/coretests/src/android/util/SingletonTest.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+import static org.junit.Assert.assertTrue;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class SingletonTest {
+ @Test
+ public void testSimple() throws Exception {
+ final Singleton<Object> singleton = new Singleton<>() {
+ @Override
+ protected Object create() {
+ return new Object();
+ }
+ };
+
+ final Object first = singleton.get();
+ final Object second = singleton.get();
+ assertTrue(first == second);
+ }
+}
diff --git a/core/tests/coretests/src/android/view/DisplayInfoTest.java b/core/tests/coretests/src/android/view/DisplayInfoTest.java
index 803d38c4208a..4c5b7e508e34 100644
--- a/core/tests/coretests/src/android/view/DisplayInfoTest.java
+++ b/core/tests/coretests/src/android/view/DisplayInfoTest.java
@@ -21,9 +21,12 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import android.platform.test.ravenwood.RavenwoodRule;
+
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -32,6 +35,9 @@ import org.junit.runner.RunWith;
public class DisplayInfoTest {
private static final float FLOAT_EQUAL_DELTA = 0.0001f;
+ @Rule
+ public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
@Test
public void testDefaultDisplayInfosAreEqual() {
DisplayInfo displayInfo1 = new DisplayInfo();
diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
index a709d7be898b..52ff0d4037e8 100644
--- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
+++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
@@ -20,6 +20,7 @@ import static android.window.OnBackInvokedDispatcher.PRIORITY_DEFAULT;
import static android.window.OnBackInvokedDispatcher.PRIORITY_OVERLAY;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.atLeast;
@@ -358,7 +359,7 @@ public class WindowOnBackInvokedDispatcherTest {
}
@Test
- public void onDetachFromWindow_cancelCallbackAndIgnoreOnBackInvoked() throws RemoteException {
+ public void onDetachFromWindow_cancelsBackAnimation() throws RemoteException {
mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1);
OnBackInvokedCallbackInfo callbackInfo = assertSetCallbackInfo();
@@ -368,13 +369,12 @@ public class WindowOnBackInvokedDispatcherTest {
waitForIdle();
verify(mCallback1).onBackStarted(any(BackEvent.class));
- // This should trigger mCallback1.onBackCancelled()
+ // This should trigger mCallback1.onBackCancelled() and unset the callback in WM
mDispatcher.detachFromWindow();
- // This should be ignored by mCallback1
- callbackInfo.getCallback().onBackInvoked();
+ OnBackInvokedCallbackInfo callbackInfo1 = assertSetCallbackInfo();
+ assertNull(callbackInfo1);
waitForIdle();
- verify(mCallback1, never()).onBackInvoked();
verify(mCallback1).onBackCancelled();
}
}
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
index 75b0d4a159d9..145aa60dc495 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
@@ -21,7 +21,6 @@ import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHO
import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN;
import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE;
import static android.provider.Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES;
-import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
import static com.android.internal.accessibility.AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME;
import static com.android.internal.accessibility.AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME;
@@ -83,6 +82,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.internal.R;
import com.android.internal.accessibility.AccessibilityShortcutController.FrameworkObjectProvider;
+import com.android.internal.accessibility.common.ShortcutConstants;
import com.android.internal.util.test.FakeSettingsProvider;
import org.junit.AfterClass;
@@ -726,14 +726,14 @@ public class AccessibilityShortcutControllerTest {
private void configureNoShortcutService() throws Exception {
when(mAccessibilityManagerService
- .getAccessibilityShortcutTargets(ACCESSIBILITY_SHORTCUT_KEY))
+ .getAccessibilityShortcutTargets(ShortcutConstants.UserShortcutType.HARDWARE))
.thenReturn(Collections.emptyList());
Settings.Secure.putString(mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, "");
}
private void configureValidShortcutService() throws Exception {
when(mAccessibilityManagerService
- .getAccessibilityShortcutTargets(ACCESSIBILITY_SHORTCUT_KEY))
+ .getAccessibilityShortcutTargets(ShortcutConstants.UserShortcutType.HARDWARE))
.thenReturn(Collections.singletonList(SERVICE_NAME_STRING));
Settings.Secure.putString(
mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, SERVICE_NAME_STRING);
@@ -744,7 +744,7 @@ public class AccessibilityShortcutControllerTest {
(ComponentName) AccessibilityShortcutController.getFrameworkShortcutFeaturesMap()
.keySet().toArray()[0];
when(mAccessibilityManagerService
- .getAccessibilityShortcutTargets(ACCESSIBILITY_SHORTCUT_KEY))
+ .getAccessibilityShortcutTargets(ShortcutConstants.UserShortcutType.HARDWARE))
.thenReturn(Collections.singletonList(featureComponentName.flattenToString()));
Settings.Secure.putString(mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
featureComponentName.flattenToString());
@@ -806,7 +806,7 @@ public class AccessibilityShortcutControllerTest {
private void configureDefaultAccessibilityService() throws Exception {
when(mAccessibilityManagerService
- .getAccessibilityShortcutTargets(ACCESSIBILITY_SHORTCUT_KEY))
+ .getAccessibilityShortcutTargets(ShortcutConstants.UserShortcutType.HARDWARE))
.thenReturn(Collections.singletonList(SERVICE_NAME_STRING));
when(mResources.getString(R.string.config_defaultAccessibilityService)).thenReturn(
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTargetTest.java b/core/tests/coretests/src/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTargetTest.java
index 69b6a9b7aa87..2ea044ccfb52 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTargetTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTargetTest.java
@@ -39,6 +39,7 @@ import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.accessibility.TestUtils;
+import com.android.internal.accessibility.common.ShortcutConstants;
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.internal.util.test.FakeSettingsProviderRule;
@@ -99,7 +100,7 @@ public class InvisibleToggleAccessibilityServiceTargetTest {
mSut = new InvisibleToggleAccessibilityServiceTarget(
mContextSpy,
- AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY, accessibilityServiceInfo);
+ ShortcutConstants.UserShortcutType.HARDWARE, accessibilityServiceInfo);
}
@Test
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 2de305f8e925..28734283e9b7 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -450,7 +450,7 @@ applications that come with the platform
<!-- Permissions required for CTS test - android.server.biometrics -->
<permission name="android.permission.USE_BIOMETRIC" />
<permission name="android.permission.TEST_BIOMETRIC" />
- <permission name="android.permission.MANAGE_BIOMETRIC_DIALOG" />
+ <permission name="android.permission.SET_BIOMETRIC_DIALOG_LOGO" />
<permission name="android.permission.USE_BACKGROUND_FACE_AUTHENTICATION" />
<!-- Permissions required for CTS test - CtsContactsProviderTestCases -->
<permission name="android.contacts.permission.MANAGE_SIM_ACCOUNTS" />
@@ -642,5 +642,6 @@ applications that come with the platform
<privapp-permissions package="com.android.devicediagnostics">
<permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
+ <permission name="android.permission.BATTERY_STATS"/>
</privapp-permissions>
</permissions>
diff --git a/libs/WindowManager/Shell/multivalentTests/OWNERS b/libs/WindowManager/Shell/multivalentTests/OWNERS
new file mode 100644
index 000000000000..24c1a3a6d400
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentTests/OWNERS
@@ -0,0 +1,4 @@
+atsjenk@google.com
+liranb@google.com
+madym@google.com
+
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDoubleTapHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDoubleTapHelper.java
index 1b1ebc39b558..4cbb78f2dae2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDoubleTapHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDoubleTapHelper.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,14 +14,12 @@
* limitations under the License.
*/
-package com.android.wm.shell.pip.phone;
+package com.android.wm.shell.common.pip;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.graphics.Rect;
-import com.android.wm.shell.common.pip.PipBoundsState;
-
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -50,9 +48,9 @@ public class PipDoubleTapHelper {
@Retention(RetentionPolicy.SOURCE)
@interface PipSizeSpec {}
- static final int SIZE_SPEC_DEFAULT = 0;
- static final int SIZE_SPEC_MAX = 1;
- static final int SIZE_SPEC_CUSTOM = 2;
+ public static final int SIZE_SPEC_DEFAULT = 0;
+ public static final int SIZE_SPEC_MAX = 1;
+ public static final int SIZE_SPEC_CUSTOM = 2;
/**
* Returns MAX or DEFAULT {@link PipSizeSpec} to toggle to/from.
@@ -84,7 +82,7 @@ public class PipDoubleTapHelper {
* @return pip screen size to switch to
*/
@PipSizeSpec
- static int nextSizeSpec(@NonNull PipBoundsState mPipBoundsState,
+ public static int nextSizeSpec(@NonNull PipBoundsState mPipBoundsState,
@NonNull Rect userResizeBounds) {
// is pip screen at its maximum
boolean isScreenMax = mPipBoundsState.getBounds().width()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
index 2dd27430e348..dbf7186def8a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
@@ -80,8 +80,7 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract {
Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> onRestartButtonClicked) {
super(context, taskInfo, syncQueue, taskListener, displayLayout);
mCallback = callback;
- mHasSizeCompat = taskInfo.appCompatTaskInfo.topActivityInSizeCompat
- && shouldShowSizeCompatRestartButton(taskInfo);
+ mHasSizeCompat = taskInfo.appCompatTaskInfo.topActivityInSizeCompat;
mCameraCompatControlState = taskInfo.appCompatTaskInfo.cameraCompatControlState;
mCompatUIHintsState = compatUIHintsState;
mCompatUIConfiguration = compatUIConfiguration;
@@ -106,7 +105,8 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract {
@Override
protected boolean eligibleToShowLayout() {
- return mHasSizeCompat || shouldShowCameraControl();
+ return (mHasSizeCompat && shouldShowSizeCompatRestartButton(getLastTaskInfo()))
+ || shouldShowCameraControl();
}
@Override
@@ -114,11 +114,6 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract {
mLayout = inflateLayout();
mLayout.inject(this);
- final TaskInfo taskInfo = getLastTaskInfo();
- if (taskInfo != null) {
- mHasSizeCompat = mHasSizeCompat && shouldShowSizeCompatRestartButton(taskInfo);
- }
-
updateVisibilityOfViews();
if (mHasSizeCompat) {
@@ -139,8 +134,7 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract {
boolean canShow) {
final boolean prevHasSizeCompat = mHasSizeCompat;
final int prevCameraCompatControlState = mCameraCompatControlState;
- mHasSizeCompat = taskInfo.appCompatTaskInfo.topActivityInSizeCompat
- && shouldShowSizeCompatRestartButton(taskInfo);
+ mHasSizeCompat = taskInfo.appCompatTaskInfo.topActivityInSizeCompat;
mCameraCompatControlState = taskInfo.appCompatTaskInfo.cameraCompatControlState;
if (!super.updateCompatInfo(taskInfo, taskListener, canShow)) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java
index 180498c50c78..0564c95aef5c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java
@@ -332,7 +332,7 @@ public abstract class CompatUIWindowManagerAbstract extends WindowlessWindowMana
updateSurfacePosition();
}
- @Nullable
+ @NonNull
protected TaskInfo getLastTaskInfo() {
return mTaskInfo;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
index 3b48c67a5bbd..7b98fa6523cb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
@@ -50,15 +50,16 @@ import java.util.Optional;
public abstract class Pip2Module {
@WMSingleton
@Provides
- static PipTransition providePipTransition(@NonNull ShellInit shellInit,
+ static PipTransition providePipTransition(Context context,
+ @NonNull ShellInit shellInit,
@NonNull ShellTaskOrganizer shellTaskOrganizer,
@NonNull Transitions transitions,
PipBoundsState pipBoundsState,
PipBoundsAlgorithm pipBoundsAlgorithm,
Optional<PipController> pipController,
@NonNull PipScheduler pipScheduler) {
- return new PipTransition(shellInit, shellTaskOrganizer, transitions, pipBoundsState, null,
- pipBoundsAlgorithm, pipScheduler);
+ return new PipTransition(context, shellInit, shellTaskOrganizer, transitions,
+ pipBoundsState, null, pipBoundsAlgorithm, pipScheduler);
}
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
index 04911c0bc064..0e7073688ec4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
@@ -47,6 +47,7 @@ import com.android.wm.shell.transition.Transitions;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
+import java.util.function.Consumer;
/**
* Responsible supplying PiP Transitions.
@@ -116,6 +117,17 @@ public abstract class PipTransitionController implements Transitions.TransitionH
}
/**
+ * Called when the Shell wants to start resizing Pip transition/animation.
+ *
+ * @param onFinishResizeCallback callback guaranteed to execute when animation ends and
+ * client completes any potential draws upon WM state updates.
+ */
+ public void startResizeTransition(WindowContainerTransaction wct,
+ Consumer<Rect> onFinishResizeCallback) {
+ // Default implementation does nothing.
+ }
+
+ /**
* Called when the transition animation can't continue (eg. task is removed during
* animation)
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
index 452a41696fcf..81705e20a1df 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
@@ -52,6 +52,7 @@ import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDoubleTapHelper;
import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.common.pip.SizeSpecSource;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
index 0b8f60e44c7e..57b73b3019f4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
@@ -24,10 +24,12 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.graphics.Rect;
import android.view.SurfaceControl;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
+import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
@@ -36,6 +38,10 @@ import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.pip.PipTransitionController;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.function.Consumer;
+
/**
* Scheduler for Shell initiated PiP transitions and animations.
*/
@@ -58,13 +64,37 @@ public class PipScheduler {
private SurfaceControl mPinnedTaskLeash;
/**
- * A temporary broadcast receiver to initiate exit PiP via expand.
- * This will later be modified to be triggered by the PiP menu.
+ * Temporary PiP CUJ codes to schedule PiP related transitions directly from Shell.
+ * This is used for a broadcast receiver to resolve intents. This should be removed once
+ * there is an equivalent of PipTouchHandler and PipResizeGestureHandler for PiP2.
+ */
+ private static final int PIP_EXIT_VIA_EXPAND_CODE = 0;
+ private static final int PIP_DOUBLE_TAP = 1;
+
+ @IntDef(value = {
+ PIP_EXIT_VIA_EXPAND_CODE,
+ PIP_DOUBLE_TAP
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface PipUserJourneyCode {}
+
+ /**
+ * A temporary broadcast receiver to initiate PiP CUJs.
*/
private class PipSchedulerReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
- scheduleExitPipViaExpand();
+ int userJourneyCode = intent.getIntExtra("cuj_code_extra", 0);
+ switch (userJourneyCode) {
+ case PIP_EXIT_VIA_EXPAND_CODE:
+ scheduleExitPipViaExpand();
+ break;
+ case PIP_DOUBLE_TAP:
+ scheduleDoubleTapToResize();
+ break;
+ default:
+ throw new IllegalStateException("unexpected CUJ code=" + userJourneyCode);
+ }
}
}
@@ -121,6 +151,23 @@ public class PipScheduler {
}
}
+ /**
+ * Schedules resize PiP via double tap.
+ */
+ public void scheduleDoubleTapToResize() {}
+
+ /**
+ * Animates resizing of the pinned stack given the duration.
+ */
+ public void scheduleAnimateResizePip(Rect toBounds, Consumer<Rect> onFinishResizeCallback) {
+ if (mPipTaskToken == null) {
+ return;
+ }
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.setBounds(mPipTaskToken, toBounds);
+ mPipTransitionController.startResizeTransition(wct, onFinishResizeCallback);
+ }
+
void onExitPip() {
mPipTaskToken = null;
mPinnedTaskLeash = null;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index 3b0e7c139bed..f3d178aef4ea 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -22,10 +22,12 @@ import static android.view.WindowManager.TRANSIT_PIP;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_RESIZE_PIP;
import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.PictureInPictureParams;
+import android.content.Context;
import android.graphics.Rect;
import android.os.IBinder;
import android.view.SurfaceControl;
@@ -36,6 +38,7 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.Nullable;
+import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
@@ -45,25 +48,29 @@ import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
+import java.util.function.Consumer;
+
/**
* Implementation of transitions for PiP on phone.
*/
public class PipTransition extends PipTransitionController {
private static final String TAG = PipTransition.class.getSimpleName();
+ private final Context mContext;
private final PipScheduler mPipScheduler;
@Nullable
private WindowContainerToken mPipTaskToken;
@Nullable
private IBinder mEnterTransition;
@Nullable
- private IBinder mAutoEnterButtonNavTransition;
- @Nullable
private IBinder mExitViaExpandTransition;
@Nullable
- private IBinder mLegacyEnterTransition;
+ private IBinder mResizeTransition;
+
+ private Consumer<Rect> mFinishResizeCallback;
public PipTransition(
+ Context context,
@NonNull ShellInit shellInit,
@NonNull ShellTaskOrganizer shellTaskOrganizer,
@NonNull Transitions transitions,
@@ -74,6 +81,7 @@ public class PipTransition extends PipTransitionController {
super(shellInit, shellTaskOrganizer, transitions, pipBoundsState, pipMenuController,
pipBoundsAlgorithm);
+ mContext = context;
mPipScheduler = pipScheduler;
mPipScheduler.setPipTransitionController(this);
}
@@ -87,7 +95,7 @@ public class PipTransition extends PipTransitionController {
@Override
public void startExitTransition(int type, WindowContainerTransaction out,
- @android.annotation.Nullable Rect destinationBounds) {
+ @Nullable Rect destinationBounds) {
if (out == null) {
return;
}
@@ -97,6 +105,16 @@ public class PipTransition extends PipTransitionController {
}
}
+ @Override
+ public void startResizeTransition(WindowContainerTransaction wct,
+ Consumer<Rect> onFinishResizeCallback) {
+ if (wct == null) {
+ return;
+ }
+ mResizeTransition = mTransitions.startTransition(TRANSIT_RESIZE_PIP, wct, this);
+ mFinishResizeCallback = onFinishResizeCallback;
+ }
+
@Nullable
@Override
public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
@@ -126,43 +144,6 @@ public class PipTransition extends PipTransitionController {
public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
@Nullable SurfaceControl.Transaction finishT) {}
- private WindowContainerTransaction getEnterPipTransaction(@NonNull IBinder transition,
- @NonNull TransitionRequestInfo request) {
- // cache the original task token to check for multi-activity case later
- final ActivityManager.RunningTaskInfo pipTask = request.getPipTask();
- PictureInPictureParams pipParams = pipTask.pictureInPictureParams;
- mPipBoundsState.setBoundsStateForEntry(pipTask.topActivity, pipTask.topActivityInfo,
- pipParams, mPipBoundsAlgorithm);
-
- // calculate the entry bounds and notify core to move task to pinned with final bounds
- final Rect entryBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
- mPipBoundsState.setBounds(entryBounds);
-
- WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.movePipActivityToPinnedRootTask(pipTask.token, entryBounds);
- return wct;
- }
-
- private boolean isAutoEnterInButtonNavigation(@NonNull TransitionRequestInfo requestInfo) {
- final ActivityManager.RunningTaskInfo pipTask = requestInfo.getPipTask();
- if (pipTask == null) {
- return false;
- }
- if (pipTask.pictureInPictureParams == null) {
- return false;
- }
-
- // Assuming auto-enter is enabled and pipTask is non-null, the TRANSIT_OPEN request type
- // implies that we are entering PiP in button navigation mode. This is guaranteed by
- // TaskFragment#startPausing()` in Core which wouldn't get called in gesture nav.
- return requestInfo.getType() == TRANSIT_OPEN
- && pipTask.pictureInPictureParams.isAutoEnterEnabled();
- }
-
- private boolean isEnterPictureInPictureModeRequest(@NonNull TransitionRequestInfo requestInfo) {
- return requestInfo.getType() == TRANSIT_PIP;
- }
-
@Override
public boolean startAnimation(@NonNull IBinder transition,
@NonNull TransitionInfo info,
@@ -182,16 +163,48 @@ public class PipTransition extends PipTransitionController {
} else if (transition == mExitViaExpandTransition) {
mExitViaExpandTransition = null;
return startExpandAnimation(info, startTransaction, finishTransaction, finishCallback);
+ } else if (transition == mResizeTransition) {
+ mResizeTransition = null;
+ return startResizeAnimation(info, startTransaction, finishTransaction, finishCallback);
}
return false;
}
- private boolean isLegacyEnter(@NonNull TransitionInfo info) {
+ private boolean startResizeAnimation(@NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
TransitionInfo.Change pipChange = getPipChange(info);
- // If the only change in the changes list is a TO_FRONT mode PiP task,
- // then this is legacy-enter PiP.
- return pipChange != null && pipChange.getMode() == TRANSIT_TO_FRONT
- && info.getChanges().size() == 1;
+ if (pipChange == null) {
+ return false;
+ }
+ SurfaceControl pipLeash = pipChange.getLeash();
+ Rect destinationBounds = pipChange.getEndAbsBounds();
+
+ // Even though the final bounds and crop are applied with finishTransaction since
+ // this is a visible change, we still need to handle the app draw coming in. Snapshot
+ // covering app draw during collection will be removed by startTransaction. So we make
+ // the crop equal to the final bounds and then scale the leash back to starting bounds.
+ startTransaction.setWindowCrop(pipLeash, pipChange.getEndAbsBounds().width(),
+ pipChange.getEndAbsBounds().height());
+ startTransaction.setScale(pipLeash,
+ (float) mPipBoundsState.getBounds().width() / destinationBounds.width(),
+ (float) mPipBoundsState.getBounds().height() / destinationBounds.height());
+ startTransaction.apply();
+
+ finishTransaction.setScale(pipLeash,
+ (float) mPipBoundsState.getBounds().width() / destinationBounds.width(),
+ (float) mPipBoundsState.getBounds().height() / destinationBounds.height());
+
+ // We are done with the transition, but will continue animating leash to final bounds.
+ finishCallback.onTransitionFinished(null);
+
+ // Animate the pip leash with the new buffer
+ final int duration = mContext.getResources().getInteger(
+ R.integer.config_pipResizeAnimationDuration);
+ // TODO: b/275910498 Couple this routine with a new implementation of the PiP animator.
+ startResizeAnimation(pipLeash, mPipBoundsState.getBounds(), destinationBounds, duration);
+ return true;
}
private boolean startBoundsTypeEnterAnimation(@NonNull TransitionInfo info,
@@ -251,6 +264,57 @@ public class PipTransition extends PipTransitionController {
return null;
}
+ private WindowContainerTransaction getEnterPipTransaction(@NonNull IBinder transition,
+ @NonNull TransitionRequestInfo request) {
+ // cache the original task token to check for multi-activity case later
+ final ActivityManager.RunningTaskInfo pipTask = request.getPipTask();
+ PictureInPictureParams pipParams = pipTask.pictureInPictureParams;
+ mPipBoundsState.setBoundsStateForEntry(pipTask.topActivity, pipTask.topActivityInfo,
+ pipParams, mPipBoundsAlgorithm);
+
+ // calculate the entry bounds and notify core to move task to pinned with final bounds
+ final Rect entryBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
+ mPipBoundsState.setBounds(entryBounds);
+
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.movePipActivityToPinnedRootTask(pipTask.token, entryBounds);
+ return wct;
+ }
+
+ private boolean isAutoEnterInButtonNavigation(@NonNull TransitionRequestInfo requestInfo) {
+ final ActivityManager.RunningTaskInfo pipTask = requestInfo.getPipTask();
+ if (pipTask == null) {
+ return false;
+ }
+ if (pipTask.pictureInPictureParams == null) {
+ return false;
+ }
+
+ // Assuming auto-enter is enabled and pipTask is non-null, the TRANSIT_OPEN request type
+ // implies that we are entering PiP in button navigation mode. This is guaranteed by
+ // TaskFragment#startPausing()` in Core which wouldn't get called in gesture nav.
+ return requestInfo.getType() == TRANSIT_OPEN
+ && pipTask.pictureInPictureParams.isAutoEnterEnabled();
+ }
+
+ private boolean isEnterPictureInPictureModeRequest(@NonNull TransitionRequestInfo requestInfo) {
+ return requestInfo.getType() == TRANSIT_PIP;
+ }
+
+ private boolean isLegacyEnter(@NonNull TransitionInfo info) {
+ TransitionInfo.Change pipChange = getPipChange(info);
+ // If the only change in the changes list is a TO_FRONT mode PiP task,
+ // then this is legacy-enter PiP.
+ return pipChange != null && pipChange.getMode() == TRANSIT_TO_FRONT
+ && info.getChanges().size() == 1;
+ }
+
+ /**
+ * TODO: b/275910498 Use a new implementation of the PiP animator here.
+ */
+ private void startResizeAnimation(SurfaceControl leash, Rect startBounds,
+ Rect endBounds, int duration) {}
+
private void onExitPip() {
mPipTaskToken = null;
mPipScheduler.onExitPip();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
index 253acc49071a..0ca244c4b96a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
@@ -158,5 +158,10 @@ interface ISplitScreen {
* does not expect split to currently be running.
*/
RemoteAnimationTarget[] onStartingSplitLegacy(in RemoteAnimationTarget[] appTargets) = 14;
+
+ /**
+ * Reverse the split.
+ */
+ oneway void switchSplitPosition() = 22;
}
-// Last id = 21 \ No newline at end of file
+// Last id = 22 \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 2ec52bb028c6..70cb2fc6d52c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -1109,6 +1109,12 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
mStageCoordinator.onDroppedToSplit(position, dragSessionId);
}
+ void switchSplitPosition(String reason) {
+ if (isSplitScreenVisible()) {
+ mStageCoordinator.switchSplitPosition(reason);
+ }
+ }
+
/**
* Return the {@param exitReason} as a string.
*/
@@ -1473,5 +1479,11 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
true /* blocking */);
return out[0];
}
+
+ @Override
+ public void switchSplitPosition() {
+ executeRemoteCallWithTaskPermission(mController, "switchSplitPosition",
+ (controller) -> controller.switchSplitPosition("remoteCall"));
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java
index 7fd03a9a306b..7f16c5e3592e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java
@@ -43,6 +43,8 @@ public class SplitScreenShellCommandHandler implements
return runRemoveFromSideStage(args, pw);
case "setSideStagePosition":
return runSetSideStagePosition(args, pw);
+ case "switchSplitPosition":
+ return runSwitchSplitPosition();
default:
pw.println("Invalid command: " + args[0]);
return false;
@@ -84,6 +86,11 @@ public class SplitScreenShellCommandHandler implements
return true;
}
+ private boolean runSwitchSplitPosition() {
+ mController.switchSplitPosition("shellCommand");
+ return true;
+ }
+
@Override
public void printShellCommandHelp(PrintWriter pw, String prefix) {
pw.println(prefix + "moveToSideStage <taskId> <SideStagePosition>");
@@ -92,5 +99,7 @@ public class SplitScreenShellCommandHandler implements
pw.println(prefix + " Remove a task with given id in split-screen mode.");
pw.println(prefix + "setSideStagePosition <SideStagePosition>");
pw.println(prefix + " Sets the position of the side-stage.");
+ pw.println(prefix + "switchSplitPosition");
+ pw.println(prefix + " Reverses the split.");
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 3fb0dbfaa63d..67fc7e2b4ea6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -175,6 +175,9 @@ public class Transitions implements RemoteCallable<Transitions>,
/** Transition to animate task to desktop. */
public static final int TRANSIT_MOVE_TO_DESKTOP = WindowManager.TRANSIT_FIRST_CUSTOM + 15;
+ /** Transition to resize PiP task. */
+ public static final int TRANSIT_RESIZE_PIP = TRANSIT_FIRST_CUSTOM + 16;
+
private final ShellTaskOrganizer mOrganizer;
private final Context mContext;
private final ShellExecutor mMainExecutor;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 554b1fb99550..4ba05ce8aef1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -32,6 +32,7 @@ import static com.android.wm.shell.windowdecor.MoveToDesktopAnimator.DRAG_FREEFO
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
+import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityTaskManager;
@@ -60,7 +61,6 @@ import android.view.ViewConfiguration;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
-import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
@@ -544,12 +544,22 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
return true;
}
+ /**
+ * Perform a task size toggle on release of the double-tap, assuming no drag event
+ * was handled during the double-tap.
+ * @param e The motion event that occurred during the double-tap gesture.
+ * @return true if the event should be consumed, false if not
+ */
@Override
- public boolean onDoubleTap(@NonNull MotionEvent e) {
+ public boolean onDoubleTapEvent(@NonNull MotionEvent e) {
+ final int action = e.getActionMasked();
+ if (mIsDragging || (action != MotionEvent.ACTION_UP
+ && action != MotionEvent.ACTION_CANCEL)) {
+ return false;
+ }
final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
- mDesktopTasksController.ifPresent(c -> {
- c.toggleDesktopTaskSize(taskInfo, mWindowDecorByTaskId.get(taskInfo.taskId));
- });
+ mDesktopTasksController.ifPresent(c -> c.toggleDesktopTaskSize(taskInfo,
+ mWindowDecorByTaskId.get(taskInfo.taskId)));
return true;
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
index 4ddc539eb220..dd358e757fde 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
@@ -30,6 +30,7 @@ import static org.mockito.Mockito.verify;
import android.app.ActivityManager;
import android.app.AppCompatTaskInfo.CameraCompatControlState;
import android.app.TaskInfo;
+import android.graphics.Rect;
import android.testing.AndroidTestingRunner;
import android.util.Pair;
import android.view.LayoutInflater;
@@ -83,6 +84,7 @@ public class CompatUILayoutTest extends ShellTestCase {
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
+ doReturn(100).when(mCompatUIConfiguration).getHideSizeCompatRestartButtonTolerance();
mTaskInfo = createTaskInfo(/* hasSizeCompat= */ false, CAMERA_COMPAT_CONTROL_HIDDEN);
mWindowManager = new CompatUIWindowManager(mContext, mTaskInfo, mSyncTransactionQueue,
mCallback, mTaskListener, new DisplayLayout(), new CompatUIHintsState(),
@@ -127,7 +129,6 @@ public class CompatUILayoutTest extends ShellTestCase {
@Test
public void testOnClickForSizeCompatHint() {
mWindowManager.mHasSizeCompat = true;
- doReturn(true).when(mWindowManager).shouldShowSizeCompatRestartButton(mTaskInfo);
mWindowManager.createLayout(/* canShow= */ true);
final LinearLayout sizeCompatHint = mLayout.findViewById(R.id.size_compat_hint);
sizeCompatHint.performClick();
@@ -222,6 +223,9 @@ public class CompatUILayoutTest extends ShellTestCase {
taskInfo.taskId = TASK_ID;
taskInfo.appCompatTaskInfo.topActivityInSizeCompat = hasSizeCompat;
taskInfo.appCompatTaskInfo.cameraCompatControlState = cameraCompatControlState;
+ taskInfo.appCompatTaskInfo.topActivityLetterboxHeight = 1000;
+ taskInfo.appCompatTaskInfo.topActivityLetterboxWidth = 1000;
+ taskInfo.configuration.windowConfiguration.setBounds(new Rect(0, 0, 2000, 2000));
return taskInfo;
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
index 2acfd83084ab..4f261cd79d39 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
@@ -20,7 +20,6 @@ import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_DISMISSED;
import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN;
import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
import static android.view.WindowInsets.Type.navigationBars;
@@ -86,6 +85,8 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
private static final int TASK_ID = 1;
+ private static final int TASK_WIDTH = 2000;
+ private static final int TASK_HEIGHT = 2000;
@Mock private SyncTransactionQueue mSyncTransactionQueue;
@Mock private CompatUIController.CompatUICallback mCallback;
@@ -101,6 +102,7 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
+ doReturn(100).when(mCompatUIConfiguration).getHideSizeCompatRestartButtonTolerance();
mTaskInfo = createTaskInfo(/* hasSizeCompat= */ false, CAMERA_COMPAT_CONTROL_HIDDEN);
mWindowManager = new CompatUIWindowManager(mContext, mTaskInfo, mSyncTransactionQueue,
mCallback, mTaskListener, new DisplayLayout(), new CompatUIHintsState(),
@@ -115,7 +117,6 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
public void testCreateSizeCompatButton() {
// Doesn't create layout if show is false.
mWindowManager.mHasSizeCompat = true;
- doReturn(true).when(mWindowManager).shouldShowSizeCompatRestartButton(mTaskInfo);
assertTrue(mWindowManager.createLayout(/* canShow= */ false));
verify(mWindowManager, never()).inflateLayout();
@@ -147,6 +148,13 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
mWindowManager.mHasSizeCompat = false;
assertFalse(mWindowManager.createLayout(/* canShow= */ true));
+ // Returns false and doesn't create layout if restart button should be hidden.
+ clearInvocations(mWindowManager);
+ mWindowManager.mHasSizeCompat = true;
+ mTaskInfo.appCompatTaskInfo.topActivityLetterboxWidth = TASK_WIDTH;
+ mTaskInfo.appCompatTaskInfo.topActivityLetterboxHeight = TASK_HEIGHT;
+ assertFalse(mWindowManager.createLayout(/* canShow= */ true));
+
verify(mWindowManager, never()).inflateLayout();
}
@@ -293,8 +301,6 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
@Test
public void testUpdateCompatInfoLayoutNotInflatedYet() {
- mWindowManager.mHasSizeCompat = true;
- doReturn(true).when(mWindowManager).shouldShowSizeCompatRestartButton(any());
mWindowManager.createLayout(/* canShow= */ false);
verify(mWindowManager, never()).inflateLayout();
@@ -314,6 +320,15 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true);
verify(mWindowManager).inflateLayout();
+
+ // Change shouldShowSizeCompatRestartButton to false and pass canShow true, layout
+ // shouldn't be inflated
+ clearInvocations(mWindowManager);
+ taskInfo.appCompatTaskInfo.topActivityLetterboxWidth = TASK_WIDTH;
+ taskInfo.appCompatTaskInfo.topActivityLetterboxHeight = TASK_HEIGHT;
+ mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true);
+
+ verify(mWindowManager, never()).inflateLayout();
}
@Test
@@ -364,7 +379,6 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
// Create button if it is not created.
mWindowManager.mLayout = null;
mWindowManager.mHasSizeCompat = true;
- doReturn(true).when(mWindowManager).shouldShowSizeCompatRestartButton(mTaskInfo);
mWindowManager.updateVisibility(/* canShow= */ true);
verify(mWindowManager).createLayout(/* canShow= */ true);
@@ -489,7 +503,6 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
TaskInfo taskInfo = createTaskInfo(true, CAMERA_COMPAT_CONTROL_HIDDEN);
taskInfo.configuration.windowConfiguration.setBounds(new Rect(0, 0, 2000, 2000));
taskInfo.appCompatTaskInfo.topActivityLetterboxHeight = 2000;
- taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
taskInfo.appCompatTaskInfo.topActivityLetterboxWidth = 1850;
assertFalse(mWindowManager.shouldShowSizeCompatRestartButton(taskInfo));
@@ -514,6 +527,11 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
taskInfo.appCompatTaskInfo.topActivityInSizeCompat = hasSizeCompat;
taskInfo.appCompatTaskInfo.cameraCompatControlState = cameraCompatControlState;
taskInfo.configuration.uiMode &= ~Configuration.UI_MODE_TYPE_DESK;
+ // Letterboxed activity that takes half the screen should show size compat restart button
+ taskInfo.configuration.windowConfiguration.setBounds(
+ new Rect(0, 0, TASK_WIDTH, TASK_HEIGHT));
+ taskInfo.appCompatTaskInfo.topActivityLetterboxHeight = 1000;
+ taskInfo.appCompatTaskInfo.topActivityLetterboxWidth = 1000;
return taskInfo;
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipDoubleTapHelperTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipDoubleTapHelperTest.java
index 0f8db85dcef4..b583acda1c9a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipDoubleTapHelperTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipDoubleTapHelperTest.java
@@ -16,10 +16,10 @@
package com.android.wm.shell.pip.phone;
-import static com.android.wm.shell.pip.phone.PipDoubleTapHelper.SIZE_SPEC_CUSTOM;
-import static com.android.wm.shell.pip.phone.PipDoubleTapHelper.SIZE_SPEC_DEFAULT;
-import static com.android.wm.shell.pip.phone.PipDoubleTapHelper.SIZE_SPEC_MAX;
-import static com.android.wm.shell.pip.phone.PipDoubleTapHelper.nextSizeSpec;
+import static com.android.wm.shell.common.pip.PipDoubleTapHelper.SIZE_SPEC_CUSTOM;
+import static com.android.wm.shell.common.pip.PipDoubleTapHelper.SIZE_SPEC_DEFAULT;
+import static com.android.wm.shell.common.pip.PipDoubleTapHelper.SIZE_SPEC_MAX;
+import static com.android.wm.shell.common.pip.PipDoubleTapHelper.nextSizeSpec;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -30,6 +30,7 @@ import android.testing.AndroidTestingRunner;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDoubleTapHelper;
import org.junit.Assert;
import org.junit.Before;
@@ -38,7 +39,7 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
/**
- * Unit test against {@link PipDoubleTapHelper}.
+ * Unit test against {@link com.android.wm.shell.common.pip.PipDoubleTapHelper}.
*/
@RunWith(AndroidTestingRunner.class)
public class PipDoubleTapHelperTest extends ShellTestCase {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
index 12a5594ae1da..7f3bfbb0e81d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -421,6 +421,15 @@ public class SplitScreenControllerTests extends ShellTestCase {
assertEquals(false, controller.supportsMultiInstanceSplit(component));
}
+ @Test
+ public void testSwitchSplitPosition_checksIsSplitScreenVisible() {
+ final String reason = "test";
+ when(mSplitScreenController.isSplitScreenVisible()).thenReturn(true, false);
+ mSplitScreenController.switchSplitPosition(reason);
+ mSplitScreenController.switchSplitPosition(reason);
+ verify(mStageCoordinator, times(1)).switchSplitPosition(reason);
+ }
+
private Intent createStartIntent(String activityName) {
Intent intent = new Intent();
intent.setComponent(new ComponentName(mContext, activityName));
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index b40b73c111d0..4e330da417be 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -229,15 +229,6 @@ filegroup {
path: "apex/java",
}
-java_api_contribution {
- name: "framework-graphics-public-stubs",
- api_surface: "public",
- api_file: "api/current.txt",
- visibility: [
- "//build/orchestrator/apis",
- ],
-}
-
// ------------------------
// APEX
// ------------------------
@@ -638,14 +629,6 @@ cc_defaults {
// Allow implicit fallthroughs in HardwareBitmapUploader.cpp until they are fixed.
cflags: ["-Wno-implicit-fallthrough"],
},
- host: {
- srcs: [
- "utils/HostColorSpace.cpp",
- ],
- export_static_lib_headers: [
- "libarect",
- ],
- },
},
}
diff --git a/libs/hwui/utils/HostColorSpace.cpp b/libs/hwui/utils/HostColorSpace.cpp
deleted file mode 100644
index 77a6820c6999..000000000000
--- a/libs/hwui/utils/HostColorSpace.cpp
+++ /dev/null
@@ -1,417 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-// This is copied from framework/native/libs/ui in order not to include libui in host build
-
-#include <ui/ColorSpace.h>
-
-using namespace std::placeholders;
-
-namespace android {
-
-static constexpr float linearResponse(float v) {
- return v;
-}
-
-static constexpr float rcpResponse(float x, const ColorSpace::TransferParameters& p) {
- return x >= p.d * p.c ? (std::pow(x, 1.0f / p.g) - p.b) / p.a : x / p.c;
-}
-
-static constexpr float response(float x, const ColorSpace::TransferParameters& p) {
- return x >= p.d ? std::pow(p.a * x + p.b, p.g) : p.c * x;
-}
-
-static constexpr float rcpFullResponse(float x, const ColorSpace::TransferParameters& p) {
- return x >= p.d * p.c ? (std::pow(x - p.e, 1.0f / p.g) - p.b) / p.a : (x - p.f) / p.c;
-}
-
-static constexpr float fullResponse(float x, const ColorSpace::TransferParameters& p) {
- return x >= p.d ? std::pow(p.a * x + p.b, p.g) + p.e : p.c * x + p.f;
-}
-
-static float absRcpResponse(float x, float g,float a, float b, float c, float d) {
- float xx = std::abs(x);
- return std::copysign(xx >= d * c ? (std::pow(xx, 1.0f / g) - b) / a : xx / c, x);
-}
-
-static float absResponse(float x, float g, float a, float b, float c, float d) {
- float xx = std::abs(x);
- return std::copysign(xx >= d ? std::pow(a * xx + b, g) : c * xx, x);
-}
-
-static float safePow(float x, float e) {
- return powf(x < 0.0f ? 0.0f : x, e);
-}
-
-static ColorSpace::transfer_function toOETF(const ColorSpace::TransferParameters& parameters) {
- if (parameters.e == 0.0f && parameters.f == 0.0f) {
- return std::bind(rcpResponse, _1, parameters);
- }
- return std::bind(rcpFullResponse, _1, parameters);
-}
-
-static ColorSpace::transfer_function toEOTF( const ColorSpace::TransferParameters& parameters) {
- if (parameters.e == 0.0f && parameters.f == 0.0f) {
- return std::bind(response, _1, parameters);
- }
- return std::bind(fullResponse, _1, parameters);
-}
-
-static ColorSpace::transfer_function toOETF(float gamma) {
- if (gamma == 1.0f) {
- return linearResponse;
- }
- return std::bind(safePow, _1, 1.0f / gamma);
-}
-
-static ColorSpace::transfer_function toEOTF(float gamma) {
- if (gamma == 1.0f) {
- return linearResponse;
- }
- return std::bind(safePow, _1, gamma);
-}
-
-static constexpr std::array<float2, 3> computePrimaries(const mat3& rgbToXYZ) {
- float3 r(rgbToXYZ * float3{1, 0, 0});
- float3 g(rgbToXYZ * float3{0, 1, 0});
- float3 b(rgbToXYZ * float3{0, 0, 1});
-
- return {{r.xy / dot(r, float3{1}),
- g.xy / dot(g, float3{1}),
- b.xy / dot(b, float3{1})}};
-}
-
-static constexpr float2 computeWhitePoint(const mat3& rgbToXYZ) {
- float3 w(rgbToXYZ * float3{1});
- return w.xy / dot(w, float3{1});
-}
-
-ColorSpace::ColorSpace(
- const std::string& name,
- const mat3& rgbToXYZ,
- transfer_function OETF,
- transfer_function EOTF,
- clamping_function clamper) noexcept
- : mName(name)
- , mRGBtoXYZ(rgbToXYZ)
- , mXYZtoRGB(inverse(rgbToXYZ))
- , mOETF(std::move(OETF))
- , mEOTF(std::move(EOTF))
- , mClamper(std::move(clamper))
- , mPrimaries(computePrimaries(rgbToXYZ))
- , mWhitePoint(computeWhitePoint(rgbToXYZ)) {
-}
-
-ColorSpace::ColorSpace(
- const std::string& name,
- const mat3& rgbToXYZ,
- const TransferParameters parameters,
- clamping_function clamper) noexcept
- : mName(name)
- , mRGBtoXYZ(rgbToXYZ)
- , mXYZtoRGB(inverse(rgbToXYZ))
- , mParameters(parameters)
- , mOETF(toOETF(mParameters))
- , mEOTF(toEOTF(mParameters))
- , mClamper(std::move(clamper))
- , mPrimaries(computePrimaries(rgbToXYZ))
- , mWhitePoint(computeWhitePoint(rgbToXYZ)) {
-}
-
-ColorSpace::ColorSpace(
- const std::string& name,
- const mat3& rgbToXYZ,
- float gamma,
- clamping_function clamper) noexcept
- : mName(name)
- , mRGBtoXYZ(rgbToXYZ)
- , mXYZtoRGB(inverse(rgbToXYZ))
- , mParameters({gamma, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f})
- , mOETF(toOETF(gamma))
- , mEOTF(toEOTF(gamma))
- , mClamper(std::move(clamper))
- , mPrimaries(computePrimaries(rgbToXYZ))
- , mWhitePoint(computeWhitePoint(rgbToXYZ)) {
-}
-
-ColorSpace::ColorSpace(
- const std::string& name,
- const std::array<float2, 3>& primaries,
- const float2& whitePoint,
- transfer_function OETF,
- transfer_function EOTF,
- clamping_function clamper) noexcept
- : mName(name)
- , mRGBtoXYZ(computeXYZMatrix(primaries, whitePoint))
- , mXYZtoRGB(inverse(mRGBtoXYZ))
- , mOETF(std::move(OETF))
- , mEOTF(std::move(EOTF))
- , mClamper(std::move(clamper))
- , mPrimaries(primaries)
- , mWhitePoint(whitePoint) {
-}
-
-ColorSpace::ColorSpace(
- const std::string& name,
- const std::array<float2, 3>& primaries,
- const float2& whitePoint,
- const TransferParameters parameters,
- clamping_function clamper) noexcept
- : mName(name)
- , mRGBtoXYZ(computeXYZMatrix(primaries, whitePoint))
- , mXYZtoRGB(inverse(mRGBtoXYZ))
- , mParameters(parameters)
- , mOETF(toOETF(mParameters))
- , mEOTF(toEOTF(mParameters))
- , mClamper(std::move(clamper))
- , mPrimaries(primaries)
- , mWhitePoint(whitePoint) {
-}
-
-ColorSpace::ColorSpace(
- const std::string& name,
- const std::array<float2, 3>& primaries,
- const float2& whitePoint,
- float gamma,
- clamping_function clamper) noexcept
- : mName(name)
- , mRGBtoXYZ(computeXYZMatrix(primaries, whitePoint))
- , mXYZtoRGB(inverse(mRGBtoXYZ))
- , mParameters({gamma, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f})
- , mOETF(toOETF(gamma))
- , mEOTF(toEOTF(gamma))
- , mClamper(std::move(clamper))
- , mPrimaries(primaries)
- , mWhitePoint(whitePoint) {
-}
-
-constexpr mat3 ColorSpace::computeXYZMatrix(
- const std::array<float2, 3>& primaries, const float2& whitePoint) {
- const float2& R = primaries[0];
- const float2& G = primaries[1];
- const float2& B = primaries[2];
- const float2& W = whitePoint;
-
- float oneRxRy = (1 - R.x) / R.y;
- float oneGxGy = (1 - G.x) / G.y;
- float oneBxBy = (1 - B.x) / B.y;
- float oneWxWy = (1 - W.x) / W.y;
-
- float RxRy = R.x / R.y;
- float GxGy = G.x / G.y;
- float BxBy = B.x / B.y;
- float WxWy = W.x / W.y;
-
- float BY =
- ((oneWxWy - oneRxRy) * (GxGy - RxRy) - (WxWy - RxRy) * (oneGxGy - oneRxRy)) /
- ((oneBxBy - oneRxRy) * (GxGy - RxRy) - (BxBy - RxRy) * (oneGxGy - oneRxRy));
- float GY = (WxWy - RxRy - BY * (BxBy - RxRy)) / (GxGy - RxRy);
- float RY = 1 - GY - BY;
-
- float RYRy = RY / R.y;
- float GYGy = GY / G.y;
- float BYBy = BY / B.y;
-
- return {
- float3{RYRy * R.x, RY, RYRy * (1 - R.x - R.y)},
- float3{GYGy * G.x, GY, GYGy * (1 - G.x - G.y)},
- float3{BYBy * B.x, BY, BYBy * (1 - B.x - B.y)}
- };
-}
-
-const ColorSpace ColorSpace::sRGB() {
- return {
- "sRGB IEC61966-2.1",
- {{float2{0.640f, 0.330f}, {0.300f, 0.600f}, {0.150f, 0.060f}}},
- {0.3127f, 0.3290f},
- {2.4f, 1 / 1.055f, 0.055f / 1.055f, 1 / 12.92f, 0.04045f, 0.0f, 0.0f}
- };
-}
-
-const ColorSpace ColorSpace::linearSRGB() {
- return {
- "sRGB IEC61966-2.1 (Linear)",
- {{float2{0.640f, 0.330f}, {0.300f, 0.600f}, {0.150f, 0.060f}}},
- {0.3127f, 0.3290f}
- };
-}
-
-const ColorSpace ColorSpace::extendedSRGB() {
- return {
- "scRGB-nl IEC 61966-2-2:2003",
- {{float2{0.640f, 0.330f}, {0.300f, 0.600f}, {0.150f, 0.060f}}},
- {0.3127f, 0.3290f},
- std::bind(absRcpResponse, _1, 2.4f, 1 / 1.055f, 0.055f / 1.055f, 1 / 12.92f, 0.04045f),
- std::bind(absResponse, _1, 2.4f, 1 / 1.055f, 0.055f / 1.055f, 1 / 12.92f, 0.04045f),
- std::bind(clamp<float>, _1, -0.799f, 2.399f)
- };
-}
-
-const ColorSpace ColorSpace::linearExtendedSRGB() {
- return {
- "scRGB IEC 61966-2-2:2003",
- {{float2{0.640f, 0.330f}, {0.300f, 0.600f}, {0.150f, 0.060f}}},
- {0.3127f, 0.3290f},
- 1.0f,
- std::bind(clamp<float>, _1, -0.5f, 7.499f)
- };
-}
-
-const ColorSpace ColorSpace::NTSC() {
- return {
- "NTSC (1953)",
- {{float2{0.67f, 0.33f}, {0.21f, 0.71f}, {0.14f, 0.08f}}},
- {0.310f, 0.316f},
- {1 / 0.45f, 1 / 1.099f, 0.099f / 1.099f, 1 / 4.5f, 0.081f, 0.0f, 0.0f}
- };
-}
-
-const ColorSpace ColorSpace::BT709() {
- return {
- "Rec. ITU-R BT.709-5",
- {{float2{0.640f, 0.330f}, {0.300f, 0.600f}, {0.150f, 0.060f}}},
- {0.3127f, 0.3290f},
- {1 / 0.45f, 1 / 1.099f, 0.099f / 1.099f, 1 / 4.5f, 0.081f, 0.0f, 0.0f}
- };
-}
-
-const ColorSpace ColorSpace::BT2020() {
- return {
- "Rec. ITU-R BT.2020-1",
- {{float2{0.708f, 0.292f}, {0.170f, 0.797f}, {0.131f, 0.046f}}},
- {0.3127f, 0.3290f},
- {1 / 0.45f, 1 / 1.099f, 0.099f / 1.099f, 1 / 4.5f, 0.081f, 0.0f, 0.0f}
- };
-}
-
-const ColorSpace ColorSpace::AdobeRGB() {
- return {
- "Adobe RGB (1998)",
- {{float2{0.64f, 0.33f}, {0.21f, 0.71f}, {0.15f, 0.06f}}},
- {0.3127f, 0.3290f},
- 2.2f
- };
-}
-
-const ColorSpace ColorSpace::ProPhotoRGB() {
- return {
- "ROMM RGB ISO 22028-2:2013",
- {{float2{0.7347f, 0.2653f}, {0.1596f, 0.8404f}, {0.0366f, 0.0001f}}},
- {0.34567f, 0.35850f},
- {1.8f, 1.0f, 0.0f, 1 / 16.0f, 0.031248f, 0.0f, 0.0f}
- };
-}
-
-const ColorSpace ColorSpace::DisplayP3() {
- return {
- "Display P3",
- {{float2{0.680f, 0.320f}, {0.265f, 0.690f}, {0.150f, 0.060f}}},
- {0.3127f, 0.3290f},
- {2.4f, 1 / 1.055f, 0.055f / 1.055f, 1 / 12.92f, 0.039f, 0.0f, 0.0f}
- };
-}
-
-const ColorSpace ColorSpace::DCIP3() {
- return {
- "SMPTE RP 431-2-2007 DCI (P3)",
- {{float2{0.680f, 0.320f}, {0.265f, 0.690f}, {0.150f, 0.060f}}},
- {0.314f, 0.351f},
- 2.6f
- };
-}
-
-const ColorSpace ColorSpace::ACES() {
- return {
- "SMPTE ST 2065-1:2012 ACES",
- {{float2{0.73470f, 0.26530f}, {0.0f, 1.0f}, {0.00010f, -0.0770f}}},
- {0.32168f, 0.33767f},
- 1.0f,
- std::bind(clamp<float>, _1, -65504.0f, 65504.0f)
- };
-}
-
-const ColorSpace ColorSpace::ACEScg() {
- return {
- "Academy S-2014-004 ACEScg",
- {{float2{0.713f, 0.293f}, {0.165f, 0.830f}, {0.128f, 0.044f}}},
- {0.32168f, 0.33767f},
- 1.0f,
- std::bind(clamp<float>, _1, -65504.0f, 65504.0f)
- };
-}
-
-std::unique_ptr<float3[]> ColorSpace::createLUT(uint32_t size, const ColorSpace& src,
- const ColorSpace& dst) {
- size = clamp(size, 2u, 256u);
- float m = 1.0f / float(size - 1);
-
- std::unique_ptr<float3[]> lut(new float3[size * size * size]);
- float3* data = lut.get();
-
- ColorSpaceConnector connector(src, dst);
-
- for (uint32_t z = 0; z < size; z++) {
- for (int32_t y = int32_t(size - 1); y >= 0; y--) {
- for (uint32_t x = 0; x < size; x++) {
- *data++ = connector.transform({x * m, y * m, z * m});
- }
- }
- }
-
- return lut;
-}
-
-static const float2 ILLUMINANT_D50_XY = {0.34567f, 0.35850f};
-static const float3 ILLUMINANT_D50_XYZ = {0.964212f, 1.0f, 0.825188f};
-static const mat3 BRADFORD = mat3{
- float3{ 0.8951f, -0.7502f, 0.0389f},
- float3{ 0.2664f, 1.7135f, -0.0685f},
- float3{-0.1614f, 0.0367f, 1.0296f}
-};
-
-static mat3 adaptation(const mat3& matrix, const float3& srcWhitePoint, const float3& dstWhitePoint) {
- float3 srcLMS = matrix * srcWhitePoint;
- float3 dstLMS = matrix * dstWhitePoint;
- return inverse(matrix) * mat3{dstLMS / srcLMS} * matrix;
-}
-
-ColorSpaceConnector::ColorSpaceConnector(
- const ColorSpace& src,
- const ColorSpace& dst) noexcept
- : mSource(src)
- , mDestination(dst) {
-
- if (all(lessThan(abs(src.getWhitePoint() - dst.getWhitePoint()), float2{1e-3f}))) {
- mTransform = dst.getXYZtoRGB() * src.getRGBtoXYZ();
- } else {
- mat3 rgbToXYZ(src.getRGBtoXYZ());
- mat3 xyzToRGB(dst.getXYZtoRGB());
-
- float3 srcXYZ = ColorSpace::XYZ(float3{src.getWhitePoint(), 1});
- float3 dstXYZ = ColorSpace::XYZ(float3{dst.getWhitePoint(), 1});
-
- if (any(greaterThan(abs(src.getWhitePoint() - ILLUMINANT_D50_XY), float2{1e-3f}))) {
- rgbToXYZ = adaptation(BRADFORD, srcXYZ, ILLUMINANT_D50_XYZ) * src.getRGBtoXYZ();
- }
-
- if (any(greaterThan(abs(dst.getWhitePoint() - ILLUMINANT_D50_XY), float2{1e-3f}))) {
- xyzToRGB = inverse(adaptation(BRADFORD, dstXYZ, ILLUMINANT_D50_XYZ) * dst.getRGBtoXYZ());
- }
-
- mTransform = xyzToRGB * rgbToXYZ;
- }
-}
-
-}; // namespace android
diff --git a/location/java/android/location/altitude/AltitudeConverter.java b/location/java/android/location/altitude/AltitudeConverter.java
index 3dc024efef56..6f8891216bed 100644
--- a/location/java/android/location/altitude/AltitudeConverter.java
+++ b/location/java/android/location/altitude/AltitudeConverter.java
@@ -19,9 +19,11 @@ package android.location.altitude;
import android.annotation.NonNull;
import android.annotation.WorkerThread;
import android.content.Context;
+import android.frameworks.location.altitude.GetGeoidHeightRequest;
+import android.frameworks.location.altitude.GetGeoidHeightResponse;
import android.location.Location;
-import com.android.internal.location.altitude.GeoidHeightMap;
+import com.android.internal.location.altitude.GeoidMap;
import com.android.internal.location.altitude.S2CellIdUtils;
import com.android.internal.location.altitude.nano.MapParamsProto;
import com.android.internal.util.Preconditions;
@@ -37,7 +39,7 @@ import java.io.IOException;
* <pre>
* Brian Julian and Michael Angermann.
* "Resource efficient and accurate altitude conversion to Mean Sea Level."
- * To appear in 2023 IEEE/ION Position, Location and Navigation Symposium (PLANS).
+ * 2023 IEEE/ION Position, Location and Navigation Symposium (PLANS).
* </pre>
*/
public final class AltitudeConverter {
@@ -45,8 +47,8 @@ public final class AltitudeConverter {
private static final double MAX_ABS_VALID_LATITUDE = 90;
private static final double MAX_ABS_VALID_LONGITUDE = 180;
- /** Manages a mapping of geoid heights associated with S2 cells. */
- private final GeoidHeightMap mGeoidHeightMap = new GeoidHeightMap();
+ /** Manages a mapping of geoid heights and expiration distances associated with S2 cells. */
+ private final GeoidMap mGeoidMap = new GeoidMap();
/**
* Creates an instance that manages an independent cache to optimized conversions of locations
@@ -78,75 +80,87 @@ public final class AltitudeConverter {
/**
* Returns the four S2 cell IDs for the map square associated with the {@code location}.
*
- * <p>The first map cell contains the location, while the others are located horizontally,
- * vertically, and diagonally, in that order, with respect to the S2 (i,j) coordinate system. If
- * the diagonal map cell does not exist (i.e., the location is near an S2 cube vertex), its
- * corresponding ID is set to zero.
+ * <p>The first map cell, denoted z11 in the appendix of the referenced paper above, contains
+ * the location. The others are the map cells denoted z21, z12, and z22, in that order.
*/
- @NonNull
- private static long[] findMapSquare(@NonNull MapParamsProto params,
+ private static long[] findMapSquare(@NonNull MapParamsProto geoidHeightParams,
@NonNull Location location) {
long s2CellId = S2CellIdUtils.fromLatLngDegrees(location.getLatitude(),
location.getLongitude());
// Cell-space properties and coordinates.
- int sizeIj = 1 << (S2CellIdUtils.MAX_LEVEL - params.mapS2Level);
+ int sizeIj = 1 << (S2CellIdUtils.MAX_LEVEL - geoidHeightParams.mapS2Level);
int maxIj = 1 << S2CellIdUtils.MAX_LEVEL;
- long s0 = S2CellIdUtils.getParent(s2CellId, params.mapS2Level);
- int f0 = S2CellIdUtils.getFace(s2CellId);
- int i0 = S2CellIdUtils.getI(s2CellId);
- int j0 = S2CellIdUtils.getJ(s2CellId);
- int i1 = i0 + sizeIj;
- int j1 = j0 + sizeIj;
+ long z11 = S2CellIdUtils.getParent(s2CellId, geoidHeightParams.mapS2Level);
+ int f11 = S2CellIdUtils.getFace(s2CellId);
+ int i1 = S2CellIdUtils.getI(s2CellId);
+ int j1 = S2CellIdUtils.getJ(s2CellId);
+ int i2 = i1 + sizeIj;
+ int j2 = j1 + sizeIj;
// Non-boundary region calculation - simplest and most common case.
- if (i1 < maxIj && j1 < maxIj) {
- return new long[]{
- s0,
- S2CellIdUtils.getParent(S2CellIdUtils.fromFij(f0, i1, j0), params.mapS2Level),
- S2CellIdUtils.getParent(S2CellIdUtils.fromFij(f0, i0, j1), params.mapS2Level),
- S2CellIdUtils.getParent(S2CellIdUtils.fromFij(f0, i1, j1), params.mapS2Level)
- };
+ if (i2 < maxIj && j2 < maxIj) {
+ return new long[]{z11, S2CellIdUtils.getParent(S2CellIdUtils.fromFij(f11, i2, j1),
+ geoidHeightParams.mapS2Level), S2CellIdUtils.getParent(
+ S2CellIdUtils.fromFij(f11, i1, j2), geoidHeightParams.mapS2Level),
+ S2CellIdUtils.getParent(S2CellIdUtils.fromFij(f11, i2, j2),
+ geoidHeightParams.mapS2Level)};
}
- // Boundary region calculation.
+ // Boundary region calculation
long[] edgeNeighbors = new long[4];
- S2CellIdUtils.getEdgeNeighbors(s0, edgeNeighbors);
- long s1 = edgeNeighbors[1];
- long s2 = edgeNeighbors[2];
- long s3;
- if (f0 % 2 == 1) {
- S2CellIdUtils.getEdgeNeighbors(s1, edgeNeighbors);
- if (i1 < maxIj) {
- s3 = edgeNeighbors[2];
- } else {
- s3 = s1;
- s1 = edgeNeighbors[1];
- }
- } else {
- S2CellIdUtils.getEdgeNeighbors(s2, edgeNeighbors);
- if (j1 < maxIj) {
- s3 = edgeNeighbors[1];
- } else {
- s3 = s2;
- s2 = edgeNeighbors[3];
- }
- }
+ S2CellIdUtils.getEdgeNeighbors(z11, edgeNeighbors);
+ long z11W = edgeNeighbors[0];
+ long z11S = edgeNeighbors[1];
+ long z11E = edgeNeighbors[2];
+ long z11N = edgeNeighbors[3];
+
+ long[] otherEdgeNeighbors = new long[4];
+ S2CellIdUtils.getEdgeNeighbors(z11W, otherEdgeNeighbors);
+ S2CellIdUtils.getEdgeNeighbors(z11S, edgeNeighbors);
+ long z11Sw = findCommonNeighbor(edgeNeighbors, otherEdgeNeighbors, z11);
+ S2CellIdUtils.getEdgeNeighbors(z11E, otherEdgeNeighbors);
+ long z11Se = findCommonNeighbor(edgeNeighbors, otherEdgeNeighbors, z11);
+ S2CellIdUtils.getEdgeNeighbors(z11N, edgeNeighbors);
+ long z11Ne = findCommonNeighbor(edgeNeighbors, otherEdgeNeighbors, z11);
+
+ long z21 = (f11 % 2 == 1 && i2 >= maxIj) ? z11Sw : z11S;
+ long z12 = (f11 % 2 == 0 && j2 >= maxIj) ? z11Ne : z11E;
+ long z22 = (z21 == z11Sw) ? z11S : (z12 == z11Ne) ? z11E : z11Se;
// Reuse edge neighbors' array to avoid an extra allocation.
- edgeNeighbors[0] = s0;
- edgeNeighbors[1] = s1;
- edgeNeighbors[2] = s2;
- edgeNeighbors[3] = s3;
+ edgeNeighbors[0] = z11;
+ edgeNeighbors[1] = z21;
+ edgeNeighbors[2] = z12;
+ edgeNeighbors[3] = z22;
return edgeNeighbors;
}
/**
+ * Returns the first common non-z11 neighbor found between the two arrays of edge neighbors. If
+ * such a common neighbor does not exist, returns z11.
+ */
+ private static long findCommonNeighbor(long[] edgeNeighbors, long[] otherEdgeNeighbors,
+ long z11) {
+ for (long edgeNeighbor : edgeNeighbors) {
+ if (edgeNeighbor == z11) {
+ continue;
+ }
+ for (long otherEdgeNeighbor : otherEdgeNeighbors) {
+ if (edgeNeighbor == otherEdgeNeighbor) {
+ return edgeNeighbor;
+ }
+ }
+ }
+ return z11;
+ }
+
+ /**
* Adds to {@code location} the bilinearly interpolated Mean Sea Level altitude. In addition, a
* Mean Sea Level altitude accuracy is added if the {@code location} has a valid vertical
* accuracy; otherwise, does not add a corresponding accuracy.
*/
- private static void addMslAltitude(@NonNull MapParamsProto params,
+ private static void addMslAltitude(@NonNull MapParamsProto geoidHeightParams,
@NonNull double[] geoidHeightsMeters, @NonNull Location location) {
double h0 = geoidHeightsMeters[0];
double h1 = geoidHeightsMeters[1];
@@ -158,7 +172,7 @@ public final class AltitudeConverter {
// employ the simplified unit square formulation.
long s2CellId = S2CellIdUtils.fromLatLngDegrees(location.getLatitude(),
location.getLongitude());
- double sizeIj = 1 << (S2CellIdUtils.MAX_LEVEL - params.mapS2Level);
+ double sizeIj = 1 << (S2CellIdUtils.MAX_LEVEL - geoidHeightParams.mapS2Level);
double wi = (S2CellIdUtils.getI(s2CellId) % sizeIj) / sizeIj;
double wj = (S2CellIdUtils.getJ(s2CellId) % sizeIj) / sizeIj;
double offsetMeters = h0 + (h1 - h0) * wi + (h2 - h0) * wj + (h3 - h1 - h2 + h0) * wi * wj;
@@ -167,8 +181,8 @@ public final class AltitudeConverter {
if (location.hasVerticalAccuracy()) {
double verticalAccuracyMeters = location.getVerticalAccuracyMeters();
if (Double.isFinite(verticalAccuracyMeters) && verticalAccuracyMeters >= 0) {
- location.setMslAltitudeAccuracyMeters(
- (float) Math.hypot(verticalAccuracyMeters, params.modelRmseMeters));
+ location.setMslAltitudeAccuracyMeters((float) Math.hypot(verticalAccuracyMeters,
+ geoidHeightParams.modelRmseMeters));
}
}
}
@@ -191,10 +205,11 @@ public final class AltitudeConverter {
public void addMslAltitudeToLocation(@NonNull Context context, @NonNull Location location)
throws IOException {
validate(location);
- MapParamsProto params = GeoidHeightMap.getParams(context);
- long[] s2CellIds = findMapSquare(params, location);
- double[] geoidHeightsMeters = mGeoidHeightMap.readGeoidHeights(params, context, s2CellIds);
- addMslAltitude(params, geoidHeightsMeters, location);
+ MapParamsProto geoidHeightParams = GeoidMap.getGeoidHeightParams(context);
+ long[] mapCells = findMapSquare(geoidHeightParams, location);
+ double[] geoidHeightsMeters = mGeoidMap.readGeoidHeights(geoidHeightParams, context,
+ mapCells);
+ addMslAltitude(geoidHeightParams, geoidHeightsMeters, location);
}
/**
@@ -206,18 +221,68 @@ public final class AltitudeConverter {
*/
public boolean addMslAltitudeToLocation(@NonNull Location location) {
validate(location);
- MapParamsProto params = GeoidHeightMap.getParams();
- if (params == null) {
+ MapParamsProto geoidHeightParams = GeoidMap.getGeoidHeightParams();
+ if (geoidHeightParams == null) {
return false;
}
- long[] s2CellIds = findMapSquare(params, location);
- double[] geoidHeightsMeters = mGeoidHeightMap.readGeoidHeights(params, s2CellIds);
+ long[] mapCells = findMapSquare(geoidHeightParams, location);
+ double[] geoidHeightsMeters = mGeoidMap.readGeoidHeights(geoidHeightParams, mapCells);
if (geoidHeightsMeters == null) {
return false;
}
- addMslAltitude(params, geoidHeightsMeters, location);
+ addMslAltitude(geoidHeightParams, geoidHeightsMeters, location);
return true;
}
+
+ /**
+ * Returns the geoid height (a.k.a. geoid undulation) at the location specified in {@code
+ * request}. The geoid height at a location is defined as the difference between an altitude
+ * measured above the World Geodetic System 1984 reference ellipsoid (WGS84) and its
+ * corresponding Mean Sea Level altitude.
+ *
+ * <p>Must be called off the main thread as data may be loaded from raw assets.
+ *
+ * @throws IOException if an I/O error occurs when loading data from raw assets.
+ * @throws IllegalArgumentException if the {@code request} has an invalid latitude or longitude.
+ * Specifically, the latitude must be between -90 and 90 (both
+ * inclusive), and the longitude must be between -180 and 180
+ * (both inclusive).
+ * @hide
+ */
+ @WorkerThread
+ public @NonNull GetGeoidHeightResponse getGeoidHeight(@NonNull Context context,
+ @NonNull GetGeoidHeightRequest request) throws IOException {
+ // Create a valid location from which the geoid height and its accuracy will be extracted.
+ Location location = new Location("");
+ location.setLatitude(request.latitudeDegrees);
+ location.setLongitude(request.longitudeDegrees);
+ location.setAltitude(0.0);
+ location.setVerticalAccuracyMeters(0.0f);
+
+ addMslAltitudeToLocation(context, location);
+ // The geoid height for a location with zero WGS84 altitude is equal in value to the
+ // negative of corresponding MSL altitude.
+ double geoidHeightMeters = -location.getMslAltitudeMeters();
+ // The geoid height error for a location with zero vertical accuracy is equal in value to
+ // the corresponding MSL altitude accuracy.
+ float geoidHeightErrorMeters = location.getMslAltitudeAccuracyMeters();
+
+ MapParamsProto expirationDistanceParams = GeoidMap.getExpirationDistanceParams(context);
+ long s2CellId = S2CellIdUtils.fromLatLngDegrees(location.getLatitude(),
+ location.getLongitude());
+ long[] mapCell = {S2CellIdUtils.getParent(s2CellId, expirationDistanceParams.mapS2Level)};
+ double expirationDistanceMeters = mGeoidMap.readExpirationDistances(
+ expirationDistanceParams, context, mapCell)[0];
+ float additionalGeoidHeightErrorMeters = (float) expirationDistanceParams.modelRmseMeters;
+
+ GetGeoidHeightResponse response = new GetGeoidHeightResponse();
+ response.geoidHeightMeters = geoidHeightMeters;
+ response.geoidHeightErrorMeters = geoidHeightErrorMeters;
+ response.expirationDistanceMeters = expirationDistanceMeters;
+ response.additionalGeoidHeightErrorMeters = additionalGeoidHeightErrorMeters;
+ response.success = true;
+ return response;
+ }
}
diff --git a/location/java/com/android/internal/location/altitude/GeoidHeightMap.java b/location/java/com/android/internal/location/altitude/GeoidMap.java
index 8067050d9da3..9bf5689c1028 100644
--- a/location/java/com/android/internal/location/altitude/GeoidHeightMap.java
+++ b/location/java/com/android/internal/location/altitude/GeoidMap.java
@@ -34,11 +34,12 @@ import java.nio.ByteBuffer;
import java.util.Objects;
/**
- * Manages a mapping of geoid heights associated with S2 cells, referred to as MAP CELLS.
+ * Manages a mapping of geoid heights and expiration distances associated with S2 cells, referred to
+ * as MAP CELLS.
*
* <p>Tiles are used extensively to reduce the number of entries needed to be stored in memory and
- * on disk. A tile associates geoid heights with all map cells of a common parent at a specified S2
- * level.
+ * on disk. A tile associates geoid heights or expiration distances with all map cells of a common
+ * parent at a specified S2 level.
*
* <p>Since bilinear interpolation considers at most four map cells at a time, at most four tiles
* are simultaneously stored in memory. These tiles, referred to as CACHE TILES, are each keyed by
@@ -48,42 +49,79 @@ import java.util.Objects;
* The latter tiles, referred to as DISK TILES, are each keyed by its common parent's S2 cell token,
* referred to as a DISK TOKEN.
*/
-public final class GeoidHeightMap {
+public final class GeoidMap {
- private static final Object sLock = new Object();
+ private static final Object GEOID_HEIGHT_PARAMS_LOCK = new Object();
- @GuardedBy("sLock")
+ private static final Object EXPIRATION_DISTANCE_PARAMS_LOCK = new Object();
+
+ @GuardedBy("GEOID_HEIGHT_PARAMS_LOCK")
+ @Nullable
+ private static MapParamsProto sGeoidHeightParams;
+
+ @GuardedBy("EXPIRATION_DISTANCE_PARAMS_LOCK")
@Nullable
- private static MapParamsProto sParams;
+ private static MapParamsProto sExpirationDistanceParams;
+
+ /**
+ * Defines a cache large enough to hold all geoid height cache tiles needed for interpolation.
+ */
+ private final LruCache<Long, S2TileProto> mGeoidHeightCacheTiles = new LruCache<>(4);
- /** Defines a cache large enough to hold all cache tiles needed for interpolation. */
- private final LruCache<Long, S2TileProto> mCacheTiles = new LruCache<>(4);
+ /**
+ * Defines a cache large enough to hold all expiration distance cache tiles needed for
+ * interpolation.
+ */
+ private final LruCache<Long, S2TileProto> mExpirationDistanceCacheTiles = new LruCache<>(4);
/**
- * Returns the singleton parameter instance for a spherically projected geoid height map and its
- * corresponding tile management.
+ * Returns the singleton parameter instance for geoid height parameters of a spherically
+ * projected map.
*/
@NonNull
- public static MapParamsProto getParams(@NonNull Context context) throws IOException {
- synchronized (sLock) {
- if (sParams == null) {
- try (InputStream is = context.getApplicationContext().getAssets().open(
- "geoid_height_map/map-params.pb")) {
- sParams = MapParamsProto.parseFrom(is.readAllBytes());
- }
+ public static MapParamsProto getGeoidHeightParams(@NonNull Context context) throws IOException {
+ synchronized (GEOID_HEIGHT_PARAMS_LOCK) {
+ if (sGeoidHeightParams == null) {
+ // TODO: b/304375846 - Configure with disk tile prefix once resources are updated.
+ sGeoidHeightParams = parseParams(context);
+ }
+ return sGeoidHeightParams;
+ }
+ }
+
+ /**
+ * Returns the singleton parameter instance for expiration distance parameters of a spherically
+ * projected
+ * map.
+ */
+ @NonNull
+ public static MapParamsProto getExpirationDistanceParams(@NonNull Context context)
+ throws IOException {
+ synchronized (EXPIRATION_DISTANCE_PARAMS_LOCK) {
+ if (sExpirationDistanceParams == null) {
+ // TODO: b/304375846 - Configure with disk tile prefix once resources are updated.
+ sExpirationDistanceParams = parseParams(context);
}
- return sParams;
+ return sExpirationDistanceParams;
+ }
+ }
+
+ @NonNull
+ private static MapParamsProto parseParams(@NonNull Context context) throws IOException {
+ try (InputStream is = context.getApplicationContext().getAssets().open(
+ "geoid_height_map/map-params.pb")) {
+ return MapParamsProto.parseFrom(is.readAllBytes());
}
}
/**
- * Same as {@link #getParams(Context)} except that null is returned if the singleton parameter
- * instance is not yet initialized.
+ * Same as {@link #getGeoidHeightParams(Context)} except that null is returned if the singleton
+ * parameter instance is not yet initialized.
*/
@Nullable
- public static MapParamsProto getParams() {
- synchronized (sLock) {
- return sParams;
+ public static MapParamsProto getGeoidHeightParams() {
+ synchronized (GEOID_HEIGHT_PARAMS_LOCK) {
+ return sGeoidHeightParams;
}
}
@@ -93,18 +131,17 @@ public final class GeoidHeightMap {
@NonNull
private static String getDiskToken(@NonNull MapParamsProto params, long s2CellId) {
- return S2CellIdUtils.getToken(
- S2CellIdUtils.getParent(s2CellId, params.diskTileS2Level));
+ return S2CellIdUtils.getToken(S2CellIdUtils.getParent(s2CellId, params.diskTileS2Level));
}
/**
* Adds to {@code values} values in the unit interval [0, 1] for the map cells identified by
- * {@code s2CellIds}. Returns true if values are present for all IDs; otherwise, returns false
- * and adds NaNs for absent values.
+ * {@code s2CellIds}. Returns true if values are present for all IDs; otherwise, adds NaNs for
+ * absent values and returns false.
*/
private static boolean getUnitIntervalValues(@NonNull MapParamsProto params,
- @NonNull TileFunction tileFunction,
- @NonNull long[] s2CellIds, @NonNull double[] values) {
+ @NonNull TileFunction tileFunction, @NonNull long[] s2CellIds,
+ @NonNull double[] values) {
int len = s2CellIds.length;
S2TileProto[] tiles = new S2TileProto[len];
@@ -137,9 +174,8 @@ public final class GeoidHeightMap {
@SuppressWarnings("ReferenceEquality")
private static void mergeByteBufferValues(@NonNull MapParamsProto params,
- @NonNull long[] s2CellIds,
- @NonNull S2TileProto[] tiles,
- int tileIndex, @NonNull double[] values) {
+ @NonNull long[] s2CellIds, @NonNull S2TileProto[] tiles, int tileIndex,
+ @NonNull double[] values) {
byte[] bytes = tiles[tileIndex].byteBuffer;
if (bytes == null || bytes.length == 0) {
return;
@@ -163,24 +199,22 @@ public final class GeoidHeightMap {
}
private static void mergeByteJpegValues(@NonNull MapParamsProto params,
- @NonNull long[] s2CellIds,
- @NonNull S2TileProto[] tiles,
- int tileIndex, @NonNull double[] values) {
+ @NonNull long[] s2CellIds, @NonNull S2TileProto[] tiles, int tileIndex,
+ @NonNull double[] values) {
mergeByteImageValues(params, tiles[tileIndex].byteJpeg, s2CellIds, tiles, tileIndex,
values);
}
private static void mergeBytePngValues(@NonNull MapParamsProto params,
- @NonNull long[] s2CellIds,
- @NonNull S2TileProto[] tiles,
- int tileIndex, @NonNull double[] values) {
+ @NonNull long[] s2CellIds, @NonNull S2TileProto[] tiles, int tileIndex,
+ @NonNull double[] values) {
mergeByteImageValues(params, tiles[tileIndex].bytePng, s2CellIds, tiles, tileIndex, values);
}
@SuppressWarnings("ReferenceEquality")
private static void mergeByteImageValues(@NonNull MapParamsProto params, @NonNull byte[] bytes,
- @NonNull long[] s2CellIds,
- @NonNull S2TileProto[] tiles, int tileIndex, @NonNull double[] values) {
+ @NonNull long[] s2CellIds, @NonNull S2TileProto[] tiles, int tileIndex,
+ @NonNull double[] values) {
if (bytes == null || bytes.length == 0) {
return;
}
@@ -219,7 +253,7 @@ public final class GeoidHeightMap {
* ID.
*/
private static void validate(@NonNull MapParamsProto params, @NonNull long[] s2CellIds) {
- Preconditions.checkArgument(s2CellIds.length == 4);
+ Preconditions.checkArgument(s2CellIds.length <= 4);
for (long s2CellId : s2CellIds) {
Preconditions.checkArgument(S2CellIdUtils.getLevel(s2CellId) == params.mapS2Level);
}
@@ -233,15 +267,38 @@ public final class GeoidHeightMap {
@NonNull
public double[] readGeoidHeights(@NonNull MapParamsProto params, @NonNull Context context,
@NonNull long[] s2CellIds) throws IOException {
+ return readMapValues(params, context, s2CellIds, mGeoidHeightCacheTiles);
+ }
+
+ /**
+ * Returns the expiration distances in meters associated with the map cells identified by
+ * {@code s2CellIds}. Throws an {@link IOException} if a geoid height cannot be calculated for
+ * an ID.
+ */
+ @NonNull
+ public double[] readExpirationDistances(@NonNull MapParamsProto params,
+ @NonNull Context context, @NonNull long[] s2CellIds) throws IOException {
+ return readMapValues(params, context, s2CellIds, mExpirationDistanceCacheTiles);
+ }
+
+ /**
+ * Returns the map values in meters associated with the map cells identified by
+ * {@code s2CellIds}. Throws an {@link IOException} if a map value cannot be calculated for an
+ * ID.
+ */
+ @NonNull
+ private static double[] readMapValues(@NonNull MapParamsProto params, @NonNull Context context,
+ @NonNull long[] s2CellIds, @NonNull LruCache<Long, S2TileProto> cacheTiles)
+ throws IOException {
validate(params, s2CellIds);
- double[] heightsMeters = new double[s2CellIds.length];
- if (getGeoidHeights(params, mCacheTiles::get, s2CellIds, heightsMeters)) {
- return heightsMeters;
+ double[] mapValuesMeters = new double[s2CellIds.length];
+ if (getMapValues(params, cacheTiles::get, s2CellIds, mapValuesMeters)) {
+ return mapValuesMeters;
}
- TileFunction loadedTiles = loadFromCacheAndDisk(params, context, s2CellIds);
- if (getGeoidHeights(params, loadedTiles, s2CellIds, heightsMeters)) {
- return heightsMeters;
+ TileFunction loadedTiles = loadFromCacheAndDisk(params, context, s2CellIds, cacheTiles);
+ if (getMapValues(params, loadedTiles, s2CellIds, mapValuesMeters)) {
+ return mapValuesMeters;
}
throw new IOException("Unable to calculate geoid heights from raw assets.");
}
@@ -255,32 +312,33 @@ public final class GeoidHeightMap {
public double[] readGeoidHeights(@NonNull MapParamsProto params, @NonNull long[] s2CellIds) {
validate(params, s2CellIds);
double[] heightsMeters = new double[s2CellIds.length];
- if (getGeoidHeights(params, mCacheTiles::get, s2CellIds, heightsMeters)) {
+ if (getMapValues(params, mGeoidHeightCacheTiles::get, s2CellIds, heightsMeters)) {
return heightsMeters;
}
return null;
}
/**
- * Adds to {@code heightsMeters} the geoid heights in meters associated with the map cells
+ * Adds to {@code mapValuesMeters} the map values in meters associated with the map cells
* identified by {@code s2CellIds}. Returns true if heights are present for all IDs; otherwise,
- * returns false and adds NaNs for absent heights.
+ * adds NaNs for absent heights and returns false.
*/
- private boolean getGeoidHeights(@NonNull MapParamsProto params,
+ private static boolean getMapValues(@NonNull MapParamsProto params,
@NonNull TileFunction tileFunction, @NonNull long[] s2CellIds,
- @NonNull double[] heightsMeters) {
- boolean allFound = getUnitIntervalValues(params, tileFunction, s2CellIds, heightsMeters);
- for (int i = 0; i < heightsMeters.length; i++) {
+ @NonNull double[] mapValuesMeters) {
+ boolean allFound = getUnitIntervalValues(params, tileFunction, s2CellIds, mapValuesMeters);
+ for (int i = 0; i < mapValuesMeters.length; i++) {
// NaNs are properly preserved.
- heightsMeters[i] *= params.modelAMeters;
- heightsMeters[i] += params.modelBMeters;
+ mapValuesMeters[i] *= params.modelAMeters;
+ mapValuesMeters[i] += params.modelBMeters;
}
return allFound;
}
@NonNull
- private TileFunction loadFromCacheAndDisk(@NonNull MapParamsProto params,
- @NonNull Context context, @NonNull long[] s2CellIds) throws IOException {
+ private static TileFunction loadFromCacheAndDisk(@NonNull MapParamsProto params,
+ @NonNull Context context, @NonNull long[] s2CellIds,
+ @NonNull LruCache<Long, S2TileProto> cacheTiles) throws IOException {
int len = s2CellIds.length;
// Enable batch loading by finding all cache keys upfront.
@@ -296,7 +354,7 @@ public final class GeoidHeightMap {
if (diskTokens[i] != null) {
continue;
}
- loadedTiles[i] = mCacheTiles.get(cacheKeys[i]);
+ loadedTiles[i] = cacheTiles.get(cacheKeys[i]);
diskTokens[i] = getDiskToken(params, cacheKeys[i]);
// Batch across common cache key.
@@ -319,7 +377,7 @@ public final class GeoidHeightMap {
"geoid_height_map/tile-" + diskTokens[i] + ".pb")) {
tile = S2TileProto.parseFrom(is.readAllBytes());
}
- mergeFromDiskTile(params, tile, cacheKeys, diskTokens, i, loadedTiles);
+ mergeFromDiskTile(params, tile, cacheKeys, diskTokens, i, loadedTiles, cacheTiles);
}
return cacheKey -> {
@@ -332,9 +390,10 @@ public final class GeoidHeightMap {
};
}
- private void mergeFromDiskTile(@NonNull MapParamsProto params, @NonNull S2TileProto diskTile,
- @NonNull long[] cacheKeys, @NonNull String[] diskTokens, int diskTokenIndex,
- @NonNull S2TileProto[] loadedTiles) throws IOException {
+ private static void mergeFromDiskTile(@NonNull MapParamsProto params,
+ @NonNull S2TileProto diskTile, @NonNull long[] cacheKeys, @NonNull String[] diskTokens,
+ int diskTokenIndex, @NonNull S2TileProto[] loadedTiles,
+ @NonNull LruCache<Long, S2TileProto> cacheTiles) throws IOException {
int len = cacheKeys.length;
int numMapCellsPerCacheTile = 1 << (2 * (params.mapS2Level - params.cacheTileS2Level));
@@ -375,7 +434,7 @@ public final class GeoidHeightMap {
}
// Side load into tile cache.
- mCacheTiles.put(cacheKeys[i], loadedTiles[i]);
+ cacheTiles.put(cacheKeys[i], loadedTiles[i]);
}
}
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 691aa7784d7a..425db06ce55f 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -183,23 +183,23 @@ public final class MediaRouter2 {
* preference} passed by a proxy router. Use {@link RouteDiscoveryPreference#EMPTY} when
* setting a route callback.
* <li>
- * <p>Methods returning non-system {@link RoutingController controllers} always return
- * new instances with the latest data. Do not attempt to compare or store them. Instead,
- * use {@link #getController(String)} or {@link #getControllers()} to query the most
+ * <p>Methods returning non-system {@link RoutingController controllers} always return new
+ * instances with the latest data. Do not attempt to compare or store them. Instead, use
+ * {@link #getController(String)} or {@link #getControllers()} to query the most
* up-to-date state.
* <li>
* <p>Calls to {@link #setOnGetControllerHintsListener} are ignored.
* </ul>
*
* @param clientPackageName the package name of the app to control
- * @throws SecurityException if the caller doesn't have {@link
- * Manifest.permission#MEDIA_CONTENT_CONTROL MEDIA_CONTENT_CONTROL} permission.
- * @hide
+ * @return a proxy MediaRouter2 instance if {@code clientPackageName} exists or {@code null}.
*/
- // TODO (b/311711420): Deprecate once #getInstance(Context, String, UserHandle) reaches public
- // SDK.
- @SystemApi
- @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
+ @FlaggedApi(FLAG_ENABLE_CROSS_USER_ROUTING_IN_MEDIA_ROUTER2)
+ @RequiresPermission(
+ anyOf = {
+ Manifest.permission.MEDIA_CONTENT_CONTROL,
+ Manifest.permission.MEDIA_ROUTING_CONTROL
+ })
@Nullable
public static MediaRouter2 getInstance(
@NonNull Context context, @NonNull String clientPackageName) {
@@ -226,9 +226,9 @@ public final class MediaRouter2 {
* {@link RouteDiscoveryPreference.Builder#setPreferredFeatures(List) preferred features}
* when setting a route callback.
* <li>
- * <p>Methods returning non-system {@link RoutingController controllers} always return
- * new instances with the latest data. Do not attempt to compare or store them. Instead,
- * use {@link #getController(String)} or {@link #getControllers()} to query the most
+ * <p>Methods returning non-system {@link RoutingController controllers} always return new
+ * instances with the latest data. Do not attempt to compare or store them. Instead, use
+ * {@link #getController(String)} or {@link #getControllers()} to query the most
* up-to-date state.
* <li>
* <p>Calls to {@link #setOnGetControllerHintsListener} are ignored.
@@ -242,8 +242,8 @@ public final class MediaRouter2 {
* @throws SecurityException if {@code user} does not match {@link Process#myUserHandle()} and
* the caller does not hold {@code Manifest.permission#INTERACT_ACROSS_USERS_FULL}.
* @throws IllegalArgumentException if {@code clientPackageName} does not exist in {@code user}.
+ * @hide
*/
- @FlaggedApi(FLAG_ENABLE_CROSS_USER_ROUTING_IN_MEDIA_ROUTER2)
@RequiresPermission(
anyOf = {
Manifest.permission.MEDIA_CONTENT_CONTROL,
@@ -251,9 +251,7 @@ public final class MediaRouter2 {
})
@NonNull
public static MediaRouter2 getInstance(
- @NonNull Context context,
- @NonNull String clientPackageName,
- @NonNull UserHandle user) {
+ @NonNull Context context, @NonNull String clientPackageName, @NonNull UserHandle user) {
return findOrCreateProxyInstanceForCallingUser(context, clientPackageName, user);
}
diff --git a/media/java/android/media/RoutingSessionInfo.java b/media/java/android/media/RoutingSessionInfo.java
index d28c26df6749..2202766ef016 100644
--- a/media/java/android/media/RoutingSessionInfo.java
+++ b/media/java/android/media/RoutingSessionInfo.java
@@ -182,7 +182,7 @@ public final class RoutingSessionInfo implements Parcelable {
mControlHints = src.readBundle();
mIsSystemSession = src.readBoolean();
mTransferReason = src.readInt();
- mTransferInitiatorUserHandle = src.readParcelable(null, android.os.UserHandle.class);
+ mTransferInitiatorUserHandle = UserHandle.readFromParcel(src);
mTransferInitiatorPackageName = src.readString();
}
@@ -417,11 +417,7 @@ public final class RoutingSessionInfo implements Parcelable {
dest.writeBundle(mControlHints);
dest.writeBoolean(mIsSystemSession);
dest.writeInt(mTransferReason);
- if (mTransferInitiatorUserHandle != null) {
- mTransferInitiatorUserHandle.writeToParcel(dest, /* flags= */ 0);
- } else {
- dest.writeParcelable(null, /* flags= */ 0);
- }
+ UserHandle.writeToParcel(mTransferInitiatorUserHandle, dest);
dest.writeString(mTransferInitiatorPackageName);
}
diff --git a/media/java/android/media/flags/editing.aconfig b/media/java/android/media/flags/editing.aconfig
new file mode 100644
index 000000000000..c3997e94622d
--- /dev/null
+++ b/media/java/android/media/flags/editing.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.media.editing.flags"
+
+flag {
+ name: "add_media_metrics_editing"
+ namespace: "media_solutions"
+ description: "Add media metrics for transcoding/editing events."
+ bug: "297487694"
+}
diff --git a/media/java/android/media/metrics/EditingEndedEvent.aidl b/media/java/android/media/metrics/EditingEndedEvent.aidl
new file mode 100644
index 000000000000..e099deaa6836
--- /dev/null
+++ b/media/java/android/media/metrics/EditingEndedEvent.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.metrics;
+
+parcelable EditingEndedEvent;
diff --git a/media/java/android/media/metrics/EditingEndedEvent.java b/media/java/android/media/metrics/EditingEndedEvent.java
new file mode 100644
index 000000000000..72e6db8d987f
--- /dev/null
+++ b/media/java/android/media/metrics/EditingEndedEvent.java
@@ -0,0 +1,325 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.metrics;
+
+import static com.android.media.editing.flags.Flags.FLAG_ADD_MEDIA_METRICS_EDITING;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.util.Objects;
+
+/** Event for an editing operation having ended. */
+@FlaggedApi(FLAG_ADD_MEDIA_METRICS_EDITING)
+public final class EditingEndedEvent extends Event implements Parcelable {
+
+ // The special value 0 is reserved for the field being unspecified in the proto.
+
+ /** The editing operation was successful. */
+ public static final int FINAL_STATE_SUCCEEDED = 1;
+
+ /** The editing operation was canceled. */
+ public static final int FINAL_STATE_CANCELED = 2;
+
+ /** The editing operation failed due to an error. */
+ public static final int FINAL_STATE_ERROR = 3;
+
+ /** @hide */
+ @IntDef(
+ prefix = {"FINAL_STATE_"},
+ value = {
+ FINAL_STATE_SUCCEEDED,
+ FINAL_STATE_CANCELED,
+ FINAL_STATE_ERROR,
+ })
+ @Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+ public @interface FinalState {}
+
+ private final @FinalState int mFinalState;
+
+ // The special value 0 is reserved for the field being unspecified in the proto.
+
+ /** Special value representing that no error occurred. */
+ public static final int ERROR_CODE_NONE = 1;
+
+ /** Error code for unexpected runtime errors. */
+ public static final int ERROR_CODE_FAILED_RUNTIME_CHECK = 2;
+
+ /** Error code for non-specific errors during input/output. */
+ public static final int ERROR_CODE_IO_UNSPECIFIED = 3;
+
+ /** Error code for network connection failures. */
+ public static final int ERROR_CODE_IO_NETWORK_CONNECTION_FAILED = 4;
+
+ /** Error code for network timeouts. */
+ public static final int ERROR_CODE_IO_NETWORK_CONNECTION_TIMEOUT = 5;
+
+ /** Caused by an HTTP server returning an unexpected HTTP response status code. */
+ public static final int ERROR_CODE_IO_BAD_HTTP_STATUS = 6;
+
+ /** Caused by a non-existent file. */
+ public static final int ERROR_CODE_IO_FILE_NOT_FOUND = 7;
+
+ /**
+ * Caused by lack of permission to perform an IO operation. For example, lack of permission to
+ * access internet or external storage.
+ */
+ public static final int ERROR_CODE_IO_NO_PERMISSION = 8;
+
+ /** */
+ public static final int ERROR_CODE_IO_CLEARTEXT_NOT_PERMITTED = 9;
+
+ /** Caused by reading data out of the data bounds. */
+ public static final int ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE = 10;
+
+ /** Caused by a decoder initialization failure. */
+ public static final int ERROR_CODE_DECODER_INIT_FAILED = 11;
+
+ /** Caused by a failure while trying to decode media samples. */
+ public static final int ERROR_CODE_DECODING_FAILED = 12;
+
+ /** Caused by trying to decode content whose format is not supported. */
+ public static final int ERROR_CODE_DECODING_FORMAT_UNSUPPORTED = 13;
+
+ /** Caused by an encoder initialization failure. */
+ public static final int ERROR_CODE_ENCODER_INIT_FAILED = 14;
+
+ /** Caused by a failure while trying to encode media samples. */
+ public static final int ERROR_CODE_ENCODING_FAILED = 15;
+
+ /** Caused by trying to encode content whose format is not supported. */
+ public static final int ERROR_CODE_ENCODING_FORMAT_UNSUPPORTED = 16;
+
+ /** Caused by a video frame processing failure. */
+ public static final int ERROR_CODE_VIDEO_FRAME_PROCESSING_FAILED = 17;
+
+ /** Caused by an audio processing failure. */
+ public static final int ERROR_CODE_AUDIO_PROCESSING_FAILED = 18;
+
+ /** Caused by a failure while muxing media samples. */
+ public static final int ERROR_CODE_MUXING_FAILED = 19;
+
+ /** @hide */
+ @IntDef(
+ prefix = {"ERROR_CODE_"},
+ value = {
+ ERROR_CODE_NONE,
+ ERROR_CODE_FAILED_RUNTIME_CHECK,
+ ERROR_CODE_IO_UNSPECIFIED,
+ ERROR_CODE_IO_NETWORK_CONNECTION_FAILED,
+ ERROR_CODE_IO_NETWORK_CONNECTION_TIMEOUT,
+ ERROR_CODE_IO_BAD_HTTP_STATUS,
+ ERROR_CODE_IO_FILE_NOT_FOUND,
+ ERROR_CODE_IO_NO_PERMISSION,
+ ERROR_CODE_IO_CLEARTEXT_NOT_PERMITTED,
+ ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE,
+ ERROR_CODE_DECODER_INIT_FAILED,
+ ERROR_CODE_DECODING_FAILED,
+ ERROR_CODE_DECODING_FORMAT_UNSUPPORTED,
+ ERROR_CODE_ENCODER_INIT_FAILED,
+ ERROR_CODE_ENCODING_FAILED,
+ ERROR_CODE_ENCODING_FORMAT_UNSUPPORTED,
+ ERROR_CODE_VIDEO_FRAME_PROCESSING_FAILED,
+ ERROR_CODE_AUDIO_PROCESSING_FAILED,
+ ERROR_CODE_MUXING_FAILED,
+ })
+ @Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+ public @interface ErrorCode {}
+
+ private final @ErrorCode int mErrorCode;
+ @SuppressWarnings("HidingField") // Hiding field from superclass as for playback events.
+ private final long mTimeSinceCreatedMillis;
+
+ private EditingEndedEvent(
+ @FinalState int finalState,
+ @ErrorCode int errorCode,
+ long timeSinceCreatedMillis,
+ @NonNull Bundle extras) {
+ mFinalState = finalState;
+ mErrorCode = errorCode;
+ mTimeSinceCreatedMillis = timeSinceCreatedMillis;
+ mMetricsBundle = extras.deepCopy();
+ }
+
+ /** Returns the state of the editing session when it ended. */
+ @FinalState
+ public int getFinalState() {
+ return mFinalState;
+ }
+
+ /** Returns the error code for a {@linkplain #FINAL_STATE_ERROR failed} editing session. */
+ @ErrorCode
+ public int getErrorCode() {
+ return mErrorCode;
+ }
+
+ /**
+ * Gets the elapsed time since creating of the editing session, in milliseconds, or -1 if
+ * unknown.
+ *
+ * @return The elapsed time since creating the editing session, in milliseconds, or -1 if
+ * unknown.
+ * @see LogSessionId
+ * @see EditingSession
+ */
+ @Override
+ @IntRange(from = -1)
+ public long getTimeSinceCreatedMillis() {
+ return mTimeSinceCreatedMillis;
+ }
+
+ /**
+ * Gets metrics-related information that is not supported by dedicated methods.
+ *
+ * <p>It is intended to be used for backwards compatibility by the metrics infrastructure.
+ */
+ @Override
+ @NonNull
+ public Bundle getMetricsBundle() {
+ return mMetricsBundle;
+ }
+
+ @Override
+ @NonNull
+ public String toString() {
+ return "PlaybackErrorEvent { "
+ + "finalState = "
+ + mFinalState
+ + ", "
+ + "errorCode = "
+ + mErrorCode
+ + ", "
+ + "timeSinceCreatedMillis = "
+ + mTimeSinceCreatedMillis
+ + " }";
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ EditingEndedEvent that = (EditingEndedEvent) o;
+ return mFinalState == that.mFinalState
+ && mErrorCode == that.mErrorCode
+ && mTimeSinceCreatedMillis == that.mTimeSinceCreatedMillis;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mFinalState, mErrorCode, mTimeSinceCreatedMillis);
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mFinalState);
+ dest.writeInt(mErrorCode);
+ dest.writeLong(mTimeSinceCreatedMillis);
+ dest.writeBundle(mMetricsBundle);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ private EditingEndedEvent(@NonNull Parcel in) {
+ int finalState = in.readInt();
+ int errorCode = in.readInt();
+ long timeSinceCreatedMillis = in.readLong();
+ Bundle metricsBundle = in.readBundle();
+
+ mFinalState = finalState;
+ mErrorCode = errorCode;
+ mTimeSinceCreatedMillis = timeSinceCreatedMillis;
+ mMetricsBundle = metricsBundle;
+ }
+
+ public static final @NonNull Creator<EditingEndedEvent> CREATOR =
+ new Creator<>() {
+ @Override
+ public EditingEndedEvent[] newArray(int size) {
+ return new EditingEndedEvent[size];
+ }
+
+ @Override
+ public EditingEndedEvent createFromParcel(@NonNull Parcel in) {
+ return new EditingEndedEvent(in);
+ }
+ };
+
+ /** Builder for {@link EditingEndedEvent} */
+ @FlaggedApi(FLAG_ADD_MEDIA_METRICS_EDITING)
+ public static final class Builder {
+ private final @FinalState int mFinalState;
+ private @ErrorCode int mErrorCode;
+ private long mTimeSinceCreatedMillis;
+ private Bundle mMetricsBundle;
+
+ /**
+ * Creates a new Builder.
+ *
+ * @param finalState The state of the editing session when it ended.
+ */
+ public Builder(@FinalState int finalState) {
+ mFinalState = finalState;
+ mErrorCode = ERROR_CODE_NONE;
+ mTimeSinceCreatedMillis = -1;
+ mMetricsBundle = new Bundle();
+ }
+
+ /**
+ * Sets the elapsed time since creating the editing session, in milliseconds.
+ *
+ * @param timeSinceCreatedMillis The elapsed time since creating the editing session, in
+ * milliseconds, or -1 if the value is unknown.
+ * @see #getTimeSinceCreatedMillis()
+ */
+ public @NonNull Builder setTimeSinceCreatedMillis(
+ @IntRange(from = -1) long timeSinceCreatedMillis) {
+ mTimeSinceCreatedMillis = timeSinceCreatedMillis;
+ return this;
+ }
+
+ /** Sets the error code for a {@linkplain #FINAL_STATE_ERROR failed} editing session. */
+ public @NonNull Builder setErrorCode(@ErrorCode int value) {
+ mErrorCode = value;
+ return this;
+ }
+
+ /**
+ * Sets metrics-related information that is not supported by dedicated methods.
+ *
+ * <p>Used for backwards compatibility by the metrics infrastructure.
+ */
+ public @NonNull Builder setMetricsBundle(@NonNull Bundle metricsBundle) {
+ mMetricsBundle = metricsBundle;
+ return this;
+ }
+
+ /** Builds an instance. */
+ public @NonNull EditingEndedEvent build() {
+ return new EditingEndedEvent(
+ mFinalState, mErrorCode, mTimeSinceCreatedMillis, mMetricsBundle);
+ }
+ }
+}
diff --git a/media/java/android/media/metrics/EditingSession.java b/media/java/android/media/metrics/EditingSession.java
index 2ddf623b1ed3..964e12cfcc05 100644
--- a/media/java/android/media/metrics/EditingSession.java
+++ b/media/java/android/media/metrics/EditingSession.java
@@ -16,6 +16,9 @@
package android.media.metrics;
+import static com.android.media.editing.flags.Flags.FLAG_ADD_MEDIA_METRICS_EDITING;
+
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -24,7 +27,8 @@ import com.android.internal.util.AnnotationValidations;
import java.util.Objects;
/**
- * An instances of this class represents a session of media editing.
+ * Represents a session of media editing, for example, transcoding between formats, transmuxing or
+ * applying trimming or audio/video effects to a stream.
*/
public final class EditingSession implements AutoCloseable {
private final @NonNull String mId;
@@ -40,6 +44,13 @@ public final class EditingSession implements AutoCloseable {
mLogSessionId = new LogSessionId(mId);
}
+ /** Reports that an editing operation ended. */
+ @FlaggedApi(FLAG_ADD_MEDIA_METRICS_EDITING)
+ public void reportEditingEndedEvent(@NonNull EditingEndedEvent editingEndedEvent) {
+ mManager.reportEditingEndedEvent(mId, editingEndedEvent);
+ }
+
+ /** Returns the identifier for logging this session. */
public @NonNull LogSessionId getSessionId() {
return mLogSessionId;
}
diff --git a/media/java/android/media/metrics/IMediaMetricsManager.aidl b/media/java/android/media/metrics/IMediaMetricsManager.aidl
index 51b1cc27c8a7..e07ca67296b3 100644
--- a/media/java/android/media/metrics/IMediaMetricsManager.aidl
+++ b/media/java/android/media/metrics/IMediaMetricsManager.aidl
@@ -16,6 +16,7 @@
package android.media.metrics;
+import android.media.metrics.EditingEndedEvent;
import android.media.metrics.NetworkEvent;
import android.media.metrics.PlaybackErrorEvent;
import android.media.metrics.PlaybackMetrics;
@@ -24,7 +25,7 @@ import android.media.metrics.TrackChangeEvent;
import android.os.PersistableBundle;
/**
- * Interface to the playback manager service.
+ * Interface to the media metrics manager service.
* @hide
*/
interface IMediaMetricsManager {
@@ -37,6 +38,8 @@ interface IMediaMetricsManager {
void reportPlaybackStateEvent(in String sessionId, in PlaybackStateEvent event, int userId);
void reportTrackChangeEvent(in String sessionId, in TrackChangeEvent event, int userId);
+ void reportEditingEndedEvent(in String sessionId, in EditingEndedEvent event, int userId);
+
String getTranscodingSessionId(int userId);
String getEditingSessionId(int userId);
String getBundleSessionId(int userId);
diff --git a/media/java/android/media/metrics/MediaMetricsManager.java b/media/java/android/media/metrics/MediaMetricsManager.java
index 0898874c2f65..622b0c158b8a 100644
--- a/media/java/android/media/metrics/MediaMetricsManager.java
+++ b/media/java/android/media/metrics/MediaMetricsManager.java
@@ -193,4 +193,18 @@ public final class MediaMetricsManager {
throw e.rethrowFromSystemServer();
}
}
+
+ /**
+ * Reports the event of an editing session ending.
+ *
+ * @hide
+ */
+ public void reportEditingEndedEvent(
+ @NonNull String sessionId, EditingEndedEvent editingEndedEvent) {
+ try {
+ mService.reportEditingEndedEvent(sessionId, editingEndedEvent, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl
index e3dba03d6093..7b5853169923 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl
@@ -61,7 +61,8 @@ oneway interface ITvInteractiveAppClient {
void onSetTvRecordingInfo(in String recordingId, in TvRecordingInfo recordingInfo, int seq);
void onRequestTvRecordingInfo(in String recordingId, int seq);
void onRequestTvRecordingInfoList(in int type, int seq);
- void onRequestSigning(
- in String id, in String algorithm, in String alias, in byte[] data, int seq);
+ void onRequestSigning(in String id, in String algorithm, in String alias, in byte[] data,
+ int seq);
+ void onRequestCertificate(in String host, int port, int seq);
void onAdRequest(in AdRequest request, int Seq);
}
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl
index 0f58b29247bb..1b9450b240ac 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl
@@ -58,6 +58,8 @@ interface ITvInteractiveAppManager {
void sendAvailableSpeeds(in IBinder sessionToken, in float[] speeds, int userId);
void sendSigningResult(in IBinder sessionToken, in String signingId, in byte[] result,
int userId);
+ void sendCertificate(in IBinder sessionToken, in String host, int port,
+ in Bundle certBundle, int userId);
void sendTvRecordingInfo(in IBinder sessionToken, in TvRecordingInfo recordingInfo, int userId);
void sendTvRecordingInfoList(in IBinder sessionToken,
in List<TvRecordingInfo> recordingInfoList, int userId);
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
index 06808c9ff915..3969315ab655 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
@@ -49,6 +49,7 @@ oneway interface ITvInteractiveAppSession {
void sendTimeShiftMode(int mode);
void sendAvailableSpeeds(in float[] speeds);
void sendSigningResult(in String signingId, in byte[] result);
+ void sendCertificate(in String host, int port, in Bundle certBundle);
void sendTvRecordingInfo(in TvRecordingInfo recordingInfo);
void sendTvRecordingInfoList(in List<TvRecordingInfo> recordingInfoList);
void notifyError(in String errMsg, in Bundle params);
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl
index 416b8f12d5ea..cb89181fd714 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl
@@ -61,5 +61,6 @@ oneway interface ITvInteractiveAppSessionCallback {
void onRequestTvRecordingInfo(in String recordingId);
void onRequestTvRecordingInfoList(in int type);
void onRequestSigning(in String id, in String algorithm, in String alias, in byte[] data);
+ void onRequestCertificate(in String host, int port);
void onAdRequest(in AdRequest request);
}
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
index 77730aa46d0a..ec6c2bfab576 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
@@ -104,6 +104,7 @@ public class ITvInteractiveAppSessionWrapper
private static final int DO_SEND_AVAILABLE_SPEEDS = 47;
private static final int DO_SEND_SELECTED_TRACK_INFO = 48;
private static final int DO_NOTIFY_VIDEO_FREEZE_UPDATED = 49;
+ private static final int DO_SEND_CERTIFICATE = 50;
private final HandlerCaller mCaller;
private Session mSessionImpl;
@@ -369,6 +370,13 @@ public class ITvInteractiveAppSessionWrapper
mSessionImpl.notifyVideoFreezeUpdated((Boolean) msg.obj);
break;
}
+ case DO_SEND_CERTIFICATE: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ mSessionImpl.sendCertificate((String) args.arg1, (Integer) args.arg2,
+ (Bundle) args.arg3);
+ args.recycle();
+ break;
+ }
default: {
Log.w(TAG, "Unhandled message code: " + msg.what);
break;
@@ -483,6 +491,12 @@ public class ITvInteractiveAppSessionWrapper
}
@Override
+ public void sendCertificate(@NonNull String host, int port, @NonNull Bundle certBundle) {
+ mCaller.executeOrSendMessage(
+ mCaller.obtainMessageOOO(DO_SEND_CERTIFICATE, host, port, certBundle));
+ }
+
+ @Override
public void notifyError(@NonNull String errMsg, @NonNull Bundle params) {
mCaller.executeOrSendMessage(
mCaller.obtainMessageOO(DO_NOTIFY_ERROR, errMsg, params));
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
index 8a340f6862bb..011744f94edb 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
@@ -34,6 +34,7 @@ import android.media.tv.TvInputManager;
import android.media.tv.TvRecordingInfo;
import android.media.tv.TvTrackInfo;
import android.net.Uri;
+import android.net.http.SslCertificate;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -656,6 +657,18 @@ public final class TvInteractiveAppManager {
}
@Override
+ public void onRequestCertificate(String host, int port, int seq) {
+ synchronized (mSessionCallbackRecordMap) {
+ SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+ if (record == null) {
+ Log.e(TAG, "Callback not found for seq " + seq);
+ return;
+ }
+ record.postRequestCertificate(host, port);
+ }
+ }
+
+ @Override
public void onSessionStateChanged(int state, int err, int seq) {
synchronized (mSessionCallbackRecordMap) {
SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
@@ -1328,6 +1341,19 @@ public final class TvInteractiveAppManager {
}
}
+ void sendCertificate(@NonNull String host, int port, @NonNull SslCertificate cert) {
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ try {
+ mService.sendCertificate(mToken, host, port, SslCertificate.saveState(cert),
+ mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
void notifyError(@NonNull String errMsg, @NonNull Bundle params) {
if (mToken == null) {
Log.w(TAG, "The session has been already released");
@@ -2232,6 +2258,15 @@ public final class TvInteractiveAppManager {
});
}
+ void postRequestCertificate(String host, int port) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mSessionCallback.onRequestCertificate(mSession, host, port);
+ }
+ });
+ }
+
void postRequestTvRecordingInfo(String recordingId) {
mHandler.post(new Runnable() {
@Override
@@ -2574,6 +2609,17 @@ public final class TvInteractiveAppManager {
}
/**
+ * This is called when the service requests a SSL certificate for client validation.
+ *
+ * @param session A {@link TvInteractiveAppService.Session} associated with this callback.
+ * @param host the host name of the SSL authentication server.
+ * @param port the port of the SSL authentication server. E.g., 443
+ * @hide
+ */
+ public void onRequestCertificate(Session session, String host, int port) {
+ }
+
+ /**
* This is called when {@link TvInteractiveAppService.Session#notifySessionStateChanged} is
* called.
*
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
index 5247a0ebe6e0..054b272d820f 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
@@ -46,6 +46,7 @@ import android.media.tv.TvTrackInfo;
import android.media.tv.TvView;
import android.media.tv.interactive.TvInteractiveAppView.TvInteractiveAppCallback;
import android.net.Uri;
+import android.net.http.SslCertificate;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
@@ -734,6 +735,17 @@ public abstract class TvInteractiveAppService extends Service {
}
/**
+ * Receives the requested Certificate
+ *
+ * @param host the host name of the SSL authentication server.
+ * @param port the port of the SSL authentication server. E.g., 443
+ * @param cert the SSL certificate received.
+ * @hide
+ */
+ public void onCertificate(@NonNull String host, int port, @NonNull SslCertificate cert) {
+ }
+
+ /**
* Called when the application sends information of an error.
*
* @param errMsg the message of the error.
@@ -1633,6 +1645,32 @@ public abstract class TvInteractiveAppService extends Service {
}
/**
+ * Requests a SSL certificate for client validation.
+ *
+ * @param host the host name of the SSL authentication server.
+ * @param port the port of the SSL authentication server. E.g., 443
+ * @hide
+ */
+ public void requestCertificate(@NonNull String host, int port) {
+ executeOrPostRunnableOnMainThread(new Runnable() {
+ @MainThread
+ @Override
+ public void run() {
+ try {
+ if (DEBUG) {
+ Log.d(TAG, "requestCertificate");
+ }
+ if (mSessionCallback != null) {
+ mSessionCallback.onRequestCertificate(host, port);
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "error in requestCertificate", e);
+ }
+ }
+ });
+ }
+
+ /**
* Sends an advertisement request to be processed by the related TV input.
*
* @param request The advertisement request
@@ -1725,6 +1763,11 @@ public abstract class TvInteractiveAppService extends Service {
onSigningResult(signingId, result);
}
+ void sendCertificate(String host, int port, Bundle certBundle) {
+ SslCertificate cert = SslCertificate.restoreState(certBundle);
+ onCertificate(host, port, cert);
+ }
+
void notifyError(String errMsg, Bundle params) {
onError(errMsg, params);
}
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppView.java b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
index 5bb61c261ae2..3b295742c244 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppView.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
@@ -34,6 +34,7 @@ import android.media.tv.interactive.TvInteractiveAppManager.Session;
import android.media.tv.interactive.TvInteractiveAppManager.Session.FinishedInputEventCallback;
import android.media.tv.interactive.TvInteractiveAppManager.SessionCallback;
import android.net.Uri;
+import android.net.http.SslCertificate;
import android.os.Bundle;
import android.os.Handler;
import android.util.AttributeSet;
@@ -756,6 +757,22 @@ public class TvInteractiveAppView extends ViewGroup {
}
/**
+ * Send the requested SSL certificate to the TV Interactive App
+ * @param host the host name of the SSL authentication server.
+ * @param port the port of the SSL authentication server. E.g., 443
+ * @param cert the SSL certificate requested
+ * @hide
+ */
+ public void sendCertificate(@NonNull String host, int port, @NonNull SslCertificate cert) {
+ if (DEBUG) {
+ Log.d(TAG, "sendCertificate");
+ }
+ if (mSession != null) {
+ mSession.sendCertificate(host, port, cert);
+ }
+ }
+
+ /**
* Notifies the corresponding {@link TvInteractiveAppService} when there is an error.
*
* @param errMsg the message of the error.
diff --git a/native/graphics/jni/Android.bp b/native/graphics/jni/Android.bp
index 10c570b30d7a..8ea46329af58 100644
--- a/native/graphics/jni/Android.bp
+++ b/native/graphics/jni/Android.bp
@@ -72,6 +72,9 @@ cc_library_shared {
],
},
},
+ stubs: {
+ symbol_file: "libjnigraphics.map.txt",
+ },
}
// The headers module is in frameworks/native/Android.bp.
@@ -93,15 +96,18 @@ cc_defaults {
],
static_libs: ["libarect"],
fuzz_config: {
- cc: ["dichenzhang@google.com","scroggo@google.com"],
+ cc: [
+ "dichenzhang@google.com",
+ "scroggo@google.com",
+ ],
asan_options: [
"detect_odr_violation=1",
],
hwasan_options: [
- // Image decoders may attempt to allocate a large amount of memory
- // (especially if the encoded image is large). This doesn't
- // necessarily mean there is a bug. Set allocator_may_return_null=1
- // for hwasan so the fuzzer can continue running.
+ // Image decoders may attempt to allocate a large amount of memory
+ // (especially if the encoded image is large). This doesn't
+ // necessarily mean there is a bug. Set allocator_may_return_null=1
+ // for hwasan so the fuzzer can continue running.
"allocator_may_return_null = 1",
],
},
diff --git a/nfc/jarjar-rules.txt b/nfc/jarjar-rules.txt
index 4cd652d6af7f..99ae14414dad 100644
--- a/nfc/jarjar-rules.txt
+++ b/nfc/jarjar-rules.txt
@@ -4,6 +4,7 @@ rule android.content.ComponentNameProto* com.android.nfc.x.@0
rule android.content.IntentProto* com.android.nfc.x.@0
rule android.content.IntentFilterProto* com.android.nfc.x.@0
rule android.content.AuthorityEntryProto* com.android.nfc.x.@0
+rule android.content.UriRelativeFilter* com.android.nfc.x.@0
rule android.nfc.cardemulation.AidGroupProto* com.android.nfc.x.@0
rule android.nfc.cardemulation.ApduServiceInfoProto* com.android.nfc.x.@0
rule android.nfc.cardemulation.NfcFServiceInfoProto* com.android.nfc.x.@0
diff --git a/nfc/java/android/nfc/cardemulation/CardEmulation.java b/nfc/java/android/nfc/cardemulation/CardEmulation.java
index 9d38e4c5b297..1f41b812164c 100644
--- a/nfc/java/android/nfc/cardemulation/CardEmulation.java
+++ b/nfc/java/android/nfc/cardemulation/CardEmulation.java
@@ -338,8 +338,10 @@ 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.
+ * 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.
*
* @param service The component name of the service
* @param enable Whether the servic should default to observe mode or not
diff --git a/packages/CrashRecovery/aconfig/flags.aconfig b/packages/CrashRecovery/aconfig/flags.aconfig
new file mode 100644
index 000000000000..563626634068
--- /dev/null
+++ b/packages/CrashRecovery/aconfig/flags.aconfig
@@ -0,0 +1,9 @@
+package: "android.crashrecovery.flags"
+
+flag {
+ name: "recoverability_detection"
+ namespace: "package_watchdog"
+ description: "Feature flag for recoverability detection"
+ bug: "310236690"
+ is_fixed_read_only: true
+} \ No newline at end of file
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
index 3a0e51b24c2c..4f6196648634 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
@@ -64,6 +64,8 @@ import androidx.compose.ui.layout.layoutId
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.semantics.clearAndSetSemantics
+import androidx.compose.ui.semantics.heading
+import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Constraints
@@ -129,7 +131,8 @@ private fun Title(title: String, maxLines: Int = Int.MAX_VALUE) {
modifier = Modifier.padding(
start = SettingsDimension.itemPaddingAround,
end = SettingsDimension.itemPaddingEnd,
- ),
+ )
+ .semantics { heading() },
overflow = TextOverflow.Ellipsis,
maxLines = maxLines,
)
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlow.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlow.kt
index e099f1124bf1..0a424bc4230a 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlow.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlow.kt
@@ -37,10 +37,11 @@ private const val TAG = "BroadcastReceiverFlow"
fun Context.broadcastReceiverFlow(intentFilter: IntentFilter): Flow<Intent> = callbackFlow {
val broadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
+ Log.d(TAG, "onReceive: $intent")
trySend(intent)
}
}
- registerReceiver(broadcastReceiver, intentFilter, Context.RECEIVER_NOT_EXPORTED)
+ registerReceiver(broadcastReceiver, intentFilter, Context.RECEIVER_VISIBLE_TO_INSTANT_APPS)
awaitClose { unregisterReceiver(broadcastReceiver) }
}.catch { e ->
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt
index 81a8b324f70f..cea3d13e27ab 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt
@@ -37,6 +37,7 @@ fun rememberAppRepository(): AppRepository = rememberContext(::AppRepositoryImpl
interface AppRepository {
fun loadLabel(app: ApplicationInfo): String
+ @Suppress("ABSTRACT_COMPOSABLE_DEFAULT_PARAMETER_VALUE")
@Composable
fun produceLabel(app: ApplicationInfo, isClonedAppPage: Boolean = false): State<String> {
val context = LocalContext.current
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlowTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlowTest.kt
index eef5225aef42..772f925c0a77 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlowTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlowTest.kt
@@ -43,7 +43,7 @@ class BroadcastReceiverFlowTest {
private val context = mock<Context> {
on {
- registerReceiver(any(), eq(INTENT_FILTER), eq(Context.RECEIVER_NOT_EXPORTED))
+ registerReceiver(any(), eq(INTENT_FILTER), eq(Context.RECEIVER_VISIBLE_TO_INSTANT_APPS))
} doAnswer {
registeredBroadcastReceiver = it.arguments[0] as BroadcastReceiver
null
diff --git a/packages/SettingsLib/res/drawable/ic_external_display.xml b/packages/SettingsLib/res/drawable/ic_external_display.xml
new file mode 100644
index 000000000000..de50de8c07c8
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_external_display.xml
@@ -0,0 +1,28 @@
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="25dp"
+ android:viewportWidth="24"
+ android:viewportHeight="25">
+ <group>
+ <clip-path
+ android:pathData="M0,0.307h24v24h-24z"/>
+ <path
+ android:pathData="M8,21.307V19.307H10V17.307H4C3.45,17.307 2.975,17.115 2.575,16.732C2.192,16.332 2,15.857 2,15.307V5.307C2,4.757 2.192,4.29 2.575,3.907C2.975,3.507 3.45,3.307 4,3.307H20C20.55,3.307 21.017,3.507 21.4,3.907C21.8,4.29 22,4.757 22,5.307V15.307C22,15.857 21.8,16.332 21.4,16.732C21.017,17.115 20.55,17.307 20,17.307H14V19.307H16V21.307H8ZM4,15.307H20V5.307H4V15.307ZM4,15.307V5.307V15.307Z"
+ android:fillColor="#E5E3D6"/>
+ </group>
+</vector>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java
index cf4d6be9a042..0613676113f5 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java
@@ -99,6 +99,8 @@ public class HapClientProfile implements LocalBluetoothProfile {
device.refresh();
}
+ // Check current list of CachedDevices to see if any are hearing aid devices.
+ mDeviceManager.updateHearingAidsDevices();
mIsProfileReady = true;
mProfileManager.callServiceConnectedListeners();
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
index 3a15b7108e3f..9fd174d4586c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
@@ -15,8 +15,8 @@
*/
package com.android.settingslib.bluetooth;
-import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHearingAid;
+import android.bluetooth.BluetoothLeAudio;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothUuid;
import android.bluetooth.le.ScanFilter;
@@ -68,14 +68,9 @@ public class HearingAidDeviceManager {
void initHearingAidDeviceIfNeeded(CachedBluetoothDevice newDevice,
List<ScanFilter> leScanFilters) {
- long hiSyncId = getHiSyncId(newDevice.getDevice());
- if (isValidHiSyncId(hiSyncId)) {
- // Once hiSyncId is valid, assign hearing aid info
- final HearingAidInfo.Builder infoBuilder = new HearingAidInfo.Builder()
- .setAshaDeviceSide(getDeviceSide(newDevice.getDevice()))
- .setAshaDeviceMode(getDeviceMode(newDevice.getDevice()))
- .setHiSyncId(hiSyncId);
- newDevice.setHearingAidInfo(infoBuilder.build());
+ HearingAidInfo info = generateHearingAidInfo(newDevice);
+ if (info != null) {
+ newDevice.setHearingAidInfo(info);
} else if (leScanFilters != null && !newDevice.isHearingAidDevice()) {
// If the device is added with hearing aid scan filter during pairing, set an empty
// hearing aid info to indicate it's a hearing aid device. The info will be updated
@@ -94,38 +89,6 @@ public class HearingAidDeviceManager {
}
}
- private long getHiSyncId(BluetoothDevice device) {
- final LocalBluetoothProfileManager profileManager = mBtManager.getProfileManager();
- final HearingAidProfile profileProxy = profileManager.getHearingAidProfile();
- if (profileProxy == null) {
- return BluetoothHearingAid.HI_SYNC_ID_INVALID;
- }
-
- return profileProxy.getHiSyncId(device);
- }
-
- private int getDeviceSide(BluetoothDevice device) {
- final LocalBluetoothProfileManager profileManager = mBtManager.getProfileManager();
- final HearingAidProfile profileProxy = profileManager.getHearingAidProfile();
- if (profileProxy == null) {
- Log.w(TAG, "HearingAidProfile is not supported and not ready to fetch device side");
- return HearingAidProfile.DeviceSide.SIDE_INVALID;
- }
-
- return profileProxy.getDeviceSide(device);
- }
-
- private int getDeviceMode(BluetoothDevice device) {
- final LocalBluetoothProfileManager profileManager = mBtManager.getProfileManager();
- final HearingAidProfile profileProxy = profileManager.getHearingAidProfile();
- if (profileProxy == null) {
- Log.w(TAG, "HearingAidProfile is not supported and not ready to fetch device mode");
- return HearingAidProfile.DeviceMode.MODE_INVALID;
- }
-
- return profileProxy.getDeviceMode(device);
- }
-
boolean setSubDeviceIfNeeded(CachedBluetoothDevice newDevice) {
final long hiSyncId = newDevice.getHiSyncId();
if (isValidHiSyncId(hiSyncId)) {
@@ -157,21 +120,17 @@ public class HearingAidDeviceManager {
// To collect all HearingAid devices and call #onHiSyncIdChanged to group device by HiSyncId
void updateHearingAidsDevices() {
- final Set<Long> newSyncIdSet = new HashSet<Long>();
+ final Set<Long> newSyncIdSet = new HashSet<>();
for (CachedBluetoothDevice cachedDevice : mCachedDevices) {
// Do nothing if HiSyncId has been assigned
- if (!isValidHiSyncId(cachedDevice.getHiSyncId())) {
- final long newHiSyncId = getHiSyncId(cachedDevice.getDevice());
- // Do nothing if there is no HiSyncId on Bluetooth device
- if (isValidHiSyncId(newHiSyncId)) {
- // Once hiSyncId is valid, assign hearing aid info
- final HearingAidInfo.Builder infoBuilder = new HearingAidInfo.Builder()
- .setAshaDeviceSide(getDeviceSide(cachedDevice.getDevice()))
- .setAshaDeviceMode(getDeviceMode(cachedDevice.getDevice()))
- .setHiSyncId(newHiSyncId);
- cachedDevice.setHearingAidInfo(infoBuilder.build());
-
- newSyncIdSet.add(newHiSyncId);
+ if (isValidHiSyncId(cachedDevice.getHiSyncId())) {
+ continue;
+ }
+ HearingAidInfo info = generateHearingAidInfo(cachedDevice);
+ if (info != null) {
+ cachedDevice.setHearingAidInfo(info);
+ if (isValidHiSyncId(info.getHiSyncId())) {
+ newSyncIdSet.add(info.getHiSyncId());
}
}
}
@@ -378,6 +337,54 @@ public class HearingAidDeviceManager {
return null;
}
+ private boolean isLeAudioHearingAid(CachedBluetoothDevice cachedDevice) {
+ List<LocalBluetoothProfile> profiles = cachedDevice.getProfiles();
+ boolean supportLeAudio = profiles.stream().anyMatch(p -> p instanceof LeAudioProfile);
+ boolean supportHapClient = profiles.stream().anyMatch(p -> p instanceof HapClientProfile);
+ return supportLeAudio && supportHapClient;
+ }
+
+ private boolean isAshaHearingAid(CachedBluetoothDevice cachedDevice) {
+ return cachedDevice.getProfiles().stream().anyMatch(p -> p instanceof HearingAidProfile);
+ }
+
+ private HearingAidInfo generateHearingAidInfo(CachedBluetoothDevice cachedDevice) {
+ final LocalBluetoothProfileManager profileManager = mBtManager.getProfileManager();
+ if (isAshaHearingAid(cachedDevice)) {
+ final HearingAidProfile asha = profileManager.getHearingAidProfile();
+ if (asha == null) {
+ Log.w(TAG, "HearingAidProfile is not supported on this device");
+ } else {
+ long hiSyncId = asha.getHiSyncId(cachedDevice.getDevice());
+ if (isValidHiSyncId(hiSyncId)) {
+ final HearingAidInfo.Builder infoBuilder = new HearingAidInfo.Builder()
+ .setAshaDeviceSide(asha.getDeviceSide(cachedDevice.getDevice()))
+ .setAshaDeviceMode(asha.getDeviceMode(cachedDevice.getDevice()))
+ .setHiSyncId(hiSyncId);
+ return infoBuilder.build();
+ }
+ }
+ }
+ if (isLeAudioHearingAid(cachedDevice)) {
+ final HapClientProfile hapClientProfile = profileManager.getHapClientProfile();
+ final LeAudioProfile leAudioProfile = profileManager.getLeAudioProfile();
+ if (hapClientProfile == null || leAudioProfile == null) {
+ Log.w(TAG, "HapClientProfile or LeAudioProfile is not supported on this device");
+ } else {
+ int audioLocation = leAudioProfile.getAudioLocation(cachedDevice.getDevice());
+ int hearingAidType = hapClientProfile.getHearingAidType(cachedDevice.getDevice());
+ if (audioLocation != BluetoothLeAudio.AUDIO_LOCATION_INVALID
+ && hearingAidType != HapClientProfile.HearingAidType.TYPE_INVALID) {
+ final HearingAidInfo.Builder infoBuilder = new HearingAidInfo.Builder()
+ .setLeAudioLocation(audioLocation)
+ .setHapDeviceType(hearingAidType);
+ return infoBuilder.build();
+ }
+ }
+ }
+ return null;
+ }
+
private void log(String msg) {
if (DEBUG) {
Log.d(TAG, msg);
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
index 14fab16de3e6..f2450de60878 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
@@ -109,7 +109,7 @@ public class HearingAidProfile implements LocalBluetoothProfile {
device.refresh();
}
- // Check current list of CachedDevices to see if any are Hearing Aid devices.
+ // Check current list of CachedDevices to see if any are hearing aid devices.
mDeviceManager.updateHearingAidsDevices();
mIsProfileReady = true;
mProfileManager.callServiceConnectedListeners();
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java
index 931a6f149b84..6be4336178eb 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java
@@ -83,6 +83,8 @@ public class LeAudioProfile implements LocalBluetoothProfile {
device.refresh();
}
+ // Check current list of CachedDevices to see if any are hearing aid devices.
+ mDeviceManager.updateHearingAidsDevices();
mProfileManager.callServiceConnectedListeners();
mIsProfileReady = true;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
index 934870507a20..1d2f7902b781 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
@@ -759,6 +759,20 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
}
/**
+ * Update the LE Broadcast by calling {@link BluetoothLeBroadcast#updateBroadcast(int,
+ * BluetoothLeAudioContentMetadata)}, currently only updates programInfo.
+ */
+ public void updateBroadcast() {
+ if (mServiceBroadcast == null) {
+ Log.d(TAG, "The BluetoothLeBroadcast is null when updating the broadcast.");
+ return;
+ }
+ String programInfo = getProgramInfo();
+ mBluetoothLeAudioContentMetadata = mBuilder.setProgramInfo(programInfo).build();
+ mServiceBroadcast.updateBroadcast(mBroadcastId, mBluetoothLeAudioContentMetadata);
+ }
+
+ /**
* Register Broadcast Callbacks to track its state and receivers
*
* @param executor Executor object for callback
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java b/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java
index cf224dc3be5f..3de49336f427 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java
@@ -71,15 +71,15 @@ public class DeviceIconUtil {
new Device(
AudioDeviceInfo.TYPE_HDMI,
MediaRoute2Info.TYPE_HDMI,
- mIsTv ? R.drawable.ic_tv : R.drawable.ic_headphone),
+ mIsTv ? R.drawable.ic_tv : R.drawable.ic_external_display),
new Device(
AudioDeviceInfo.TYPE_HDMI_ARC,
MediaRoute2Info.TYPE_HDMI_ARC,
- mIsTv ? R.drawable.ic_hdmi : R.drawable.ic_headphone),
+ mIsTv ? R.drawable.ic_hdmi : R.drawable.ic_external_display),
new Device(
AudioDeviceInfo.TYPE_HDMI_EARC,
MediaRoute2Info.TYPE_HDMI_EARC,
- mIsTv ? R.drawable.ic_hdmi : R.drawable.ic_headphone),
+ mIsTv ? R.drawable.ic_hdmi : R.drawable.ic_external_display),
new Device(
AudioDeviceInfo.TYPE_WIRED_HEADSET,
MediaRoute2Info.TYPE_WIRED_HEADSET,
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
index 8a122fcddcb3..aef09ac236f3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
@@ -26,11 +26,13 @@ import android.media.MediaRouter2Manager;
import android.media.RouteDiscoveryPreference;
import android.media.RouteListingPreference;
import android.media.RoutingSessionInfo;
+import android.os.Process;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.media.flags.Flags;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import java.util.ArrayList;
@@ -62,21 +64,33 @@ public final class RouterInfoMediaManager extends InfoMediaManager {
refreshDevices();
};
- // TODO: b/192657812 - Create factory method in InfoMediaManager to return
- // RouterInfoMediaManager or ManagerInfoMediaManager based on flag.
+ // TODO (b/321969740): Plumb target UserHandle between UMO and RouterInfoMediaManager.
public RouterInfoMediaManager(
Context context,
String packageName,
Notification notification,
- LocalBluetoothManager localBluetoothManager) throws PackageNotAvailableException {
+ LocalBluetoothManager localBluetoothManager)
+ throws PackageNotAvailableException {
super(context, packageName, notification, localBluetoothManager);
- mRouter = MediaRouter2.getInstance(context, packageName);
+ MediaRouter2 router = null;
- if (mRouter == null) {
+ if (Flags.enableCrossUserRoutingInMediaRouter2()) {
+ try {
+ router = MediaRouter2.getInstance(context, packageName, Process.myUserHandle());
+ } catch (IllegalArgumentException ex) {
+ // Do nothing
+ }
+ } else {
+ router = MediaRouter2.getInstance(context, packageName);
+ }
+ if (router == null) {
throw new PackageNotAvailableException(
"Package name " + packageName + " does not exist.");
}
+ // We have to defer initialization because mRouter is final.
+ mRouter = router;
+
mRouterManager = MediaRouter2Manager.getInstance(context);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
new file mode 100644
index 000000000000..3355fb395ca0
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.volume.data.repository
+
+import android.media.AudioManager
+import com.android.internal.util.ConcurrentUtils
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+
+/** Provides audio managing functionality and data. */
+interface AudioRepository {
+
+ /** Current [AudioManager.getMode]. */
+ val mode: StateFlow<Int>
+}
+
+class AudioRepositoryImpl(
+ private val audioManager: AudioManager,
+ backgroundCoroutineContext: CoroutineContext,
+ coroutineScope: CoroutineScope,
+) : AudioRepository {
+
+ override val mode: StateFlow<Int> =
+ callbackFlow {
+ val listener =
+ AudioManager.OnModeChangedListener { newMode -> launch { send(newMode) } }
+ audioManager.addOnModeChangedListener(ConcurrentUtils.DIRECT_EXECUTOR, listener)
+ awaitClose { audioManager.removeOnModeChangedListener(listener) }
+ }
+ .flowOn(backgroundCoroutineContext)
+ .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), audioManager.mode)
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioModeInteractor.kt b/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioModeInteractor.kt
new file mode 100644
index 000000000000..053c59b15067
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioModeInteractor.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.volume.domain.interactor
+
+import android.media.AudioManager
+import com.android.settingslib.volume.data.repository.AudioRepository
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+class AudioModeInteractor(repository: AudioRepository) {
+
+ private val ongoingCallModes =
+ setOf(
+ AudioManager.MODE_RINGTONE,
+ AudioManager.MODE_IN_CALL,
+ AudioManager.MODE_IN_COMMUNICATION,
+ )
+
+ /** Returns if current [AudioManager.getMode] call is an ongoing call */
+ val isOngoingCall: Flow<Boolean> = repository.mode.map { it in ongoingCallModes }
+}
diff --git a/packages/SettingsLib/tests/integ/Android.bp b/packages/SettingsLib/tests/integ/Android.bp
index 644b72c383ac..ce3a7baf6be6 100644
--- a/packages/SettingsLib/tests/integ/Android.bp
+++ b/packages/SettingsLib/tests/integ/Android.bp
@@ -57,6 +57,7 @@ android_test {
"SettingsLibSettingsSpinner",
"SettingsLibUsageProgressBarPreference",
"settingslib_media_flags_lib",
+ "kotlinx_coroutines_test",
],
dxflags: ["--multi-dex"],
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioRepository.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioRepository.kt
new file mode 100644
index 000000000000..686362fadf02
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioRepository.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.volume.data.repository
+
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeAudioRepository : AudioRepository {
+
+ private val mutableMode = MutableStateFlow(0)
+ override val mode: StateFlow<Int>
+ get() = mutableMode.asStateFlow()
+
+ fun setMode(newMode: Int) {
+ mutableMode.value = newMode
+ }
+}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/domain/interactor/AudioModeInteractorTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/domain/interactor/AudioModeInteractorTest.kt
new file mode 100644
index 000000000000..3bc1edc9c944
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/domain/interactor/AudioModeInteractorTest.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.volume.domain.interactor
+
+import android.media.AudioManager
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.settingslib.volume.data.repository.FakeAudioRepository
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class AudioModeInteractorTest {
+
+ private val testScope = TestScope()
+ private val fakeAudioRepository = FakeAudioRepository()
+
+ private val underTest = AudioModeInteractor(fakeAudioRepository)
+
+ @Test
+ fun ongoingCallModes_isOnGoingCall() {
+ testScope.runTest {
+ for (mode in ongoingCallModes) {
+ var isOngoingCall = false
+ underTest.isOngoingCall.onEach { isOngoingCall = it }.launchIn(backgroundScope)
+
+ fakeAudioRepository.setMode(mode)
+ runCurrent()
+
+ assertThat(isOngoingCall).isTrue()
+ }
+ }
+ }
+
+ @Test
+ fun notOngoingCallModes_isNotOnGoingCall() {
+ testScope.runTest {
+ var isOngoingCall = true
+ underTest.isOngoingCall.onEach { isOngoingCall = it }.launchIn(backgroundScope)
+
+ fakeAudioRepository.setMode(AudioManager.MODE_CURRENT)
+ runCurrent()
+
+ assertThat(isOngoingCall).isFalse()
+ }
+ }
+
+ private companion object {
+ private val ongoingCallModes =
+ setOf(
+ AudioManager.MODE_RINGTONE,
+ AudioManager.MODE_IN_CALL,
+ AudioManager.MODE_IN_COMMUNICATION,
+ )
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java
index e7487e857464..aa5a2984e70c 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java
@@ -15,6 +15,15 @@
*/
package com.android.settingslib.bluetooth;
+import static android.bluetooth.BluetoothHearingAid.HI_SYNC_ID_INVALID;
+import static android.bluetooth.BluetoothLeAudio.AUDIO_LOCATION_FRONT_LEFT;
+import static android.bluetooth.BluetoothLeAudio.AUDIO_LOCATION_INVALID;
+
+import static com.android.settingslib.bluetooth.HapClientProfile.HearingAidType.TYPE_BINAURAL;
+import static com.android.settingslib.bluetooth.HapClientProfile.HearingAidType.TYPE_INVALID;
+import static com.android.settingslib.bluetooth.HearingAidProfile.DeviceMode.MODE_BINAURAL;
+import static com.android.settingslib.bluetooth.HearingAidProfile.DeviceSide.SIDE_RIGHT;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
@@ -32,7 +41,6 @@ import static org.mockito.Mockito.when;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothHearingAid;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothUuid;
import android.bluetooth.le.ScanFilter;
@@ -92,6 +100,10 @@ public class HearingAidDeviceManagerTest {
@Mock
private HearingAidProfile mHearingAidProfile;
@Mock
+ private LeAudioProfile mLeAudioProfile;
+ @Mock
+ private HapClientProfile mHapClientProfile;
+ @Mock
private AudioProductStrategy mAudioStrategy;
@Mock
private BluetoothDevice mDevice1;
@@ -123,6 +135,8 @@ public class HearingAidDeviceManagerTest {
when(mLocalBluetoothManager.getEventManager()).thenReturn(mBluetoothEventManager);
when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalProfileManager);
when(mLocalProfileManager.getHearingAidProfile()).thenReturn(mHearingAidProfile);
+ when(mLocalProfileManager.getLeAudioProfile()).thenReturn(mLeAudioProfile);
+ when(mLocalProfileManager.getHapClientProfile()).thenReturn(mHapClientProfile);
when(mAudioStrategy.getAudioAttributesForLegacyStreamType(
AudioManager.STREAM_MUSIC))
.thenReturn((new AudioAttributes.Builder()).build());
@@ -140,34 +154,43 @@ public class HearingAidDeviceManagerTest {
}
/**
- * Test initHearingAidDeviceIfNeeded, set HearingAid's information, including HiSyncId,
- * deviceSide, deviceMode.
+ * Test initHearingAidDeviceIfNeeded
+ *
+ * Conditions:
+ * 1) ASHA hearing aid
+ * 2) Valid HiSyncId
+ * Result:
+ * Set hearing aid info to the device.
*/
@Test
- public void initHearingAidDeviceIfNeeded_validHiSyncId_setHearingAidInfo() {
+ public void initHearingAidDeviceIfNeeded_asha_validHiSyncId_setHearingAidInfo() {
+ when(mCachedDevice1.getProfiles()).thenReturn(List.of(mHearingAidProfile));
when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(HISYNCID1);
- when(mHearingAidProfile.getDeviceMode(mDevice1)).thenReturn(
- HearingAidProfile.DeviceMode.MODE_BINAURAL);
- when(mHearingAidProfile.getDeviceSide(mDevice1)).thenReturn(
- HearingAidProfile.DeviceSide.SIDE_RIGHT);
+ when(mHearingAidProfile.getDeviceMode(mDevice1)).thenReturn(MODE_BINAURAL);
+ when(mHearingAidProfile.getDeviceSide(mDevice1)).thenReturn(SIDE_RIGHT);
assertThat(mCachedDevice1.getHiSyncId()).isNotEqualTo(HISYNCID1);
mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(mCachedDevice1, null);
assertThat(mCachedDevice1.getHiSyncId()).isEqualTo(HISYNCID1);
+ assertThat(mCachedDevice1.getDeviceSide()).isEqualTo(HearingAidInfo.DeviceSide.SIDE_RIGHT);
assertThat(mCachedDevice1.getDeviceMode()).isEqualTo(
HearingAidInfo.DeviceMode.MODE_BINAURAL);
- assertThat(mCachedDevice1.getDeviceSide()).isEqualTo(
- HearingAidInfo.DeviceSide.SIDE_RIGHT);
}
/**
- * Test initHearingAidDeviceIfNeeded, an invalid HiSyncId will not be assigned
+ * Test initHearingAidDeviceIfNeeded
+ *
+ * Conditions:
+ * 1) ASHA hearing aid
+ * 2) Invalid HiSyncId
+ * Result:
+ * Do not set hearing aid info to the device.
*/
@Test
- public void initHearingAidDeviceIfNeeded_invalidHiSyncId_notToSetHearingAidInfo() {
- when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(
- BluetoothHearingAid.HI_SYNC_ID_INVALID);
+ public void initHearingAidDeviceIfNeeded_asha_invalidHiSyncId_notToSetHearingAidInfo() {
+ when(mCachedDevice1.getProfiles()).thenReturn(List.of(mHearingAidProfile));
+ when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(HI_SYNC_ID_INVALID);
mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(mCachedDevice1, null);
@@ -175,34 +198,89 @@ public class HearingAidDeviceManagerTest {
}
/**
- * Test initHearingAidDeviceIfNeeded, an invalid HiSyncId and hearing aid scan filter, set an
- * empty hearing aid info on the device.
+ * Test initHearingAidDeviceIfNeeded
+ *
+ * Conditions:
+ * 1) ASHA hearing aid
+ * 2) Invalid HiSyncId
+ * 3) ASHA uuid scan filter
+ * Result:
+ * Set an empty hearing aid info to the device.
*/
@Test
- public void initHearingAidDeviceIfNeeded_hearingAidScanFilter_setHearingAidInfo() {
- when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(
- BluetoothHearingAid.HI_SYNC_ID_INVALID);
+ public void initHearingAidDeviceIfNeeded_asha_scanFilterNotNull_setEmptyHearingAidInfo() {
+ when(mCachedDevice1.getProfiles()).thenReturn(List.of(mHearingAidProfile));
+ when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(HI_SYNC_ID_INVALID);
final ScanFilter scanFilter = new ScanFilter.Builder()
.setServiceData(BluetoothUuid.HEARING_AID, new byte[]{0}).build();
mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(mCachedDevice1, List.of(scanFilter));
- assertThat(mCachedDevice1.isHearingAidDevice()).isTrue();
+ verify(mCachedDevice1).setHearingAidInfo(new HearingAidInfo.Builder().build());
}
/**
- * Test initHearingAidDeviceIfNeeded, an invalid HiSyncId and random scan filter, not to set
- * hearing aid info on the device.
+ * Test initHearingAidDeviceIfNeeded
+ *
+ * Conditions:
+ * 1) Asha hearing aid
+ * 2) Invalid HiSyncId
+ * 3) Random scan filter
+ * Result:
+ * Do not set hearing aid info to the device.
*/
@Test
- public void initHearingAidDeviceIfNeeded_randomScanFilter_setHearingAidInfo() {
- when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(
- BluetoothHearingAid.HI_SYNC_ID_INVALID);
+ public void initHearingAidDeviceIfNeeded_asha_randomScanFilter_notToSetHearingAidInfo() {
+ when(mCachedDevice1.getProfiles()).thenReturn(List.of(mHearingAidProfile));
+ when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(HI_SYNC_ID_INVALID);
final ScanFilter scanFilter = new ScanFilter.Builder().build();
mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(mCachedDevice1, List.of(scanFilter));
- assertThat(mCachedDevice1.isHearingAidDevice()).isFalse();
+ verify(mCachedDevice1, never()).setHearingAidInfo(any(HearingAidInfo.class));
+ }
+
+ /**
+ * Test initHearingAidDeviceIfNeeded
+ *
+ * Conditions:
+ * 1) LeAudio hearing aid
+ * 2) Valid audio location and device type
+ * Result:
+ * Set hearing aid info to the device.
+ */
+ @Test
+ public void initHearingAidDeviceIfNeeded_leAudio_validInfo_setHearingAidInfo() {
+ when(mCachedDevice1.getProfiles()).thenReturn(List.of(mLeAudioProfile, mHapClientProfile));
+ when(mLeAudioProfile.getAudioLocation(mDevice1)).thenReturn(AUDIO_LOCATION_FRONT_LEFT);
+ when(mHapClientProfile.getHearingAidType(mDevice1)).thenReturn(TYPE_BINAURAL);
+
+ mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(mCachedDevice1, null);
+
+ verify(mCachedDevice1).setHearingAidInfo(any(HearingAidInfo.class));
+ assertThat(mCachedDevice1.getDeviceSide()).isEqualTo(HearingAidInfo.DeviceSide.SIDE_LEFT);
+ assertThat(mCachedDevice1.getDeviceMode()).isEqualTo(
+ HearingAidInfo.DeviceMode.MODE_BINAURAL);
+ }
+
+ /**
+ * Test initHearingAidDeviceIfNeeded
+ *
+ * Conditions:
+ * 1) LeAudio hearing aid
+ * 2) Invalid audio location and device type
+ * Result:
+ * Do not set hearing aid info to the device.
+ */
+ @Test
+ public void initHearingAidDeviceIfNeeded_leAudio_invalidInfo_notToSetHearingAidInfo() {
+ when(mCachedDevice1.getProfiles()).thenReturn(List.of(mLeAudioProfile, mHapClientProfile));
+ when(mLeAudioProfile.getAudioLocation(mDevice1)).thenReturn(AUDIO_LOCATION_INVALID);
+ when(mHapClientProfile.getHearingAidType(mDevice1)).thenReturn(TYPE_INVALID);
+
+ mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(mCachedDevice1, null);
+
+ verify(mCachedDevice1, never()).setHearingAidInfo(any(HearingAidInfo.class));
}
/**
@@ -234,13 +312,20 @@ public class HearingAidDeviceManagerTest {
}
/**
- * Test updateHearingAidsDevices, to link two devices with the same HiSyncId.
- * When first paired devices is connected and second paired device is disconnected, first
- * paired device would be set as main device and second device will be removed from
- * CachedDevices list.
+ * Test updateHearingAidsDevices
+ *
+ * Conditions:
+ * 1) Two ASHA hearing aids with the same HiSyncId
+ * 2) First paired devices is connected
+ * 3) Second paired device is disconnected
+ * Result:
+ * First paired device would be set as main device and second paired device will be set
+ * as sub device and removed from CachedDevices list.
*/
@Test
- public void updateHearingAidsDevices_firstPairedDevicesConnected_verifySubDevice() {
+ public void updateHearingAidsDevices_asha_firstPairedDevicesConnected_verifySubDevice() {
+ when(mCachedDevice1.getProfiles()).thenReturn(List.of(mHearingAidProfile));
+ when(mCachedDevice2.getProfiles()).thenReturn(List.of(mHearingAidProfile));
when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(HISYNCID1);
when(mHearingAidProfile.getHiSyncId(mDevice2)).thenReturn(HISYNCID1);
when(mCachedDevice1.isConnected()).thenReturn(true);
@@ -257,13 +342,20 @@ public class HearingAidDeviceManagerTest {
}
/**
- * Test updateHearingAidsDevices, to link two devices with the same HiSyncId.
- * When second paired devices is connected and first paired device is disconnected, second
- * paired device would be set as main device and first device will be removed from
- * CachedDevices list.
+ * Test updateHearingAidsDevices
+ *
+ * Conditions:
+ * 1) Two ASHA hearing aids with the same HiSyncId
+ * 2) First paired devices is disconnected
+ * 3) Second paired device is connected
+ * Result:
+ * Second paired device would be set as main device and first paired device will be set
+ * as sub device and removed from CachedDevices list.
*/
@Test
- public void updateHearingAidsDevices_secondPairedDeviceConnected_verifySubDevice() {
+ public void updateHearingAidsDevices_asha_secondPairedDeviceConnected_verifySubDevice() {
+ when(mCachedDevice1.getProfiles()).thenReturn(List.of(mHearingAidProfile));
+ when(mCachedDevice2.getProfiles()).thenReturn(List.of(mHearingAidProfile));
when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(HISYNCID1);
when(mHearingAidProfile.getHiSyncId(mDevice2)).thenReturn(HISYNCID1);
when(mCachedDevice1.isConnected()).thenReturn(false);
@@ -280,12 +372,20 @@ public class HearingAidDeviceManagerTest {
}
/**
- * Test updateHearingAidsDevices, to link two devices with the same HiSyncId.
- * When both devices are connected, to build up main and sub relationship and to remove sub
- * device from CachedDevices list.
+ * Test updateHearingAidsDevices
+ *
+ * Conditions:
+ * 1) Two ASHA hearing aids with the same HiSyncId
+ * 2) First paired devices is connected
+ * 3) Second paired device is connected
+ * Result:
+ * First paired device would be set as main device and second paired device will be set
+ * as sub device and removed from CachedDevices list.
*/
@Test
- public void updateHearingAidsDevices_BothConnected_verifySubDevice() {
+ public void updateHearingAidsDevices_asha_bothConnected_verifySubDevice() {
+ when(mCachedDevice1.getProfiles()).thenReturn(List.of(mHearingAidProfile));
+ when(mCachedDevice2.getProfiles()).thenReturn(List.of(mHearingAidProfile));
when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(HISYNCID1);
when(mHearingAidProfile.getHiSyncId(mDevice2)).thenReturn(HISYNCID1);
when(mCachedDevice1.isConnected()).thenReturn(true);
@@ -302,46 +402,64 @@ public class HearingAidDeviceManagerTest {
}
/**
- * Test updateHearingAidsDevices, dispatch callback
+ * Test updateHearingAidsDevices
+ *
+ * Conditions:
+ * 1) Two ASHA hearing aids with the same HiSyncId
+ * Result:
+ * Dispatch device removed callback
*/
@Test
- public void updateHearingAidsDevices_dispatchDeviceRemovedCallback() {
+ public void updateHearingAidsDevices_asha_dispatchDeviceRemovedCallback() {
+ when(mCachedDevice1.getProfiles()).thenReturn(List.of(mHearingAidProfile));
+ when(mCachedDevice2.getProfiles()).thenReturn(List.of(mHearingAidProfile));
when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(HISYNCID1);
when(mHearingAidProfile.getHiSyncId(mDevice2)).thenReturn(HISYNCID1);
mCachedDeviceManager.mCachedDevices.add(mCachedDevice1);
mCachedDeviceManager.mCachedDevices.add(mCachedDevice2);
+
mHearingAidDeviceManager.updateHearingAidsDevices();
verify(mBluetoothEventManager).dispatchDeviceRemoved(mCachedDevice1);
}
/**
- * Test updateHearingAidsDevices, do nothing when HiSyncId is invalid
+ * Test updateHearingAidsDevices
+ *
+ * Conditions:
+ * 1) Two ASHA hearing aids with invalid HiSyncId
+ * Result:
+ * Do nothing
*/
@Test
- public void updateHearingAidsDevices_invalidHiSyncId_doNothing() {
- when(mHearingAidProfile.getHiSyncId(mDevice1)).
- thenReturn(BluetoothHearingAid.HI_SYNC_ID_INVALID);
- when(mHearingAidProfile.getHiSyncId(mDevice2)).
- thenReturn(BluetoothHearingAid.HI_SYNC_ID_INVALID);
+ public void updateHearingAidsDevices_asha_invalidHiSyncId_doNothing() {
+ when(mCachedDevice1.getProfiles()).thenReturn(List.of(mHearingAidProfile));
+ when(mCachedDevice2.getProfiles()).thenReturn(List.of(mHearingAidProfile));
+ when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(HI_SYNC_ID_INVALID);
+ when(mHearingAidProfile.getHiSyncId(mDevice2)).thenReturn(HI_SYNC_ID_INVALID);
mCachedDeviceManager.mCachedDevices.add(mCachedDevice1);
mCachedDeviceManager.mCachedDevices.add(mCachedDevice2);
+
mHearingAidDeviceManager.updateHearingAidsDevices();
verify(mHearingAidDeviceManager, never()).onHiSyncIdChanged(anyLong());
}
/**
- * Test updateHearingAidsDevices, set HearingAid's information, including HiSyncId, deviceSide,
- * deviceMode.
+ * Test updateHearingAidsDevices
+ *
+ * Conditions:
+ * 1) ASHA hearing aids
+ * 2) Valid HiSync Id
+ * Result:
+ * Set hearing aid info to the device.
*/
@Test
- public void updateHearingAidsDevices_validHiSyncId_setHearingAidInfos() {
+ public void updateHearingAidsDevices_asha_validHiSyncId_setHearingAidInfo() {
+ when(mCachedDevice1.getProfiles()).thenReturn(List.of(mHearingAidProfile));
when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(HISYNCID1);
- when(mHearingAidProfile.getDeviceMode(mDevice1)).thenReturn(
- HearingAidProfile.DeviceMode.MODE_BINAURAL);
- when(mHearingAidProfile.getDeviceSide(mDevice1)).thenReturn(
- HearingAidProfile.DeviceSide.SIDE_RIGHT);
+ when(mHearingAidProfile.getDeviceMode(mDevice1)).thenReturn(MODE_BINAURAL);
+ when(mHearingAidProfile.getDeviceSide(mDevice1)).thenReturn(SIDE_RIGHT);
mCachedDeviceManager.mCachedDevices.add(mCachedDevice1);
mHearingAidDeviceManager.updateHearingAidsDevices();
@@ -355,6 +473,51 @@ public class HearingAidDeviceManagerTest {
}
/**
+ * Test updateHearingAidsDevices
+ *
+ * Conditions:
+ * 1) LeAudio hearing aid
+ * 2) Valid audio location and device type
+ * Result:
+ * Set hearing aid info to the device.
+ */
+ @Test
+ public void updateHearingAidsDevices_leAudio_validInfo_setHearingAidInfo() {
+ when(mCachedDevice1.getProfiles()).thenReturn(List.of(mLeAudioProfile, mHapClientProfile));
+ when(mLeAudioProfile.getAudioLocation(mDevice1)).thenReturn(AUDIO_LOCATION_FRONT_LEFT);
+ when(mHapClientProfile.getHearingAidType(mDevice1)).thenReturn(TYPE_BINAURAL);
+ mCachedDeviceManager.mCachedDevices.add(mCachedDevice1);
+
+ mHearingAidDeviceManager.updateHearingAidsDevices();
+
+ verify(mCachedDevice1).setHearingAidInfo(any(HearingAidInfo.class));
+ assertThat(mCachedDevice1.getDeviceSide()).isEqualTo(HearingAidInfo.DeviceSide.SIDE_LEFT);
+ assertThat(mCachedDevice1.getDeviceMode()).isEqualTo(
+ HearingAidInfo.DeviceMode.MODE_BINAURAL);
+ }
+
+ /**
+ * Test updateHearingAidsDevices
+ *
+ * Conditions:
+ * 1) LeAudio hearing aid
+ * 2) Invalid audio location and device type
+ * Result:
+ * Do not set hearing aid info to the device.
+ */
+ @Test
+ public void updateHearingAidsDevices_leAudio_invalidInfo_notToSetHearingAidInfo() {
+ when(mCachedDevice1.getProfiles()).thenReturn(List.of(mLeAudioProfile, mHapClientProfile));
+ when(mLeAudioProfile.getAudioLocation(mDevice1)).thenReturn(AUDIO_LOCATION_INVALID);
+ when(mHapClientProfile.getHearingAidType(mDevice1)).thenReturn(TYPE_INVALID);
+ mCachedDeviceManager.mCachedDevices.add(mCachedDevice1);
+
+ mHearingAidDeviceManager.updateHearingAidsDevices();
+
+ verify(mCachedDevice1, never()).setHearingAidInfo(any(HearingAidInfo.class));
+ }
+
+ /**
* Test onProfileConnectionStateChangedIfProcessed.
* When first hearing aid device is connected, to process it same as other generic devices.
* No need to process it.
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index 89a8dd95d3c3..17d9f1b87fac 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -335,4 +335,7 @@
<!-- Default for Settings.BATTERY_CHARGING_STATE_ENFORCE_LEVEL.
-1 means system internal default value is used. -->
<integer name="def_battery_charging_state_enforce_level">-1</integer>
+
+ <!-- Value to use as default scale for fonts -->
+ <item name="def_device_font_scale" format="float" type="dimen">1.0</item>
</resources>
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 8ae50eb7ffad..8ad5f244b659 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -74,6 +74,7 @@ public class SecureSettings {
Settings.Secure.TTS_DEFAULT_LOCALE,
Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD,
Settings.Secure.ACCESSIBILITY_BOUNCE_KEYS,
+ Settings.Secure.ACCESSIBILITY_SLOW_KEYS,
Settings.Secure.ACCESSIBILITY_STICKY_KEYS,
Settings.Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, // moved to global
Settings.Secure.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY, // moved to global
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
index e7d7bb01e180..38ec931612f0 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
@@ -48,6 +48,7 @@ public class SystemSettings {
Settings.System.WIFI_STATIC_DNS2,
Settings.System.BLUETOOTH_DISCOVERABILITY,
Settings.System.BLUETOOTH_DISCOVERABILITY_TIMEOUT,
+ Settings.System.DEFAULT_DEVICE_FONT_SCALE,
Settings.System.FONT_SCALE,
Settings.System.DIM_SCREEN,
Settings.System.SCREEN_OFF_TIMEOUT,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 285c8c969343..d854df38a9ef 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -120,6 +120,7 @@ public class SecureSettingsValidators {
VALIDATORS.put(Secure.TTS_DEFAULT_LOCALE, TTS_LIST_VALIDATOR);
VALIDATORS.put(Secure.SHOW_IME_WITH_HARD_KEYBOARD, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ACCESSIBILITY_BOUNCE_KEYS, ANY_INTEGER_VALIDATOR);
+ VALIDATORS.put(Secure.ACCESSIBILITY_SLOW_KEYS, ANY_INTEGER_VALIDATOR);
VALIDATORS.put(Secure.ACCESSIBILITY_STICKY_KEYS, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY, NON_NEGATIVE_INTEGER_VALIDATOR);
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SettingsValidators.java
index a8a659ee1e5c..677c81ad9271 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SettingsValidators.java
@@ -45,6 +45,9 @@ public class SettingsValidators {
}
};
+ public static final Validator FONT_SCALE_VALIDATOR = new InclusiveFloatRangeValidator(0.25f,
+ 5.0f);
+
public static final Validator NON_NEGATIVE_INTEGER_VALIDATOR = new Validator() {
@Override
public boolean validate(@Nullable String value) {
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
index 572303a813bf..98941c7cc116 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
@@ -20,6 +20,7 @@ import static android.provider.settings.validators.SettingsValidators.ANY_INTEGE
import static android.provider.settings.validators.SettingsValidators.ANY_STRING_VALIDATOR;
import static android.provider.settings.validators.SettingsValidators.BOOLEAN_VALIDATOR;
import static android.provider.settings.validators.SettingsValidators.COMPONENT_NAME_VALIDATOR;
+import static android.provider.settings.validators.SettingsValidators.FONT_SCALE_VALIDATOR;
import static android.provider.settings.validators.SettingsValidators.LENIENT_IP_ADDRESS_VALIDATOR;
import static android.provider.settings.validators.SettingsValidators.NON_NEGATIVE_FLOAT_VALIDATOR;
import static android.provider.settings.validators.SettingsValidators.NON_NEGATIVE_INTEGER_VALIDATOR;
@@ -31,7 +32,6 @@ import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.hardware.display.ColorDisplayManager;
import android.os.BatteryManager;
-import android.provider.Settings.Global;
import android.provider.Settings.System;
import android.util.ArrayMap;
@@ -93,7 +93,8 @@ public class SystemSettingsValidators {
return value == null || value.length() < MAX_LENGTH;
}
});
- VALIDATORS.put(System.FONT_SCALE, new InclusiveFloatRangeValidator(0.25f, 5.0f));
+ VALIDATORS.put(System.DEFAULT_DEVICE_FONT_SCALE, FONT_SCALE_VALIDATOR);
+ VALIDATORS.put(System.FONT_SCALE, FONT_SCALE_VALIDATOR);
VALIDATORS.put(System.DIM_SCREEN, BOOLEAN_VALIDATOR);
VALIDATORS.put(
System.DISPLAY_COLOR_MODE,
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 3a46f4e96ccb..febce97031bb 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -3812,7 +3812,7 @@ public class SettingsProvider extends ContentProvider {
}
private final class UpgradeController {
- private static final int SETTINGS_VERSION = 224;
+ private static final int SETTINGS_VERSION = 225;
private final int mUserId;
@@ -6004,6 +6004,13 @@ public class SettingsProvider extends ContentProvider {
currentVersion = 224;
}
+ // Version 224: Update the default font scale depending on the
+ // R.dimen.def_device_font_scale configuration property.
+ if (currentVersion == 224) {
+ handleDefaultFontScale(getSystemSettingsLocked(userId));
+ currentVersion = 225;
+ }
+
// vXXX: Add new settings above this point.
if (currentVersion != newVersion) {
@@ -6021,6 +6028,32 @@ public class SettingsProvider extends ContentProvider {
return currentVersion;
}
+ @SuppressWarnings("GuardedBy")
+ @GuardedBy("mLock")
+ private void handleDefaultFontScale(@NonNull SettingsState systemSettings) {
+ final float defaultFontScale = getContext().getResources()
+ .getFloat(R.dimen.def_device_font_scale);
+ // Persist the value for future use (e.g. Reset Settings option)
+ systemSettings.insertSettingLocked(
+ Settings.System.DEFAULT_DEVICE_FONT_SCALE,
+ String.valueOf(defaultFontScale),
+ /* tag= */ null,
+ /* makeDefault= */ false,
+ SettingsState.SYSTEM_PACKAGE_NAME);
+ // We verify if there is a pre existing value for font_scale.
+ final Setting existingFontScale = systemSettings.getSettingLocked(
+ Settings.System.FONT_SCALE);
+ if (existingFontScale == null || existingFontScale.isNull()) {
+ // Set the default value only if it didn't exist before
+ systemSettings.insertSettingLocked(
+ Settings.System.FONT_SCALE,
+ String.valueOf(defaultFontScale),
+ /* tag= */ null,
+ /* makeDefault= */ false,
+ SettingsState.SYSTEM_PACKAGE_NAME);
+ }
+ }
+
@GuardedBy("mLock")
private void initGlobalSettingsDefaultValLocked(String key, boolean val) {
initGlobalSettingsDefaultValLocked(key, val ? "1" : "0");
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index d38454221f76..cdb4aea6ee79 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -561,7 +561,7 @@
<uses-permission android:name="android.permission.TEST_BIOMETRIC" />
<!-- Permission required for CTS test - android.server.biometrics -->
- <uses-permission android:name="android.permission.MANAGE_BIOMETRIC_DIALOG" />
+ <uses-permission android:name="android.permission.SET_BIOMETRIC_DIALOG_LOGO" />
<!-- Permission required for CTS test - android.server.biometrics -->
<uses-permission android:name="android.permission.USE_BACKGROUND_FACE_AUTHENTICATION" />
@@ -902,6 +902,9 @@
<!-- Permission required for BinaryTransparencyService shell API and host test -->
<uses-permission android:name="android.permission.GET_BACKGROUND_INSTALLED_PACKAGES" />
+ <!-- Permissions required for CTS test - CtsPermissionUiTestCases -->
+ <uses-permission android:name="android.permission.MANAGE_ENHANCED_CONFIRMATION_STATES" />
+
<application
android:label="@string/app_label"
android:theme="@android:style/Theme.DeviceDefault.DayNight"
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 80656e9253db..90593d6b1937 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -330,6 +330,7 @@ android_library {
"androidx.core_core-animation-testing-nodeps",
"androidx.compose.ui_ui",
"flag-junit",
+ "ravenwood-junit",
"platform-test-annotations",
"notification_flags_lib",
],
@@ -386,7 +387,8 @@ android_library {
android_app {
name: "SystemUIRobo-stub",
- use_resource_processor: true,
+ // SystemUiRavenTests references the .aapt.srcjar
+ use_resource_processor: false,
defaults: [
"platform_app_defaults",
"SystemUI_optimized_defaults",
@@ -458,6 +460,34 @@ android_robolectric_test {
],
}
+android_ravenwood_test {
+ name: "SystemUiRavenTests",
+ srcs: [
+ ":SystemUI-tests-utils",
+ ":SystemUI-tests-multivalent",
+ // TODO(b/294256649): pivot to using {.aapt.jar} and re-enable
+ // use_resource_processor: true when better supported by soong
+ ":SystemUIRobo-stub{.aapt.srcjar}",
+ ],
+ static_libs: [
+ "SystemUI-core",
+ "SystemUI-res",
+ "SystemUI-tests-base",
+ "androidx.test.uiautomator_uiautomator",
+ "androidx.core_core-animation-testing",
+ "androidx.test.ext.junit",
+ ],
+ libs: [
+ "android.test.runner",
+ "android.test.base",
+ "android.test.mock",
+ ],
+ auto_gen_config: true,
+ plugins: [
+ "dagger2-compiler",
+ ],
+}
+
// Opt-out config for optimizing the SystemUI target using R8.
// Disabled via `export SYSTEMUI_OPTIMIZE_JAVA=false`, or explicitly in Make via
// `SYSTEMUI_OPTIMIZE_JAVA := false`.
diff --git a/packages/SystemUI/aconfig/accessibility.aconfig b/packages/SystemUI/aconfig/accessibility.aconfig
index 7ba889bc8fee..866aa8945525 100644
--- a/packages/SystemUI/aconfig/accessibility.aconfig
+++ b/packages/SystemUI/aconfig/accessibility.aconfig
@@ -17,6 +17,13 @@ flag {
}
flag {
+ name: "floating_menu_drag_to_edit"
+ namespace: "accessibility"
+ description: "adds a second drag button to allow the user edit the shortcut."
+ bug: "297583708"
+}
+
+flag {
name: "floating_menu_ime_displacement_animation"
namespace: "accessibility"
description: "Adds an animation for when the FAB is displaced by an IME becoming visible."
diff --git a/packages/SystemUI/aconfig/biometrics_framework.aconfig b/packages/SystemUI/aconfig/biometrics_framework.aconfig
index 5fd3b485e9ed..7cc0c83abfea 100644
--- a/packages/SystemUI/aconfig/biometrics_framework.aconfig
+++ b/packages/SystemUI/aconfig/biometrics_framework.aconfig
@@ -7,4 +7,11 @@ flag {
namespace: "biometrics_framework"
description: "Adds talkback directional guidance when using UDFPS with biometric prompt"
bug: "310044658"
+}
+
+flag {
+ name: "constraint_bp"
+ namespace: "biometrics_framework"
+ description: "Refactors Biometric Prompt to use a ConstraintLayout"
+ bug: "288175072"
} \ No newline at end of file
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 7eca04a5f85b..a2530d59e2e6 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -366,8 +366,8 @@ flag {
}
flag {
- name: "enable_notif_linearlayout_optimized"
- namespace: "systemui"
- description: "Enables notification specific LinearLayout optimization"
- bug: "316110233"
+ name: "keyguard_wm_state_refactor"
+ namespace: "systemui"
+ description: "Enables refactored logic for SysUI+WM unlock/occlusion code paths"
+ bug: "278086361"
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index a22fecf3688d..ff5a69801c56 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
@@ -64,14 +64,6 @@ fun CommunalContainer(
transitions = sceneTransitions,
)
- // Don't show hub mode UI if communal is not available. Communal is only available if it has
- // been enabled via settings and either keyguard is showing, or, the device is currently
- // dreaming.
- val isCommunalAvailable by viewModel.isCommunalAvailable.collectAsState()
- if (!isCommunalAvailable) {
- return
- }
-
// This effect exposes the SceneTransitionLayout's observable transition state to the rest of
// the system, and unsets it when the view is disposed to avoid a memory leak.
DisposableEffect(viewModel, sceneTransitionLayoutState) {
@@ -84,13 +76,14 @@ fun CommunalContainer(
SceneTransitionLayout(
state = sceneTransitionLayoutState,
modifier = modifier.fillMaxSize(),
- edgeDetector = FixedSizeEdgeDetector(ContainerDimensions.EdgeSwipeSize),
+ swipeSourceDetector = FixedSizeEdgeDetector(ContainerDimensions.EdgeSwipeSize),
) {
scene(
TransitionSceneKey.Blank,
userActions =
mapOf(
- Swipe(SwipeDirection.Left, fromEdge = Edge.Right) to TransitionSceneKey.Communal
+ Swipe(SwipeDirection.Left, fromSource = Edge.Right) to
+ TransitionSceneKey.Communal
)
) {
// This scene shows nothing only allowing for transitions to the communal scene.
@@ -101,7 +94,7 @@ fun CommunalContainer(
TransitionSceneKey.Communal,
userActions =
mapOf(
- Swipe(SwipeDirection.Right, fromEdge = Edge.Left) to TransitionSceneKey.Blank
+ Swipe(SwipeDirection.Right, fromSource = Edge.Left) to TransitionSceneKey.Blank
),
) {
CommunalScene(viewModel, modifier = modifier)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 761e74e52237..576596fa67fe 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
@@ -48,7 +48,6 @@ import androidx.compose.foundation.lazy.grid.rememberLazyGridState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
-import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.outlined.Delete
import androidx.compose.material.icons.outlined.Edit
import androidx.compose.material.icons.outlined.TouchApp
@@ -60,7 +59,6 @@ import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.FilledIconButton
import androidx.compose.material3.Icon
-import androidx.compose.material3.IconButton
import androidx.compose.material3.IconButtonColors
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
@@ -130,12 +128,11 @@ fun CommunalHub(
val gridState = rememberLazyGridState()
val contentListState = rememberContentListState(widgetConfigurator, communalContent, viewModel)
val reorderingWidgets by viewModel.reorderingWidgets.collectAsState()
- val selectedIndex = viewModel.selectedIndex.collectAsState()
+ val selectedKey = viewModel.selectedKey.collectAsState()
val removeButtonEnabled by remember {
- derivedStateOf { selectedIndex.value != null || reorderingWidgets }
+ derivedStateOf { selectedKey.value != null || reorderingWidgets }
}
- val (isButtonToEditWidgetsShowing, setIsButtonToEditWidgetsShowing) =
- remember { mutableStateOf(false) }
+ var isButtonToEditWidgetsShowing by remember { mutableStateOf(false) }
val contentPadding = gridContentPadding(viewModel.isEditMode, toolbarSize)
val contentOffset = beforeContentPadding(contentPadding).toOffset()
@@ -150,22 +147,30 @@ fun CommunalHub(
if (!viewModel.isEditMode) return@pointerInput
observeTapsWithoutConsuming { offset ->
val adjustedOffset = offset - contentOffset
- val index =
- gridState.layoutInfo.visibleItemsInfo
- .firstItemAtOffset(adjustedOffset)
- ?.index
- val newIndex =
- if (index?.let(contentListState::isItemEditable) == true) {
- index
- } else {
- null
- }
- viewModel.setSelectedIndex(newIndex)
+ val index = firstIndexAtOffset(gridState, adjustedOffset)
+ val key = index?.let { keyAtIndexIfEditable(contentListState.list, index) }
+ viewModel.setSelectedKey(key)
}
}
.thenIf(!viewModel.isEditMode) {
- Modifier.pointerInput(Unit) {
- detectLongPressGesture { offset -> setIsButtonToEditWidgetsShowing(true) }
+ Modifier.pointerInput(
+ gridState,
+ contentOffset,
+ communalContent,
+ gridCoordinates
+ ) {
+ detectLongPressGesture { offset ->
+ isButtonToEditWidgetsShowing = true
+
+ // Deduct both grid offset relative to its container and content offset.
+ val adjustedOffset =
+ gridCoordinates?.let {
+ offset - it.positionInWindow() - contentOffset
+ }
+ val index = adjustedOffset?.let { firstIndexAtOffset(gridState, it) }
+ val key = index?.let { keyAtIndexIfEditable(communalContent, index) }
+ viewModel.setSelectedKey(key)
+ }
}
},
) {
@@ -186,7 +191,7 @@ fun CommunalHub(
onOpenWidgetPicker = onOpenWidgetPicker,
gridState = gridState,
contentListState = contentListState,
- selectedIndex = selectedIndex,
+ selectedKey = selectedKey,
widgetConfigurator = widgetConfigurator,
)
@@ -198,18 +203,18 @@ fun CommunalHub(
onEditDone = onEditDone,
onOpenWidgetPicker = onOpenWidgetPicker,
onRemoveClicked = {
- selectedIndex.value?.let { index ->
- contentListState.onRemove(index)
+ val index =
+ selectedKey.value?.let { key ->
+ contentListState.list.indexOfFirst { it.key == key }
+ }
+ index?.let {
+ contentListState.onRemove(it)
contentListState.onSaveList()
- viewModel.setSelectedIndex(null)
+ viewModel.setSelectedKey(null)
}
},
removeEnabled = removeButtonEnabled
)
- } else {
- IconButton(onClick = viewModel::onOpenWidgetEditor) {
- Icon(Icons.Default.Edit, stringResource(R.string.button_to_open_widget_editor))
- }
}
if (isPopupOnDismissCtaShowing) {
@@ -219,10 +224,10 @@ fun CommunalHub(
if (isButtonToEditWidgetsShowing) {
ButtonToEditWidgets(
onClick = {
- setIsButtonToEditWidgetsShowing(false)
- viewModel.onOpenWidgetEditor()
+ isButtonToEditWidgetsShowing = false
+ viewModel.onOpenWidgetEditor(selectedKey.value)
},
- onHide = { setIsButtonToEditWidgetsShowing(false) },
+ onHide = { isButtonToEditWidgetsShowing = false },
)
}
@@ -244,7 +249,7 @@ private fun BoxScope.CommunalHubLazyGrid(
communalContent: List<CommunalContentModel>,
viewModel: BaseCommunalViewModel,
contentPadding: PaddingValues,
- selectedIndex: State<Int?>,
+ selectedKey: State<String?>,
contentOffset: Offset,
gridState: LazyGridState,
contentListState: ContentListState,
@@ -253,7 +258,8 @@ private fun BoxScope.CommunalHubLazyGrid(
onOpenWidgetPicker: (() -> Unit)? = null,
widgetConfigurator: WidgetConfigurator?,
) {
- var gridModifier = Modifier.align(Alignment.CenterStart)
+ var gridModifier =
+ Modifier.align(Alignment.CenterStart).onGloballyPositioned { setGridCoordinates(it) }
var list = communalContent
var dragDropState: GridDragDropState? = null
if (viewModel.isEditMode && viewModel is CommunalEditModeViewModel) {
@@ -266,10 +272,7 @@ private fun BoxScope.CommunalHubLazyGrid(
updateDragPositionForRemove = updateDragPositionForRemove
)
gridModifier =
- gridModifier
- .fillMaxSize()
- .dragContainer(dragDropState, contentOffset, viewModel)
- .onGloballyPositioned { setGridCoordinates(it) }
+ gridModifier.fillMaxSize().dragContainer(dragDropState, contentOffset, viewModel)
// for widgets dropped from other activities
val dragAndDropTargetState =
rememberDragAndDropTargetState(
@@ -307,7 +310,8 @@ private fun BoxScope.CommunalHubLazyGrid(
list[index].size.dp().value,
)
if (viewModel.isEditMode && dragDropState != null) {
- val selected by remember(index) { derivedStateOf { index == selectedIndex.value } }
+ val selected by
+ remember(index) { derivedStateOf { list[index].key == selectedKey.value } }
DraggableItem(
dragDropState = dragDropState,
selected = selected,
@@ -378,7 +382,7 @@ private fun Toolbar(
colors = filledButtonColors(),
contentPadding = Dimensions.ButtonPadding
) {
- Icon(Icons.Default.Add, stringResource(R.string.button_to_open_widget_editor))
+ Icon(Icons.Default.Add, stringResource(R.string.hub_mode_add_widget_button_text))
Spacer(spacerModifier)
Text(
text = stringResource(R.string.hub_mode_add_widget_button_text),
@@ -832,6 +836,13 @@ private fun CommunalContentSize.dp(): Dp {
}
}
+private fun firstIndexAtOffset(gridState: LazyGridState, offset: Offset): Int? =
+ gridState.layoutInfo.visibleItemsInfo.firstItemAtOffset(offset)?.index
+
+/** Returns the key of item if it's editable at the given index. Only widget is editable. */
+private fun keyAtIndexIfEditable(list: List<CommunalContentModel>, index: Int): String? =
+ if (index in list.indices && list[index].isWidget()) list[index].key else null
+
data class ContentPaddingInPx(val start: Float, val top: Float) {
fun toOffset(): Offset = Offset(start, top)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt
index 2a6bea75791c..be6f022d8d52 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt
@@ -33,6 +33,7 @@ import com.android.keyguard.LockIconView
import com.android.keyguard.LockIconViewController
import com.android.systemui.Flags.keyguardBottomAreaRefactor
import com.android.systemui.biometrics.AuthController
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
@@ -47,10 +48,12 @@ import com.android.systemui.res.R
import com.android.systemui.statusbar.VibratorHelper
import dagger.Lazy
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
class LockSection
@Inject
constructor(
+ @Application private val applicationScope: CoroutineScope,
private val windowManager: WindowManager,
private val authController: AuthController,
private val featureFlags: FeatureFlagsClassic,
@@ -76,6 +79,7 @@ constructor(
DeviceEntryIconView(context, null).apply {
id = R.id.device_entry_icon_view
DeviceEntryIconViewBinder.bind(
+ applicationScope,
this,
deviceEntryIconViewModel.get(),
deviceEntryForegroundViewModel.get(),
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
index c35202cd830a..9f9e1f5bb56a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
@@ -183,7 +183,7 @@ private fun UserAction.toTransitionUserAction(): SceneTransitionUserAction {
is UserAction.Swipe ->
Swipe(
pointerCount = pointerCount,
- fromEdge =
+ fromSource =
when (this.fromEdge) {
null -> null
Edge.LEFT -> SceneTransitionEdge.Left
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/EdgeDetector.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/EdgeDetector.kt
index 82d4239d7eb5..b0dc3a144533 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/EdgeDetector.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/EdgeDetector.kt
@@ -23,24 +23,19 @@ import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
-interface EdgeDetector {
- /**
- * Return the [Edge] associated to [position] inside a layout of size [layoutSize], given
- * [density] and [orientation].
- */
- fun edge(
- layoutSize: IntSize,
- position: IntOffset,
- density: Density,
- orientation: Orientation,
- ): Edge?
+/** The edge of a [SceneTransitionLayout]. */
+enum class Edge : SwipeSource {
+ Left,
+ Right,
+ Top,
+ Bottom,
}
val DefaultEdgeDetector = FixedSizeEdgeDetector(40.dp)
-/** An [EdgeDetector] that detects edges assuming a fixed edge size of [size]. */
-class FixedSizeEdgeDetector(val size: Dp) : EdgeDetector {
- override fun edge(
+/** An [SwipeSourceDetector] that detects edges assuming a fixed edge size of [size]. */
+class FixedSizeEdgeDetector(val size: Dp) : SwipeSourceDetector {
+ override fun source(
layoutSize: IntSize,
position: IntOffset,
density: Density,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
index 90f46bd4dcaa..9d4b69c51690 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
@@ -44,7 +44,7 @@ sealed class Key(val debugName: String, val identity: Any) {
class SceneKey(
name: String,
identity: Any = Object(),
-) : Key(name, identity) {
+) : Key(name, identity), UserActionResult {
@VisibleForTesting
// TODO(b/240432457): Make internal once PlatformComposeSceneTransitionLayoutTestsUtils can
// access internal members.
@@ -53,6 +53,10 @@ class SceneKey(
/** The unique [ElementKey] identifying this scene's root element. */
val rootElementKey = ElementKey(name, identity)
+ // Implementation of [UserActionResult].
+ override val toScene: SceneKey = this
+ override val distance: UserActionDistance? = null
+
override fun toString(): String {
return "SceneKey(debugName=$debugName)"
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
index 38738782c889..8552aaf0ebff 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
@@ -64,8 +64,8 @@ import androidx.compose.ui.util.fastForEach
@Stable
internal fun Modifier.multiPointerDraggable(
orientation: Orientation,
- enabled: Boolean,
- startDragImmediately: Boolean,
+ enabled: () -> Boolean,
+ startDragImmediately: () -> Boolean,
onDragStarted: (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> Unit,
onDragDelta: (delta: Float) -> Unit,
onDragStopped: (velocity: Float) -> Unit,
@@ -83,8 +83,8 @@ internal fun Modifier.multiPointerDraggable(
private data class MultiPointerDraggableElement(
private val orientation: Orientation,
- private val enabled: Boolean,
- private val startDragImmediately: Boolean,
+ private val enabled: () -> Boolean,
+ private val startDragImmediately: () -> Boolean,
private val onDragStarted:
(startedPosition: Offset, overSlop: Float, pointersDown: Int) -> Unit,
private val onDragDelta: (Float) -> Unit,
@@ -110,10 +110,10 @@ private data class MultiPointerDraggableElement(
}
}
-private class MultiPointerDraggableNode(
+internal class MultiPointerDraggableNode(
orientation: Orientation,
- enabled: Boolean,
- var startDragImmediately: Boolean,
+ enabled: () -> Boolean,
+ var startDragImmediately: () -> Boolean,
var onDragStarted: (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> Unit,
var onDragDelta: (Float) -> Unit,
var onDragStopped: (velocity: Float) -> Unit,
@@ -122,7 +122,7 @@ private class MultiPointerDraggableNode(
private val delegate = delegate(SuspendingPointerInputModifierNode(pointerInputHandler))
private val velocityTracker = VelocityTracker()
- var enabled: Boolean = enabled
+ var enabled: () -> Boolean = enabled
set(value) {
// Reset the pointer input whenever enabled changed.
if (value != field) {
@@ -133,7 +133,7 @@ private class MultiPointerDraggableNode(
var orientation: Orientation = orientation
set(value) {
- // Reset the pointer input whenever enabled orientation.
+ // Reset the pointer input whenever orientation changed.
if (value != field) {
field = value
delegate.resetPointerInputHandler()
@@ -149,7 +149,7 @@ private class MultiPointerDraggableNode(
) = delegate.onPointerEvent(pointerEvent, pass, bounds)
private suspend fun PointerInputScope.pointerInput() {
- if (!enabled) {
+ if (!enabled()) {
return
}
@@ -163,8 +163,7 @@ private class MultiPointerDraggableNode(
val onDragEnd: () -> Unit = {
val maxFlingVelocity =
currentValueOf(LocalViewConfiguration).maximumFlingVelocity.let { max ->
- val maxF = max.toFloat()
- Velocity(maxF, maxF)
+ Velocity(max, max)
}
val velocity = velocityTracker.calculateVelocity(maxFlingVelocity)
@@ -183,7 +182,7 @@ private class MultiPointerDraggableNode(
detectDragGestures(
orientation = orientation,
- startDragImmediately = { startDragImmediately },
+ startDragImmediately = startDragImmediately,
onDragStart = onDragStart,
onDragEnd = onDragEnd,
onDragCancel = onDragCancel,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
index f67df54b088c..af51cee2a255 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
@@ -38,7 +38,7 @@ internal class Scene(
val key: SceneKey,
layoutImpl: SceneTransitionLayoutImpl,
content: @Composable SceneScope.() -> Unit,
- actions: Map<UserAction, SceneKey>,
+ actions: Map<UserAction, UserActionResult>,
zIndex: Float,
) {
internal val scope = SceneScopeImpl(layoutImpl, this)
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
index ff054786cf52..aed04f688628 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
@@ -28,6 +28,8 @@ import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.round
import com.android.compose.nestedscroll.PriorityNestedScrollConnection
@@ -75,23 +77,25 @@ internal class SceneGestureHandler(
internal var currentSource: Any? = null
- /** The [UserAction]s associated to the current swipe. */
- private var actionUpOrLeft: UserAction? = null
- private var actionDownOrRight: UserAction? = null
- private var actionUpOrLeftNoEdge: UserAction? = null
- private var actionDownOrRightNoEdge: UserAction? = null
- private var upOrLeftScene: SceneKey? = null
- private var downOrRightScene: SceneKey? = null
+ /** The [Swipes] associated to the current gesture. */
+ private var swipes: Swipes? = null
+
+ /** The [UserActionResult] associated to up and down swipes. */
+ private var upOrLeftResult: UserActionResult? = null
+ private var downOrRightResult: UserActionResult? = null
internal fun onDragStarted(pointersDown: Int, startedPosition: Offset?, overSlop: Float) {
if (isDrivingTransition) {
// This [transition] was already driving the animation: simply take over it.
// Stop animating and start from where the current offset.
swipeTransition.cancelOffsetAnimation()
- updateTargetScenes(swipeTransition._fromScene)
+ updateSwipesResults(swipeTransition._fromScene)
return
}
+ check(overSlop != 0f) {
+ "onDragStarted() called while isDrivingTransition=false overSlop=0f"
+ }
val transitionState = layoutState.transitionState
if (transitionState is TransitionState.Transition) {
// TODO(b/290184746): Better handle interruptions here if state != idle.
@@ -104,18 +108,25 @@ internal class SceneGestureHandler(
}
val fromScene = layoutImpl.scene(transitionState.currentScene)
- setCurrentActions(fromScene, startedPosition, pointersDown)
+ updateSwipes(fromScene, startedPosition, pointersDown)
val (targetScene, distance) =
- findTargetSceneAndDistance(fromScene, overSlop, updateScenes = true) ?: return
-
+ findTargetSceneAndDistance(fromScene, overSlop, updateSwipesResults = true) ?: return
updateTransition(SwipeTransition(fromScene, targetScene, distance), force = true)
}
- private fun setCurrentActions(fromScene: Scene, startedPosition: Offset?, pointersDown: Int) {
- val fromEdge =
+ private fun updateSwipes(fromScene: Scene, startedPosition: Offset?, pointersDown: Int) {
+ this.swipes = computeSwipes(fromScene, startedPosition, pointersDown)
+ }
+
+ private fun computeSwipes(
+ fromScene: Scene,
+ startedPosition: Offset?,
+ pointersDown: Int
+ ): Swipes {
+ val fromSource =
startedPosition?.let { position ->
- layoutImpl.edgeDetector.edge(
+ layoutImpl.swipeSourceDetector.source(
fromScene.targetSize,
position.round(),
layoutImpl.density,
@@ -131,7 +142,7 @@ internal class SceneGestureHandler(
Orientation.Vertical -> SwipeDirection.Up
},
pointerCount = pointersDown,
- fromEdge = fromEdge,
+ fromSource = fromSource,
)
val downOrRight =
@@ -142,33 +153,31 @@ internal class SceneGestureHandler(
Orientation.Vertical -> SwipeDirection.Down
},
pointerCount = pointersDown,
- fromEdge = fromEdge,
+ fromSource = fromSource,
)
- if (fromEdge == null) {
- actionUpOrLeft = null
- actionDownOrRight = null
- actionUpOrLeftNoEdge = upOrLeft
- actionDownOrRightNoEdge = downOrRight
+ return if (fromSource == null) {
+ Swipes(
+ upOrLeft = null,
+ downOrRight = null,
+ upOrLeftNoSource = upOrLeft,
+ downOrRightNoSource = downOrRight,
+ )
} else {
- actionUpOrLeft = upOrLeft
- actionDownOrRight = downOrRight
- actionUpOrLeftNoEdge = upOrLeft.copy(fromEdge = null)
- actionDownOrRightNoEdge = downOrRight.copy(fromEdge = null)
+ Swipes(
+ upOrLeft = upOrLeft,
+ downOrRight = downOrRight,
+ upOrLeftNoSource = upOrLeft.copy(fromSource = null),
+ downOrRightNoSource = downOrRight.copy(fromSource = null),
+ )
}
}
- /**
- * Use the layout size in the swipe orientation for swipe distance.
- *
- * TODO(b/290184746): Also handle custom distances for transitions. With smaller distances, we
- * will also have to make sure that we correctly handle overscroll.
- */
- private fun Scene.getAbsoluteDistance(): Float {
- return when (orientation) {
- Orientation.Horizontal -> targetSize.width
- Orientation.Vertical -> targetSize.height
- }.toFloat()
+ private fun Scene.getAbsoluteDistance(distance: UserActionDistance?): Float {
+ val targetSize = this.targetSize
+ return with(distance ?: DefaultSwipeDistance) {
+ layoutImpl.density.absoluteDistance(targetSize, orientation)
+ }
}
internal fun onDrag(delta: Float) {
@@ -183,7 +192,7 @@ internal class SceneGestureHandler(
findTargetSceneAndDistance(
fromScene,
swipeTransition.dragOffset,
- updateScenes = isNewFromScene,
+ updateSwipesResults = isNewFromScene,
)
?: run {
onDragStopped(delta, true)
@@ -200,9 +209,31 @@ internal class SceneGestureHandler(
}
}
- private fun updateTargetScenes(fromScene: Scene) {
- upOrLeftScene = fromScene.upOrLeft()
- downOrRightScene = fromScene.downOrRight()
+ private fun updateSwipesResults(fromScene: Scene) {
+ val (upOrLeftResult, downOrRightResult) =
+ swipesResults(
+ fromScene,
+ this.swipes ?: error("updateSwipes() should be called before updateSwipesResults()")
+ )
+
+ this.upOrLeftResult = upOrLeftResult
+ this.downOrRightResult = downOrRightResult
+ }
+
+ private fun swipesResults(
+ fromScene: Scene,
+ swipes: Swipes
+ ): Pair<UserActionResult?, UserActionResult?> {
+ val userActions = fromScene.userActions
+ fun sceneToSwipePair(swipe: Swipe?): UserActionResult? {
+ return userActions[swipe ?: return null]
+ }
+
+ val upOrLeftResult =
+ sceneToSwipePair(swipes.upOrLeft) ?: sceneToSwipePair(swipes.upOrLeftNoSource)
+ val downOrRightResult =
+ sceneToSwipePair(swipes.downOrRight) ?: sceneToSwipePair(swipes.downOrRightNoSource)
+ return Pair(upOrLeftResult, downOrRightResult)
}
/**
@@ -229,9 +260,9 @@ internal class SceneGestureHandler(
// If the offset is past the distance then let's change fromScene so that the user can swipe
// to the next screen or go back to the previous one.
val offset = swipeTransition.dragOffset
- return if (offset <= -absoluteDistance && upOrLeftScene == toScene.key) {
+ return if (offset <= -absoluteDistance && upOrLeftResult?.toScene == toScene.key) {
Pair(toScene, absoluteDistance)
- } else if (offset >= absoluteDistance && downOrRightScene == toScene.key) {
+ } else if (offset >= absoluteDistance && downOrRightResult?.toScene == toScene.key) {
Pair(toScene, -absoluteDistance)
} else {
Pair(fromScene, 0f)
@@ -244,31 +275,41 @@ internal class SceneGestureHandler(
* @param fromScene the scene from which we look for the target
* @param directionOffset signed float that indicates the direction. Positive is down or right
* negative is up or left.
- * @param updateScenes whether the target scenes should be updated to the current values held in
- * the Scenes map. Usually we don't want to update them while doing a drag, because this could
- * change the target scene (jump cutting) to a different scene, when some system state changed
- * the targets the background. However, an update is needed any time we calculate the targets
- * for a new fromScene.
+ * @param updateSwipesResults whether the target scenes should be updated to the current values
+ * held in the Scenes map. Usually we don't want to update them while doing a drag, because
+ * this could change the target scene (jump cutting) to a different scene, when some system
+ * state changed the targets the background. However, an update is needed any time we
+ * calculate the targets for a new fromScene.
* @return null when there are no targets in either direction. If one direction is null and you
* drag into the null direction this function will return the opposite direction, assuming
* that the users intention is to start the drag into the other direction eventually. If
* [directionOffset] is 0f and both direction are available, it will default to
- * [upOrLeftScene].
+ * [upOrLeftResult].
*/
private inline fun findTargetSceneAndDistance(
fromScene: Scene,
directionOffset: Float,
- updateScenes: Boolean,
+ updateSwipesResults: Boolean,
): Pair<Scene, Float>? {
- if (updateScenes) updateTargetScenes(fromScene)
- val absoluteDistance = fromScene.getAbsoluteDistance()
+ if (updateSwipesResults) updateSwipesResults(fromScene)
// Compute the target scene depending on the current offset.
return when {
- upOrLeftScene == null && downOrRightScene == null -> null
- (directionOffset < 0f && upOrLeftScene != null) || downOrRightScene == null ->
- Pair(layoutImpl.scene(upOrLeftScene!!), -absoluteDistance)
- else -> Pair(layoutImpl.scene(downOrRightScene!!), absoluteDistance)
+ upOrLeftResult == null && downOrRightResult == null -> null
+ (directionOffset < 0f && upOrLeftResult != null) || downOrRightResult == null ->
+ upOrLeftResult?.let { result ->
+ Pair(
+ layoutImpl.scene(result.toScene),
+ -fromScene.getAbsoluteDistance(result.distance)
+ )
+ }
+ else ->
+ downOrRightResult?.let { result ->
+ Pair(
+ layoutImpl.scene(result.toScene),
+ fromScene.getAbsoluteDistance(result.distance)
+ )
+ }
}
}
@@ -280,24 +321,25 @@ internal class SceneGestureHandler(
fromScene: Scene,
directionOffset: Float,
): Pair<Scene, Float>? {
- val absoluteDistance = fromScene.getAbsoluteDistance()
return when {
directionOffset > 0f ->
- upOrLeftScene?.let { Pair(layoutImpl.scene(it), -absoluteDistance) }
+ upOrLeftResult?.let { result ->
+ Pair(
+ layoutImpl.scene(result.toScene),
+ -fromScene.getAbsoluteDistance(result.distance),
+ )
+ }
directionOffset < 0f ->
- downOrRightScene?.let { Pair(layoutImpl.scene(it), absoluteDistance) }
+ downOrRightResult?.let { result ->
+ Pair(
+ layoutImpl.scene(result.toScene),
+ fromScene.getAbsoluteDistance(result.distance),
+ )
+ }
else -> null
}
}
- private fun Scene.upOrLeft(): SceneKey? {
- return userActions[actionUpOrLeft] ?: userActions[actionUpOrLeftNoEdge]
- }
-
- private fun Scene.downOrRight(): SceneKey? {
- return userActions[actionDownOrRight] ?: userActions[actionDownOrRightNoEdge]
- }
-
internal fun onDragStopped(velocity: Float, canChangeScene: Boolean) {
// The state was changed since the drag started; don't do anything.
if (!isDrivingTransition) {
@@ -515,6 +557,26 @@ internal class SceneGestureHandler(
companion object {
private const val TAG = "SceneGestureHandler"
}
+
+ private object DefaultSwipeDistance : UserActionDistance {
+ override fun Density.absoluteDistance(
+ fromSceneSize: IntSize,
+ orientation: Orientation,
+ ): Float {
+ return when (orientation) {
+ Orientation.Horizontal -> fromSceneSize.width
+ Orientation.Vertical -> fromSceneSize.height
+ }.toFloat()
+ }
+ }
+
+ /** The [Swipe] associated to a given fromScene, startedPosition and pointersDown. */
+ private class Swipes(
+ val upOrLeft: Swipe?,
+ val downOrRight: Swipe?,
+ val upOrLeftNoSource: Swipe?,
+ val downOrRightNoSource: Swipe?,
+ )
}
private class SceneDraggableHandler(
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
index 80f8c1c9e987..7e0aa9c3e2b1 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
@@ -27,6 +27,10 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.IntSize
/**
* [SceneTransitionLayout] is a container that automatically animates its content whenever its state
@@ -38,7 +42,8 @@ import androidx.compose.ui.platform.LocalDensity
* UI code.
*
* @param state the state of this layout.
- * @param edgeDetector the edge detector used to detect which edge a swipe is started from, if any.
+ * @param swipeSourceDetector the edge detector used to detect which edge a swipe is started from,
+ * if any.
* @param transitionInterceptionThreshold used during a scene transition. For the scene to be
* intercepted, the progress value must be above the threshold, and below (1 - threshold).
* @param scenes the configuration of the different scenes of this layout.
@@ -48,14 +53,14 @@ import androidx.compose.ui.platform.LocalDensity
fun SceneTransitionLayout(
state: SceneTransitionLayoutState,
modifier: Modifier = Modifier,
- edgeDetector: EdgeDetector = DefaultEdgeDetector,
+ swipeSourceDetector: SwipeSourceDetector = DefaultEdgeDetector,
@FloatRange(from = 0.0, to = 0.5) transitionInterceptionThreshold: Float = 0f,
scenes: SceneTransitionLayoutScope.() -> Unit,
) {
SceneTransitionLayoutForTesting(
state,
modifier,
- edgeDetector,
+ swipeSourceDetector,
transitionInterceptionThreshold,
onLayoutImpl = null,
scenes,
@@ -76,7 +81,8 @@ fun SceneTransitionLayout(
* This is called when the user commits a transition to a new scene because of a [UserAction], for
* instance by triggering back navigation or by swiping to a new scene.
* @param transitions the definition of the transitions used to animate a change of scene.
- * @param edgeDetector the edge detector used to detect which edge a swipe is started from, if any.
+ * @param swipeSourceDetector the source detector used to detect which source a swipe is started
+ * from, if any.
* @param transitionInterceptionThreshold used during a scene transition. For the scene to be
* intercepted, the progress value must be above the threshold, and below (1 - threshold).
* @param scenes the configuration of the different scenes of this layout.
@@ -87,7 +93,7 @@ fun SceneTransitionLayout(
onChangeScene: (SceneKey) -> Unit,
transitions: SceneTransitions,
modifier: Modifier = Modifier,
- edgeDetector: EdgeDetector = DefaultEdgeDetector,
+ swipeSourceDetector: SwipeSourceDetector = DefaultEdgeDetector,
@FloatRange(from = 0.0, to = 0.5) transitionInterceptionThreshold: Float = 0f,
scenes: SceneTransitionLayoutScope.() -> Unit,
) {
@@ -95,7 +101,7 @@ fun SceneTransitionLayout(
SceneTransitionLayout(
state,
modifier,
- edgeDetector,
+ swipeSourceDetector,
transitionInterceptionThreshold,
scenes,
)
@@ -113,7 +119,7 @@ interface SceneTransitionLayoutScope {
*/
fun scene(
key: SceneKey,
- userActions: Map<UserAction, SceneKey> = emptyMap(),
+ userActions: Map<UserAction, UserActionResult> = emptyMap(),
content: @Composable SceneScope.() -> Unit,
)
}
@@ -335,7 +341,7 @@ data object Back : UserAction
data class Swipe(
val direction: SwipeDirection,
val pointerCount: Int = 1,
- val fromEdge: Edge? = null,
+ val fromSource: SwipeSource? = null,
) : UserAction {
companion object {
val Left = Swipe(SwipeDirection.Left)
@@ -353,6 +359,95 @@ enum class SwipeDirection(val orientation: Orientation) {
}
/**
+ * The source of a Swipe.
+ *
+ * Important: This can be anything that can be returned by any [SwipeSourceDetector], but this must
+ * implement [equals] and [hashCode]. Note that those can be trivially implemented using data
+ * classes.
+ */
+interface SwipeSource {
+ // Require equals() and hashCode() to be implemented.
+ override fun equals(other: Any?): Boolean
+
+ override fun hashCode(): Int
+}
+
+interface SwipeSourceDetector {
+ /**
+ * Return the [SwipeSource] associated to [position] inside a layout of size [layoutSize], given
+ * [density] and [orientation].
+ */
+ fun source(
+ layoutSize: IntSize,
+ position: IntOffset,
+ density: Density,
+ orientation: Orientation,
+ ): SwipeSource?
+}
+
+/**
+ * The result of performing a [UserAction].
+ *
+ * Note: [UserActionResult] is implemented by [SceneKey], and you can also use [withDistance] to
+ * easily create a [UserActionResult] with a fixed distance:
+ * ```
+ * SceneTransitionLayout(...) {
+ * scene(
+ * Scenes.Foo,
+ * userActions =
+ * mapOf(
+ * Swipe.Right to Scene.Bar,
+ * Swipe.Down to Scene.Doe withDistance 100.dp,
+ * )
+ * )
+ * ) { ... }
+ * }
+ * ```
+ */
+interface UserActionResult {
+ /** The scene we should be transitioning to during the [UserAction]. */
+ val toScene: SceneKey
+
+ /**
+ * The distance the action takes to animate from 0% to 100%.
+ *
+ * If `null`, a default distance will be used that depends on the [UserAction] performed.
+ */
+ val distance: UserActionDistance?
+}
+
+interface UserActionDistance {
+ /**
+ * Return the **absolute** distance of the user action given the size of the scene we are
+ * animating from and the [orientation].
+ */
+ fun Density.absoluteDistance(fromSceneSize: IntSize, orientation: Orientation): Float
+}
+
+/**
+ * A utility function to make it possible to define user actions with a distance using the syntax
+ * `Swipe.Up to Scene.foo withDistance 100.dp`
+ */
+infix fun Pair<UserAction, SceneKey>.withDistance(
+ distance: Dp
+): Pair<UserAction, UserActionResult> {
+ val scene = second
+ val distance = FixedDistance(distance)
+ return first to
+ object : UserActionResult {
+ override val toScene: SceneKey = scene
+ override val distance: UserActionDistance = distance
+ }
+}
+
+/** The user action has a fixed [absoluteDistance]. */
+private class FixedDistance(private val distance: Dp) : UserActionDistance {
+ override fun Density.absoluteDistance(fromSceneSize: IntSize, orientation: Orientation): Float {
+ return distance.toPx()
+ }
+}
+
+/**
* An internal version of [SceneTransitionLayout] to be used for tests.
*
* Important: You should use this only in tests and if you need to access the underlying
@@ -362,7 +457,7 @@ enum class SwipeDirection(val orientation: Orientation) {
internal fun SceneTransitionLayoutForTesting(
state: SceneTransitionLayoutState,
modifier: Modifier = Modifier,
- edgeDetector: EdgeDetector = DefaultEdgeDetector,
+ swipeSourceDetector: SwipeSourceDetector = DefaultEdgeDetector,
transitionInterceptionThreshold: Float = 0f,
onLayoutImpl: ((SceneTransitionLayoutImpl) -> Unit)? = null,
scenes: SceneTransitionLayoutScope.() -> Unit,
@@ -373,7 +468,7 @@ internal fun SceneTransitionLayoutForTesting(
SceneTransitionLayoutImpl(
state = state as BaseSceneTransitionLayoutState,
density = density,
- edgeDetector = edgeDetector,
+ swipeSourceDetector = swipeSourceDetector,
transitionInterceptionThreshold = transitionInterceptionThreshold,
builder = scenes,
coroutineScope = coroutineScope,
@@ -394,7 +489,7 @@ internal fun SceneTransitionLayoutForTesting(
}
layoutImpl.density = density
- layoutImpl.edgeDetector = edgeDetector
+ layoutImpl.swipeSourceDetector = swipeSourceDetector
}
layoutImpl.Content(modifier)
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
index 7cc9d2623e9c..8c5a4720e7fb 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
@@ -47,7 +47,7 @@ internal typealias MovableElementContent =
internal class SceneTransitionLayoutImpl(
internal val state: BaseSceneTransitionLayoutState,
internal var density: Density,
- internal var edgeDetector: EdgeDetector,
+ internal var swipeSourceDetector: SwipeSourceDetector,
internal var transitionInterceptionThreshold: Float,
builder: SceneTransitionLayoutScope.() -> Unit,
private val coroutineScope: CoroutineScope,
@@ -140,7 +140,7 @@ internal class SceneTransitionLayoutImpl(
object : SceneTransitionLayoutScope {
override fun scene(
key: SceneKey,
- userActions: Map<UserAction, SceneKey>,
+ userActions: Map<UserAction, UserActionResult>,
content: @Composable SceneScope.() -> Unit,
) {
scenesToRemove.remove(key)
@@ -229,8 +229,10 @@ internal class SceneTransitionLayoutImpl(
// Handle back events.
// TODO(b/290184746): Make sure that this works with SystemUI once we use
// SceneTransitionLayout in Flexiglass.
- scene(state.transitionState.currentScene).userActions[Back]?.let { backScene ->
- BackHandler { with(state) { coroutineScope.onChangeScene(backScene) } }
+ scene(state.transitionState.currentScene).userActions[Back]?.let { result ->
+ // TODO(b/290184746): Handle predictive back and use result.distance if
+ // specified.
+ BackHandler { with(state) { coroutineScope.onChangeScene(result.toScene) } }
}
Box {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
index 0d3bc7d0cd85..b9c4ac0cd006 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
@@ -17,40 +17,98 @@
package com.android.compose.animation.scene
import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.runtime.Stable
import androidx.compose.ui.Modifier
+import androidx.compose.ui.input.pointer.PointerEvent
+import androidx.compose.ui.input.pointer.PointerEventPass
+import androidx.compose.ui.node.DelegatingNode
+import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.node.PointerInputModifierNode
+import androidx.compose.ui.unit.IntSize
/**
* Configures the swipeable behavior of a [SceneTransitionLayout] depending on the current state.
*/
+@Stable
internal fun Modifier.swipeToScene(gestureHandler: SceneGestureHandler): Modifier {
- /** Whether swipe should be enabled in the given [orientation]. */
- fun Scene.shouldEnableSwipes(orientation: Orientation): Boolean =
- userActions.keys.any { it is Swipe && it.direction.orientation == orientation }
-
- val layoutImpl = gestureHandler.layoutImpl
- val currentScene = layoutImpl.scene(layoutImpl.state.transitionState.currentScene)
- val orientation = gestureHandler.orientation
- val canSwipe = currentScene.shouldEnableSwipes(orientation)
- val canOppositeSwipe =
- currentScene.shouldEnableSwipes(
- when (orientation) {
- Orientation.Vertical -> Orientation.Horizontal
- Orientation.Horizontal -> Orientation.Vertical
- }
+ return this.then(SwipeToSceneElement(gestureHandler))
+}
+
+private data class SwipeToSceneElement(
+ val gestureHandler: SceneGestureHandler,
+) : ModifierNodeElement<SwipeToSceneNode>() {
+ override fun create(): SwipeToSceneNode = SwipeToSceneNode(gestureHandler)
+
+ override fun update(node: SwipeToSceneNode) {
+ node.gestureHandler = gestureHandler
+ }
+}
+
+private class SwipeToSceneNode(
+ gestureHandler: SceneGestureHandler,
+) : DelegatingNode(), PointerInputModifierNode {
+ private val delegate =
+ delegate(
+ MultiPointerDraggableNode(
+ orientation = gestureHandler.orientation,
+ enabled = ::enabled,
+ startDragImmediately = ::startDragImmediately,
+ onDragStarted = gestureHandler.draggable::onDragStarted,
+ onDragDelta = gestureHandler.draggable::onDelta,
+ onDragStopped = gestureHandler.draggable::onDragStopped,
+ )
)
- return multiPointerDraggable(
- orientation = orientation,
- enabled = gestureHandler.isDrivingTransition || canSwipe,
- // Immediately start the drag if this our [transition] is currently animating to a scene
+ var gestureHandler: SceneGestureHandler = gestureHandler
+ set(value) {
+ if (value != field) {
+ field = value
+
+ // Make sure to update the delegate orientation. Note that this will automatically
+ // reset the underlying pointer input handler, so previous gestures will be
+ // cancelled.
+ delegate.orientation = value.orientation
+ }
+ }
+
+ override fun onPointerEvent(
+ pointerEvent: PointerEvent,
+ pass: PointerEventPass,
+ bounds: IntSize,
+ ) = delegate.onPointerEvent(pointerEvent, pass, bounds)
+
+ override fun onCancelPointerInput() = delegate.onCancelPointerInput()
+
+ private fun enabled(): Boolean {
+ return gestureHandler.isDrivingTransition ||
+ currentScene().shouldEnableSwipes(gestureHandler.orientation)
+ }
+
+ private fun currentScene(): Scene {
+ val layoutImpl = gestureHandler.layoutImpl
+ return layoutImpl.scene(layoutImpl.state.transitionState.currentScene)
+ }
+
+ /** Whether swipe should be enabled in the given [orientation]. */
+ private fun Scene.shouldEnableSwipes(orientation: Orientation): Boolean {
+ return userActions.keys.any { it is Swipe && it.direction.orientation == orientation }
+ }
+
+ private fun startDragImmediately(): Boolean {
+ // Immediately start the drag if this our transition is currently animating to a scene
// (i.e. the user released their input pointer after swiping in this orientation) and the
// user can't swipe in the other direction.
- startDragImmediately =
- gestureHandler.isDrivingTransition &&
- gestureHandler.swipeTransition.isAnimatingOffset &&
- !canOppositeSwipe,
- onDragStarted = gestureHandler.draggable::onDragStarted,
- onDragDelta = gestureHandler.draggable::onDelta,
- onDragStopped = gestureHandler.draggable::onDragStopped,
- )
+ return gestureHandler.isDrivingTransition &&
+ gestureHandler.swipeTransition.isAnimatingOffset &&
+ !canOppositeSwipe()
+ }
+
+ private fun canOppositeSwipe(): Boolean {
+ val oppositeOrientation =
+ when (gestureHandler.orientation) {
+ Orientation.Vertical -> Orientation.Horizontal
+ Orientation.Horizontal -> Orientation.Vertical
+ }
+ return currentScene().shouldEnableSwipes(oppositeOrientation)
+ }
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
index dc8505c43889..a764a52723af 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
@@ -320,11 +320,3 @@ interface PropertyTransformationBuilder {
anchorHeight: Boolean = true,
)
}
-
-/** The edge of a [SceneTransitionLayout]. */
-enum class Edge {
- Left,
- Right,
- Top,
- Bottom,
-}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
index 2841bcf4e40c..ac11d3040d67 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
@@ -22,6 +22,7 @@ import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.unit.Velocity
import com.android.compose.ui.util.SpaceVectorConverter
+import kotlin.math.sign
/**
* This [NestedScrollConnection] waits for a child to scroll ([onPreScroll] or [onPostScroll]), and
@@ -117,7 +118,12 @@ class PriorityNestedScrollConnection(
return Velocity.Zero
}
- onPriorityStart(available = Offset.Zero)
+ // The offset passed to onPriorityStart() must be != 0f, so we create a small offset of 1px
+ // given the available velocity.
+ // TODO(b/291053278): Remove canStartPostFling() and instead make it possible to define the
+ // overscroll behavior on the Scene level.
+ val smallOffset = Offset(available.x.sign, available.y.sign)
+ onPriorityStart(available = smallOffset)
// This is the last event of a scroll gesture.
return onPriorityStop(available)
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/FixedSizeEdgeDetectorTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/FixedSizeEdgeDetectorTest.kt
index a68282ae78f4..cceaf57ca82b 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/FixedSizeEdgeDetectorTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/FixedSizeEdgeDetectorTest.kt
@@ -35,7 +35,7 @@ class FixedSizeEdgeDetectorTest {
@Test
fun horizontalEdges() {
fun horizontalEdge(position: Int): Edge? =
- detector.edge(
+ detector.source(
layoutSize,
position = IntOffset(position, 0),
density,
@@ -53,7 +53,7 @@ class FixedSizeEdgeDetectorTest {
@Test
fun verticalEdges() {
fun verticalEdge(position: Int): Edge? =
- detector.edge(
+ detector.source(
layoutSize,
position = IntOffset(0, position),
density,
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
index 066a3e45fb3c..88363ad24d9a 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
@@ -77,7 +77,7 @@ class SceneGestureHandlerTest {
userActions =
mapOf(
Swipe.Up to SceneB,
- Swipe(SwipeDirection.Up, fromEdge = Edge.Bottom) to SceneA
+ Swipe(SwipeDirection.Up, fromSource = Edge.Bottom) to SceneA
),
) {
Text("SceneC")
@@ -90,7 +90,7 @@ class SceneGestureHandlerTest {
SceneTransitionLayoutImpl(
state = layoutState,
density = Density(1f),
- edgeDetector = DefaultEdgeDetector,
+ swipeSourceDetector = DefaultEdgeDetector,
transitionInterceptionThreshold = transitionInterceptionThreshold,
builder = scenesBuilder,
coroutineScope = coroutineScope,
@@ -192,16 +192,14 @@ class SceneGestureHandlerTest {
@Test
fun onDragStarted_shouldStartATransition() = runGestureTest {
- draggable.onDragStarted()
+ draggable.onDragStarted(overSlop = down(0.1f))
assertTransition(currentScene = SceneA)
}
@Test
fun afterSceneTransitionIsStarted_interceptDragEvents() = runGestureTest {
- draggable.onDragStarted()
+ draggable.onDragStarted(overSlop = down(0.1f))
assertTransition(currentScene = SceneA)
-
- draggable.onDelta(pixels = down(0.1f))
assertThat(progress).isEqualTo(0.1f)
draggable.onDelta(pixels = down(0.1f))
@@ -210,10 +208,7 @@ class SceneGestureHandlerTest {
@Test
fun onDragStoppedAfterDrag_velocityLowerThanThreshold_remainSameScene() = runGestureTest {
- draggable.onDragStarted()
- assertTransition(currentScene = SceneA)
-
- draggable.onDelta(pixels = down(0.1f))
+ draggable.onDragStarted(overSlop = down(0.1f))
assertTransition(currentScene = SceneA)
draggable.onDragStopped(
@@ -228,10 +223,7 @@ class SceneGestureHandlerTest {
@Test
fun onDragStoppedAfterDrag_velocityAtLeastThreshold_goToNextScene() = runGestureTest {
- draggable.onDragStarted()
- assertTransition(currentScene = SceneA)
-
- draggable.onDelta(pixels = down(0.1f))
+ draggable.onDragStarted(overSlop = down(0.1f))
assertTransition(currentScene = SceneA)
draggable.onDragStopped(velocity = velocityThreshold)
@@ -245,7 +237,7 @@ class SceneGestureHandlerTest {
@Test
fun onDragStoppedAfterStarted_returnToIdle() = runGestureTest {
- draggable.onDragStarted()
+ draggable.onDragStarted(overSlop = down(0.1f))
assertTransition(currentScene = SceneA)
draggable.onDragStopped(velocity = 0f)
@@ -256,8 +248,7 @@ class SceneGestureHandlerTest {
@Test
fun onDragReversedDirection_changeToScene() = runGestureTest {
// Drag A -> B with progress 0.6
- draggable.onDragStarted()
- draggable.onDelta(up(0.6f))
+ draggable.onDragStarted(overSlop = up(0.6f))
assertTransition(
currentScene = SceneA,
fromScene = SceneA,
@@ -366,8 +357,7 @@ class SceneGestureHandlerTest {
@Test
fun onAccelaratedScroll_scrollToThirdScene() = runGestureTest {
// Drag A -> B with progress 0.2
- draggable.onDragStarted()
- draggable.onDelta(up(0.2f))
+ draggable.onDragStarted(overSlop = up(0.2f))
assertTransition(
currentScene = SceneA,
fromScene = SceneA,
@@ -401,9 +391,7 @@ class SceneGestureHandlerTest {
@Test
fun onAccelaratedScrollBothTargetsBecomeNull_settlesToIdle() = runGestureTest {
- draggable.onDragStarted()
- draggable.onDelta(up(0.2f))
-
+ draggable.onDragStarted(overSlop = up(0.2f))
draggable.onDelta(up(0.2f))
draggable.onDragStopped(velocity = -velocityThreshold)
assertTransition(currentScene = SceneB, fromScene = SceneA, toScene = SceneB)
@@ -459,16 +447,14 @@ class SceneGestureHandlerTest {
draggable.onDragStopped(down(0.1f))
// now target changed to C for new drag that started before previous drag settled to Idle
- draggable.onDragStarted(up(0.1f))
+ draggable.onDragStarted(overSlop = 0f)
+ draggable.onDelta(up(0.1f))
assertTransition(fromScene = SceneA, toScene = SceneC, progress = 0.3f)
}
@Test
fun startGestureDuringAnimatingOffset_shouldImmediatelyStopTheAnimation() = runGestureTest {
- draggable.onDragStarted()
- assertTransition(currentScene = SceneA)
-
- draggable.onDelta(pixels = down(0.1f))
+ draggable.onDragStarted(overSlop = down(0.1f))
assertTransition(currentScene = SceneA)
draggable.onDragStopped(
@@ -759,10 +745,8 @@ class SceneGestureHandlerTest {
@Test
fun startNestedScrollWhileDragging() = runGestureTest {
val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeAlways)
- draggable.onDragStarted()
+ draggable.onDragStarted(overSlop = down(0.1f))
assertTransition(currentScene = SceneA)
-
- draggable.onDelta(down(0.1f))
assertThat(progress).isEqualTo(0.1f)
// now we can intercept the scroll events
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
index 1ec3c8ba2301..940335853221 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
@@ -17,6 +17,7 @@
package com.android.compose.animation.scene
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
@@ -89,8 +90,8 @@ class SwipeToSceneTest {
mapOf(
Swipe.Down to TestScenes.SceneA,
Swipe(SwipeDirection.Down, pointerCount = 2) to TestScenes.SceneB,
- Swipe(SwipeDirection.Right, fromEdge = Edge.Left) to TestScenes.SceneB,
- Swipe(SwipeDirection.Down, fromEdge = Edge.Top) to TestScenes.SceneB,
+ Swipe(SwipeDirection.Right, fromSource = Edge.Left) to TestScenes.SceneB,
+ Swipe(SwipeDirection.Down, fromSource = Edge.Top) to TestScenes.SceneB,
),
) {
Box(Modifier.fillMaxSize())
@@ -349,4 +350,46 @@ class SwipeToSceneTest {
assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneC)
}
+
+ @Test
+ fun swipeDistance() {
+ // The draggable touch slop, i.e. the min px distance a touch pointer must move before it is
+ // detected as a drag event.
+ var touchSlop = 0f
+
+ val layoutState = MutableSceneTransitionLayoutState(TestScenes.SceneA)
+ val verticalSwipeDistance = 50.dp
+ assertThat(verticalSwipeDistance).isNotEqualTo(LayoutHeight)
+
+ rule.setContent {
+ touchSlop = LocalViewConfiguration.current.touchSlop
+
+ SceneTransitionLayout(
+ state = layoutState,
+ modifier = Modifier.size(LayoutWidth, LayoutHeight)
+ ) {
+ scene(
+ TestScenes.SceneA,
+ userActions =
+ mapOf(Swipe.Down to TestScenes.SceneB withDistance verticalSwipeDistance),
+ ) {
+ Spacer(Modifier.fillMaxSize())
+ }
+ scene(TestScenes.SceneB) { Spacer(Modifier.fillMaxSize()) }
+ }
+ }
+
+ assertThat(layoutState.currentTransition).isNull()
+
+ // Swipe by half of verticalSwipeDistance.
+ rule.onRoot().performTouchInput {
+ down(middleTop)
+ moveBy(Offset(0f, touchSlop + (verticalSwipeDistance / 2).toPx()), delayMillis = 1_000)
+ }
+
+ // We should be at 50%
+ val transition = layoutState.currentTransition
+ assertThat(transition).isNotNull()
+ assertThat(transition!!.progress).isEqualTo(0.5f)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index 030d41ddd8fb..c82688c2772a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -203,11 +203,17 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() {
whenever(deviceProvisionedController.isUserSetup(anyInt())).thenReturn(true)
featureFlags = FakeFeatureFlags()
- featureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false)
featureFlags.set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false)
featureFlags.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false)
- mSetFlagsRule.enableFlags(AConfigFlags.FLAG_REVAMPED_BOUNCER_MESSAGES)
+ mSetFlagsRule.enableFlags(
+ AConfigFlags.FLAG_REVAMPED_BOUNCER_MESSAGES,
+ )
+ mSetFlagsRule.disableFlags(
+ FLAG_SIDEFPS_CONTROLLER_REFACTOR,
+ AConfigFlags.FLAG_KEYGUARD_WM_STATE_REFACTOR
+ )
+
keyguardPasswordViewController =
KeyguardPasswordViewController(
keyguardPasswordView,
@@ -238,7 +244,6 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() {
sceneInteractor.setTransitionState(sceneTransitionStateFlow)
deviceEntryInteractor = kosmos.deviceEntryInteractor
- mSetFlagsRule.disableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR)
underTest =
KeyguardSecurityContainerController(
view,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplTest.kt
new file mode 100644
index 000000000000..9287edf4ee51
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplTest.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility.data.repository
+
+import android.provider.Settings
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AccessibilityQsShortcutsRepositoryImplTest : SysuiTestCase() {
+ private val testDispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+ private val secureSettings = FakeSettings()
+
+ private val userA11yQsShortcutsRepositoryFactory =
+ object : UserA11yQsShortcutsRepository.Factory {
+ override fun create(userId: Int): UserA11yQsShortcutsRepository {
+ return UserA11yQsShortcutsRepository(
+ userId,
+ secureSettings,
+ testScope.backgroundScope,
+ testDispatcher,
+ )
+ }
+ }
+
+ private val underTest =
+ AccessibilityQsShortcutsRepositoryImpl(userA11yQsShortcutsRepositoryFactory)
+
+ @Test
+ fun a11yQsShortcutTargetsForCorrectUsers() =
+ testScope.runTest {
+ val user0 = 0
+ val targetsForUser0 = setOf("a", "b", "c")
+ val user1 = 1
+ val targetsForUser1 = setOf("A")
+ val targetsFromUser0 by collectLastValue(underTest.a11yQsShortcutTargets(user0))
+ val targetsFromUser1 by collectLastValue(underTest.a11yQsShortcutTargets(user1))
+
+ storeA11yQsShortcutTargetsForUser(targetsForUser0, user0)
+ storeA11yQsShortcutTargetsForUser(targetsForUser1, user1)
+
+ assertThat(targetsFromUser0).isEqualTo(targetsForUser0)
+ assertThat(targetsFromUser1).isEqualTo(targetsForUser1)
+ }
+
+ private fun storeA11yQsShortcutTargetsForUser(a11yQsTargets: Set<String>, forUser: Int) {
+ secureSettings.putStringForUser(
+ SETTING_NAME,
+ a11yQsTargets.joinToString(separator = ":"),
+ forUser
+ )
+ }
+
+ companion object {
+ private const val SETTING_NAME = Settings.Secure.ACCESSIBILITY_QS_TARGETS
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/UserA11yQsShortcutsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/UserA11yQsShortcutsRepositoryTest.kt
new file mode 100644
index 000000000000..ce22e288e292
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/UserA11yQsShortcutsRepositoryTest.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility.data.repository
+
+import android.provider.Settings
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class UserA11yQsShortcutsRepositoryTest : SysuiTestCase() {
+ private val secureSettings = FakeSettings()
+ private val testDispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+
+ private val underTest =
+ UserA11yQsShortcutsRepository(
+ USER_ID,
+ secureSettings,
+ testScope.backgroundScope,
+ testDispatcher
+ )
+
+ @Test
+ fun targetsMatchesSetting() =
+ testScope.runTest {
+ val observedTargets by collectLastValue(underTest.targets)
+ val a11yQsTargets = setOf("a", "b", "c")
+ secureSettings.putStringForUser(
+ SETTING_NAME,
+ a11yQsTargets.joinToString(SEPARATOR),
+ USER_ID
+ )
+
+ assertThat(observedTargets).isEqualTo(a11yQsTargets)
+ }
+
+ companion object {
+ private const val USER_ID = 0
+ private const val SEPARATOR = ":"
+ private const val SETTING_NAME = Settings.Secure.ACCESSIBILITY_QS_TARGETS
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
index bb3429e72b35..c979ca63950a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
@@ -30,7 +30,6 @@ import com.android.systemui.communal.data.db.CommunalWidgetItem
import com.android.systemui.communal.shared.CommunalWidgetHost
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
import com.android.systemui.communal.widgets.CommunalAppWidgetHost
-import com.android.systemui.communal.widgets.WidgetConfigurator
import com.android.systemui.communal.widgets.widgetConfiguratorFail
import com.android.systemui.communal.widgets.widgetConfiguratorSuccess
import com.android.systemui.coroutines.collectLastValue
@@ -45,8 +44,7 @@ import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import java.util.Optional
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -62,24 +60,17 @@ import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidJUnit4::class)
class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
- @Mock private lateinit var appWidgetManagerOptional: Optional<AppWidgetManager>
-
@Mock private lateinit var appWidgetManager: AppWidgetManager
-
@Mock private lateinit var appWidgetHost: CommunalAppWidgetHost
-
@Mock private lateinit var stopwatchProviderInfo: AppWidgetProviderInfo
-
@Mock private lateinit var providerInfoA: AppWidgetProviderInfo
-
@Mock private lateinit var communalWidgetHost: CommunalWidgetHost
-
@Mock private lateinit var communalWidgetDao: CommunalWidgetDao
private lateinit var logBuffer: LogBuffer
+ private lateinit var fakeWidgets: MutableStateFlow<Map<CommunalItemRank, CommunalWidgetItem>>
private val kosmos = testKosmos()
- private val testDispatcher = kosmos.testDispatcher
private val testScope = kosmos.testScope
private val fakeAllowlist =
@@ -94,7 +85,7 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
-
+ fakeWidgets = MutableStateFlow(emptyMap())
logBuffer = logcatLogBuffer(name = "CommunalWidgetRepoImplTest")
setAppWidgetIds(emptyList())
@@ -102,13 +93,11 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
overrideResource(R.array.config_communalWidgetAllowlist, fakeAllowlist.toTypedArray())
whenever(stopwatchProviderInfo.loadLabel(any())).thenReturn("Stopwatch")
- whenever(communalWidgetDao.getWidgets()).thenReturn(flowOf(emptyMap()))
- whenever(appWidgetManagerOptional.isPresent).thenReturn(true)
- whenever(appWidgetManagerOptional.get()).thenReturn(appWidgetManager)
+ whenever(communalWidgetDao.getWidgets()).thenReturn(fakeWidgets)
underTest =
CommunalWidgetRepositoryImpl(
- appWidgetManagerOptional,
+ Optional.of(appWidgetManager),
appWidgetHost,
testScope.backgroundScope,
kosmos.testDispatcher,
@@ -119,30 +108,16 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
}
@Test
- fun neverQueryDbForWidgets_whenHostIsInactive() =
- testScope.runTest {
- underTest.updateAppWidgetHostActive(false)
- underTest.communalWidgets.launchIn(testScope.backgroundScope)
- runCurrent()
-
- verify(communalWidgetDao, never()).getWidgets()
- }
-
- @Test
- fun communalWidgets_whenHostIsActive_queryWidgetsFromDb() =
+ fun communalWidgets_queryWidgetsFromDb() =
testScope.runTest {
- underTest.updateAppWidgetHostActive(true)
-
val communalItemRankEntry = CommunalItemRank(uid = 1L, rank = 1)
val communalWidgetItemEntry = CommunalWidgetItem(uid = 1L, 1, "pk_name/cls_name", 1L)
- whenever(communalWidgetDao.getWidgets())
- .thenReturn(flowOf(mapOf(communalItemRankEntry to communalWidgetItemEntry)))
+ fakeWidgets.value = mapOf(communalItemRankEntry to communalWidgetItemEntry)
whenever(appWidgetManager.getAppWidgetInfo(anyInt())).thenReturn(providerInfoA)
installedProviders(listOf(stopwatchProviderInfo))
val communalWidgets by collectLastValue(underTest.communalWidgets)
- runCurrent()
verify(communalWidgetDao).getWidgets()
assertThat(communalWidgets)
.containsExactly(
@@ -157,8 +132,6 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
@Test
fun addWidget_allocateId_bindWidget_andAddToDb() =
testScope.runTest {
- underTest.updateAppWidgetHostActive(true)
-
val provider = ComponentName("pkg_name", "cls_name")
val id = 1
val priority = 1
@@ -176,8 +149,6 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
@Test
fun addWidget_configurationFails_doNotAddWidgetToDb() =
testScope.runTest {
- underTest.updateAppWidgetHostActive(true)
-
val provider = ComponentName("pkg_name", "cls_name")
val id = 1
val priority = 1
@@ -195,23 +166,13 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
@Test
fun addWidget_configurationThrowsError_doNotAddWidgetToDb() =
testScope.runTest {
- underTest.updateAppWidgetHostActive(true)
-
val provider = ComponentName("pkg_name", "cls_name")
val id = 1
val priority = 1
whenever(communalWidgetHost.getAppWidgetInfo(id))
.thenReturn(PROVIDER_INFO_REQUIRES_CONFIGURATION)
whenever(communalWidgetHost.allocateIdAndBindWidget(provider)).thenReturn(id)
- underTest.addWidget(
- provider,
- priority,
- object : WidgetConfigurator {
- override suspend fun configureWidget(appWidgetId: Int): Boolean {
- throw IllegalStateException("some error")
- }
- }
- )
+ underTest.addWidget(provider, priority) { throw IllegalStateException("some error") }
runCurrent()
verify(communalWidgetHost).allocateIdAndBindWidget(provider)
@@ -222,8 +183,6 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
@Test
fun addWidget_configurationNotRequired_doesNotConfigure_addWidgetToDb() =
testScope.runTest {
- underTest.updateAppWidgetHostActive(true)
-
val provider = ComponentName("pkg_name", "cls_name")
val id = 1
val priority = 1
@@ -241,8 +200,6 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
@Test
fun deleteWidget_removeWidgetId_andDeleteFromDb() =
testScope.runTest {
- underTest.updateAppWidgetHostActive(true)
-
val id = 1
underTest.deleteWidget(id)
runCurrent()
@@ -254,8 +211,6 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
@Test
fun reorderWidgets_queryDb() =
testScope.runTest {
- underTest.updateAppWidgetHostActive(true)
-
val widgetIdToPriorityMap = mapOf(104 to 1, 103 to 2, 101 to 3)
underTest.updateWidgetOrder(widgetIdToPriorityMap)
runCurrent()
@@ -263,28 +218,6 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
verify(communalWidgetDao).updateWidgetOrder(widgetIdToPriorityMap)
}
- @Test
- fun appWidgetHost_startListening() =
- testScope.runTest {
- verify(appWidgetHost, never()).startListening()
-
- underTest.updateAppWidgetHostActive(true)
-
- verify(appWidgetHost).startListening()
- }
-
- @Test
- fun appWidgetHost_stopListening() =
- testScope.runTest {
- underTest.updateAppWidgetHostActive(true)
-
- verify(appWidgetHost).startListening()
-
- underTest.updateAppWidgetHostActive(false)
-
- verify(appWidgetHost).stopListening()
- }
-
private fun installedProviders(providers: List<AppWidgetProviderInfo>) {
whenever(appWidgetManager.installedProviders).thenReturn(providers)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt
index e8216735fb5d..6a3fc2a060eb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt
@@ -80,15 +80,4 @@ class CommunalInteractorCommunalDisabledTest : SysuiTestCase() {
assertThat(isCommunalAvailable).isFalse()
}
-
- @Test
- fun updateAppWidgetHostActive_whenStorageUnlock_false() =
- testScope.runTest {
- assertThat(widgetRepository.isHostActive()).isFalse()
-
- keyguardRepository.setIsEncryptedOrLockdown(false)
- runCurrent()
-
- assertThat(widgetRepository.isHostActive()).isFalse()
- }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index 1b7117f41bbb..ee01bf9c26e4 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
@@ -125,7 +125,7 @@ class CommunalInteractorTest : SysuiTestCase() {
keyguardRepository.setIsEncryptedOrLockdown(false)
userRepository.setSelectedUserInfo(mainUser)
keyguardRepository.setKeyguardShowing(true)
- runCurrent()
+ communalRepository.setCommunalEnabledState(true)
assertThat(isAvailable).isTrue()
}
@@ -138,7 +138,8 @@ class CommunalInteractorTest : SysuiTestCase() {
keyguardRepository.setIsEncryptedOrLockdown(true)
userRepository.setSelectedUserInfo(mainUser)
- runCurrent()
+ keyguardRepository.setKeyguardShowing(true)
+ communalRepository.setCommunalEnabledState(true)
assertThat(isAvailable).isFalse()
}
@@ -152,7 +153,7 @@ class CommunalInteractorTest : SysuiTestCase() {
keyguardRepository.setIsEncryptedOrLockdown(false)
userRepository.setSelectedUserInfo(secondaryUser)
keyguardRepository.setKeyguardShowing(true)
- runCurrent()
+ communalRepository.setCommunalEnabledState(true)
assertThat(isAvailable).isFalse()
}
@@ -166,23 +167,23 @@ class CommunalInteractorTest : SysuiTestCase() {
keyguardRepository.setIsEncryptedOrLockdown(false)
userRepository.setSelectedUserInfo(mainUser)
keyguardRepository.setDreaming(true)
- runCurrent()
+ communalRepository.setCommunalEnabledState(true)
assertThat(isAvailable).isTrue()
}
@Test
- fun updateAppWidgetHostActive_uponStorageUnlockAsMainUser_true() =
+ fun isCommunalAvailable_communalDisabled_false() =
testScope.runTest {
- collectLastValue(underTest.isCommunalAvailable)
- assertThat(widgetRepository.isHostActive()).isFalse()
+ val isAvailable by collectLastValue(underTest.isCommunalAvailable)
+ assertThat(isAvailable).isFalse()
keyguardRepository.setIsEncryptedOrLockdown(false)
userRepository.setSelectedUserInfo(mainUser)
keyguardRepository.setKeyguardShowing(true)
- runCurrent()
+ communalRepository.setCommunalEnabledState(false)
- assertThat(widgetRepository.isHostActive()).isTrue()
+ assertThat(isAvailable).isFalse()
}
@Test
@@ -647,6 +648,14 @@ class CommunalInteractorTest : SysuiTestCase() {
verify(editWidgetsActivityStarter).startActivity()
}
+ @Test
+ fun showWidgetEditor_withPreselectedKey_startsActivity() =
+ testScope.runTest {
+ val widgetKey = CommunalContentModel.KEY.widget(123)
+ underTest.showWidgetEditor(preselectedKey = widgetKey)
+ verify(editWidgetsActivityStarter).startActivity(widgetKey)
+ }
+
private fun smartspaceTimer(id: String, timestamp: Long = 0L): SmartspaceTarget {
val timer = mock(SmartspaceTarget::class.java)
whenever(timer.smartspaceTargetId).thenReturn(id)
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 352463f5a198..6c87e0f2eb23 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
@@ -34,20 +34,15 @@ import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.user.data.repository.fakeUserRepository
-import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidJUnit4::class)
class CommunalTutorialInteractorTest : SysuiTestCase() {
- @Mock lateinit var user: UserInfo
-
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
@@ -60,14 +55,14 @@ class CommunalTutorialInteractorTest : SysuiTestCase() {
@Before
fun setUp() {
- MockitoAnnotations.initMocks(this)
-
keyguardRepository = kosmos.fakeKeyguardRepository
communalTutorialRepository = kosmos.fakeCommunalTutorialRepository
communalRepository = kosmos.fakeCommunalRepository
communalInteractor = kosmos.communalInteractor
userRepository = kosmos.fakeUserRepository
+ userRepository.setUserInfos(listOf(MAIN_USER_INFO))
+
underTest = kosmos.communalTutorialInteractor
}
@@ -204,12 +199,17 @@ class CommunalTutorialInteractorTest : SysuiTestCase() {
private suspend fun setCommunalAvailable(available: Boolean) {
if (available) {
communalRepository.setIsCommunalEnabled(true)
+ communalRepository.setCommunalEnabledState(true)
keyguardRepository.setIsEncryptedOrLockdown(false)
- whenever(user.isMain).thenReturn(true)
- userRepository.setUserInfos(listOf(user))
- userRepository.setSelectedUserInfo(user)
+ userRepository.setSelectedUserInfo(MAIN_USER_INFO)
+ keyguardRepository.setKeyguardShowing(true)
} else {
- keyguardRepository.setIsEncryptedOrLockdown(true)
+ communalRepository.setIsCommunalEnabled(false)
+ communalRepository.setCommunalEnabledState(false)
}
}
+
+ private companion object {
+ val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN)
+ }
}
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 a2dec5ff8830..273d1cd55626 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
@@ -129,6 +129,19 @@ class CommunalEditModeViewModelTest : SysuiTestCase() {
}
@Test
+ fun selectedKey_onReorderWidgets_isCleared() =
+ testScope.runTest {
+ val selectedKey by collectLastValue(underTest.selectedKey)
+
+ val key = CommunalContentModel.KEY.widget(123)
+ underTest.setSelectedKey(key)
+ assertThat(selectedKey).isEqualTo(key)
+
+ underTest.onReorderWidgetStart()
+ assertThat(selectedKey).isNull()
+ }
+
+ @Test
fun reorderWidget_uiEventLogging_start() {
underTest.onReorderWidgetStart()
verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_START)
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 c814f3f1db6a..0723e8306f71 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
@@ -27,6 +27,7 @@ import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository
import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository
import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
import com.android.systemui.communal.data.repository.fakeCommunalMediaRepository
+import com.android.systemui.communal.data.repository.fakeCommunalRepository
import com.android.systemui.communal.data.repository.fakeCommunalTutorialRepository
import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository
import com.android.systemui.communal.domain.interactor.communalInteractor
@@ -90,6 +91,8 @@ class CommunalViewModelTest : SysuiTestCase() {
mediaRepository = kosmos.fakeCommunalMediaRepository
userRepository = kosmos.fakeUserRepository
+ kosmos.fakeCommunalRepository.setCommunalEnabledState(true)
+
underTest =
CommunalViewModel(
testScope,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt
new file mode 100644
index 000000000000..a3654b6e8963
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.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.systemui.communal.widgets
+
+import android.content.pm.UserInfo
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.data.repository.fakeCommunalRepository
+import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.fakeUserRepository
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CommunalAppWidgetHostStartableTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+
+ @Mock private lateinit var appWidgetHost: CommunalAppWidgetHost
+
+ private lateinit var underTest: CommunalAppWidgetHostStartable
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ kosmos.fakeUserRepository.setUserInfos(listOf(MAIN_USER_INFO))
+
+ underTest =
+ CommunalAppWidgetHostStartable(
+ appWidgetHost,
+ kosmos.communalInteractor,
+ kosmos.applicationCoroutineScope,
+ kosmos.testDispatcher,
+ )
+ }
+
+ @Test
+ fun editModeShowingStartsAppWidgetHost() =
+ with(kosmos) {
+ testScope.runTest {
+ setCommunalAvailable(false)
+ communalInteractor.setEditModeOpen(true)
+ verify(appWidgetHost, never()).startListening()
+
+ underTest.start()
+ runCurrent()
+
+ verify(appWidgetHost).startListening()
+ verify(appWidgetHost, never()).stopListening()
+
+ communalInteractor.setEditModeOpen(false)
+ runCurrent()
+
+ verify(appWidgetHost).stopListening()
+ }
+ }
+
+ @Test
+ fun communalShowingStartsAppWidgetHost() =
+ with(kosmos) {
+ testScope.runTest {
+ setCommunalAvailable(true)
+ communalInteractor.setEditModeOpen(false)
+ verify(appWidgetHost, never()).startListening()
+
+ underTest.start()
+ runCurrent()
+
+ verify(appWidgetHost).startListening()
+ verify(appWidgetHost, never()).stopListening()
+
+ setCommunalAvailable(false)
+ runCurrent()
+
+ verify(appWidgetHost).stopListening()
+ }
+ }
+
+ @Test
+ fun communalAndEditModeNotShowingNeverStartListening() =
+ with(kosmos) {
+ testScope.runTest {
+ setCommunalAvailable(false)
+ communalInteractor.setEditModeOpen(false)
+
+ underTest.start()
+ runCurrent()
+
+ verify(appWidgetHost, never()).startListening()
+ verify(appWidgetHost, never()).stopListening()
+ }
+ }
+
+ private suspend fun setCommunalAvailable(available: Boolean) =
+ with(kosmos) {
+ fakeKeyguardRepository.setIsEncryptedOrLockdown(false)
+ fakeUserRepository.setSelectedUserInfo(MAIN_USER_INFO)
+ fakeKeyguardRepository.setKeyguardShowing(true)
+ fakeCommunalRepository.setCommunalEnabledState(available)
+ }
+
+ private companion object {
+ val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
index 6a14220e6a42..6808f5d643a9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
@@ -38,6 +38,7 @@ import androidx.test.filters.SmallTest
import com.android.internal.logging.InstanceId.fakeInstanceId
import com.android.internal.logging.UiEventLogger
import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.Flags as AConfigFlags
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository
import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository
@@ -62,7 +63,6 @@ import com.android.systemui.display.data.repository.FakeDisplayRepository
import com.android.systemui.display.data.repository.display
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags.KEYGUARD_WM_STATE_REFACTOR
import com.android.systemui.keyguard.data.repository.BiometricType
import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.FakeCommandQueue
@@ -194,7 +194,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
biometricSettingsRepository = FakeBiometricSettingsRepository()
deviceEntryFingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository()
trustRepository = FakeTrustRepository()
- featureFlags = FakeFeatureFlags().apply { set(KEYGUARD_WM_STATE_REFACTOR, false) }
+ featureFlags = FakeFeatureFlags()
powerRepository = FakePowerRepository()
powerInteractor =
@@ -252,6 +252,10 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
.thenReturn(listOf(createFaceSensorProperties(supportsFaceDetection = true)))
whenever(bypassController.bypassEnabled).thenReturn(true)
underTest = createDeviceEntryFaceAuthRepositoryImpl(faceManager, bypassController)
+
+ mSetFlagsRule.disableFlags(
+ AConfigFlags.FLAG_KEYGUARD_WM_STATE_REFACTOR,
+ )
}
private fun createDeviceEntryFaceAuthRepositoryImpl(
@@ -301,7 +305,6 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
faceAuthBuffer,
keyguardTransitionInteractor,
displayStateInteractor,
- featureFlags,
dumpManager,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractorTest.kt
new file mode 100644
index 000000000000..88ad3f37dacd
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractorTest.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.deviceentry.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AuthRippleInteractorTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val deviceEntrySourceInteractor = kosmos.deviceEntrySourceInteractor
+ private val fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository
+ private val keyguardRepository = kosmos.fakeKeyguardRepository
+ private val underTest = kosmos.authRippleInteractor
+
+ @Test
+ fun enteringDeviceFromDeviceEntryIcon_udfpsNotSupported_doesNotShowAuthRipple() =
+ testScope.runTest {
+ val showUnlockRipple by collectLastValue(underTest.showUnlockRipple)
+ fingerprintPropertyRepository.supportsRearFps()
+ keyguardRepository.setKeyguardDismissible(true)
+ runCurrent()
+ deviceEntrySourceInteractor.attemptEnterDeviceFromDeviceEntryIcon()
+ assertThat(showUnlockRipple).isNull()
+ }
+
+ @Test
+ fun enteringDeviceFromDeviceEntryIcon_udfpsSupported_showsAuthRipple() =
+ testScope.runTest {
+ val showUnlockRipple by collectLastValue(underTest.showUnlockRipple)
+ fingerprintPropertyRepository.supportsUdfps()
+ keyguardRepository.setKeyguardDismissible(true)
+ runCurrent()
+ deviceEntrySourceInteractor.attemptEnterDeviceFromDeviceEntryIcon()
+ assertThat(showUnlockRipple).isEqualTo(BiometricUnlockSource.FINGERPRINT_SENSOR)
+ }
+
+ @Test
+ fun faceUnlocked_showsAuthRipple() =
+ testScope.runTest {
+ val showUnlockRipple by collectLastValue(underTest.showUnlockRipple)
+ keyguardRepository.setBiometricUnlockSource(BiometricUnlockSource.FACE_SENSOR)
+ keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK)
+ assertThat(showUnlockRipple).isEqualTo(BiometricUnlockSource.FACE_SENSOR)
+ }
+
+ @Test
+ fun fingerprintUnlocked_showsAuthRipple() =
+ testScope.runTest {
+ val showUnlockRippleFromBiometricUnlock by collectLastValue(underTest.showUnlockRipple)
+ keyguardRepository.setBiometricUnlockSource(BiometricUnlockSource.FINGERPRINT_SENSOR)
+ keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK)
+ assertThat(showUnlockRippleFromBiometricUnlock)
+ .isEqualTo(BiometricUnlockSource.FINGERPRINT_SENSOR)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractorTest.kt
new file mode 100644
index 000000000000..d216fa0d0eff
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractorTest.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.deviceentry.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DeviceEntrySourceInteractorTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val keyguardRepository = kosmos.fakeKeyguardRepository
+ private val underTest = kosmos.deviceEntrySourceInteractor
+
+ @Test
+ fun deviceEntryFromFaceUnlock() =
+ testScope.runTest {
+ val deviceEntryFromBiometricAuthentication by
+ collectLastValue(underTest.deviceEntryFromBiometricSource)
+ keyguardRepository.setBiometricUnlockSource(BiometricUnlockSource.FACE_SENSOR)
+ keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK)
+ runCurrent()
+ assertThat(deviceEntryFromBiometricAuthentication)
+ .isEqualTo(BiometricUnlockSource.FACE_SENSOR)
+ }
+
+ @Test
+ fun deviceEntryFromFingerprintUnlock() = runTest {
+ val deviceEntryFromBiometricAuthentication by
+ collectLastValue(underTest.deviceEntryFromBiometricSource)
+ keyguardRepository.setBiometricUnlockSource(BiometricUnlockSource.FINGERPRINT_SENSOR)
+ keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK)
+ runCurrent()
+ assertThat(deviceEntryFromBiometricAuthentication)
+ .isEqualTo(BiometricUnlockSource.FINGERPRINT_SENSOR)
+ }
+
+ @Test
+ fun noDeviceEntry() = runTest {
+ val deviceEntryFromBiometricAuthentication by
+ collectLastValue(underTest.deviceEntryFromBiometricSource)
+ keyguardRepository.setBiometricUnlockSource(BiometricUnlockSource.FINGERPRINT_SENSOR)
+ // doesn't dismiss keyguard:
+ keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.ONLY_WAKE)
+ runCurrent()
+ assertThat(deviceEntryFromBiometricAuthentication).isNull()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index dc8b97abbfe8..c01c79dd2601 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -238,10 +238,10 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
}
@Test
- fun isKeyguardUnlocked() =
+ fun isKeyguardDismissible() =
testScope.runTest {
whenever(keyguardStateController.isUnlocked).thenReturn(false)
- val isKeyguardUnlocked by collectLastValue(underTest.isKeyguardUnlocked)
+ val isKeyguardUnlocked by collectLastValue(underTest.isKeyguardDismissible)
runCurrent()
assertThat(isKeyguardUnlocked).isFalse()
@@ -574,7 +574,7 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
@Test
fun isEncryptedOrLockdown() =
- testScope.runTest {
+ TestScope(mainDispatcher).runTest {
whenever(userTracker.userId).thenReturn(0)
whenever(keyguardUpdateMonitor.isEncryptedOrLockdown(0)).thenReturn(true)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/AutoAddSettingsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/AutoAddSettingsRepositoryTest.kt
index d277fcab3690..1545e7444ffb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/AutoAddSettingsRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/AutoAddSettingsRepositoryTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.qs.pipeline.data.repository
+import android.platform.test.annotations.EnabledOnRavenwood
import android.provider.Settings
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -38,6 +39,7 @@ import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
+@EnabledOnRavenwood
@RunWith(AndroidJUnit4::class)
class AutoAddSettingsRepositoryTest : SysuiTestCase() {
private val secureSettings = FakeSettings()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/CustomTileAddedSharedPreferencesRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/CustomTileAddedSharedPreferencesRepositoryTest.kt
index 3db676d68f42..ee7a97a0846a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/CustomTileAddedSharedPreferencesRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/CustomTileAddedSharedPreferencesRepositoryTest.kt
@@ -18,6 +18,7 @@ package com.android.systemui.qs.pipeline.data.repository
import android.content.ComponentName
import android.content.SharedPreferences
+import android.platform.test.annotations.EnabledOnRavenwood
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -29,6 +30,7 @@ import org.junit.Test
import org.junit.runner.RunWith
@SmallTest
+@EnabledOnRavenwood
@RunWith(AndroidJUnit4::class)
class CustomTileAddedSharedPreferencesRepositoryTest : SysuiTestCase() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt
index eb845b2b423c..d9f24b36dac2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt
@@ -35,7 +35,6 @@ import com.android.systemui.common.data.repository.fakePackageChangeRepository
import com.android.systemui.common.data.repository.packageChangeRepository
import com.android.systemui.common.data.shared.model.PackageChangeModel
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
@@ -44,6 +43,8 @@ import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@@ -52,6 +53,7 @@ import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
import org.mockito.MockitoAnnotations
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
@@ -82,7 +84,7 @@ class InstalledTilesComponentRepositoryImplTest : SysuiTestCase() {
underTest =
InstalledTilesComponentRepositoryImpl(
context,
- kosmos.testDispatcher,
+ testScope.backgroundScope,
kosmos.packageChangeRepository
)
}
@@ -103,6 +105,7 @@ class InstalledTilesComponentRepositoryImplTest : SysuiTestCase() {
.thenReturn(listOf(resolveInfo))
val componentNames by collectLastValue(underTest.getInstalledTilesComponents(userId))
+ runCurrent()
assertThat(componentNames).containsExactly(TEST_COMPONENT)
}
@@ -115,6 +118,8 @@ class InstalledTilesComponentRepositoryImplTest : SysuiTestCase() {
ResolveInfo(TEST_COMPONENT, hasPermission = true, defaultEnabled = true)
val componentNames by collectLastValue(underTest.getInstalledTilesComponents(userId))
+ runCurrent()
+
assertThat(componentNames).isEmpty()
whenever(
@@ -126,6 +131,7 @@ class InstalledTilesComponentRepositoryImplTest : SysuiTestCase() {
)
.thenReturn(listOf(resolveInfo))
kosmos.fakePackageChangeRepository.notifyChange(PackageChangeModel.Empty)
+ runCurrent()
assertThat(componentNames).containsExactly(TEST_COMPONENT)
}
@@ -146,6 +152,8 @@ class InstalledTilesComponentRepositoryImplTest : SysuiTestCase() {
.thenReturn(listOf(resolveInfo))
val componentNames by collectLastValue(underTest.getInstalledTilesComponents(userId))
+ runCurrent()
+
assertThat(componentNames).isEmpty()
}
@@ -165,6 +173,8 @@ class InstalledTilesComponentRepositoryImplTest : SysuiTestCase() {
.thenReturn(listOf(resolveInfo))
val componentNames by collectLastValue(underTest.getInstalledTilesComponents(userId))
+ runCurrent()
+
assertThat(componentNames).isEmpty()
}
@@ -210,10 +220,31 @@ class InstalledTilesComponentRepositoryImplTest : SysuiTestCase() {
.thenReturn(listOf(resolveInfo))
val componentNames by collectLastValue(underTest.getInstalledTilesComponents(userId))
+ runCurrent()
assertThat(componentNames).containsExactly(TEST_COMPONENT)
}
+ @Test
+ fun loadComponentsForSameUserTwice_returnsSameFlow() =
+ testScope.runTest {
+ val flowForUser1 = underTest.getInstalledTilesComponents(1)
+ val flowForUser1TheSecondTime = underTest.getInstalledTilesComponents(1)
+ runCurrent()
+
+ assertThat(flowForUser1TheSecondTime).isEqualTo(flowForUser1)
+ }
+
+ @Test
+ fun loadComponentsForDifferentUsers_returnsDifferentFlow() =
+ testScope.runTest {
+ val flowForUser1 = underTest.getInstalledTilesComponents(1)
+ val flowForUser2 = underTest.getInstalledTilesComponents(2)
+ runCurrent()
+
+ assertThat(flowForUser2).isNotEqualTo(flowForUser1)
+ }
+
companion object {
private val INTENT = Intent(TileService.ACTION_QS_TILE)
private val FLAGS =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
index f7c3b213730c..3418977c3211 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
@@ -16,6 +16,7 @@
package com.android.systemui.qs.pipeline.data.repository
+import android.platform.test.annotations.EnabledOnRavenwood
import android.provider.Settings
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -39,6 +40,7 @@ import org.mockito.Mock
import org.mockito.MockitoAnnotations
@SmallTest
+@EnabledOnRavenwood
@RunWith(AndroidJUnit4::class)
@OptIn(ExperimentalCoroutinesApi::class)
class TileSpecSettingsRepositoryTest : SysuiTestCase() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/TilesSettingConverterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/TilesSettingConverterTest.kt
index 9516c2181ac0..9e99fc006acc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/TilesSettingConverterTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/TilesSettingConverterTest.kt
@@ -1,5 +1,6 @@
package com.android.systemui.qs.pipeline.data.repository
+import android.platform.test.annotations.EnabledOnRavenwood
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -9,6 +10,7 @@ import org.junit.Test
import org.junit.runner.RunWith
@SmallTest
+@EnabledOnRavenwood
@RunWith(AndroidJUnit4::class)
class TilesSettingConverterTest : SysuiTestCase() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/UserAutoAddRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/UserAutoAddRepositoryTest.kt
index 36e860e37ffa..1ca3c0637824 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/UserAutoAddRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/UserAutoAddRepositoryTest.kt
@@ -1,5 +1,6 @@
package com.android.systemui.qs.pipeline.data.repository
+import android.platform.test.annotations.EnabledOnRavenwood
import android.provider.Settings
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -24,6 +25,7 @@ import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
+@EnabledOnRavenwood
@RunWith(AndroidJUnit4::class)
class UserAutoAddRepositoryTest : SysuiTestCase() {
private val secureSettings = FakeSettings()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepositoryTest.kt
index d4a9fabd6806..58fc10917d44 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepositoryTest.kt
@@ -1,5 +1,6 @@
package com.android.systemui.qs.pipeline.data.repository
+import android.platform.test.annotations.EnabledOnRavenwood
import android.provider.Settings
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -23,6 +24,7 @@ import org.mockito.Mock
import org.mockito.MockitoAnnotations
@SmallTest
+@EnabledOnRavenwood
@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidJUnit4::class)
class UserTileSpecRepositoryTest : SysuiTestCase() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/restoreprocessors/WorkTileRestoreProcessorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/restoreprocessors/WorkTileRestoreProcessorTest.kt
index 30d1822b28da..57bb77f46e40 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/restoreprocessors/WorkTileRestoreProcessorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/restoreprocessors/WorkTileRestoreProcessorTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.qs.pipeline.data.restoreprocessors
import android.os.UserHandle
+import android.platform.test.annotations.EnabledOnRavenwood
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -31,6 +32,7 @@ import org.junit.Test
import org.junit.runner.RunWith
@SmallTest
+@EnabledOnRavenwood
@RunWith(AndroidJUnit4::class)
@OptIn(ExperimentalCoroutinesApi::class)
class WorkTileRestoreProcessorTest : SysuiTestCase() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableListTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableListTest.kt
new file mode 100644
index 000000000000..311122d7f8d5
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableListTest.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import android.content.ComponentName
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.view.accessibility.Flags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.accessibility.AccessibilityShortcutController
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.ColorCorrectionTile
+import com.android.systemui.qs.tiles.ColorInversionTile
+import com.android.systemui.qs.tiles.OneHandedModeTile
+import com.android.systemui.qs.tiles.ReduceBrightColorsTile
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class A11yShortcutAutoAddableListTest : SysuiTestCase() {
+
+ private val factory =
+ object : A11yShortcutAutoAddable.Factory {
+ override fun create(
+ spec: TileSpec,
+ componentName: ComponentName
+ ): A11yShortcutAutoAddable {
+ return A11yShortcutAutoAddable(mock(), mock(), spec, componentName)
+ }
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
+ fun getA11yShortcutAutoAddables_withA11yQsShortcutFlagOff_emptyResult() {
+ val autoAddables = A11yShortcutAutoAddableList.getA11yShortcutAutoAddables(factory)
+
+ assertThat(autoAddables).isEmpty()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
+ fun getA11yShortcutAutoAddables_withA11yQsShortcutFlagOn_correctAutoAddables() {
+ val expected =
+ setOf(
+ factory.create(
+ TileSpec.create(ColorCorrectionTile.TILE_SPEC),
+ AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME
+ ),
+ factory.create(
+ TileSpec.create(ColorInversionTile.TILE_SPEC),
+ AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME
+ ),
+ factory.create(
+ TileSpec.create(OneHandedModeTile.TILE_SPEC),
+ AccessibilityShortcutController.ONE_HANDED_COMPONENT_NAME
+ ),
+ factory.create(
+ TileSpec.create(ReduceBrightColorsTile.TILE_SPEC),
+ AccessibilityShortcutController.REDUCE_BRIGHT_COLORS_COMPONENT_NAME
+ ),
+ )
+
+ val autoAddables = A11yShortcutAutoAddableList.getA11yShortcutAutoAddables(factory)
+
+ assertThat(autoAddables).isNotEmpty()
+ assertThat(autoAddables).containsExactlyElementsIn(expected)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableTest.kt
new file mode 100644
index 000000000000..3b33a43d9341
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableTest.kt
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import android.content.ComponentName
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.accessibility.data.repository.FakeAccessibilityQsShortcutsRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class A11yShortcutAutoAddableTest : SysuiTestCase() {
+ private val testDispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+
+ private val a11yQsShortcutsRepository = FakeAccessibilityQsShortcutsRepository()
+ private val underTest =
+ A11yShortcutAutoAddable(a11yQsShortcutsRepository, testDispatcher, SPEC, TARGET_COMPONENT)
+
+ @Test
+ fun settingNotSet_noSignal() =
+ testScope.runTest {
+ val signal by collectLastValue(underTest.autoAddSignal(USER_ID))
+
+ assertThat(signal).isNull() // null means no emitted value
+ }
+
+ @Test
+ fun settingSetWithTarget_addSignal() =
+ testScope.runTest {
+ val signal by collectLastValue(underTest.autoAddSignal(USER_ID))
+ assertThat(signal).isNull()
+
+ a11yQsShortcutsRepository.setA11yQsShortcutTargets(
+ USER_ID,
+ setOf(TARGET_COMPONENT_FLATTEN)
+ )
+
+ assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC))
+ }
+
+ @Test
+ fun settingSetWithoutTarget_removeSignal() =
+ testScope.runTest {
+ val signal by collectLastValue(flow = underTest.autoAddSignal(USER_ID))
+ assertThat(signal).isNull()
+
+ a11yQsShortcutsRepository.setA11yQsShortcutTargets(
+ USER_ID,
+ setOf(OTHER_COMPONENT_FLATTEN)
+ )
+
+ assertThat(signal).isEqualTo(AutoAddSignal.Remove(SPEC))
+ }
+
+ @Test
+ fun settingSetWithMultipleComponents_containsTarget_addSignal() =
+ testScope.runTest {
+ val signal by collectLastValue(underTest.autoAddSignal(USER_ID))
+ assertThat(signal).isNull()
+
+ a11yQsShortcutsRepository.setA11yQsShortcutTargets(
+ USER_ID,
+ setOf(OTHER_COMPONENT_FLATTEN, TARGET_COMPONENT_FLATTEN)
+ )
+
+ assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC))
+ }
+
+ @Test
+ fun settingSetWithMultipleComponents_doesNotContainTarget_removeSignal() =
+ testScope.runTest {
+ val signal by collectLastValue(underTest.autoAddSignal(USER_ID))
+ assertThat(signal).isNull()
+
+ a11yQsShortcutsRepository.setA11yQsShortcutTargets(
+ USER_ID,
+ setOf(OTHER_COMPONENT_FLATTEN, OTHER_COMPONENT_FLATTEN)
+ )
+
+ assertThat(signal).isEqualTo(AutoAddSignal.Remove(SPEC))
+ }
+
+ @Test
+ fun multipleChangesWithTarget_onlyOneAddSignal() =
+ testScope.runTest {
+ val signals by collectValues(underTest.autoAddSignal(USER_ID))
+ assertThat(signals).isEmpty()
+
+ repeat(3) {
+ a11yQsShortcutsRepository.setA11yQsShortcutTargets(
+ USER_ID,
+ setOf(TARGET_COMPONENT_FLATTEN)
+ )
+ }
+
+ assertThat(signals.size).isEqualTo(1)
+ assertThat(signals[0]).isEqualTo(AutoAddSignal.Add(SPEC))
+ }
+
+ @Test
+ fun multipleChangesWithoutTarget_onlyOneRemoveSignal() =
+ testScope.runTest {
+ val signals by collectValues(underTest.autoAddSignal(USER_ID))
+ assertThat(signals).isEmpty()
+
+ repeat(3) {
+ a11yQsShortcutsRepository.setA11yQsShortcutTargets(
+ USER_ID,
+ setOf("$OTHER_COMPONENT_FLATTEN$it")
+ )
+ }
+
+ assertThat(signals.size).isEqualTo(1)
+ assertThat(signals[0]).isEqualTo(AutoAddSignal.Remove(SPEC))
+ }
+
+ @Test
+ fun settingSetWithTargetForUsers_onlySignalInThatUser() =
+ testScope.runTest {
+ val otherUserId = USER_ID + 1
+ val signalTargetUser by collectLastValue(underTest.autoAddSignal(USER_ID))
+ val signalOtherUser by collectLastValue(underTest.autoAddSignal(otherUserId))
+ assertThat(signalTargetUser).isNull()
+ assertThat(signalOtherUser).isNull()
+
+ a11yQsShortcutsRepository.setA11yQsShortcutTargets(
+ USER_ID,
+ setOf(TARGET_COMPONENT_FLATTEN)
+ )
+
+ assertThat(signalTargetUser).isEqualTo(AutoAddSignal.Add(SPEC))
+ assertThat(signalOtherUser).isNull()
+ }
+
+ @Test
+ fun strategyAlways() {
+ assertThat(underTest.autoAddTracking).isEqualTo(AutoAddTracking.Always)
+ }
+
+ companion object {
+ private val SPEC = TileSpec.create("spec")
+ private val TARGET_COMPONENT = ComponentName("FakePkgName", "FakeClassName")
+ private val TARGET_COMPONENT_FLATTEN = TARGET_COMPONENT.flattenToString()
+ private val OTHER_COMPONENT_FLATTEN =
+ ComponentName("FakePkgName", "OtherClassName").flattenToString()
+ private const val USER_ID = 0
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingListTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingListTest.kt
index 4454a3cb15fc..f185ed57de74 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingListTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingListTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.qs.pipeline.domain.autoaddable
import android.content.ComponentName
+import android.platform.test.annotations.EnabledOnRavenwood
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.res.R
@@ -28,6 +29,7 @@ import org.junit.Test
import org.junit.runner.RunWith
@SmallTest
+@EnabledOnRavenwood
@RunWith(AndroidJUnit4::class)
class AutoAddableSettingListTest : SysuiTestCase() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingTest.kt
index d153e9d1d361..cfb84a7a6709 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.qs.pipeline.domain.autoaddable
+import android.platform.test.annotations.EnabledOnRavenwood
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -35,6 +36,7 @@ import org.junit.runner.RunWith
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
+@EnabledOnRavenwood
@RunWith(AndroidJUnit4::class)
class AutoAddableSettingTest : SysuiTestCase() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CallbackControllerAutoAddableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CallbackControllerAutoAddableTest.kt
index ec139e4c515e..ea8d87358172 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CallbackControllerAutoAddableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CallbackControllerAutoAddableTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.qs.pipeline.domain.autoaddable
+import android.platform.test.annotations.EnabledOnRavenwood
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -35,6 +36,7 @@ import org.junit.runner.RunWith
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
+@EnabledOnRavenwood
@RunWith(AndroidJUnit4::class)
class CallbackControllerAutoAddableTest : SysuiTestCase() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CastAutoAddableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CastAutoAddableTest.kt
index 4fae532d4174..9bb591e1ae7c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CastAutoAddableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CastAutoAddableTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.qs.pipeline.domain.autoaddable
+import android.platform.test.annotations.EnabledOnRavenwood
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -41,6 +42,7 @@ import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
+@EnabledOnRavenwood
@RunWith(AndroidJUnit4::class)
class CastAutoAddableTest : SysuiTestCase() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DataSaverAutoAddableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DataSaverAutoAddableTest.kt
index 9e2d1f885e2d..b925b27bfcc3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DataSaverAutoAddableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DataSaverAutoAddableTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.qs.pipeline.domain.autoaddable
+import android.platform.test.annotations.EnabledOnRavenwood
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -40,6 +41,7 @@ import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
+@EnabledOnRavenwood
@RunWith(AndroidJUnit4::class)
class DataSaverAutoAddableTest : SysuiTestCase() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DeviceControlsAutoAddableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DeviceControlsAutoAddableTest.kt
index 0116bd9575d8..188c6a949104 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DeviceControlsAutoAddableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DeviceControlsAutoAddableTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.qs.pipeline.domain.autoaddable
+import android.platform.test.annotations.EnabledOnRavenwood
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -43,6 +44,7 @@ import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
+@EnabledOnRavenwood
@RunWith(AndroidJUnit4::class)
class DeviceControlsAutoAddableTest : SysuiTestCase() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/HotspotAutoAddableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/HotspotAutoAddableTest.kt
index e7ea9a66450c..02699ddad089 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/HotspotAutoAddableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/HotspotAutoAddableTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.qs.pipeline.domain.autoaddable
+import android.platform.test.annotations.EnabledOnRavenwood
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -40,6 +41,7 @@ import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
+@EnabledOnRavenwood
@RunWith(AndroidJUnit4::class)
class HotspotAutoAddableTest : SysuiTestCase() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddableTest.kt
index 19ac63c36cab..6d6fd754341d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddableTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.qs.pipeline.domain.autoaddable
+import android.platform.test.annotations.EnabledOnRavenwood
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -43,6 +44,7 @@ import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
+@EnabledOnRavenwood
@RunWith(AndroidJUnit4::class)
class ReduceBrightColorsAutoAddableTest : SysuiTestCase() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/SafetyCenterAutoAddableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/SafetyCenterAutoAddableTest.kt
index d645ee34619b..633e494b3195 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/SafetyCenterAutoAddableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/SafetyCenterAutoAddableTest.kt
@@ -18,6 +18,7 @@ package com.android.systemui.qs.pipeline.domain.autoaddable
import android.content.ComponentName
import android.content.pm.PackageManager
+import android.platform.test.annotations.EnabledOnRavenwood
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.res.R
@@ -51,6 +52,7 @@ import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
+@EnabledOnRavenwood
@RunWith(AndroidJUnit4::class)
class SafetyCenterAutoAddableTest : SysuiTestCase() {
private val testDispatcher = StandardTestDispatcher()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WalletAutoAddableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WalletAutoAddableTest.kt
index 83ff35d8022d..c5c76eb77152 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WalletAutoAddableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WalletAutoAddableTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.qs.pipeline.domain.autoaddable
+import android.platform.test.annotations.EnabledOnRavenwood
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -37,6 +38,7 @@ import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
+@EnabledOnRavenwood
@RunWith(AndroidJUnit4::class)
class WalletAutoAddableTest : SysuiTestCase() {
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 54b03a90229b..2ea12ef4c88f 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
@@ -16,6 +16,7 @@
package com.android.systemui.qs.pipeline.domain.interactor
+import android.platform.test.annotations.EnabledOnRavenwood
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -46,6 +47,7 @@ import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@SmallTest
+@EnabledOnRavenwood
@RunWith(AndroidJUnit4::class)
@OptIn(ExperimentalCoroutinesApi::class)
class AutoAddInteractorTest : SysuiTestCase() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractorImplTest.kt
index 0d9711588a1f..d38c19b5bc4a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractorImplTest.kt
@@ -15,6 +15,7 @@
*/
package com.android.systemui.qs.pipeline.domain.interactor
+import android.platform.test.annotations.EnabledOnRavenwood
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -28,6 +29,7 @@ import org.mockito.MockitoAnnotations
@RunWith(AndroidJUnit4::class)
@SmallTest
+@EnabledOnRavenwood
class PanelInteractorImplTest : SysuiTestCase() {
@Mock private lateinit var shadeController: ShadeController
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractorTest.kt
index b2a9783d2e60..0b3144ab3084 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractorTest.kt
@@ -1,5 +1,6 @@
package com.android.systemui.qs.pipeline.domain.interactor
+import android.platform.test.annotations.EnabledOnRavenwood
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -26,6 +27,7 @@ import org.mockito.Mockito.inOrder
@RunWith(AndroidJUnit4::class)
@SmallTest
+@EnabledOnRavenwood
class RestoreReconciliationInteractorTest : SysuiTestCase() {
private val tileSpecRepository = FakeTileSpecRepository()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/shared/TileSpecTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/shared/TileSpecTest.kt
index 558e7694b54c..869ab6c24fce 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/shared/TileSpecTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/shared/TileSpecTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.qs.pipeline.shared
import android.content.ComponentName
+import android.platform.test.annotations.EnabledOnRavenwood
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -25,6 +26,7 @@ import org.junit.Test
import org.junit.runner.RunWith
@SmallTest
+@EnabledOnRavenwood
@RunWith(AndroidJUnit4::class)
class TileSpecTest : SysuiTestCase() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerTest.kt
index c1049773cabf..bf48784407b8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerTest.kt
@@ -24,6 +24,7 @@ import android.content.pm.PackageManager
import android.content.pm.PackageManager.ResolveInfoFlags
import android.content.pm.ResolveInfo
import android.os.UserHandle
+import android.platform.test.annotations.EnabledOnRavenwood
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -44,6 +45,7 @@ import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@SmallTest
+@EnabledOnRavenwood
@RunWith(AndroidJUnit4::class)
class QSTileIntentUserInputHandlerTest : SysuiTestCase() {
@Mock private lateinit var packageManager: PackageManager
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/analytics/QSTileAnalyticsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/analytics/QSTileAnalyticsTest.kt
index 9861606fd1b1..fd09e3ca4bb8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/analytics/QSTileAnalyticsTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/analytics/QSTileAnalyticsTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.qs.tiles.base.analytics
+import android.platform.test.annotations.EnabledOnRavenwood
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.logging.InstanceId
@@ -33,6 +34,7 @@ import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@SmallTest
+@EnabledOnRavenwood
@RunWith(AndroidJUnit4::class)
class QSTileAnalyticsTest : SysuiTestCase() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorTest.kt
index 2bdc154dd885..a1f885c64312 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorTest.kt
@@ -20,6 +20,7 @@ import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.os.UserHandle
+import android.platform.test.annotations.EnabledOnRavenwood
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.settingslib.RestrictedLockUtils
@@ -45,6 +46,7 @@ import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@SmallTest
+@EnabledOnRavenwood
@RunWith(AndroidJUnit4::class)
class DisabledByPolicyInteractorTest : SysuiTestCase() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/airplate/domain/interactor/AirplaneModeTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/airplate/domain/interactor/AirplaneModeTileDataInteractorTest.kt
index 937744db500e..89b9b7f30297 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/airplate/domain/interactor/AirplaneModeTileDataInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/airplate/domain/interactor/AirplaneModeTileDataInteractorTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.qs.tiles.impl.airplate.domain.interactor
import android.os.UserHandle
+import android.platform.test.annotations.EnabledOnRavenwood
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -36,6 +37,7 @@ import org.junit.runner.RunWith
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
+@EnabledOnRavenwood
@RunWith(AndroidJUnit4::class)
class AirplaneModeTileDataInteractorTest : SysuiTestCase() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/airplate/domain/interactor/AirplaneModeTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/airplate/domain/interactor/AirplaneModeTileUserActionInteractorTest.kt
index 81bde8188f5e..8982d810ad8a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/airplate/domain/interactor/AirplaneModeTileUserActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/airplate/domain/interactor/AirplaneModeTileUserActionInteractorTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.qs.tiles.impl.airplate.domain.interactor
+import android.platform.test.annotations.EnabledOnRavenwood
import android.provider.Settings
import android.telephony.TelephonyManager
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -37,6 +38,7 @@ import org.junit.Test
import org.junit.runner.RunWith
@SmallTest
+@EnabledOnRavenwood
@RunWith(AndroidJUnit4::class)
class AirplaneModeTileUserActionInteractorTest : SysuiTestCase() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/interactor/ColorCorrectionTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/interactor/ColorCorrectionTileDataInteractorTest.kt
index 8c612acad887..abaf808f3f91 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/interactor/ColorCorrectionTileDataInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/interactor/ColorCorrectionTileDataInteractorTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.qs.tiles.impl.colorcorrection.domain.interactor
import android.os.UserHandle
+import android.platform.test.annotations.EnabledOnRavenwood
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -35,6 +36,7 @@ import org.junit.runner.RunWith
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
+@EnabledOnRavenwood
@RunWith(AndroidJUnit4::class)
class ColorCorrectionTileDataInteractorTest : SysuiTestCase() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/interactor/ColorCorrectionTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/interactor/ColorCorrectionTileUserActionInteractorTest.kt
index 3049cc079a1c..3bc53b273e89 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/interactor/ColorCorrectionTileUserActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/interactor/ColorCorrectionTileUserActionInteractorTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.qs.tiles.impl.colorcorrection.domain.interactor
import android.os.UserHandle
+import android.platform.test.annotations.EnabledOnRavenwood
import android.provider.Settings
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -32,6 +33,7 @@ import org.junit.Test
import org.junit.runner.RunWith
@SmallTest
+@EnabledOnRavenwood
@RunWith(AndroidJUnit4::class)
class ColorCorrectionTileUserActionInteractorTest : SysuiTestCase() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractorTest.kt
index 7f7490d9ecc3..a9e39354d131 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractorTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.qs.tiles.impl.flashlight.domain.interactor
import android.os.UserHandle
+import android.platform.test.annotations.EnabledOnRavenwood
import android.testing.LeakCheck
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -37,6 +38,7 @@ import org.junit.runner.RunWith
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
+@EnabledOnRavenwood
@RunWith(AndroidJUnit4::class)
class FlashlightTileDataInteractorTest : SysuiTestCase() {
private lateinit var controller: FakeFlashlightController
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/interactor/ColorInversionTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/interactor/ColorInversionTileDataInteractorTest.kt
index 24c7bfb2d324..75b07eedab95 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/interactor/ColorInversionTileDataInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/interactor/ColorInversionTileDataInteractorTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.qs.tiles.impl.inversion.domain.interactor
import android.os.UserHandle
+import android.platform.test.annotations.EnabledOnRavenwood
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -35,6 +36,7 @@ import org.junit.runner.RunWith
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
+@EnabledOnRavenwood
@RunWith(AndroidJUnit4::class)
class ColorInversionTileDataInteractorTest : SysuiTestCase() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/interactor/ColorInversionUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/interactor/ColorInversionUserActionInteractorTest.kt
index 99bae18d43d4..f574f793fbf2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/interactor/ColorInversionUserActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/interactor/ColorInversionUserActionInteractorTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.qs.tiles.impl.inversion.domain.interactor
import android.os.UserHandle
+import android.platform.test.annotations.EnabledOnRavenwood
import android.provider.Settings
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -32,6 +33,7 @@ import org.junit.Test
import org.junit.runner.RunWith
@SmallTest
+@EnabledOnRavenwood
@RunWith(AndroidJUnit4::class)
class ColorInversionUserActionInteractorTest : SysuiTestCase() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/interactor/LocationTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/interactor/LocationTileDataInteractorTest.kt
index 8fdc93be4ba2..9adf57ac458a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/interactor/LocationTileDataInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/interactor/LocationTileDataInteractorTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.qs.tiles.impl.location.interactor
import android.os.UserHandle
+import android.platform.test.annotations.EnabledOnRavenwood
import android.testing.LeakCheck
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -38,6 +39,7 @@ import org.junit.runner.RunWith
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
+@EnabledOnRavenwood
@RunWith(AndroidJUnit4::class)
class LocationTileDataInteractorTest : SysuiTestCase() {
private lateinit var controller: FakeLocationController
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/interactor/LocationTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/interactor/LocationTileUserActionInteractorTest.kt
index 0fb8ae697190..8b21cb4a97d5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/interactor/LocationTileUserActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/interactor/LocationTileUserActionInteractorTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.qs.tiles.impl.location.interactor
+import android.platform.test.annotations.EnabledOnRavenwood
import android.provider.Settings
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -42,6 +43,7 @@ import org.mockito.Mockito
import org.mockito.MockitoAnnotations
@SmallTest
+@EnabledOnRavenwood
@RunWith(AndroidJUnit4::class)
class LocationTileUserActionInteractorTest : SysuiTestCase() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverDialogDelegateTest.kt
index 4b9625107745..f24723a2a9f3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverDialogDelegateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverDialogDelegateTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.qs.tiles.impl.saver.domain
import android.content.SharedPreferences
+import android.platform.test.annotations.EnabledOnRavenwood
import android.testing.LeakCheck
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -37,6 +38,7 @@ import org.mockito.Mockito.verify
/** Test [DataSaverDialogDelegate]. */
@SmallTest
+@EnabledOnRavenwood
@RunWith(AndroidJUnit4::class)
class DataSaverDialogDelegateTest : SysuiTestCase() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileDataInteractorTest.kt
index 819bd03437f4..daee22d0c45c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileDataInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileDataInteractorTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.qs.tiles.impl.saver.domain.interactor
import android.os.UserHandle
+import android.platform.test.annotations.EnabledOnRavenwood
import android.testing.LeakCheck
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -36,6 +37,7 @@ import org.junit.runner.RunWith
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
+@EnabledOnRavenwood
@RunWith(AndroidJUnit4::class)
class DataSaverTileDataInteractorTest : SysuiTestCase() {
private val controller: FakeDataSaverController = FakeDataSaverController(LeakCheck())
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileUserActionInteractorTest.kt
index 004ec6250e7e..eea6d161d4ff 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileUserActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileUserActionInteractorTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.qs.tiles.impl.uimodenight.domain
import android.app.UiModeManager
+import android.platform.test.annotations.EnabledOnRavenwood
import android.provider.Settings
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -39,6 +40,7 @@ import org.mockito.Mockito.never
import org.mockito.Mockito.verify
@SmallTest
+@EnabledOnRavenwood
@RunWith(AndroidJUnit4::class)
class UiModeNightTileUserActionInteractorTest : SysuiTestCase() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProviderTest.kt
index 5eca8caa7d15..40971a87480d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProviderTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProviderTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.qs.tiles.viewmodel
+import android.platform.test.annotations.EnabledOnRavenwood
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -27,6 +28,7 @@ import org.junit.Test
import org.junit.runner.RunWith
@SmallTest
+@EnabledOnRavenwood
@RunWith(AndroidJUnit4::class)
class QSTileConfigProviderTest : SysuiTestCase() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt
index 3a0ebdbd6a17..a8bc8d6b36b6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.qs.tiles.viewmodel
import android.os.UserHandle
+import android.platform.test.annotations.EnabledOnRavenwood
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import com.android.systemui.SysuiTestCase
@@ -50,6 +51,7 @@ import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@MediumTest
+@EnabledOnRavenwood
@RunWith(AndroidJUnit4::class)
@OptIn(ExperimentalCoroutinesApi::class)
class QSTileViewModelTest : SysuiTestCase() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelUserInputTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelUserInputTest.kt
index 22fb152aee44..18cdd71e25b6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelUserInputTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelUserInputTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.qs.tiles.viewmodel
+import android.platform.test.annotations.EnabledOnRavenwood
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import com.android.settingslib.RestrictedLockUtils
@@ -53,6 +54,7 @@ import org.mockito.MockitoAnnotations
/** Tests all possible [QSTileUserAction]s. If you need */
@MediumTest
+@EnabledOnRavenwood
@RunWith(AndroidJUnit4::class)
@OptIn(ExperimentalCoroutinesApi::class)
class QSTileViewModelUserInputTest : SysuiTestCase() {
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 18a7320bf29d..d1bc686385a1 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
@@ -16,6 +16,7 @@
package com.android.systemui.qs.ui.adapter
+import android.platform.test.annotations.EnabledOnRavenwood
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -25,6 +26,7 @@ import org.junit.Test
import org.junit.runner.RunWith
@SmallTest
+@EnabledOnRavenwood
@RunWith(AndroidJUnit4::class)
class QSSceneAdapterTest : SysuiTestCase() {
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java
index 7274c0c65e69..452895745f95 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java
@@ -31,6 +31,7 @@ import android.content.res.Configuration;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.platform.test.ravenwood.RavenwoodRule;
import android.testing.TestableLooper.RunWithLooper;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -66,8 +67,11 @@ public class SystemUIDialogTest extends SysuiTestCase {
@Mock
private SystemUIDialog.Delegate mDelegate;
+ // TODO(b/292141694): build out Ravenwood support for DeviceFlagsValueProvider
+ // Ravenwood already has solid support for SetFlagsRule, but CheckFlagsRule will be added soon
@Rule
- public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+ public final CheckFlagsRule mCheckFlagsRule = RavenwoodRule.isOnRavenwood() ? null
+ : DeviceFlagsValueProvider.createCheckFlagsRule();
@Before
public void setup() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/kotlin/BooleanFlowOperatorsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/kotlin/BooleanFlowOperatorsTest.kt
new file mode 100644
index 000000000000..b4a0a37ec9ef
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/kotlin/BooleanFlowOperatorsTest.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.kotlin
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.util.kotlin.BooleanFlowOperators.and
+import com.android.systemui.util.kotlin.BooleanFlowOperators.not
+import com.android.systemui.util.kotlin.BooleanFlowOperators.or
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BooleanFlowOperatorsTest : SysuiTestCase() {
+
+ val kosmos = testKosmos()
+ val testScope = kosmos.testScope
+
+ @Test
+ fun and_allTrue_returnsTrue() =
+ testScope.runTest {
+ val result by collectLastValue(and(TRUE, TRUE))
+ assertThat(result).isTrue()
+ }
+
+ @Test
+ fun and_anyFalse_returnsFalse() =
+ testScope.runTest {
+ val result by collectLastValue(and(TRUE, FALSE, TRUE))
+ assertThat(result).isFalse()
+ }
+
+ @Test
+ fun and_allFalse_returnsFalse() =
+ testScope.runTest {
+ val result by collectLastValue(and(FALSE, FALSE, FALSE))
+ assertThat(result).isFalse()
+ }
+
+ @Test
+ fun or_allTrue_returnsTrue() =
+ testScope.runTest {
+ val result by collectLastValue(or(TRUE, TRUE))
+ assertThat(result).isTrue()
+ }
+
+ @Test
+ fun or_anyTrue_returnsTrue() =
+ testScope.runTest {
+ val result by collectLastValue(or(FALSE, TRUE, FALSE))
+ assertThat(result).isTrue()
+ }
+
+ @Test
+ fun or_allFalse_returnsFalse() =
+ testScope.runTest {
+ val result by collectLastValue(or(FALSE, FALSE, FALSE))
+ assertThat(result).isFalse()
+ }
+
+ @Test
+ fun not_true_returnsFalse() =
+ testScope.runTest {
+ val result by collectLastValue(not(TRUE))
+ assertThat(result).isFalse()
+ }
+
+ @Test
+ fun not_false_returnsFalse() =
+ testScope.runTest {
+ val result by collectLastValue(not(FALSE))
+ assertThat(result).isTrue()
+ }
+
+ private companion object {
+ val TRUE: Flow<Boolean>
+ get() = flowOf(true)
+ val FALSE: Flow<Boolean>
+ get() = flowOf(false)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModelTest.kt
new file mode 100644
index 000000000000..471c8d851879
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModelTest.kt
@@ -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 com.android.systemui.volume.panel.component.bottombar.ui.viewmodel
+
+import android.content.Intent
+import android.provider.Settings
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.activityStarter
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.volume.panel.volumePanelViewModel
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BottomBarViewModelTest : SysuiTestCase() {
+
+ @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
+
+ @Captor private lateinit var intentCaptor: ArgumentCaptor<Intent>
+
+ private val kosmos = testKosmos()
+
+ private lateinit var underTest: BottomBarViewModel
+
+ private fun initUnderTest() {
+ underTest = with(kosmos) { BottomBarViewModel(activityStarter, volumePanelViewModel) }
+ }
+
+ @Test
+ fun onDoneClicked_hidesPanel() {
+ with(kosmos) {
+ testScope.runTest {
+ initUnderTest()
+ underTest.onDoneClicked()
+ runCurrent()
+
+ val volumePanelState by collectLastValue(volumePanelViewModel.volumePanelState)
+ assertThat(volumePanelState!!.isVisible).isFalse()
+ }
+ }
+ }
+
+ @Test
+ fun onSettingsClicked_dismissesPanelAndNavigatesToSettings() {
+ with(kosmos) {
+ testScope.runTest {
+ initUnderTest()
+ underTest.onSettingsClicked()
+
+ runCurrent()
+
+ val volumePanelState by collectLastValue(volumePanelViewModel.volumePanelState)
+ assertThat(volumePanelState!!.isVisible).isFalse()
+ verify(activityStarter).startActivity(capture(intentCaptor), eq(true))
+ assertThat(intentCaptor.value.action).isEqualTo(Settings.ACTION_SOUND_SETTINGS)
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorImplTest.kt
new file mode 100644
index 000000000000..6256eece65dd
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorImplTest.kt
@@ -0,0 +1,122 @@
+/*
+ * 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.volume.panel.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.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.volume.panel.availableCriteria
+import com.android.systemui.volume.panel.criteriaByKey
+import com.android.systemui.volume.panel.defaultCriteria
+import com.android.systemui.volume.panel.domain.model.ComponentModel
+import com.android.systemui.volume.panel.enabledComponents
+import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey
+import com.android.systemui.volume.panel.unavailableCriteria
+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 ComponentsInteractorImplTest : SysuiTestCase() {
+
+ private val kosmos = Kosmos()
+
+ private lateinit var underTest: ComponentsInteractor
+
+ private fun initUnderTest() {
+ underTest =
+ with(kosmos) {
+ ComponentsInteractorImpl(
+ enabledComponents,
+ defaultCriteria,
+ testScope.backgroundScope,
+ criteriaByKey,
+ )
+ }
+ }
+
+ @Test
+ fun componentsAvailability_checked() {
+ with(kosmos) {
+ testScope.runTest {
+ enabledComponents =
+ setOf(
+ BOTTOM_BAR,
+ COMPONENT_1,
+ COMPONENT_2,
+ )
+ criteriaByKey =
+ mapOf(
+ BOTTOM_BAR to availableCriteria,
+ COMPONENT_1 to unavailableCriteria,
+ COMPONENT_2 to availableCriteria,
+ )
+ initUnderTest()
+
+ val components by collectLastValue(underTest.components)
+
+ assertThat(components)
+ .containsExactly(
+ ComponentModel(BOTTOM_BAR, true),
+ ComponentModel(COMPONENT_1, false),
+ ComponentModel(COMPONENT_2, true),
+ )
+ }
+ }
+ }
+
+ @Test
+ fun noCriteria_fallbackToDefaultCriteria() {
+ with(kosmos) {
+ testScope.runTest {
+ enabledComponents =
+ setOf(
+ BOTTOM_BAR,
+ COMPONENT_1,
+ COMPONENT_2,
+ )
+ criteriaByKey =
+ mapOf(
+ BOTTOM_BAR to availableCriteria,
+ COMPONENT_2 to availableCriteria,
+ )
+ defaultCriteria = unavailableCriteria
+ initUnderTest()
+
+ val components by collectLastValue(underTest.components)
+
+ assertThat(components)
+ .containsExactly(
+ ComponentModel(BOTTOM_BAR, true),
+ ComponentModel(COMPONENT_1, false),
+ ComponentModel(COMPONENT_2, true),
+ )
+ }
+ }
+ }
+
+ private companion object {
+ const val BOTTOM_BAR: VolumePanelComponentKey = "test_bottom_bar"
+ const val COMPONENT_1: VolumePanelComponentKey = "test_component:1"
+ const val COMPONENT_2: VolumePanelComponentKey = "test_component:2"
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/composable/ComponentsFactoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/composable/ComponentsFactoryTest.kt
new file mode 100644
index 000000000000..3dbf23e3dc58
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/composable/ComponentsFactoryTest.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.ui.composable
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.testKosmos
+import com.android.systemui.volume.panel.componentByKey
+import com.android.systemui.volume.panel.mockVolumePanelUiComponentProvider
+import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey
+import com.google.common.truth.Truth
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ComponentsFactoryTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+
+ private lateinit var underTest: ComponentsFactory
+
+ private fun initUnderTest() {
+ underTest = ComponentsFactory(kosmos.componentByKey)
+ }
+
+ @Test
+ fun existingComponent_created() {
+ kosmos.componentByKey = mapOf(TEST_COMPONENT to kosmos.mockVolumePanelUiComponentProvider)
+ initUnderTest()
+
+ val component = underTest.createComponent(TEST_COMPONENT)
+
+ Truth.assertThat(component).isNotNull()
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun componentAbsence_throws() {
+ kosmos.componentByKey = emptyMap()
+ initUnderTest()
+
+ underTest.createComponent(TEST_COMPONENT)
+ }
+
+ private companion object {
+ const val TEST_COMPONENT: VolumePanelComponentKey = "test_component"
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManagerTest.kt
index e5fb9426e8e2..35d96989e35d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManagerTest.kt
@@ -16,7 +16,78 @@
package com.android.systemui.volume.panel.ui.viewmodel
-class DefaultComponentsLayoutManagerTest {
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.testKosmos
+import com.android.systemui.volume.panel.mockVolumePanelUiComponent
+import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey
+import com.android.systemui.volume.panel.ui.layout.ComponentsLayoutManager
+import com.android.systemui.volume.panel.ui.layout.DefaultComponentsLayoutManager
+import com.google.common.truth.Truth
+import org.junit.Test
+import org.junit.runner.RunWith
- // TODO(b/318080198) Write tests
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DefaultComponentsLayoutManagerTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val underTest: ComponentsLayoutManager = DefaultComponentsLayoutManager()
+
+ @Test
+ fun bottomBar_isSet() {
+ val bottomBarComponentState =
+ ComponentState(BOTTOM_BAR, kosmos.mockVolumePanelUiComponent, false)
+ val layout =
+ underTest.layout(
+ VolumePanelState(0, false),
+ setOf(
+ bottomBarComponentState,
+ ComponentState(COMPONENT_1, kosmos.mockVolumePanelUiComponent, false),
+ ComponentState(COMPONENT_2, kosmos.mockVolumePanelUiComponent, false),
+ )
+ )
+
+ Truth.assertThat(layout.bottomBarComponent).isEqualTo(bottomBarComponentState)
+ }
+
+ @Test
+ fun componentsAreInOrder() {
+ val bottomBarComponentState =
+ ComponentState(BOTTOM_BAR, kosmos.mockVolumePanelUiComponent, false)
+ val component1State = ComponentState(COMPONENT_1, kosmos.mockVolumePanelUiComponent, false)
+ val component2State = ComponentState(COMPONENT_2, kosmos.mockVolumePanelUiComponent, false)
+ val layout =
+ underTest.layout(
+ VolumePanelState(0, false),
+ setOf(
+ bottomBarComponentState,
+ component1State,
+ component2State,
+ )
+ )
+
+ Truth.assertThat(layout.contentComponents[0]).isEqualTo(component1State)
+ Truth.assertThat(layout.contentComponents[1]).isEqualTo(component2State)
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun bottomBarAbsence_throwsException() {
+ val component1State = ComponentState(COMPONENT_1, kosmos.mockVolumePanelUiComponent, false)
+ val component2State = ComponentState(COMPONENT_2, kosmos.mockVolumePanelUiComponent, false)
+ underTest.layout(
+ VolumePanelState(0, false),
+ setOf(
+ component1State,
+ component2State,
+ )
+ )
+ }
+
+ private companion object {
+ const val BOTTOM_BAR: VolumePanelComponentKey = "bottom_bar"
+ const val COMPONENT_1: VolumePanelComponentKey = "test_component:1"
+ const val COMPONENT_2: VolumePanelComponentKey = "test_component:2"
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt
index 9795237a3e7e..c4c9cc608afa 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt
@@ -16,7 +16,120 @@
package com.android.systemui.volume.panel.ui.viewmodel
-class VolumePanelViewModelTest {
+import android.content.res.Configuration
+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.statusbar.policy.fakeConfigurationController
+import com.android.systemui.testKosmos
+import com.android.systemui.volume.panel.componentByKey
+import com.android.systemui.volume.panel.componentsLayoutManager
+import com.android.systemui.volume.panel.criteriaByKey
+import com.android.systemui.volume.panel.dagger.factory.KosmosVolumePanelComponentFactory
+import com.android.systemui.volume.panel.mockVolumePanelUiComponentProvider
+import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey
+import com.android.systemui.volume.panel.ui.layout.FakeComponentsLayoutManager
+import com.android.systemui.volume.panel.unavailableCriteria
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
- // TODO(b/318080198) Write tests
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@OptIn(ExperimentalCoroutinesApi::class)
+class VolumePanelViewModelTest : SysuiTestCase() {
+
+ private val kosmos =
+ testKosmos().apply {
+ componentsLayoutManager = FakeComponentsLayoutManager { it.key == BOTTOM_BAR }
+ }
+
+ private val testableResources = context.orCreateTestableResources
+
+ private lateinit var underTest: VolumePanelViewModel
+
+ private fun initUnderTest() {
+ underTest =
+ VolumePanelViewModel(
+ testableResources.resources,
+ KosmosVolumePanelComponentFactory(kosmos),
+ kosmos.fakeConfigurationController,
+ )
+ }
+
+ @Test
+ fun dismissingPanel_changesVisibility() {
+ with(kosmos) {
+ testScope.runTest {
+ initUnderTest()
+ assertThat(underTest.volumePanelState.value.isVisible).isTrue()
+
+ underTest.dismissPanel()
+ runCurrent()
+
+ assertThat(underTest.volumePanelState.value.isVisible).isFalse()
+ }
+ }
+ }
+
+ @Test
+ fun orientationChanges_panelOrientationChanges() {
+ with(kosmos) {
+ testScope.runTest {
+ initUnderTest()
+ val volumePanelState by collectLastValue(underTest.volumePanelState)
+ testableResources.overrideConfiguration(
+ Configuration().apply { orientation = Configuration.ORIENTATION_PORTRAIT }
+ )
+ assertThat(volumePanelState!!.orientation)
+ .isEqualTo(Configuration.ORIENTATION_PORTRAIT)
+
+ fakeConfigurationController.onConfigurationChanged(
+ Configuration().apply { orientation = Configuration.ORIENTATION_LANDSCAPE }
+ )
+ runCurrent()
+
+ assertThat(volumePanelState!!.orientation)
+ .isEqualTo(Configuration.ORIENTATION_LANDSCAPE)
+ }
+ }
+ }
+
+ @Test
+ fun components_areReturned() {
+ with(kosmos) {
+ testScope.runTest {
+ componentByKey =
+ mapOf(
+ COMPONENT_1 to mockVolumePanelUiComponentProvider,
+ COMPONENT_2 to mockVolumePanelUiComponentProvider,
+ BOTTOM_BAR to mockVolumePanelUiComponentProvider,
+ )
+ criteriaByKey = mapOf(COMPONENT_2 to unavailableCriteria)
+ initUnderTest()
+
+ val componentsLayout by collectLastValue(underTest.componentsLayout)
+ runCurrent()
+
+ assertThat(componentsLayout!!.contentComponents).hasSize(2)
+ assertThat(componentsLayout!!.contentComponents[0].key).isEqualTo(COMPONENT_1)
+ assertThat(componentsLayout!!.contentComponents[0].isVisible).isTrue()
+ assertThat(componentsLayout!!.contentComponents[1].key).isEqualTo(COMPONENT_2)
+ assertThat(componentsLayout!!.contentComponents[1].isVisible).isFalse()
+ assertThat(componentsLayout!!.bottomBarComponent.key).isEqualTo(BOTTOM_BAR)
+ assertThat(componentsLayout!!.bottomBarComponent.isVisible).isTrue()
+ }
+ }
+ }
+
+ private companion object {
+ const val BOTTOM_BAR: VolumePanelComponentKey = "test_bottom_bar"
+ const val COMPONENT_1: VolumePanelComponentKey = "test_component:1"
+ const val COMPONENT_2: VolumePanelComponentKey = "test_component:2"
+ }
}
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 3839dd98cdd3..307a6192a570 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -29,6 +29,9 @@
<color name="status_bar_icons_hover_color_light">#38FFFFFF</color> <!-- 22% white -->
<color name="status_bar_icons_hover_color_dark">#38000000</color> <!-- 22% black -->
+ <!-- The dark background color behind the shade -->
+ <color name="shade_scrim_background_dark">@*android:color/black</color>
+
<!-- The color of the background in the separated list of the Global Actions menu -->
<color name="global_actions_separated_background">#F5F5F5</color>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 8ec5ccd7a080..2ab0813300e3 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -184,6 +184,7 @@
<item type="id" name="action_move_to_edge_and_hide"/>
<item type="id" name="action_move_out_edge_and_show"/>
<item type="id" name="action_remove_menu"/>
+ <item type="id" name="action_edit"/>
<!-- rounded corner view id -->
<item type="id" name="rounded_corner_top_left"/>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 2b43360f0689..8fa4a9e3932f 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1077,8 +1077,6 @@
<!-- Indicator on keyguard to start the communal tutorial. [CHAR LIMIT=100] -->
<string name="communal_tutorial_indicator_text">Swipe left to start the communal tutorial</string>
- <!-- Description for the button that opens the widget editor on click. [CHAR LIMIT=50] -->
- <string name="button_to_open_widget_editor">Open the widget editor</string>
<!-- Text for CTA button that launches the hub mode widget editor on click. [CHAR LIMIT=50] -->
<string name="cta_tile_button_to_open_widget_editor">Customize</string>
<!-- Text for CTA button that dismisses the tile on click. [CHAR LIMIT=50] -->
@@ -2560,6 +2558,8 @@
<string name="accessibility_floating_button_action_remove_menu">Remove</string>
<!-- Action in accessibility menu to toggle on/off the accessibility feature. [CHAR LIMIT=30]-->
<string name="accessibility_floating_button_action_double_tap_to_toggle">toggle</string>
+ <!-- Action in accessibility menu to open the shortcut edit menu" [CHAR LIMIT=30]-->
+ <string name="accessibility_floating_button_action_edit">Edit</string>
<!-- Device Controls strings -->
<!-- Device Controls, Quick Settings tile title [CHAR LIMIT=30] -->
diff --git a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/FingerprintSensorType.kt b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/FingerprintSensorType.kt
index a00cdfa05726..7c674c8ca9e8 100644
--- a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/FingerprintSensorType.kt
+++ b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/FingerprintSensorType.kt
@@ -47,3 +47,14 @@ fun Int.toSensorType(): FingerprintSensorType =
FingerprintSensorProperties.TYPE_HOME_BUTTON -> FingerprintSensorType.HOME_BUTTON
else -> throw IllegalArgumentException("Invalid SensorType value: $this")
}
+
+/** Convert [this] to corresponding [Int] */
+fun FingerprintSensorType.toInt(): Int =
+ when (this) {
+ FingerprintSensorType.UNKNOWN -> FingerprintSensorProperties.TYPE_UNKNOWN
+ FingerprintSensorType.REAR -> FingerprintSensorProperties.TYPE_REAR
+ FingerprintSensorType.UDFPS_ULTRASONIC -> FingerprintSensorProperties.TYPE_UDFPS_ULTRASONIC
+ FingerprintSensorType.UDFPS_OPTICAL -> FingerprintSensorProperties.TYPE_UDFPS_OPTICAL
+ FingerprintSensorType.POWER_BUTTON -> FingerprintSensorProperties.TYPE_POWER_BUTTON
+ FingerprintSensorType.HOME_BUTTON -> FingerprintSensorProperties.TYPE_HOME_BUTTON
+ }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index ecce22315c50..25d771308aea 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -19,9 +19,9 @@ package com.android.keyguard;
import static android.app.StatusBarManager.SESSION_KEYGUARD;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISSIBLE_KEYGUARD;
import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_BIOMETRIC;
import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_EXTENDED_ACCESS;
-import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISSIBLE_KEYGUARD;
import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_NONE_SECURITY;
import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_PASSWORD;
import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_SIM;
@@ -84,6 +84,7 @@ import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInt
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
+import com.android.systemui.keyguard.KeyguardWmStateRefactor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.ActivityStarter;
@@ -100,8 +101,6 @@ import com.android.systemui.util.ViewController;
import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.settings.GlobalSettings;
-import dagger.Lazy;
-
import java.io.File;
import java.util.Arrays;
import java.util.Optional;
@@ -109,6 +108,7 @@ import java.util.Optional;
import javax.inject.Inject;
import javax.inject.Provider;
+import dagger.Lazy;
import kotlinx.coroutines.Job;
/** Controller for {@link KeyguardSecurityContainer} */
@@ -330,7 +330,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
}
}
- if (mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ if (KeyguardWmStateRefactor.isEnabled()) {
mKeyguardTransitionInteractor.startDismissKeyguardTransition();
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index d5dc85cd8715..8e9815085e31 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -63,6 +63,7 @@ import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor;
+import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
@@ -87,6 +88,8 @@ import java.util.function.Consumer;
import javax.inject.Inject;
+import kotlinx.coroutines.ExperimentalCoroutinesApi;
+
/**
* Controls when to show the LockIcon affordance (lock/unlocked icon or circle) on lock screen.
*
@@ -717,6 +720,7 @@ public class LockIconViewController implements Dumpable {
return mDownDetected;
}
+ @ExperimentalCoroutinesApi
@VisibleForTesting
protected void onLongPress() {
cancelTouches();
@@ -727,7 +731,8 @@ public class LockIconViewController implements Dumpable {
// pre-emptively set to true to hide view
mIsBouncerShowing = true;
- if (mUdfpsSupported && mShowUnlockIcon && mAuthRippleController != null) {
+ if (!DeviceEntryUdfpsRefactor.isEnabled()
+ && mUdfpsSupported && mShowUnlockIcon && mAuthRippleController != null) {
mAuthRippleController.showUnlockRipple(FINGERPRINT);
}
updateVisibility();
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.kt b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.kt
index 8c2d221e3f97..35f9344ae897 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.kt
@@ -16,6 +16,8 @@
package com.android.systemui.accessibility
+import com.android.systemui.accessibility.data.repository.AccessibilityQsShortcutsRepository
+import com.android.systemui.accessibility.data.repository.AccessibilityQsShortcutsRepositoryImpl
import com.android.systemui.accessibility.data.repository.ColorCorrectionRepository
import com.android.systemui.accessibility.data.repository.ColorCorrectionRepositoryImpl
import com.android.systemui.accessibility.data.repository.ColorInversionRepository
@@ -31,4 +33,9 @@ interface AccessibilityModule {
@Binds
fun colorInversionRepository(impl: ColorInversionRepositoryImpl): ColorInversionRepository
+
+ @Binds
+ fun accessibilityQsShortcutsRepository(
+ impl: AccessibilityQsShortcutsRepositoryImpl
+ ): AccessibilityQsShortcutsRepository
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepository.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepository.kt
new file mode 100644
index 000000000000..401ac0f9337b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepository.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility.data.repository
+
+import android.util.SparseArray
+import androidx.annotation.GuardedBy
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.flow.SharedFlow
+
+/** Provides data related to accessibility quick setting shortcut option. */
+interface AccessibilityQsShortcutsRepository {
+ /**
+ * Observable for the a11y features the user chooses in the Settings app to use the quick
+ * setting option.
+ */
+ fun a11yQsShortcutTargets(userId: Int): SharedFlow<Set<String>>
+}
+
+@SysUISingleton
+class AccessibilityQsShortcutsRepositoryImpl
+@Inject
+constructor(
+ private val userA11yQsShortcutsRepositoryFactory: UserA11yQsShortcutsRepository.Factory,
+) : AccessibilityQsShortcutsRepository {
+
+ @GuardedBy("userA11yQsShortcutsRepositories")
+ private val userA11yQsShortcutsRepositories = SparseArray<UserA11yQsShortcutsRepository>()
+
+ override fun a11yQsShortcutTargets(userId: Int): SharedFlow<Set<String>> {
+ return synchronized(userA11yQsShortcutsRepositories) {
+ if (userId !in userA11yQsShortcutsRepositories) {
+ val userA11yQsShortcutsRepository =
+ userA11yQsShortcutsRepositoryFactory.create(userId)
+ userA11yQsShortcutsRepositories.put(userId, userA11yQsShortcutsRepository)
+ }
+ userA11yQsShortcutsRepositories.get(userId).targets
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/UserA11yQsShortcutsRepository.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/UserA11yQsShortcutsRepository.kt
new file mode 100644
index 000000000000..ed91f03cc56e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/UserA11yQsShortcutsRepository.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility.data.repository
+
+import android.provider.Settings
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository
+import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.shareIn
+
+/**
+ * Single user version of [AccessibilityQsShortcutsRepository]. It provides a similar interface as
+ * [TileSpecRepository], but focusing solely on the user it was created for. It observes the changes
+ * on the [Settings.Secure.ACCESSIBILITY_QS_TARGETS] for a given user
+ */
+class UserA11yQsShortcutsRepository
+@AssistedInject
+constructor(
+ @Assisted private val userId: Int,
+ private val secureSettings: SecureSettings,
+ @Application private val applicationScope: CoroutineScope,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+) {
+ val targets =
+ secureSettings
+ .observerFlow(userId, SETTING_NAME)
+ // Force an update
+ .onStart { emit(Unit) }
+ .map { getA11yQsShortcutTargets(userId) }
+ .flowOn(backgroundDispatcher)
+ .shareIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ replay = 1
+ )
+
+ private fun getA11yQsShortcutTargets(userId: Int): Set<String> {
+ val settingValue = secureSettings.getStringForUser(SETTING_NAME, userId) ?: ""
+ return settingValue.split(SETTING_SEPARATOR).filterNot { it.isEmpty() }.toSet()
+ }
+
+ companion object {
+ const val SETTING_NAME = Settings.Secure.ACCESSIBILITY_QS_TARGETS
+ const val SETTING_SEPARATOR = ":"
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(
+ userId: Int,
+ ): UserA11yQsShortcutsRepository
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationController.java
index 568b24dbd4f3..7fd72ec8ce93 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationController.java
@@ -16,127 +16,138 @@
package com.android.systemui.accessibility.floatingmenu;
+import static android.R.id.empty;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
+import android.util.ArrayMap;
+import android.util.Pair;
import android.view.MotionEvent;
import androidx.annotation.NonNull;
import androidx.dynamicanimation.animation.DynamicAnimation;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.Flags;
+import com.android.wm.shell.common.bubbles.DismissCircleView;
import com.android.wm.shell.common.bubbles.DismissView;
import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
+import java.util.Map;
+import java.util.Objects;
+
/**
* Controls the interaction between {@link MagnetizedObject} and
* {@link MagnetizedObject.MagneticTarget}.
*/
class DragToInteractAnimationController {
- private static final boolean ENABLE_FLING_TO_DISMISS_MENU = false;
private static final float COMPLETELY_OPAQUE = 1.0f;
private static final float COMPLETELY_TRANSPARENT = 0.0f;
private static final float CIRCLE_VIEW_DEFAULT_SCALE = 1.0f;
private static final float ANIMATING_MAX_ALPHA = 0.7f;
+ private final DragToInteractView mInteractView;
private final DismissView mDismissView;
private final MenuView mMenuView;
- private final ValueAnimator mDismissAnimator;
- private final MagnetizedObject<?> mMagnetizedObject;
- private float mMinDismissSize;
+
+ /**
+ * MagnetizedObject cannot differentiate between its MagnetizedTargets,
+ * so we need an object & an animator for every interactable.
+ */
+ private final ArrayMap<Integer, Pair<MagnetizedObject<MenuView>, ValueAnimator>> mInteractMap;
+
+ private float mMinInteractSize;
private float mSizePercent;
- DragToInteractAnimationController(DismissView dismissView, MenuView menuView) {
- mDismissView = dismissView;
- mDismissView.setPivotX(dismissView.getWidth() / 2.0f);
- mDismissView.setPivotY(dismissView.getHeight() / 2.0f);
+ DragToInteractAnimationController(DragToInteractView interactView, MenuView menuView) {
+ mDismissView = null;
+ mInteractView = interactView;
+ mInteractView.setPivotX(interactView.getWidth() / 2.0f);
+ mInteractView.setPivotY(interactView.getHeight() / 2.0f);
mMenuView = menuView;
updateResources();
- mDismissAnimator = ValueAnimator.ofFloat(COMPLETELY_OPAQUE, COMPLETELY_TRANSPARENT);
- mDismissAnimator.addUpdateListener(dismissAnimation -> {
- final float animatedValue = (float) dismissAnimation.getAnimatedValue();
- final float scaleValue = Math.max(animatedValue, mSizePercent);
- dismissView.getCircle().setScaleX(scaleValue);
- dismissView.getCircle().setScaleY(scaleValue);
-
- menuView.setAlpha(Math.max(animatedValue, ANIMATING_MAX_ALPHA));
+ mInteractMap = new ArrayMap<>();
+ interactView.getInteractMap().forEach((viewId, pair) -> {
+ DismissCircleView circleView = pair.getFirst();
+ createMagnetizedObjectAndAnimator(circleView);
});
+ }
- mDismissAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(@NonNull Animator animation, boolean isReverse) {
- super.onAnimationEnd(animation, isReverse);
+ DragToInteractAnimationController(DismissView dismissView, MenuView menuView) {
+ mDismissView = dismissView;
+ mInteractView = null;
+ mDismissView.setPivotX(dismissView.getWidth() / 2.0f);
+ mDismissView.setPivotY(dismissView.getHeight() / 2.0f);
+ mMenuView = menuView;
- if (isReverse) {
- mDismissView.getCircle().setScaleX(CIRCLE_VIEW_DEFAULT_SCALE);
- mDismissView.getCircle().setScaleY(CIRCLE_VIEW_DEFAULT_SCALE);
- mMenuView.setAlpha(COMPLETELY_OPAQUE);
- }
- }
- });
+ updateResources();
- mMagnetizedObject =
- new MagnetizedObject<MenuView>(mMenuView.getContext(), mMenuView,
- new MenuAnimationController.MenuPositionProperty(
- DynamicAnimation.TRANSLATION_X),
- new MenuAnimationController.MenuPositionProperty(
- DynamicAnimation.TRANSLATION_Y)) {
- @Override
- public void getLocationOnScreen(MenuView underlyingObject, int[] loc) {
- underlyingObject.getLocationOnScreen(loc);
- }
-
- @Override
- public float getHeight(MenuView underlyingObject) {
- return underlyingObject.getHeight();
- }
-
- @Override
- public float getWidth(MenuView underlyingObject) {
- return underlyingObject.getWidth();
- }
- };
-
- final MagnetizedObject.MagneticTarget magneticTarget = new MagnetizedObject.MagneticTarget(
- dismissView.getCircle(), (int) mMinDismissSize);
- mMagnetizedObject.addTarget(magneticTarget);
- mMagnetizedObject.setFlingToTargetEnabled(ENABLE_FLING_TO_DISMISS_MENU);
+ mInteractMap = new ArrayMap<>();
+ createMagnetizedObjectAndAnimator(dismissView.getCircle());
}
- void showDismissView(boolean show) {
- if (show) {
- mDismissView.show();
- } else {
- mDismissView.hide();
+ void showInteractView(boolean show) {
+ if (Flags.floatingMenuDragToEdit() && mInteractView != null) {
+ if (show) {
+ mInteractView.show();
+ } else {
+ mInteractView.hide();
+ }
+ } else if (mDismissView != null) {
+ if (show) {
+ mDismissView.show();
+ } else {
+ mDismissView.hide();
+ }
}
}
void setMagnetListener(MagnetizedObject.MagnetListener magnetListener) {
- mMagnetizedObject.setMagnetListener(magnetListener);
+ mInteractMap.forEach((viewId, pair) -> {
+ MagnetizedObject<?> magnetizedObject = pair.first;
+ magnetizedObject.setMagnetListener(magnetListener);
+ });
}
@VisibleForTesting
- MagnetizedObject.MagnetListener getMagnetListener() {
- return mMagnetizedObject.getMagnetListener();
+ MagnetizedObject.MagnetListener getMagnetListener(int id) {
+ return Objects.requireNonNull(mInteractMap.get(id)).first.getMagnetListener();
}
void maybeConsumeDownMotionEvent(MotionEvent event) {
- mMagnetizedObject.maybeConsumeMotionEvent(event);
+ mInteractMap.forEach((viewId, pair) -> {
+ MagnetizedObject<?> magnetizedObject = pair.first;
+ magnetizedObject.maybeConsumeMotionEvent(event);
+ });
+ }
+
+ private int maybeConsumeMotionEvent(MotionEvent event) {
+ for (Map.Entry<Integer, Pair<MagnetizedObject<MenuView>, ValueAnimator>> set:
+ mInteractMap.entrySet()) {
+ MagnetizedObject<MenuView> magnetizedObject = set.getValue().first;
+ if (magnetizedObject.maybeConsumeMotionEvent(event)) {
+ return set.getKey();
+ }
+ }
+ return empty;
}
/**
- * This used to pass {@link MotionEvent#ACTION_DOWN} to the magnetized object to check if it was
- * within the magnetic field. It should be used in the {@link MenuListViewTouchHandler}.
+ * This used to pass {@link MotionEvent#ACTION_DOWN} to the magnetized objects
+ * to check if it was within a magnetic field.
+ * It should be used in the {@link MenuListViewTouchHandler}.
*
* @param event that move the magnetized object which is also the menu list view.
- * @return true if the location of the motion events moves within the magnetic field of a
- * target, but false if didn't set
+ * @return id of a target if the location of the motion events moves
+ * within the field of the target, otherwise it returns{@link android.R.id#empty}.
+ * <p>
* {@link DragToInteractAnimationController#setMagnetListener(MagnetizedObject.MagnetListener)}.
*/
- boolean maybeConsumeMoveMotionEvent(MotionEvent event) {
- return mMagnetizedObject.maybeConsumeMotionEvent(event);
+ int maybeConsumeMoveMotionEvent(MotionEvent event) {
+ return maybeConsumeMotionEvent(event);
}
/**
@@ -144,31 +155,93 @@ class DragToInteractAnimationController {
* within the magnetic field. It should be used in the {@link MenuListViewTouchHandler}.
*
* @param event that move the magnetized object which is also the menu list view.
- * @return true if the location of the motion events moves within the magnetic field of a
- * target, but false if didn't set
+ * @return id of a target if the location of the motion events moves
+ * within the field of the target, otherwise it returns{@link android.R.id#empty}.
* {@link DragToInteractAnimationController#setMagnetListener(MagnetizedObject.MagnetListener)}.
*/
- boolean maybeConsumeUpMotionEvent(MotionEvent event) {
- return mMagnetizedObject.maybeConsumeMotionEvent(event);
+ int maybeConsumeUpMotionEvent(MotionEvent event) {
+ return maybeConsumeMotionEvent(event);
}
- void animateDismissMenu(boolean scaleUp) {
+ void animateInteractMenu(int targetViewId, boolean scaleUp) {
+ Pair<MagnetizedObject<MenuView>, ValueAnimator> value = mInteractMap.get(targetViewId);
+ if (value == null) {
+ return;
+ }
+ ValueAnimator animator = value.second;
if (scaleUp) {
- mDismissAnimator.start();
+ animator.start();
} else {
- mDismissAnimator.reverse();
+ animator.reverse();
}
}
void updateResources() {
- final float maxDismissSize = mDismissView.getResources().getDimensionPixelSize(
+ final float maxInteractSize = mMenuView.getResources().getDimensionPixelSize(
com.android.wm.shell.R.dimen.dismiss_circle_size);
- mMinDismissSize = mDismissView.getResources().getDimensionPixelSize(
+ mMinInteractSize = mMenuView.getResources().getDimensionPixelSize(
com.android.wm.shell.R.dimen.dismiss_circle_small);
- mSizePercent = mMinDismissSize / maxDismissSize;
+ mSizePercent = mMinInteractSize / maxInteractSize;
}
- interface DismissCallback {
- void onDismiss();
+ /**
+ * Creates a magnetizedObject & valueAnimator pair for the provided circleView,
+ * and adds them to the interactMap.
+ *
+ * @param circleView circleView to create objects for.
+ */
+ private void createMagnetizedObjectAndAnimator(DismissCircleView circleView) {
+ MagnetizedObject<MenuView> magnetizedObject = new MagnetizedObject<MenuView>(
+ mMenuView.getContext(), mMenuView,
+ new MenuAnimationController.MenuPositionProperty(
+ DynamicAnimation.TRANSLATION_X),
+ new MenuAnimationController.MenuPositionProperty(
+ DynamicAnimation.TRANSLATION_Y)) {
+ @Override
+ public void getLocationOnScreen(MenuView underlyingObject, @NonNull int[] loc) {
+ underlyingObject.getLocationOnScreen(loc);
+ }
+
+ @Override
+ public float getHeight(MenuView underlyingObject) {
+ return underlyingObject.getHeight();
+ }
+
+ @Override
+ public float getWidth(MenuView underlyingObject) {
+ return underlyingObject.getWidth();
+ }
+ };
+ // Avoid unintended selection of an object / option
+ magnetizedObject.setFlingToTargetEnabled(false);
+ magnetizedObject.addTarget(new MagnetizedObject.MagneticTarget(
+ circleView, (int) mMinInteractSize));
+
+ final ValueAnimator animator =
+ ValueAnimator.ofFloat(COMPLETELY_OPAQUE, COMPLETELY_TRANSPARENT);
+
+ animator.addUpdateListener(dismissAnimation -> {
+ final float animatedValue = (float) dismissAnimation.getAnimatedValue();
+ final float scaleValue = Math.max(animatedValue, mSizePercent);
+ circleView.setScaleX(scaleValue);
+ circleView.setScaleY(scaleValue);
+
+ mMenuView.setAlpha(Math.max(animatedValue, ANIMATING_MAX_ALPHA));
+ });
+
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(@NonNull Animator animation, boolean isReverse) {
+ super.onAnimationEnd(animation, isReverse);
+
+ if (isReverse) {
+ circleView.setScaleX(CIRCLE_VIEW_DEFAULT_SCALE);
+ circleView.setScaleY(CIRCLE_VIEW_DEFAULT_SCALE);
+ mMenuView.setAlpha(COMPLETELY_OPAQUE);
+ }
+ }
+ });
+
+ mInteractMap.put(circleView.getId(), new Pair<>(magnetizedObject, animator));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractView.kt b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractView.kt
new file mode 100644
index 000000000000..0ef3d200d1fa
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractView.kt
@@ -0,0 +1,322 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility.floatingmenu
+
+import android.animation.ObjectAnimator
+import android.content.Context
+import android.graphics.Color
+import android.graphics.drawable.GradientDrawable
+import android.util.ArrayMap
+import android.util.IntProperty
+import android.util.Log
+import android.view.Gravity
+import android.view.View
+import android.view.ViewGroup
+import android.view.WindowInsets
+import android.view.WindowManager
+import android.widget.FrameLayout
+import android.widget.LinearLayout
+import android.widget.Space
+import androidx.annotation.ColorRes
+import androidx.annotation.DimenRes
+import androidx.annotation.DrawableRes
+import androidx.core.content.ContextCompat
+import androidx.dynamicanimation.animation.DynamicAnimation
+import androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_LOW_BOUNCY
+import androidx.dynamicanimation.animation.SpringForce.STIFFNESS_LOW
+import com.android.wm.shell.R
+import com.android.wm.shell.animation.PhysicsAnimator
+import com.android.wm.shell.common.bubbles.DismissCircleView
+import com.android.wm.shell.common.bubbles.DismissView
+
+/**
+ * View that handles interactions between DismissCircleView and BubbleStackView.
+ *
+ * @note [setup] method should be called after initialisation
+ */
+class DragToInteractView(context: Context) : FrameLayout(context) {
+ /**
+ * The configuration is used to provide module specific resource ids
+ *
+ * @see [setup] method
+ */
+ data class Config(
+ /** dimen resource id of the dismiss target circle view size */
+ @DimenRes val targetSizeResId: Int,
+ /** dimen resource id of the icon size in the dismiss target */
+ @DimenRes val iconSizeResId: Int,
+ /** dimen resource id of the bottom margin for the dismiss target */
+ @DimenRes var bottomMarginResId: Int,
+ /** dimen resource id of the height for dismiss area gradient */
+ @DimenRes val floatingGradientHeightResId: Int,
+ /** color resource id of the dismiss area gradient color */
+ @ColorRes val floatingGradientColorResId: Int,
+ /** drawable resource id of the dismiss target background */
+ @DrawableRes val backgroundResId: Int,
+ /** drawable resource id of the icon for the dismiss target */
+ @DrawableRes val iconResId: Int
+ )
+
+ companion object {
+ private const val SHOULD_SETUP = "The view isn't ready. Should be called after `setup`"
+ private val TAG = DragToInteractView::class.simpleName
+ }
+
+ // START DragToInteractView modification
+ // We could technically access each DismissCircleView from their Animator,
+ // but the animators only store a weak reference to their targets. This is safer.
+ var interactMap = ArrayMap<Int, Pair<DismissCircleView, PhysicsAnimator<DismissCircleView>>>()
+ // END DragToInteractView modification
+ var isShowing = false
+ var config: Config? = null
+
+ private val spring = PhysicsAnimator.SpringConfig(STIFFNESS_LOW, DAMPING_RATIO_LOW_BOUNCY)
+ private val INTERACT_SCRIM_FADE_MS = 200L
+ private var wm: WindowManager =
+ context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
+ private var gradientDrawable: GradientDrawable? = null
+
+ private val GRADIENT_ALPHA: IntProperty<GradientDrawable> =
+ object : IntProperty<GradientDrawable>("alpha") {
+ override fun setValue(d: GradientDrawable, percent: Int) {
+ d.alpha = percent
+ }
+ override fun get(d: GradientDrawable): Int {
+ return d.alpha
+ }
+ }
+
+ init {
+ clipToPadding = false
+ clipChildren = false
+ visibility = View.INVISIBLE
+
+ // START DragToInteractView modification
+ // Resources included within implementation as we aren't concerned with decoupling them.
+ setup(
+ Config(
+ targetSizeResId = R.dimen.dismiss_circle_size,
+ iconSizeResId = R.dimen.dismiss_target_x_size,
+ bottomMarginResId = R.dimen.floating_dismiss_bottom_margin,
+ floatingGradientHeightResId = R.dimen.floating_dismiss_gradient_height,
+ floatingGradientColorResId = android.R.color.system_neutral1_900,
+ backgroundResId = R.drawable.dismiss_circle_background,
+ iconResId = R.drawable.pip_ic_close_white
+ )
+ )
+ // END DragToInteractView modification
+ }
+
+ /**
+ * Sets up view with the provided resource ids.
+ *
+ * Decouples resource dependency in order to be used externally (e.g. Launcher). Usually called
+ * with default params in module specific extension:
+ *
+ * @see [DismissView.setup] in DismissViewExt.kt
+ */
+ fun setup(config: Config) {
+ this.config = config
+
+ // Setup layout
+ layoutParams =
+ LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ resources.getDimensionPixelSize(config.floatingGradientHeightResId),
+ Gravity.BOTTOM
+ )
+ updatePadding()
+
+ // Setup gradient
+ gradientDrawable = createGradient(color = config.floatingGradientColorResId)
+ background = gradientDrawable
+
+ // START DragToInteractView modification
+
+ // Setup LinearLayout. Added to organize multiple circles.
+ val linearLayout = LinearLayout(context)
+ linearLayout.layoutParams =
+ LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT
+ )
+ linearLayout.weightSum = 0f
+ addView(linearLayout)
+
+ // Setup DismissCircleView. Code block replaced with repeatable functions
+ addSpace(linearLayout)
+ addCircle(
+ config,
+ com.android.systemui.res.R.id.action_remove_menu,
+ R.drawable.pip_ic_close_white,
+ linearLayout
+ )
+ addCircle(
+ config,
+ com.android.systemui.res.R.id.action_edit,
+ com.android.systemui.res.R.drawable.ic_screenshot_edit,
+ linearLayout
+ )
+ // END DragToInteractView modification
+ }
+
+ /** Animates this view in. */
+ fun show() {
+ if (isShowing) return
+ val gradientDrawable = checkExists(gradientDrawable) ?: return
+ isShowing = true
+ visibility = View.VISIBLE
+ val alphaAnim =
+ ObjectAnimator.ofInt(gradientDrawable, GRADIENT_ALPHA, gradientDrawable.alpha, 255)
+ alphaAnim.duration = INTERACT_SCRIM_FADE_MS
+ alphaAnim.start()
+
+ // START DragToInteractView modification
+ interactMap.forEach {
+ val animator = it.value.second
+ animator.cancel()
+ animator.spring(DynamicAnimation.TRANSLATION_Y, 0f, spring).start()
+ }
+ // END DragToInteractView modification
+ }
+
+ /**
+ * Animates this view out, as well as the circle that encircles the bubbles, if they were
+ * dragged into the target and encircled.
+ */
+ fun hide() {
+ if (!isShowing) return
+ val gradientDrawable = checkExists(gradientDrawable) ?: return
+ isShowing = false
+ val alphaAnim =
+ ObjectAnimator.ofInt(gradientDrawable, GRADIENT_ALPHA, gradientDrawable.alpha, 0)
+ alphaAnim.duration = INTERACT_SCRIM_FADE_MS
+ alphaAnim.start()
+
+ // START DragToInteractView modification
+ interactMap.forEach {
+ val animator = it.value.second
+ animator
+ .spring(DynamicAnimation.TRANSLATION_Y, height.toFloat(), spring)
+ .withEndActions({ visibility = View.INVISIBLE })
+ .start()
+ }
+ // END DragToInteractView modification
+ }
+
+ /** Cancels the animator for the dismiss target. */
+ fun cancelAnimators() {
+ // START DragToInteractView modification
+ interactMap.forEach {
+ val animator = it.value.second
+ animator.cancel()
+ }
+ // END DragToInteractView modification
+ }
+
+ fun updateResources() {
+ val config = checkExists(config) ?: return
+ updatePadding()
+ layoutParams.height = resources.getDimensionPixelSize(config.floatingGradientHeightResId)
+ val targetSize = resources.getDimensionPixelSize(config.targetSizeResId)
+
+ // START DragToInteractView modification
+ interactMap.forEach {
+ val circle = it.value.first
+ circle.layoutParams.width = targetSize
+ circle.layoutParams.height = targetSize
+ circle.requestLayout()
+ }
+ // END DragToInteractView modification
+ }
+
+ private fun createGradient(@ColorRes color: Int): GradientDrawable {
+ val gradientColor = ContextCompat.getColor(context, color)
+ val alpha = 0.7f * 255
+ val gradientColorWithAlpha =
+ Color.argb(
+ alpha.toInt(),
+ Color.red(gradientColor),
+ Color.green(gradientColor),
+ Color.blue(gradientColor)
+ )
+ val gd =
+ GradientDrawable(
+ GradientDrawable.Orientation.BOTTOM_TOP,
+ intArrayOf(gradientColorWithAlpha, Color.TRANSPARENT)
+ )
+ gd.setDither(true)
+ gd.alpha = 0
+ return gd
+ }
+
+ private fun updatePadding() {
+ val config = checkExists(config) ?: return
+ val insets: WindowInsets = wm.currentWindowMetrics.windowInsets
+ val navInset = insets.getInsetsIgnoringVisibility(WindowInsets.Type.navigationBars())
+ setPadding(
+ 0,
+ 0,
+ 0,
+ navInset.bottom + resources.getDimensionPixelSize(config.bottomMarginResId)
+ )
+ }
+
+ /**
+ * Checks if the value is set up and exists, if not logs an exception. Used for convenient
+ * logging in case `setup` wasn't called before
+ *
+ * @return value provided as argument
+ */
+ private fun <T> checkExists(value: T?): T? {
+ if (value == null) Log.e(TAG, SHOULD_SETUP)
+ return value
+ }
+
+ // START DragToInteractView modification
+ private fun addSpace(parent: LinearLayout) {
+ val space = Space(context)
+ // Spaces are weighted equally to space out circles evenly
+ space.layoutParams =
+ LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ 1f
+ )
+ parent.addView(space)
+ parent.weightSum = parent.weightSum + 1f
+ }
+
+ private fun addCircle(config: Config, id: Int, iconResId: Int, parent: LinearLayout) {
+ val targetSize = resources.getDimensionPixelSize(config.targetSizeResId)
+ val circleLayoutParams = LinearLayout.LayoutParams(targetSize, targetSize, 0f)
+ circleLayoutParams.gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL
+ val circle = DismissCircleView(context)
+ circle.id = id
+ circle.setup(config.backgroundResId, iconResId, config.iconSizeResId)
+ circle.layoutParams = circleLayoutParams
+
+ // Initial position with circle offscreen so it's animated up
+ circle.translationY =
+ resources.getDimensionPixelSize(config.floatingGradientHeightResId).toFloat()
+
+ interactMap[circle.id] = Pair(circle, PhysicsAnimator.getInstance(circle))
+ parent.addView(circle)
+ addSpace(parent)
+ }
+ // END DragToInteractView modification
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
index a2705584d76a..d3e85e092b3a 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
@@ -37,7 +37,6 @@ import androidx.dynamicanimation.animation.SpringForce;
import androidx.recyclerview.widget.RecyclerView;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.Preconditions;
import com.android.systemui.Flags;
import java.util.HashMap;
@@ -73,7 +72,6 @@ class MenuAnimationController {
private final ValueAnimator mFadeOutAnimator;
private final Handler mHandler;
private boolean mIsFadeEffectEnabled;
- private DragToInteractAnimationController.DismissCallback mDismissCallback;
private Runnable mSpringAnimationsEndAction;
// Cache the animations state of {@link DynamicAnimation.TRANSLATION_X} and {@link
@@ -170,11 +168,6 @@ class MenuAnimationController {
mSpringAnimationsEndAction = runnable;
}
- void setDismissCallback(
- DragToInteractAnimationController.DismissCallback dismissCallback) {
- mDismissCallback = dismissCallback;
- }
-
void moveToTopLeftPosition() {
mMenuView.updateMenuMoveToTucked(/* isMoveToTucked= */ false);
final Rect draggableBounds = mMenuView.getMenuDraggableBounds();
@@ -205,13 +198,6 @@ class MenuAnimationController {
constrainPositionAndUpdate(position, /* writeToPosition = */ true);
}
- void removeMenu() {
- Preconditions.checkArgument(mDismissCallback != null,
- "The dismiss callback should be initialized first.");
-
- mDismissCallback.onDismiss();
- }
-
void flingMenuThenSpringToEdge(float x, float velocityX, float velocityY) {
final boolean shouldMenuFlingLeft = isOnLeftSide()
? velocityX < ESCAPE_VELOCITY
@@ -334,8 +320,6 @@ class MenuAnimationController {
moveToEdgeAndHide();
return true;
}
-
- fadeOutIfEnabled();
return false;
}
@@ -453,8 +437,6 @@ class MenuAnimationController {
mMenuView.onBoundsInParentChanged((int) position.x, (int) position.y);
constrainPositionAndUpdate(position, writeToPosition);
- fadeOutIfEnabled();
-
if (mSpringAnimationsEndAction != null) {
mSpringAnimationsEndAction.run();
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java
index 0538e7d55bfd..4a06ae9523fa 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java
@@ -21,7 +21,6 @@ import static android.provider.Settings.Secure.ACCESSIBILITY_FLOATING_MENU_MIGRA
import static android.provider.Settings.Secure.ACCESSIBILITY_FLOATING_MENU_OPACITY;
import static android.provider.Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE;
import static android.provider.Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES;
-import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
import static com.android.internal.accessibility.dialog.AccessibilityTargetHelper.getTargets;
import static com.android.systemui.accessibility.floatingmenu.MenuFadeEffectInfoKt.DEFAULT_FADE_EFFECT_IS_ENABLED;
@@ -47,6 +46,7 @@ import android.view.accessibility.AccessibilityManager;
import androidx.annotation.NonNull;
+import com.android.internal.accessibility.common.ShortcutConstants;
import com.android.internal.accessibility.dialog.AccessibilityTarget;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.Prefs;
@@ -182,7 +182,7 @@ class MenuInfoRepository {
}
void loadMenuTargetFeatures(OnInfoReady<List<AccessibilityTarget>> callback) {
- callback.onReady(getTargets(mContext, ACCESSIBILITY_BUTTON));
+ callback.onReady(getTargets(mContext, ShortcutConstants.UserShortcutType.SOFTWARE));
}
void loadMenuSizeType(OnInfoReady<Integer> callback) {
@@ -223,7 +223,7 @@ class MenuInfoRepository {
private void onTargetFeaturesChanged() {
mSettingsContentsCallback.onTargetFeaturesChanged(
- getTargets(mContext, ACCESSIBILITY_BUTTON));
+ getTargets(mContext, ShortcutConstants.UserShortcutType.SOFTWARE));
}
private Position getStartPosition() {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegate.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegate.java
index 9c22a7738ad6..975a6020430d 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegate.java
@@ -27,6 +27,7 @@ import androidx.annotation.NonNull;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
import androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate;
+import com.android.systemui.Flags;
import com.android.systemui.res.R;
/**
@@ -35,15 +36,18 @@ import com.android.systemui.res.R;
*/
class MenuItemAccessibilityDelegate extends RecyclerViewAccessibilityDelegate.ItemDelegate {
private final MenuAnimationController mAnimationController;
+ private final MenuViewLayer mMenuViewLayer;
MenuItemAccessibilityDelegate(@NonNull RecyclerViewAccessibilityDelegate recyclerViewDelegate,
- MenuAnimationController animationController) {
+ MenuAnimationController animationController, MenuViewLayer menuViewLayer) {
super(recyclerViewDelegate);
mAnimationController = animationController;
+ mMenuViewLayer = menuViewLayer;
}
@Override
- public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
+ public void onInitializeAccessibilityNodeInfo(
+ @NonNull View host, @NonNull AccessibilityNodeInfoCompat info) {
super.onInitializeAccessibilityNodeInfo(host, info);
final Resources res = host.getResources();
@@ -90,6 +94,15 @@ class MenuItemAccessibilityDelegate extends RecyclerViewAccessibilityDelegate.It
R.id.action_remove_menu,
res.getString(R.string.accessibility_floating_button_action_remove_menu));
info.addAction(removeMenu);
+
+ if (Flags.floatingMenuDragToEdit()) {
+ final AccessibilityNodeInfoCompat.AccessibilityActionCompat edit =
+ new AccessibilityNodeInfoCompat.AccessibilityActionCompat(
+ R.id.action_edit,
+ res.getString(
+ R.string.accessibility_floating_button_action_remove_menu));
+ info.addAction(edit);
+ }
}
@Override
@@ -132,8 +145,8 @@ class MenuItemAccessibilityDelegate extends RecyclerViewAccessibilityDelegate.It
return true;
}
- if (action == R.id.action_remove_menu) {
- mAnimationController.removeMenu();
+ if (action == R.id.action_remove_menu || action == R.id.action_edit) {
+ mMenuViewLayer.dispatchAccessibilityAction(action);
return true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java
index 52e7b91d373e..75191685b119 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java
@@ -16,6 +16,8 @@
package com.android.systemui.accessibility.floatingmenu;
+import static android.R.id.empty;
+
import android.graphics.PointF;
import android.view.MotionEvent;
import android.view.VelocityTracker;
@@ -78,10 +80,9 @@ class MenuListViewTouchHandler implements RecyclerView.OnItemTouchListener {
mMenuAnimationController.onDraggingStart();
}
- mDragToInteractAnimationController.showDismissView(/* show= */ true);
-
- if (!mDragToInteractAnimationController.maybeConsumeMoveMotionEvent(
- motionEvent)) {
+ mDragToInteractAnimationController.showInteractView(/* show= */ true);
+ if (mDragToInteractAnimationController.maybeConsumeMoveMotionEvent(motionEvent)
+ == empty) {
mMenuAnimationController.moveToPositionX(mMenuTranslationDown.x + dx);
mMenuAnimationController.moveToPositionYIfNeeded(
mMenuTranslationDown.y + dy);
@@ -94,21 +95,19 @@ class MenuListViewTouchHandler implements RecyclerView.OnItemTouchListener {
final float endX = mMenuTranslationDown.x + dx;
mIsDragging = false;
+ mDragToInteractAnimationController.showInteractView(/* show= */ false);
+
if (mMenuAnimationController.maybeMoveToEdgeAndHide(endX)) {
- mDragToInteractAnimationController.showDismissView(/* show= */ false);
mMenuAnimationController.fadeOutIfEnabled();
-
return true;
}
- if (!mDragToInteractAnimationController.maybeConsumeUpMotionEvent(
- motionEvent)) {
+ if (mDragToInteractAnimationController.maybeConsumeUpMotionEvent(motionEvent)
+ == empty) {
mVelocityTracker.computeCurrentVelocity(VELOCITY_UNIT_SECONDS);
mMenuAnimationController.flingMenuThenSpringToEdge(endX,
mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
- mDragToInteractAnimationController.showDismissView(/* show= */ false);
}
-
// Avoid triggering the listener of the item.
return true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
index 76808cb7ab7b..334cc87144f9 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
@@ -21,24 +21,28 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import android.annotation.SuppressLint;
import android.content.ComponentCallbacks;
import android.content.Context;
+import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.drawable.GradientDrawable;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.provider.SettingsStringUtil;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
-import androidx.core.view.AccessibilityDelegateCompat;
import androidx.lifecycle.Observer;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
-import androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate;
import com.android.internal.accessibility.dialog.AccessibilityTarget;
import com.android.systemui.Flags;
+import com.android.systemui.util.settings.SecureSettings;
import java.util.ArrayList;
import java.util.Collections;
@@ -72,26 +76,20 @@ class MenuView extends FrameLayout implements
private final MenuAnimationController mMenuAnimationController;
private OnTargetFeaturesChangeListener mFeaturesChangeListener;
private OnMoveToTuckedListener mMoveToTuckedListener;
+ private SecureSettings mSecureSettings;
- MenuView(Context context, MenuViewModel menuViewModel, MenuViewAppearance menuViewAppearance) {
+ MenuView(Context context, MenuViewModel menuViewModel, MenuViewAppearance menuViewAppearance,
+ SecureSettings secureSettings) {
super(context);
mMenuViewModel = menuViewModel;
mMenuViewAppearance = menuViewAppearance;
+ mSecureSettings = secureSettings;
mMenuAnimationController = new MenuAnimationController(this, menuViewAppearance);
mAdapter = new AccessibilityTargetAdapter(mTargetFeatures);
mTargetFeaturesView = new RecyclerView(context);
mTargetFeaturesView.setAdapter(mAdapter);
mTargetFeaturesView.setLayoutManager(new LinearLayoutManager(context));
- mTargetFeaturesView.setAccessibilityDelegateCompat(
- new RecyclerViewAccessibilityDelegate(mTargetFeaturesView) {
- @NonNull
- @Override
- public AccessibilityDelegateCompat getItemDelegate() {
- return new MenuItemAccessibilityDelegate(/* recyclerViewDelegate= */ this,
- mMenuAnimationController);
- }
- });
setLayoutParams(new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
// Avoid drawing out of bounds of the parent view
setClipToOutline(true);
@@ -278,6 +276,7 @@ class MenuView extends FrameLayout implements
if (mFeaturesChangeListener != null) {
mFeaturesChangeListener.onChange(newTargetFeatures);
}
+
mMenuAnimationController.fadeOutIfEnabled();
}
@@ -306,6 +305,10 @@ class MenuView extends FrameLayout implements
return mMenuViewAppearance.getMenuPosition();
}
+ RecyclerView getTargetFeaturesView() {
+ return mTargetFeaturesView;
+ }
+
void persistPositionAndUpdateEdge(Position percentagePosition) {
mMenuViewModel.updateMenuSavingPosition(percentagePosition);
mMenuViewAppearance.setPercentagePosition(percentagePosition);
@@ -424,6 +427,35 @@ class MenuView extends FrameLayout implements
onPositionChanged();
}
+ void gotoEditScreen() {
+ if (!Flags.floatingMenuDragToEdit()) {
+ return;
+ }
+ mMenuAnimationController.flingMenuThenSpringToEdge(
+ getMenuPosition().x, 100f, 0f);
+ mContext.startActivity(getIntentForEditScreen());
+ }
+
+ Intent getIntentForEditScreen() {
+ List<String> targets = new SettingsStringUtil.ColonDelimitedSet.OfStrings(
+ mSecureSettings.getStringForUser(
+ Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
+ UserHandle.USER_CURRENT)).stream().toList();
+
+ Intent intent = new Intent(
+ Settings.ACTION_ACCESSIBILITY_SHORTCUT_SETTINGS);
+ Bundle args = new Bundle();
+ Bundle fragmentArgs = new Bundle();
+ fragmentArgs.putStringArray("targets", targets.toArray(new String[0]));
+ args.putBundle(":settings:show_fragment_args", fragmentArgs);
+ // TODO: b/318748373 - The fragment should set its own title using the targets
+ args.putString(
+ ":settings:show_fragment_title", "Accessibility Shortcut");
+ intent.replaceExtras(args);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ return intent;
+ }
+
private InstantInsetLayerDrawable getContainerViewInsetLayer() {
return (InstantInsetLayerDrawable) getBackground();
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
index 97999cc19dc8..3acf80dbbe5f 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
@@ -17,7 +17,6 @@
package com.android.systemui.accessibility.floatingmenu;
import static android.view.WindowInsets.Type.ime;
-import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
import static androidx.core.view.WindowInsetsCompat.Type;
@@ -59,8 +58,12 @@ import android.widget.FrameLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
+import androidx.core.view.AccessibilityDelegateCompat;
import androidx.lifecycle.Observer;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate;
+import com.android.internal.accessibility.common.ShortcutConstants;
import com.android.internal.accessibility.dialog.AccessibilityTarget;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.messages.nano.SystemMessageProto;
@@ -94,6 +97,8 @@ class MenuViewLayer extends FrameLayout implements
private final MenuListViewTouchHandler mMenuListViewTouchHandler;
private final MenuMessageView mMessageView;
private final DismissView mDismissView;
+ private final DragToInteractView mDragToInteractView;
+
private final MenuViewAppearance mMenuViewAppearance;
private final MenuAnimationController mMenuAnimationController;
private final AccessibilityManager mAccessibilityManager;
@@ -149,7 +154,7 @@ class MenuViewLayer extends FrameLayout implements
final List<ComponentName> hardwareKeyShortcutComponents =
mAccessibilityManager.getAccessibilityShortcutTargets(
- ACCESSIBILITY_SHORTCUT_KEY)
+ ShortcutConstants.UserShortcutType.HARDWARE)
.stream()
.map(ComponentName::unflattenFromString)
.toList();
@@ -178,7 +183,10 @@ class MenuViewLayer extends FrameLayout implements
};
MenuViewLayer(@NonNull Context context, WindowManager windowManager,
- AccessibilityManager accessibilityManager, IAccessibilityFloatingMenu floatingMenu,
+ AccessibilityManager accessibilityManager,
+ MenuViewModel menuViewModel,
+ MenuViewAppearance menuViewAppearance, MenuView menuView,
+ IAccessibilityFloatingMenu floatingMenu,
SecureSettings secureSettings) {
super(context);
@@ -190,43 +198,52 @@ class MenuViewLayer extends FrameLayout implements
mFloatingMenu = floatingMenu;
mSecureSettings = secureSettings;
- mMenuViewModel = new MenuViewModel(context, accessibilityManager, secureSettings);
- mMenuViewAppearance = new MenuViewAppearance(context, windowManager);
- mMenuView = new MenuView(context, mMenuViewModel, mMenuViewAppearance);
+ mMenuViewModel = menuViewModel;
+ mMenuViewAppearance = menuViewAppearance;
+ mMenuView = menuView;
+ RecyclerView targetFeaturesView = mMenuView.getTargetFeaturesView();
+ targetFeaturesView.setAccessibilityDelegateCompat(
+ new RecyclerViewAccessibilityDelegate(targetFeaturesView) {
+ @NonNull
+ @Override
+ public AccessibilityDelegateCompat getItemDelegate() {
+ return new MenuItemAccessibilityDelegate(/* recyclerViewDelegate= */ this,
+ mMenuAnimationController, MenuViewLayer.this);
+ }
+ });
mMenuAnimationController = mMenuView.getMenuAnimationController();
- if (Flags.floatingMenuDragToHide()) {
- mMenuAnimationController.setDismissCallback(this::hideMenuAndShowNotification);
- } else {
- mMenuAnimationController.setDismissCallback(this::hideMenuAndShowMessage);
- }
mMenuAnimationController.setSpringAnimationsEndAction(this::onSpringAnimationsEndAction);
mDismissView = new DismissView(context);
+ mDragToInteractView = new DragToInteractView(context);
DismissViewUtils.setup(mDismissView);
+ mDismissView.getCircle().setId(R.id.action_remove_menu);
mNotificationFactory = new MenuNotificationFactory(context);
mNotificationManager = context.getSystemService(NotificationManager.class);
- mDragToInteractAnimationController = new DragToInteractAnimationController(
- mDismissView, mMenuView);
+
+ if (Flags.floatingMenuDragToEdit()) {
+ mDragToInteractAnimationController = new DragToInteractAnimationController(
+ mDragToInteractView, mMenuView);
+ } else {
+ mDragToInteractAnimationController = new DragToInteractAnimationController(
+ mDismissView, mMenuView);
+ }
mDragToInteractAnimationController.setMagnetListener(new MagnetizedObject.MagnetListener() {
@Override
public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target) {
- mDragToInteractAnimationController.animateDismissMenu(/* scaleUp= */ true);
+ mDragToInteractAnimationController.animateInteractMenu(
+ target.getTargetView().getId(), /* scaleUp= */ true);
}
@Override
public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
float velocityX, float velocityY, boolean wasFlungOut) {
- mDragToInteractAnimationController.animateDismissMenu(/* scaleUp= */ false);
+ mDragToInteractAnimationController.animateInteractMenu(
+ target.getTargetView().getId(), /* scaleUp= */ false);
}
@Override
public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
- if (Flags.floatingMenuDragToHide()) {
- hideMenuAndShowNotification();
- } else {
- hideMenuAndShowMessage();
- }
- mDismissView.hide();
- mDragToInteractAnimationController.animateDismissMenu(/* scaleUp= */ false);
+ dispatchAccessibilityAction(target.getTargetView().getId());
}
});
@@ -262,7 +279,11 @@ class MenuViewLayer extends FrameLayout implements
});
addView(mMenuView, LayerIndex.MENU_VIEW);
- addView(mDismissView, LayerIndex.DISMISS_VIEW);
+ if (Flags.floatingMenuDragToEdit()) {
+ addView(mDragToInteractView, LayerIndex.DISMISS_VIEW);
+ } else {
+ addView(mDismissView, LayerIndex.DISMISS_VIEW);
+ }
addView(mMessageView, LayerIndex.MESSAGE_VIEW);
if (Flags.floatingMenuAnimatedTuck()) {
@@ -272,6 +293,7 @@ class MenuViewLayer extends FrameLayout implements
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
+ mDragToInteractView.updateResources();
mDismissView.updateResources();
mDragToInteractAnimationController.updateResources();
}
@@ -428,6 +450,23 @@ class MenuViewLayer extends FrameLayout implements
}
}
+ void dispatchAccessibilityAction(int id) {
+ if (id == R.id.action_remove_menu) {
+ if (Flags.floatingMenuDragToHide()) {
+ hideMenuAndShowNotification();
+ } else {
+ hideMenuAndShowMessage();
+ }
+ } else if (id == R.id.action_edit
+ && Flags.floatingMenuDragToEdit()) {
+ mMenuView.gotoEditScreen();
+ }
+ mDismissView.hide();
+ mDragToInteractView.hide();
+ mDragToInteractAnimationController.animateInteractMenu(
+ id, /* scaleUp= */ false);
+ }
+
private CharSequence getMigrationMessage() {
final Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_DETAILS_SETTINGS);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
@@ -475,7 +514,8 @@ class MenuViewLayer extends FrameLayout implements
mEduTooltipView = Optional.empty();
}
- private void hideMenuAndShowMessage() {
+ @VisibleForTesting
+ void hideMenuAndShowMessage() {
final int delayTime = mAccessibilityManager.getRecommendedTimeoutMillis(
SHOW_MESSAGE_DELAY_MS,
AccessibilityManager.FLAG_CONTENT_TEXT
@@ -485,7 +525,8 @@ class MenuViewLayer extends FrameLayout implements
mMenuAnimationController.startShrinkAnimation(() -> mMenuView.setVisibility(GONE));
}
- private void hideMenuAndShowNotification() {
+ @VisibleForTesting
+ void hideMenuAndShowNotification() {
mMenuAnimationController.startShrinkAnimation(() -> mMenuView.setVisibility(GONE));
showNotification();
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
index 1f549525256b..bc9d1ffd259b 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
@@ -39,7 +39,16 @@ class MenuViewLayerController implements IAccessibilityFloatingMenu {
MenuViewLayerController(Context context, WindowManager windowManager,
AccessibilityManager accessibilityManager, SecureSettings secureSettings) {
mWindowManager = windowManager;
- mMenuViewLayer = new MenuViewLayer(context, windowManager, accessibilityManager, this,
+
+ MenuViewModel menuViewModel = new MenuViewModel(
+ context, accessibilityManager, secureSettings);
+ MenuViewAppearance menuViewAppearance = new MenuViewAppearance(context, windowManager);
+
+ mMenuViewLayer = new MenuViewLayer(context, windowManager, accessibilityManager,
+ menuViewModel,
+ menuViewAppearance,
+ new MenuView(context, menuViewModel, menuViewAppearance, secureSettings),
+ this,
secureSettings);
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index 71d0e7d6081a..d2c62272e2ec 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -25,6 +25,7 @@ import android.hardware.biometrics.BiometricFingerprintConstants
import android.hardware.biometrics.BiometricSourceType
import android.util.DisplayMetrics
import androidx.annotation.VisibleForTesting
+import androidx.lifecycle.repeatOnLifecycle
import com.android.app.animation.Interpolators
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
@@ -35,7 +36,11 @@ import com.android.systemui.Flags.lightRevealMigration
import com.android.systemui.biometrics.data.repository.FacePropertyRepository
import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.deviceentry.domain.interactor.AuthRippleInteractor
+import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
+import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.log.core.LogLevel
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.res.R
@@ -78,6 +83,7 @@ class AuthRippleController @Inject constructor(
private val logger: KeyguardLogger,
private val biometricUnlockController: BiometricUnlockController,
private val lightRevealScrim: LightRevealScrim,
+ private val authRippleInteractor: AuthRippleInteractor,
private val facePropertyRepository: FacePropertyRepository,
rippleView: AuthRippleView?
) :
@@ -100,6 +106,22 @@ class AuthRippleController @Inject constructor(
init()
}
+ init {
+ if (DeviceEntryUdfpsRefactor.isEnabled) {
+ rippleView?.repeatWhenAttached {
+ repeatOnLifecycle(androidx.lifecycle.Lifecycle.State.CREATED) {
+ authRippleInteractor.showUnlockRipple.collect { biometricUnlockSource ->
+ if (biometricUnlockSource == BiometricUnlockSource.FINGERPRINT_SENSOR) {
+ showUnlockRippleInternal(BiometricSourceType.FINGERPRINT)
+ } else {
+ showUnlockRippleInternal(BiometricSourceType.FACE)
+ }
+ }
+ }
+ }
+ }
+ }
+
@VisibleForTesting
public override fun onViewAttached() {
authController.addCallback(authControllerCallback)
@@ -111,7 +133,9 @@ class AuthRippleController @Inject constructor(
keyguardStateController.addCallback(this)
wakefulnessLifecycle.addObserver(this)
commandRegistry.registerCommand("auth-ripple") { AuthRippleCommand() }
- biometricUnlockController.addListener(biometricModeListener)
+ if (!DeviceEntryUdfpsRefactor.isEnabled) {
+ biometricUnlockController.addListener(biometricModeListener)
+ }
}
private val biometricModeListener =
@@ -119,8 +143,9 @@ class AuthRippleController @Inject constructor(
override fun onBiometricUnlockedWithKeyguardDismissal(
biometricSourceType: BiometricSourceType?
) {
+ DeviceEntryUdfpsRefactor.assertInLegacyMode()
if (biometricSourceType != null) {
- showUnlockRipple(biometricSourceType)
+ showUnlockRippleInternal(biometricSourceType)
} else {
logger.log(TAG,
LogLevel.ERROR,
@@ -143,7 +168,13 @@ class AuthRippleController @Inject constructor(
notificationShadeWindowController.setForcePluginOpen(false, this)
}
- fun showUnlockRipple(biometricSourceType: BiometricSourceType) {
+ @Deprecated("Update authRippleInteractor.showUnlockRipple instead of calling this.")
+ fun showUnlockRipple(biometricSourceType: BiometricSourceType) {
+ DeviceEntryUdfpsRefactor.assertInLegacyMode()
+ showUnlockRippleInternal(biometricSourceType)
+ }
+
+ private fun showUnlockRippleInternal(biometricSourceType: BiometricSourceType) {
val keyguardNotShowing = !keyguardStateController.isShowing
val unlockNotAllowed = !keyguardUpdateMonitor
.isUnlockingWithBiometricAllowed(biometricSourceType)
@@ -377,12 +408,12 @@ class AuthRippleController @Inject constructor(
}
"fingerprint" -> {
pw.println("fingerprint ripple sensorLocation=$fingerprintSensorLocation")
- showUnlockRipple(BiometricSourceType.FINGERPRINT)
+ showUnlockRippleInternal(BiometricSourceType.FINGERPRINT)
}
"face" -> {
// note: only shows when about to proceed to the home screen
pw.println("face ripple sensorLocation=$faceSensorLocation")
- showUnlockRipple(BiometricSourceType.FACE)
+ showUnlockRippleInternal(BiometricSourceType.FACE)
}
"custom" -> {
if (args.size != 3 ||
@@ -409,7 +440,7 @@ class AuthRippleController @Inject constructor(
pw.println(" custom <x-location: int> <y-location: int>")
}
- fun invalidCommand(pw: PrintWriter) {
+ private fun invalidCommand(pw: PrintWriter) {
pw.println("invalid command")
help(pw)
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricCustomizedViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricCustomizedViewBinder.kt
index 16e7f05fae37..96582cb56dd7 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricCustomizedViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricCustomizedViewBinder.kt
@@ -114,7 +114,7 @@ private fun createNewRowLayout(inflater: LayoutInflater): LinearLayout {
private fun PromptContentItem.doesExceedMaxLinesIfTwoColumn(
resources: Resources,
): Boolean {
- val passedInText: CharSequence =
+ val passedInText: String =
when (this) {
is PromptContentItemPlainText -> text
is PromptContentItemBulletedText -> text
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
index c36e0e21d021..80d37b4741e4 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
@@ -121,11 +121,13 @@ constructor(
if (it.isAttachedToWindow) {
lottie = it.requireViewById<LottieAnimationView>(R.id.sidefps_animation)
lottie?.pauseAnimation()
+ lottie?.removeAllLottieOnCompositionLoadedListener()
windowManager.get().removeView(it)
}
}
overlayView = layoutInflater.get().inflate(R.layout.sidefps_view, null, false)
+
val overlayViewModel =
SideFpsOverlayViewModel(
applicationContext,
@@ -163,8 +165,10 @@ constructor(
val lottie = it.requireViewById<LottieAnimationView>(R.id.sidefps_animation)
lottie.addLottieOnCompositionLoadedListener { composition: LottieComposition ->
- viewModel.setLottieBounds(composition.bounds)
- overlayView.visibility = View.VISIBLE
+ if (overlayView.visibility != View.VISIBLE) {
+ viewModel.setLottieBounds(composition.bounds)
+ overlayView.visibility = View.VISIBLE
+ }
}
it.alpha = 0f
val overlayShowAnimator =
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index e78a7a942993..0f1340a63032 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -259,7 +259,9 @@ constructor(
/** Custom content view for the prompt. */
val contentView: Flow<PromptContentView?> =
- promptSelectorInteractor.prompt.map { it?.contentView }.distinctUntilChanged()
+ promptSelectorInteractor.prompt
+ .map { if (customBiometricPrompt()) it?.contentView else null }
+ .distinctUntilChanged()
private val originalDescription =
promptSelectorInteractor.prompt.map { it?.description ?: "" }.distinctUntilChanged()
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
index 3287ed4d4991..f36547b01802 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
@@ -27,21 +27,17 @@ import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
import com.android.systemui.communal.widgets.CommunalAppWidgetHost
import com.android.systemui.communal.widgets.WidgetConfigurator
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
import com.android.systemui.log.dagger.CommunalLog
+import com.android.systemui.util.kotlin.getValue
import java.util.Optional
import javax.inject.Inject
import kotlin.coroutines.cancellation.CancellationException
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
@@ -67,18 +63,15 @@ interface CommunalWidgetRepository {
* @param widgetIdToPriorityMap mapping of the widget ids to the priority of the widget.
*/
fun updateWidgetOrder(widgetIdToPriorityMap: Map<Int, Int>) {}
-
- /** Update whether the app widget host should be active. */
- fun updateAppWidgetHostActive(active: Boolean)
}
@SysUISingleton
class CommunalWidgetRepositoryImpl
@Inject
constructor(
- private val appWidgetManager: Optional<AppWidgetManager>,
+ appWidgetManagerOptional: Optional<AppWidgetManager>,
private val appWidgetHost: CommunalAppWidgetHost,
- @Application private val applicationScope: CoroutineScope,
+ @Background private val bgScope: CoroutineScope,
@Background private val bgDispatcher: CoroutineDispatcher,
private val communalWidgetHost: CommunalWidgetHost,
private val communalWidgetDao: CommunalWidgetDao,
@@ -90,41 +83,22 @@ constructor(
private val logger = Logger(logBuffer, TAG)
- override fun updateAppWidgetHostActive(active: Boolean) {
- if (active == isHostActive.value) {
- return
- }
-
- if (active) {
- appWidgetHost.startListening()
- } else {
- appWidgetHost.stopListening()
- }
- isHostActive.value = active
- }
-
- private val isHostActive = MutableStateFlow(false)
+ private val appWidgetManager by appWidgetManagerOptional
- @OptIn(ExperimentalCoroutinesApi::class)
override val communalWidgets: Flow<List<CommunalWidgetContentModel>> =
- isHostActive.flatMapLatest { isHostActive ->
- if (!isHostActive || !appWidgetManager.isPresent) {
- return@flatMapLatest flowOf(emptyList())
- }
- communalWidgetDao
- .getWidgets()
- .map { it.map(::mapToContentModel) }
- // As this reads from a database and triggers IPCs to AppWidgetManager,
- // it should be executed in the background.
- .flowOn(bgDispatcher)
- }
+ communalWidgetDao
+ .getWidgets()
+ .map { it.mapNotNull(::mapToContentModel) }
+ // As this reads from a database and triggers IPCs to AppWidgetManager,
+ // it should be executed in the background.
+ .flowOn(bgDispatcher)
override fun addWidget(
provider: ComponentName,
priority: Int,
configurator: WidgetConfigurator?
) {
- applicationScope.launch(bgDispatcher) {
+ bgScope.launch {
val id = communalWidgetHost.allocateIdAndBindWidget(provider)
if (id == null) {
logger.e("Failed to allocate widget id to ${provider.flattenToString()}")
@@ -170,7 +144,7 @@ constructor(
}
override fun deleteWidget(widgetId: Int) {
- applicationScope.launch(bgDispatcher) {
+ bgScope.launch {
communalWidgetDao.deleteWidgetById(widgetId)
appWidgetHost.deleteAppWidgetId(widgetId)
logger.i("Deleted widget with id $widgetId.")
@@ -178,7 +152,7 @@ constructor(
}
override fun updateWidgetOrder(widgetIdToPriorityMap: Map<Int, Int>) {
- applicationScope.launch(bgDispatcher) {
+ bgScope.launch {
communalWidgetDao.updateWidgetOrder(widgetIdToPriorityMap)
logger.i({ "Updated the order of widget list with ids: $str1." }) {
str1 = widgetIdToPriorityMap.toString()
@@ -189,11 +163,12 @@ constructor(
@WorkerThread
private fun mapToContentModel(
entry: Map.Entry<CommunalItemRank, CommunalWidgetItem>
- ): CommunalWidgetContentModel {
+ ): CommunalWidgetContentModel? {
val (_, widgetId) = entry.value
+ val providerInfo = appWidgetManager?.getAppWidgetInfo(widgetId) ?: return null
return CommunalWidgetContentModel(
appWidgetId = widgetId,
- providerInfo = appWidgetManager.get().getAppWidgetInfo(widgetId),
+ providerInfo = providerInfo,
priority = entry.key.rank,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index c36f7fa22c82..80fee640974d 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
@@ -33,23 +33,23 @@ import com.android.systemui.communal.widgets.CommunalAppWidgetHost
import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
import com.android.systemui.communal.widgets.WidgetConfigurator
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.smartspace.data.repository.SmartspaceRepository
import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.util.kotlin.BooleanFlowOperators.and
+import com.android.systemui.util.kotlin.BooleanFlowOperators.not
+import com.android.systemui.util.kotlin.BooleanFlowOperators.or
import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.flow.stateIn
/** Encapsulates business-logic related to communal mode. */
@OptIn(ExperimentalCoroutinesApi::class)
@@ -57,7 +57,6 @@ import kotlinx.coroutines.flow.stateIn
class CommunalInteractor
@Inject
constructor(
- @Application applicationScope: CoroutineScope,
private val communalRepository: CommunalRepository,
private val widgetRepository: CommunalWidgetRepository,
private val communalPrefsRepository: CommunalPrefsRepository,
@@ -68,38 +67,24 @@ constructor(
private val appWidgetHost: CommunalAppWidgetHost,
private val editWidgetsActivityStarter: EditWidgetsActivityStarter
) {
+ private val _editModeOpen = MutableStateFlow(false)
+
+ /** Whether edit mode is currently open. */
+ val editModeOpen: StateFlow<Boolean> = _editModeOpen.asStateFlow()
+
/** Whether communal features are enabled. */
val isCommunalEnabled: Boolean
get() = communalRepository.isCommunalEnabled
- /** A {@link StateFlow} that tracks whether communal features are enabled. */
- val communalEnabledState: StateFlow<Boolean>
- get() = communalRepository.communalEnabledState
-
/** Whether communal features are enabled and available. */
- val isCommunalAvailable: StateFlow<Boolean> =
- flowOf(isCommunalEnabled)
- .flatMapLatest { enabled ->
- if (enabled)
- combine(
- keyguardInteractor.isEncryptedOrLockdown,
- userRepository.selectedUserInfo,
- keyguardInteractor.isKeyguardVisible,
- keyguardInteractor.isDreaming,
- ) { isEncryptedOrLockdown, selectedUserInfo, isKeyguardVisible, isDreaming ->
- !isEncryptedOrLockdown &&
- selectedUserInfo.isMain &&
- (isKeyguardVisible || isDreaming)
- }
- else flowOf(false)
- }
- .distinctUntilChanged()
- .onEach { available -> widgetRepository.updateAppWidgetHostActive(available) }
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = false,
+ val isCommunalAvailable: Flow<Boolean> =
+ and(
+ communalRepository.communalEnabledState,
+ userRepository.selectedUserInfo.map { it.isMain },
+ not(keyguardInteractor.isEncryptedOrLockdown),
+ or(keyguardInteractor.isKeyguardVisible, keyguardInteractor.isDreaming)
)
+ .distinctUntilChanged()
/**
* Target scene as requested by the underlying [SceneTransitionLayout] or through
@@ -166,9 +151,13 @@ constructor(
communalRepository.setDesiredScene(newScene)
}
+ fun setEditModeOpen(isOpen: Boolean) {
+ _editModeOpen.value = isOpen
+ }
+
/** Show the widget editor Activity. */
- fun showWidgetEditor() {
- editWidgetsActivityStarter.startActivity()
+ fun showWidgetEditor(preselectedKey: String? = null) {
+ editWidgetsActivityStarter.startActivity(preselectedKey)
}
/** Dismiss the CTA tile from the hub in view mode. */
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
index acd6cb09e241..ae019a187bae 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
@@ -128,4 +128,6 @@ sealed interface CommunalContentModel {
}
}
}
+
+ fun isWidget() = this is Widget
}
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 1e64d3f9cedc..a87ebd7e8e9f 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
@@ -33,18 +33,16 @@ abstract class BaseCommunalViewModel(
private val communalInteractor: CommunalInteractor,
val mediaHost: MediaHost,
) {
- val isCommunalAvailable: StateFlow<Boolean> = communalInteractor.isCommunalAvailable
-
val currentScene: StateFlow<CommunalSceneKey> = communalInteractor.desiredScene
/** Whether widgets are currently being re-ordered. */
open val reorderingWidgets: StateFlow<Boolean> = MutableStateFlow(false)
- private val _selectedIndex: MutableStateFlow<Int?> = MutableStateFlow(null)
+ private val _selectedKey: MutableStateFlow<String?> = MutableStateFlow(null)
- /** The index of the currently selected item, or null if no item selected. */
- val selectedIndex: StateFlow<Int?>
- get() = _selectedIndex
+ /** The key of the currently selected item, or null if no item selected. */
+ val selectedKey: StateFlow<String?>
+ get() = _selectedKey
fun onSceneChanged(scene: CommunalSceneKey) {
communalInteractor.onSceneChanged(scene)
@@ -94,8 +92,8 @@ abstract class BaseCommunalViewModel(
*/
open fun onReorderWidgets(widgetIdToPriorityMap: Map<Int, Int>) {}
- /** Called as the UI requests opening the widget editor. */
- open fun onOpenWidgetEditor() {}
+ /** Called as the UI requests opening the widget editor with an optional preselected widget. */
+ open fun onOpenWidgetEditor(preselectedKey: String? = null) {}
/** Called as the UI requests to dismiss the CTA tile. */
open fun onDismissCtaTile() {}
@@ -109,8 +107,8 @@ abstract class BaseCommunalViewModel(
/** Called as the user cancels dragging a widget to reorder. */
open fun onReorderWidgetCancel() {}
- /** Set the index of the currently selected item */
- fun setSelectedIndex(index: Int?) {
- _selectedIndex.value = index
+ /** Set the key of the currently selected item */
+ fun setSelectedKey(key: String?) {
+ _selectedKey.value = key
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
index 237a0c005af5..ebcfb8bb8209 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
@@ -29,7 +29,6 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onEach
/** The view model for communal hub in edit mode. */
@SysUISingleton
@@ -44,10 +43,9 @@ constructor(
// Only widgets are editable. The CTA tile comes last in the list and remains visible.
override val communalContent: Flow<List<CommunalContentModel>> =
- communalInteractor.widgetContent
- // Clear the selected index when the list is updated.
- .onEach { setSelectedIndex(null) }
- .map { widgets -> widgets + listOf(CommunalContentModel.CtaTileInEditMode()) }
+ communalInteractor.widgetContent.map { widgets ->
+ widgets + listOf(CommunalContentModel.CtaTileInEditMode())
+ }
private val _reorderingWidgets = MutableStateFlow(false)
@@ -61,7 +59,7 @@ constructor(
override fun onReorderWidgetStart() {
// Clear selection status
- setSelectedIndex(null)
+ setSelectedKey(null)
_reorderingWidgets.value = true
uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_START)
}
@@ -75,4 +73,7 @@ constructor(
_reorderingWidgets.value = false
uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_CANCEL)
}
+
+ /** Sets whether edit mode is currently open */
+ fun setEditModeOpen(isOpen: Boolean) = communalInteractor.setEditModeOpen(isOpen)
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index a909383f4a2c..d7a3705248ff 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
@@ -81,7 +81,8 @@ constructor(
}
}
- override fun onOpenWidgetEditor() = communalInteractor.showWidgetEditor()
+ override fun onOpenWidgetEditor(preselectedKey: String?) =
+ communalInteractor.showWidgetEditor(preselectedKey)
override fun onDismissCtaTile() {
scope.launch {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt
new file mode 100644
index 000000000000..586df32e6561
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.widgets
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.util.kotlin.BooleanFlowOperators.or
+import com.android.systemui.util.kotlin.pairwise
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.withContext
+
+@SysUISingleton
+class CommunalAppWidgetHostStartable
+@Inject
+constructor(
+ private val appWidgetHost: CommunalAppWidgetHost,
+ private val communalInteractor: CommunalInteractor,
+ @Background private val bgScope: CoroutineScope,
+ @Main private val uiDispatcher: CoroutineDispatcher
+) : CoreStartable {
+ override fun start() {
+ or(communalInteractor.isCommunalAvailable, communalInteractor.editModeOpen)
+ // Only trigger updates on state changes, ignoring the initial false value.
+ .pairwise(false)
+ .filter { (previous, new) -> previous != new }
+ .onEach { (_, shouldListen) -> updateAppWidgetHostActive(shouldListen) }
+ .launchIn(bgScope)
+ }
+
+ private suspend fun updateAppWidgetHostActive(active: Boolean) =
+ // Always ensure this is called on the main/ui thread.
+ withContext(uiDispatcher) {
+ if (active) {
+ appWidgetHost.startListening()
+ } else {
+ appWidgetHost.stopListening()
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
index c7a14f9eefe1..ad1327e90710 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
@@ -47,6 +47,7 @@ constructor(
private const val EXTRA_FILTER_STRATEGY = "filter_strategy"
private const val FILTER_STRATEGY_GLANCEABLE_HUB = 1
private const val TAG = "EditWidgetsActivity"
+ const val EXTRA_PRESELECTED_KEY = "preselected_key"
}
private val widgetConfigurator by lazy { widgetConfiguratorFactory.create(this) }
@@ -86,10 +87,15 @@ constructor(
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+ communalViewModel.setEditModeOpen(true)
+
val windowInsetsController = window.decorView.windowInsetsController
windowInsetsController?.hide(WindowInsets.Type.systemBars())
window.setDecorFitsSystemWindows(false)
+ val preselectedKey = intent.getStringExtra(EXTRA_PRESELECTED_KEY)
+ communalViewModel.setSelectedKey(preselectedKey)
+
setCommunalEditWidgetActivityContent(
activity = this,
viewModel = communalViewModel,
@@ -138,13 +144,16 @@ constructor(
override fun onStart() {
super.onStart()
-
uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_EDIT_MODE_SHOWN)
}
override fun onStop() {
super.onStop()
-
uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_EDIT_MODE_GONE)
}
+
+ override fun onDestroy() {
+ super.onDestroy()
+ communalViewModel.setEditModeOpen(false)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivityStarter.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivityStarter.kt
index 55acad0e9ed5..d1843afbc1c2 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivityStarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivityStarter.kt
@@ -18,12 +18,13 @@ package com.android.systemui.communal.widgets
import android.content.Context
import android.content.Intent
+import com.android.systemui.communal.widgets.EditWidgetsActivity.Companion.EXTRA_PRESELECTED_KEY
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.plugins.ActivityStarter
import javax.inject.Inject
interface EditWidgetsActivityStarter {
- fun startActivity()
+ fun startActivity(preselectedKey: String? = null)
}
class EditWidgetsActivityStarterImpl
@@ -33,10 +34,11 @@ constructor(
private val activityStarter: ActivityStarter,
) : EditWidgetsActivityStarter {
- override fun startActivity() {
+ override fun startActivity(preselectedKey: String?) {
activityStarter.startActivityDismissingKeyguard(
Intent(applicationContext, EditWidgetsActivity::class.java)
- .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK),
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
+ .apply { preselectedKey?.let { putExtra(EXTRA_PRESELECTED_KEY, preselectedKey) } },
/* onlyProvisioned = */ true,
/* dismissShade = */ true,
)
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index 8d82b552fc1e..95233f701bbb 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -25,6 +25,7 @@ import com.android.systemui.back.domain.interactor.BackActionInteractor
import com.android.systemui.biometrics.BiometricNotificationService
import com.android.systemui.clipboardoverlay.ClipboardListener
import com.android.systemui.communal.log.CommunalLoggerStartable
+import com.android.systemui.communal.widgets.CommunalAppWidgetHostStartable
import com.android.systemui.controls.dagger.StartControlsStartableModule
import com.android.systemui.dagger.qualifiers.PerUser
import com.android.systemui.dreams.AssistantAttentionMonitor
@@ -324,4 +325,11 @@ abstract class SystemUICoreStartableModule {
@IntoMap
@ClassKey(CommunalLoggerStartable::class)
abstract fun bindCommunalLoggerStartable(impl: CommunalLoggerStartable): CoreStartable
+
+ @Binds
+ @IntoMap
+ @ClassKey(CommunalAppWidgetHostStartable::class)
+ abstract fun bindCommunalAppWidgetHostStartable(
+ impl: CommunalAppWidgetHostStartable
+ ): CoreStartable
}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
index 7a70c4ac9fab..cf7d60140aee 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -40,8 +40,7 @@ import com.android.systemui.deviceentry.shared.model.FailedFaceAuthenticationSta
import com.android.systemui.deviceentry.shared.model.HelpFaceAuthenticationStatus
import com.android.systemui.deviceentry.shared.model.SuccessFaceAuthenticationStatus
import com.android.systemui.dump.DumpManager
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.KeyguardWmStateRefactor
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.BiometricType
import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
@@ -63,10 +62,6 @@ import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.user.data.model.SelectionStatus
import com.android.systemui.user.data.repository.UserRepository
import com.google.errorprone.annotations.CompileTimeConstant
-import java.io.PrintWriter
-import java.util.Arrays
-import java.util.stream.Collectors
-import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
@@ -88,6 +83,10 @@ import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
+import java.io.PrintWriter
+import java.util.Arrays
+import java.util.stream.Collectors
+import javax.inject.Inject
/**
* API to run face authentication and detection for device entry / on keyguard (as opposed to the
@@ -165,7 +164,6 @@ constructor(
@FaceAuthTableLog private val faceAuthLog: TableLogBuffer,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val displayStateInteractor: DisplayStateInteractor,
- private val featureFlags: FeatureFlags,
dumpManager: DumpManager,
) : DeviceEntryFaceAuthRepository, Dumpable {
private var authCancellationSignal: CancellationSignal? = null
@@ -315,7 +313,7 @@ constructor(
// or device starts going to sleep.
merge(
powerInteractor.isAsleep,
- if (featureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ if (KeyguardWmStateRefactor.isEnabled) {
keyguardTransitionInteractor.isInTransitionToState(KeyguardState.GONE)
} else {
keyguardRepository.keyguardDoneAnimationsFinished.map { true }
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt
index 08e8c2d8271f..82834387f7b8 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt
@@ -8,35 +8,26 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyguard.data.repository.KeyguardRepository
-import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
-import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.user.data.repository.UserRepository
-import com.android.systemui.util.kotlin.sample
import dagger.Binds
import dagger.Module
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext
/** Interface for classes that can access device-entry-related application state. */
interface DeviceEntryRepository {
- /** Whether the device is immediately entering the device after a biometric unlock. */
- val enteringDeviceFromBiometricUnlock: Flow<BiometricUnlockSource>
-
/**
* Whether the device is unlocked.
*
@@ -85,12 +76,6 @@ constructor(
keyguardStateController: KeyguardStateController,
keyguardRepository: KeyguardRepository,
) : DeviceEntryRepository {
- override val enteringDeviceFromBiometricUnlock =
- keyguardRepository.biometricUnlockState
- .filter { BiometricUnlockModel.dismissesKeyguard(it) }
- .sample(
- keyguardRepository.biometricUnlockSource.filterNotNull(),
- )
private val _isUnlocked = MutableStateFlow(false)
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractor.kt
new file mode 100644
index 000000000000..337fe1ea7d98
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractor.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.deviceentry.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+
+/** Business logic for device entry auth ripple interactions. */
+@ExperimentalCoroutinesApi
+@SysUISingleton
+class AuthRippleInteractor
+@Inject
+constructor(
+ deviceEntrySourceInteractor: DeviceEntrySourceInteractor,
+ deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
+) {
+ private val showUnlockRippleFromDeviceEntryIcon: Flow<BiometricUnlockSource> =
+ deviceEntryUdfpsInteractor.isUdfpsSupported.flatMapLatest { isUdfpsSupported ->
+ if (isUdfpsSupported) {
+ deviceEntrySourceInteractor.deviceEntryFromDeviceEntryIcon.map {
+ BiometricUnlockSource.FINGERPRINT_SENSOR
+ }
+ } else {
+ emptyFlow()
+ }
+ }
+
+ private val showUnlockRippleFromBiometricUnlock: Flow<BiometricUnlockSource> =
+ deviceEntrySourceInteractor.deviceEntryFromBiometricSource
+ val showUnlockRipple: Flow<BiometricUnlockSource> =
+ merge(
+ showUnlockRippleFromDeviceEntryIcon,
+ showUnlockRippleFromBiometricUnlock,
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt
index 649a9715ffea..782bce494d11 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt
@@ -46,7 +46,7 @@ import kotlinx.coroutines.flow.onStart
class DeviceEntryHapticsInteractor
@Inject
constructor(
- deviceEntryInteractor: DeviceEntryInteractor,
+ deviceEntrySourceInteractor: DeviceEntrySourceInteractor,
deviceEntryFingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor,
deviceEntryBiometricAuthInteractor: DeviceEntryBiometricAuthInteractor,
fingerprintPropertyRepository: FingerprintPropertyRepository,
@@ -80,7 +80,7 @@ constructor(
}
val playSuccessHaptic: Flow<Unit> =
- deviceEntryInteractor.enteringDeviceFromBiometricUnlock
+ deviceEntrySourceInteractor.deviceEntryFromBiometricSource
.sample(
combine(
powerButtonSideFpsEnrolled,
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
index 09853578d3f1..73389cb1bdce 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
@@ -23,7 +23,6 @@ import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.data.repository.DeviceEntryFaceAuthRepository
import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository
import com.android.systemui.keyguard.data.repository.TrustRepository
-import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlags
import com.android.systemui.scene.shared.model.SceneKey
@@ -31,7 +30,6 @@ import com.android.systemui.scene.shared.model.SceneModel
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.collectLatest
@@ -55,7 +53,7 @@ class DeviceEntryInteractor
@Inject
constructor(
@Application private val applicationScope: CoroutineScope,
- repository: DeviceEntryRepository,
+ private val repository: DeviceEntryRepository,
private val authenticationInteractor: AuthenticationInteractor,
private val sceneInteractor: SceneInteractor,
deviceEntryFaceAuthRepository: DeviceEntryFaceAuthRepository,
@@ -63,9 +61,6 @@ constructor(
flags: SceneContainerFlags,
deviceUnlockedInteractor: DeviceUnlockedInteractor,
) {
- val enteringDeviceFromBiometricUnlock: Flow<BiometricUnlockSource> =
- repository.enteringDeviceFromBiometricUnlock
-
/**
* Whether the device is unlocked.
*
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractor.kt
new file mode 100644
index 000000000000..d4f76a84c016
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractor.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.deviceentry.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
+import com.android.systemui.util.kotlin.sample
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.map
+
+/**
+ * Hosts application business logic related to the source of the user entering the device. Note: The
+ * source of the user entering the device isn't equivalent to the reason the device is unlocked.
+ *
+ * For example, the user successfully enters the device when they dismiss the lockscreen via a
+ * bypass biometric or, if the device is already unlocked, by triggering an affordance that
+ * dismisses the lockscreen.
+ */
+@ExperimentalCoroutinesApi
+@SysUISingleton
+class DeviceEntrySourceInteractor
+@Inject
+constructor(
+ keyguardInteractor: KeyguardInteractor,
+) {
+ val deviceEntryFromBiometricSource: Flow<BiometricUnlockSource> =
+ keyguardInteractor.biometricUnlockState
+ .filter { BiometricUnlockModel.dismissesKeyguard(it) }
+ .sample(
+ keyguardInteractor.biometricUnlockSource.filterNotNull(),
+ )
+
+ private val attemptEnterDeviceFromDeviceEntryIcon: MutableSharedFlow<Unit> = MutableSharedFlow()
+ val deviceEntryFromDeviceEntryIcon: Flow<Unit> =
+ attemptEnterDeviceFromDeviceEntryIcon
+ .sample(keyguardInteractor.isKeyguardDismissible)
+ .filter { it } // only send events if the keyguard is dismissible
+ .map {} // map to Unit
+
+ suspend fun attemptEnterDeviceFromDeviceEntryIcon() {
+ attemptEnterDeviceFromDeviceEntryIcon.emit(Unit)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index d2883cce06c2..c69c9ef93761 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -220,19 +220,6 @@ object Flags {
@JvmField
val WALLPAPER_PICKER_PREVIEW_ANIMATION = releasedFlag("wallpaper_picker_preview_animation")
- /**
- * TODO(b/278086361): Tracking bug
- * Complete rewrite of the interactions between System UI and Window Manager involving keyguard
- * state. When enabled, calls to ActivityTaskManagerService from System UI will exclusively
- * occur from [WmLockscreenVisibilityManager] rather than the legacy KeyguardViewMediator.
- *
- * This flag is under development; some types of unlock may not animate properly if you enable
- * it.
- */
- @JvmField
- val KEYGUARD_WM_STATE_REFACTOR: UnreleasedFlag =
- unreleasedFlag("keyguard_wm_state_refactor")
-
// 300 - power menu
// TODO(b/254512600): Tracking Bug
@JvmField val POWER_MENU_LITE = releasedFlag("power_menu_lite")
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/BluetoothDialog.java b/packages/SystemUI/src/com/android/systemui/keyboard/BluetoothDialogDelegate.java
index 5deea9bc737a..98642d75f907 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/BluetoothDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/BluetoothDialogDelegate.java
@@ -16,17 +16,25 @@
package com.android.systemui.keyboard;
-import android.content.Context;
import android.view.WindowManager;
import com.android.systemui.statusbar.phone.SystemUIDialog;
-public class BluetoothDialog extends SystemUIDialog {
+import javax.inject.Inject;
- public BluetoothDialog(Context context) {
- super(context);
+public class BluetoothDialogDelegate implements SystemUIDialog.Delegate{
- getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
- setShowForAllUsers(true);
+ private final SystemUIDialog.Factory mSystemUIDialogFactory;
+ @Inject
+ public BluetoothDialogDelegate(SystemUIDialog.Factory systemUIDialogFactory) {
+ mSystemUIDialogFactory = systemUIDialogFactory;
+ }
+
+ @Override
+ public SystemUIDialog createDialog() {
+ SystemUIDialog dialog = mSystemUIDialogFactory.create(this);
+ dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
+ dialog.setShowForAllUsers(true);
+ return dialog;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
index 1cdbe6fed58a..17e3ca6f1a8c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
@@ -52,6 +52,7 @@ import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
import com.android.systemui.CoreStartable;
import com.android.systemui.res.R;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.util.settings.SecureSettings;
import java.io.PrintWriter;
@@ -109,6 +110,7 @@ public class KeyboardUI implements CoreStartable, InputManager.OnTabletModeChang
private final Provider<LocalBluetoothManager> mBluetoothManagerProvider;
private final SecureSettings mSecureSettings;
+ private final BluetoothDialogDelegate mBluetoothDialogDelegate;
private boolean mEnabled;
private String mKeyboardName;
@@ -121,16 +123,20 @@ public class KeyboardUI implements CoreStartable, InputManager.OnTabletModeChang
private int mInTabletMode = InputManager.SWITCH_STATE_UNKNOWN;
private int mScanAttempt = 0;
private ScanCallback mScanCallback;
- private BluetoothDialog mDialog;
+ private SystemUIDialog mDialog;
private int mState;
@Inject
- public KeyboardUI(Context context, Provider<LocalBluetoothManager> bluetoothManagerProvider,
- SecureSettings secureSettings) {
+ public KeyboardUI(
+ Context context,
+ Provider<LocalBluetoothManager> bluetoothManagerProvider,
+ SecureSettings secureSettings,
+ BluetoothDialogDelegate bluetoothDialogDelegate) {
mContext = context;
this.mBluetoothManagerProvider = bluetoothManagerProvider;
mSecureSettings = secureSettings;
+ mBluetoothDialogDelegate = bluetoothDialogDelegate;
}
@Override
@@ -437,7 +443,7 @@ public class KeyboardUI implements CoreStartable, InputManager.OnTabletModeChang
new BluetoothDialogClickListener();
DialogInterface.OnDismissListener dismissListener =
new BluetoothDialogDismissListener();
- mDialog = new BluetoothDialog(mContext);
+ mDialog = mBluetoothDialogDelegate.createDialog();
mDialog.setTitle(R.string.enable_bluetooth_title);
mDialog.setMessage(R.string.enable_bluetooth_message);
mDialog.setPositiveButton(
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/StickyKeysLogger.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/StickyKeysLogger.kt
index 4ed812010100..5ef5ef19c809 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/StickyKeysLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/StickyKeysLogger.kt
@@ -43,4 +43,13 @@ class StickyKeysLogger @Inject constructor(@KeyboardLog private val buffer: LogB
{ "new sticky keys state received: $str1" }
)
}
-} \ No newline at end of file
+
+ fun logNewSettingValue(enabled: Boolean) {
+ buffer.log(
+ TAG,
+ LogLevel.INFO,
+ { bool1 = enabled },
+ { "sticky key setting changed, new state: ${if (bool1) "enabled" else "disabled"}" }
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt
index 34d288815570..ec29bd6014ef 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt
@@ -19,8 +19,10 @@ package com.android.systemui.keyboard.stickykeys.data.repository
import android.hardware.input.InputManager
import android.hardware.input.InputManager.StickyModifierStateListener
import android.hardware.input.StickyModifierState
+import android.provider.Settings
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyboard.stickykeys.StickyKeysLogger
import com.android.systemui.keyboard.stickykeys.shared.model.Locked
@@ -30,14 +32,19 @@ import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.ALT_GR
import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.CTRL
import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.META
import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.SHIFT
+import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.onStart
import javax.inject.Inject
interface StickyKeysRepository {
@@ -45,11 +52,15 @@ interface StickyKeysRepository {
val settingEnabled: Flow<Boolean>
}
+@SysUISingleton
+@OptIn(ExperimentalCoroutinesApi::class)
class StickyKeysRepositoryImpl
@Inject
constructor(
private val inputManager: InputManager,
@Background private val backgroundDispatcher: CoroutineDispatcher,
+ private val secureSettings: SecureSettings,
+ userRepository: UserRepository,
private val stickyKeysLogger: StickyKeysLogger,
) : StickyKeysRepository {
@@ -66,8 +77,26 @@ constructor(
.onEach { stickyKeysLogger.logNewStickyKeysReceived(it) }
.flowOn(backgroundDispatcher)
- // TODO(b/319837892): Implement reading actual setting
- override val settingEnabled: StateFlow<Boolean> = MutableStateFlow(true)
+ override val settingEnabled: Flow<Boolean> =
+ userRepository.selectedUserInfo
+ .flatMapLatest { stickyKeySettingObserver(it.id) }
+ .flowOn(backgroundDispatcher)
+
+ private fun stickyKeySettingObserver(userId: Int): Flow<Boolean> {
+ return secureSettings
+ .observerFlow(userId, SETTING_KEY)
+ .onStart { emit(Unit) }
+ .map { isSettingEnabledForCurrentUser(userId) }
+ .distinctUntilChanged()
+ .onEach { stickyKeysLogger.logNewSettingValue(it) }
+ }
+
+ private fun isSettingEnabledForCurrentUser(userId: Int) =
+ secureSettings.getIntForUser(
+ /* name= */ SETTING_KEY,
+ /* default= */ 0,
+ /* userHandle= */ userId
+ ) != 0
private fun toStickyKeysMap(state: StickyModifierState): LinkedHashMap<ModifierKey, Locked> {
val keys = linkedMapOf<ModifierKey, Locked>()
@@ -88,5 +117,6 @@ constructor(
companion object {
const val TAG = "StickyKeysRepositoryImpl"
+ const val SETTING_KEY = Settings.Secure.ACCESSIBILITY_STICKY_KEYS
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index e2ab20e29e2d..f10b87ee1cc5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -77,7 +77,6 @@ import com.android.keyguard.mediator.ScreenOnCoordinator;
import com.android.systemui.SystemUIApplication;
import com.android.systemui.dagger.qualifiers.Application;
import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.ui.binder.KeyguardSurfaceBehindParamsApplier;
import com.android.systemui.keyguard.ui.binder.KeyguardSurfaceBehindViewBinder;
import com.android.systemui.keyguard.ui.binder.WindowManagerLockscreenVisibilityViewBinder;
@@ -329,7 +328,7 @@ public class KeyguardService extends Service {
mFlags = featureFlags;
mPowerInteractor = powerInteractor;
- if (mFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ if (KeyguardWmStateRefactor.isEnabled()) {
WindowManagerLockscreenVisibilityViewBinder.bind(
wmLockscreenVisibilityViewModel,
wmLockscreenVisibilityManager,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index 01ba0d214a97..53c81e537708 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -419,7 +419,7 @@ class KeyguardUnlockAnimationController @Inject constructor(
*/
fun canPerformInWindowLauncherAnimations(): Boolean {
// TODO(b/278086361): Refactor in-window animations.
- return !featureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR) &&
+ return !KeyguardWmStateRefactor.isEnabled &&
isSupportedLauncherUnderneath() &&
// If the launcher is underneath, but we're about to launch an activity, don't do
// the animations since they won't be visible.
@@ -866,7 +866,7 @@ class KeyguardUnlockAnimationController @Inject constructor(
}
surfaceBehindRemoteAnimationTargets?.forEach { surfaceBehindRemoteAnimationTarget ->
- if (!featureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ if (!KeyguardWmStateRefactor.isEnabled) {
val surfaceHeight: Int =
surfaceBehindRemoteAnimationTarget.screenSpaceBounds.height()
@@ -1005,7 +1005,7 @@ class KeyguardUnlockAnimationController @Inject constructor(
if (keyguardStateController.isShowing) {
// Hide the keyguard, with no fade out since we animated it away during the unlock.
- if (!featureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ if (!KeyguardWmStateRefactor.isEnabled) {
keyguardViewController.hide(
surfaceBehindRemoteAnimationStartTime,
0 /* fadeOutDuration */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 50caf17f71dd..794befa3725d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -139,7 +139,6 @@ import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.flags.SystemPropertiesHelper;
import com.android.systemui.keyguard.dagger.KeyguardModule;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
@@ -175,8 +174,6 @@ import com.android.systemui.util.time.SystemClock;
import com.android.systemui.wallpapers.data.repository.WallpaperRepository;
import com.android.wm.shell.keyguard.KeyguardTransitions;
-import dagger.Lazy;
-
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -186,6 +183,7 @@ import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
+import dagger.Lazy;
import kotlinx.coroutines.CoroutineDispatcher;
/**
@@ -1051,7 +1049,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
IRemoteAnimationFinishedCallback finishedCallback) {
Trace.beginSection("mExitAnimationRunner.onAnimationStart#startKeyguardExitAnimation");
startKeyguardExitAnimation(transit, apps, wallpapers, nonApps, finishedCallback);
- if (mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ if (KeyguardWmStateRefactor.isEnabled()) {
mWmLockscreenVisibilityManager.get().onKeyguardGoingAwayRemoteAnimationStart(
transit, apps, wallpapers, nonApps, finishedCallback);
}
@@ -1061,7 +1059,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
@Override // Binder interface
public void onAnimationCancelled() {
cancelKeyguardExitAnimation();
- if (mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ if (KeyguardWmStateRefactor.isEnabled()) {
mWmLockscreenVisibilityManager.get().onKeyguardGoingAwayRemoteAnimationCancelled();
}
}
@@ -2186,6 +2184,10 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
*/
public void showDismissibleKeyguard() {
if (mFoldGracePeriodProvider.isEnabled()) {
+ if (!mUpdateMonitor.isDeviceProvisioned()) {
+ Log.d(TAG, "Device not provisioned, so ignore request to show keyguard.");
+ return;
+ }
Bundle showKeyguardUnlocked = new Bundle();
showKeyguardUnlocked.putBoolean(OPTION_SHOW_DISMISSIBLE, true);
showKeyguard(showKeyguardUnlocked);
@@ -2757,7 +2759,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
mUiBgExecutor.execute(() -> {
Log.d(TAG, "updateActivityLockScreenState(" + showing + ", " + aodShowing + ")");
- if (mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ if (KeyguardWmStateRefactor.isEnabled()) {
// Handled in WmLockscreenVisibilityManager if flag is enabled.
return;
}
@@ -2811,7 +2813,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
setShowingLocked(true, hidingOrGoingAway /* force */);
mHiding = false;
- if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ if (!KeyguardWmStateRefactor.isEnabled()) {
// Handled directly in StatusBarKeyguardViewManager if enabled.
mKeyguardViewControllerLazy.get().show(options);
}
@@ -2888,7 +2890,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
mKeyguardViewControllerLazy.get().setKeyguardGoingAwayState(true);
// Handled in WmLockscreenVisibilityManager if flag is enabled.
- if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ if (!KeyguardWmStateRefactor.isEnabled()) {
// Don't actually hide the Keyguard at the moment, wait for window manager
// until it tells us it's safe to do so with startKeyguardExitAnimation.
// Posting to mUiOffloadThread to ensure that calls to ActivityTaskManager
@@ -2994,7 +2996,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
} else {
Log.d(TAG, "Hiding keyguard while occluded. Just hide the keyguard view and exit.");
- if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ if (!KeyguardWmStateRefactor.isEnabled()) {
mKeyguardViewControllerLazy.get().hide(
mSystemClock.uptimeMillis() + mHideAnimation.getStartOffset(),
mHideAnimation.getDuration());
@@ -3030,7 +3032,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
// If the flag is enabled, remote animation state is handled in
// WmLockscreenVisibilityManager.
if (finishedCallback != null
- && !mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ && !KeyguardWmStateRefactor.isEnabled()) {
// There will not execute animation, send a finish callback to ensure the remote
// animation won't hang there.
try {
@@ -3056,7 +3058,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
new IRemoteAnimationFinishedCallback() {
@Override
public void onAnimationFinished() throws RemoteException {
- if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ if (!KeyguardWmStateRefactor.isEnabled()) {
try {
finishedCallback.onAnimationFinished();
} catch (RemoteException e) {
@@ -3088,7 +3090,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
// it will dismiss the panel in that case.
} else if (!mStatusBarStateController.leaveOpenOnKeyguardHide()
&& apps != null && apps.length > 0) {
- if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ if (!KeyguardWmStateRefactor.isEnabled()) {
// Handled in WmLockscreenVisibilityManager. Other logic in this class will
// short circuit when this is null.
mSurfaceBehindRemoteAnimationFinishedCallback = finishedCallback;
@@ -3112,7 +3114,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
createInteractionJankMonitorConf(
CUJ_LOCKSCREEN_UNLOCK_ANIMATION, "RemoteAnimationDisabled"));
- if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ if (!KeyguardWmStateRefactor.isEnabled()) {
// Handled directly in StatusBarKeyguardViewManager if enabled.
mKeyguardViewControllerLazy.get().hide(startTime, fadeoutDuration);
}
@@ -3131,7 +3133,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
Slog.e(TAG, "Keyguard exit without a corresponding app to show.");
try {
- if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ if (!KeyguardWmStateRefactor.isEnabled()) {
finishedCallback.onAnimationFinished();
}
} catch (RemoteException e) {
@@ -3163,7 +3165,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
@Override
public void onAnimationEnd(Animator animation) {
try {
- if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ if (!KeyguardWmStateRefactor.isEnabled()) {
finishedCallback.onAnimationFinished();
}
} catch (RemoteException e) {
@@ -3176,7 +3178,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
@Override
public void onAnimationCancel(Animator animation) {
try {
- if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ if (!KeyguardWmStateRefactor.isEnabled()) {
finishedCallback.onAnimationFinished();
}
} catch (RemoteException e) {
@@ -3341,7 +3343,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
flags |= KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT;
}
- if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ if (!KeyguardWmStateRefactor.isEnabled()) {
// Handled in WmLockscreenVisibilityManager.
mActivityTaskManagerService.keyguardGoingAway(flags);
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardWmStateRefactor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardWmStateRefactor.kt
new file mode 100644
index 000000000000..ddccc5d9e96d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardWmStateRefactor.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the keyguard wm state refactor flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object KeyguardWmStateRefactor {
+ /** The aconfig flag name */
+ const val FLAG_NAME = Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR
+
+ /** A token used for dependency declaration */
+ val token: FlagToken
+ get() = FlagToken(FLAG_NAME, isEnabled)
+
+ /** Is the refactor enabled */
+ @JvmStatic
+ inline val isEnabled
+ get() = Flags.keyguardWmStateRefactor()
+
+ /**
+ * Called to ensure code is only run when the flag is enabled. This protects users from the
+ * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+ * build to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun isUnexpectedlyInLegacyMode() =
+ RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+ /**
+ * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+ * the flag is enabled to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index 14371949c9c8..64e28700aa74 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -101,7 +101,7 @@ interface KeyguardRepository {
* Whether the device is locked or unlocked right now. This is true when keyguard has been
* dismissed or can be dismissed by a swipe
*/
- val isKeyguardUnlocked: StateFlow<Boolean>
+ val isKeyguardDismissible: StateFlow<Boolean>
/**
* Observable for the signal that keyguard is about to go away.
@@ -388,7 +388,7 @@ constructor(
}
.distinctUntilChanged()
- override val isKeyguardUnlocked: StateFlow<Boolean> =
+ override val isKeyguardDismissible: StateFlow<Boolean> =
conflatedCallbackFlow {
val callback =
object : KeyguardStateController.Callback {
@@ -396,7 +396,7 @@ constructor(
trySendWithFailureLogging(
keyguardStateController.isUnlocked,
TAG,
- "updated isKeyguardUnlocked due to onUnlockedChanged"
+ "updated isKeyguardDismissible due to onUnlockedChanged"
)
}
@@ -404,7 +404,7 @@ constructor(
trySendWithFailureLogging(
keyguardStateController.isUnlocked,
TAG,
- "updated isKeyguardUnlocked due to onKeyguardShowingChanged"
+ "updated isKeyguardDismissible due to onKeyguardShowingChanged"
)
}
}
@@ -567,17 +567,17 @@ constructor(
val callback =
object : KeyguardUpdateMonitorCallback() {
override fun onStrongAuthStateChanged(userId: Int) {
- trySend(userId)
+ trySendWithFailureLogging(userId, TAG, "strong auth state change")
}
}
-
keyguardUpdateMonitor.registerCallback(callback)
-
awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
}
.filter { userId -> userId == userTracker.userId }
.onStart { emit(userTracker.userId) }
.mapLatest { userId -> keyguardUpdateMonitor.isEncryptedOrLockdown(userId) }
+ // KeyguardUpdateMonitor#registerCallback needs to be called on the main thread.
+ .flowOn(mainDispatcher)
override fun isKeyguardShowing(): Boolean {
return keyguardStateController.isShowing
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index 8b2b45f1bf57..3965648bd224 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -23,7 +23,7 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.KeyguardWmStateRefactor
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
@@ -230,7 +230,7 @@ constructor(
combine(
startedKeyguardTransitionStep,
keyguardInteractor.statusBarState,
- keyguardInteractor.isKeyguardUnlocked,
+ keyguardInteractor.isKeyguardDismissible,
::Triple
),
::toQuad
@@ -307,7 +307,7 @@ constructor(
}
private fun listenForLockscreenToGone() {
- if (flags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ if (KeyguardWmStateRefactor.isEnabled) {
return
}
@@ -324,7 +324,7 @@ constructor(
}
private fun listenForLockscreenToGoneDragging() {
- if (flags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ if (KeyguardWmStateRefactor.isEnabled) {
return
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
index 33b6373d5876..acbd9fb4c407 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
@@ -23,7 +23,7 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.KeyguardWmStateRefactor
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
@@ -217,7 +217,7 @@ constructor(
}
private fun listenForPrimaryBouncerToGone() {
- if (flags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ if (KeyguardWmStateRefactor.isEnabled) {
// This is handled in KeyguardSecurityContainerController and
// StatusBarKeyguardViewManager, which calls the transition interactor to kick off a
// transition vs. listening to legacy state flags.
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 36bd905d23ac..22d11d08054e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -32,6 +32,7 @@ import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
import com.android.systemui.keyguard.shared.model.CameraLaunchSourceModel
import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff
@@ -162,8 +163,8 @@ constructor(
/** Whether the keyguard is showing or not. */
val isKeyguardShowing: Flow<Boolean> = repository.isKeyguardShowing
- /** Whether the keyguard is unlocked or not. */
- val isKeyguardUnlocked: Flow<Boolean> = repository.isKeyguardUnlocked
+ /** Whether the keyguard is dismissible or not. */
+ val isKeyguardDismissible: Flow<Boolean> = repository.isKeyguardDismissible
/** Whether the keyguard is occluded (covered by an activity). */
val isKeyguardOccluded: Flow<Boolean> = repository.isKeyguardOccluded
@@ -194,6 +195,9 @@ constructor(
/** Observable for the [StatusBarState] */
val statusBarState: Flow<StatusBarState> = repository.statusBarState
+ /** Source of the most recent biometric unlock, such as fingerprint or face. */
+ val biometricUnlockSource: Flow<BiometricUnlockSource?> = repository.biometricUnlockSource
+
/**
* Observable for [BiometricUnlockModel] when biometrics like face or any fingerprint (rear,
* side, under display) is used to unlock the device.
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
index a02e8ac84a75..703bb879533c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
@@ -32,6 +32,7 @@ import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.statusbar.VibratorHelper
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch
@@ -48,6 +49,7 @@ object DeviceEntryIconViewBinder {
@SuppressLint("ClickableViewAccessibility")
@JvmStatic
fun bind(
+ applicationScope: CoroutineScope,
view: DeviceEntryIconView,
viewModel: DeviceEntryIconViewModel,
fgViewModel: DeviceEntryForegroundViewModel,
@@ -69,7 +71,7 @@ object DeviceEntryIconViewBinder {
view,
HapticFeedbackConstants.CONFIRM,
)
- viewModel.onLongPress()
+ applicationScope.launch { viewModel.onLongPress() }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
index bc6c7cbf35fb..ad589dfcff9e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
@@ -120,8 +120,20 @@ constructor(
} else {
connect(nicId, TOP, R.id.keyguard_status_view, topAlignment, bottomMargin)
}
- connect(nicId, START, PARENT_ID, START)
- connect(nicId, END, PARENT_ID, END)
+ connect(
+ nicId,
+ START,
+ PARENT_ID,
+ START,
+ context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal)
+ )
+ connect(
+ nicId,
+ END,
+ PARENT_ID,
+ END,
+ context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal)
+ )
constrainHeight(
nicId,
context.resources.getDimensionPixelSize(R.dimen.notification_shelf_height)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
index a1b3f270f642..fe4f07d022dd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
@@ -31,6 +31,7 @@ import androidx.constraintlayout.widget.ConstraintSet.TOP
import androidx.constraintlayout.widget.ConstraintSet.VISIBLE
import androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT
import com.android.systemui.Flags
+import com.android.systemui.customization.R as customizationR
import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.shared.model.KeyguardSection
@@ -160,16 +161,14 @@ constructor(
var largeClockTopMargin =
context.resources.getDimensionPixelSize(R.dimen.status_bar_height) +
context.resources.getDimensionPixelSize(
- com.android.systemui.customization.R.dimen.small_clock_padding_top
+ customizationR.dimen.small_clock_padding_top
) +
context.resources.getDimensionPixelSize(R.dimen.keyguard_smartspace_top_offset)
largeClockTopMargin += getDimen(DATE_WEATHER_VIEW_HEIGHT)
largeClockTopMargin += getDimen(ENHANCED_SMARTSPACE_HEIGHT)
if (!keyguardClockViewModel.useLargeClock) {
largeClockTopMargin -=
- context.resources.getDimensionPixelSize(
- com.android.systemui.customization.R.dimen.small_clock_height
- )
+ context.resources.getDimensionPixelSize(customizationR.dimen.small_clock_height)
}
connect(R.id.lockscreen_clock_view_large, TOP, PARENT_ID, TOP, largeClockTopMargin)
constrainHeight(R.id.lockscreen_clock_view_large, WRAP_CONTENT)
@@ -177,18 +176,15 @@ constructor(
constrainWidth(R.id.lockscreen_clock_view, WRAP_CONTENT)
constrainHeight(
R.id.lockscreen_clock_view,
- context.resources.getDimensionPixelSize(
- com.android.systemui.customization.R.dimen.small_clock_height
- )
+ context.resources.getDimensionPixelSize(customizationR.dimen.small_clock_height)
)
connect(
R.id.lockscreen_clock_view,
START,
PARENT_ID,
START,
- context.resources.getDimensionPixelSize(
- com.android.systemui.customization.R.dimen.clock_padding_start
- )
+ context.resources.getDimensionPixelSize(customizationR.dimen.clock_padding_start) +
+ context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal)
)
var smallClockTopMargin =
if (splitShadeStateController.shouldUseSplitNotificationShade(context.resources)) {
@@ -199,9 +195,7 @@ constructor(
}
if (keyguardClockViewModel.useLargeClock) {
smallClockTopMargin -=
- context.resources.getDimensionPixelSize(
- com.android.systemui.customization.R.dimen.small_clock_height
- )
+ context.resources.getDimensionPixelSize(customizationR.dimen.small_clock_height)
}
connect(R.id.lockscreen_clock_view, TOP, PARENT_ID, TOP, smallClockTopMargin)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
index 0bf9ad02eb58..3fc9b4200f35 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
@@ -31,6 +31,7 @@ import com.android.keyguard.LockIconView
import com.android.keyguard.LockIconViewController
import com.android.systemui.Flags.keyguardBottomAreaRefactor
import com.android.systemui.biometrics.AuthController
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
@@ -46,6 +47,7 @@ import com.android.systemui.shade.NotificationPanelView
import com.android.systemui.statusbar.VibratorHelper
import dagger.Lazy
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
/** Includes the device entry icon. */
@@ -53,6 +55,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
class DefaultDeviceEntrySection
@Inject
constructor(
+ @Application private val applicationScope: CoroutineScope,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
private val authController: AuthController,
private val windowManager: WindowManager,
@@ -91,6 +94,7 @@ constructor(
if (DeviceEntryUdfpsRefactor.isEnabled) {
constraintLayout.findViewById<DeviceEntryIconView?>(deviceEntryIconViewId)?.let {
DeviceEntryIconViewBinder.bind(
+ applicationScope,
it,
deviceEntryIconViewModel.get(),
deviceEntryForegroundViewModel.get(),
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
index 8c5e9b4c6817..d75a72f91061 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
@@ -18,7 +18,6 @@
package com.android.systemui.keyguard.ui.view.layout.sections
import android.content.Context
-import android.view.View
import androidx.constraintlayout.widget.ConstraintSet
import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
import androidx.constraintlayout.widget.ConstraintSet.END
@@ -67,7 +66,6 @@ constructor(
notificationStackSizeCalculator,
mainDispatcher,
) {
- private val smartSpaceBarrier = View.generateViewId()
override fun applyConstraints(constraintSet: ConstraintSet) {
if (!KeyguardShadeMigrationNssl.isEnabled) {
return
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
index 37842a84c3d3..2f99719df36c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
@@ -31,7 +31,8 @@ import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.keyguard.ui.binder.KeyguardSmartspaceViewBinder
import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
-import com.android.systemui.shared.R
+import com.android.systemui.res.R as R
+import com.android.systemui.shared.R as sharedR
import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController
import dagger.Lazy
import javax.inject.Inject
@@ -100,94 +101,94 @@ constructor(
if (!migrateClocksToBlueprint()) {
return
}
+ val horizontalPaddingStart =
+ context.resources.getDimensionPixelSize(R.dimen.below_clock_padding_start) +
+ context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal)
+ val horizontalPaddingEnd =
+ context.resources.getDimensionPixelSize(R.dimen.below_clock_padding_end) +
+ context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal)
constraintSet.apply {
// migrate addDateWeatherView, addWeatherView from KeyguardClockSwitchController
- constrainHeight(R.id.date_smartspace_view, ConstraintSet.WRAP_CONTENT)
- constrainWidth(R.id.date_smartspace_view, ConstraintSet.WRAP_CONTENT)
+ constrainHeight(sharedR.id.date_smartspace_view, ConstraintSet.WRAP_CONTENT)
+ constrainWidth(sharedR.id.date_smartspace_view, ConstraintSet.WRAP_CONTENT)
connect(
- R.id.date_smartspace_view,
+ sharedR.id.date_smartspace_view,
ConstraintSet.START,
ConstraintSet.PARENT_ID,
ConstraintSet.START,
- context.resources.getDimensionPixelSize(
- com.android.systemui.res.R.dimen.below_clock_padding_start
- )
+ horizontalPaddingStart
)
- constrainWidth(R.id.weather_smartspace_view, ConstraintSet.WRAP_CONTENT)
+ constrainWidth(sharedR.id.weather_smartspace_view, ConstraintSet.WRAP_CONTENT)
connect(
- R.id.weather_smartspace_view,
+ sharedR.id.weather_smartspace_view,
ConstraintSet.TOP,
- R.id.date_smartspace_view,
+ sharedR.id.date_smartspace_view,
ConstraintSet.TOP
)
connect(
- R.id.weather_smartspace_view,
+ sharedR.id.weather_smartspace_view,
ConstraintSet.BOTTOM,
- R.id.date_smartspace_view,
+ sharedR.id.date_smartspace_view,
ConstraintSet.BOTTOM
)
connect(
- R.id.weather_smartspace_view,
+ sharedR.id.weather_smartspace_view,
ConstraintSet.START,
- R.id.date_smartspace_view,
+ sharedR.id.date_smartspace_view,
ConstraintSet.END,
4
)
// migrate addSmartspaceView from KeyguardClockSwitchController
- constrainHeight(R.id.bc_smartspace_view, ConstraintSet.WRAP_CONTENT)
+ constrainHeight(sharedR.id.bc_smartspace_view, ConstraintSet.WRAP_CONTENT)
connect(
- R.id.bc_smartspace_view,
+ sharedR.id.bc_smartspace_view,
ConstraintSet.START,
ConstraintSet.PARENT_ID,
ConstraintSet.START,
- context.resources.getDimensionPixelSize(
- com.android.systemui.res.R.dimen.below_clock_padding_start
- )
+ horizontalPaddingStart
)
connect(
- R.id.bc_smartspace_view,
+ sharedR.id.bc_smartspace_view,
ConstraintSet.END,
if (keyguardClockViewModel.clockShouldBeCentered.value) ConstraintSet.PARENT_ID
- else com.android.systemui.res.R.id.split_shade_guideline,
+ else R.id.split_shade_guideline,
ConstraintSet.END,
- context.resources.getDimensionPixelSize(
- com.android.systemui.res.R.dimen.below_clock_padding_end
- )
+ horizontalPaddingEnd
)
if (keyguardClockViewModel.hasCustomWeatherDataDisplay.value) {
- clear(R.id.date_smartspace_view, ConstraintSet.TOP)
+ clear(sharedR.id.date_smartspace_view, ConstraintSet.TOP)
connect(
- R.id.date_smartspace_view,
+ sharedR.id.date_smartspace_view,
ConstraintSet.BOTTOM,
- R.id.bc_smartspace_view,
+ sharedR.id.bc_smartspace_view,
ConstraintSet.TOP
)
} else {
- clear(R.id.date_smartspace_view, ConstraintSet.BOTTOM)
+ clear(sharedR.id.date_smartspace_view, ConstraintSet.BOTTOM)
connect(
- R.id.date_smartspace_view,
+ sharedR.id.date_smartspace_view,
ConstraintSet.TOP,
- com.android.systemui.res.R.id.lockscreen_clock_view,
+ R.id.lockscreen_clock_view,
ConstraintSet.BOTTOM
)
connect(
- R.id.bc_smartspace_view,
+ sharedR.id.bc_smartspace_view,
ConstraintSet.TOP,
- R.id.date_smartspace_view,
+ sharedR.id.date_smartspace_view,
ConstraintSet.BOTTOM
)
}
createBarrier(
- com.android.systemui.res.R.id.smart_space_barrier_bottom,
+ R.id.smart_space_barrier_bottom,
Barrier.BOTTOM,
0,
*intArrayOf(
- R.id.bc_smartspace_view,
- R.id.date_smartspace_view,
- R.id.weather_smartspace_view,
+ sharedR.id.bc_smartspace_view,
+ sharedR.id.date_smartspace_view,
+ sharedR.id.weather_smartspace_view,
)
)
}
@@ -212,7 +213,7 @@ constructor(
private fun updateVisibility(constraintSet: ConstraintSet) {
constraintSet.apply {
setVisibility(
- R.id.weather_smartspace_view,
+ sharedR.id.weather_smartspace_view,
when (keyguardClockViewModel.hasCustomWeatherDataDisplay.value) {
true -> ConstraintSet.GONE
false ->
@@ -223,7 +224,7 @@ constructor(
}
)
setVisibility(
- R.id.date_smartspace_view,
+ sharedR.id.date_smartspace_view,
if (keyguardClockViewModel.hasCustomWeatherDataDisplay.value) ConstraintSet.GONE
else ConstraintSet.VISIBLE
)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
index eacaa40de821..a3d54532411c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
@@ -20,6 +20,7 @@ import android.animation.FloatEvaluator
import android.animation.IntEvaluator
import com.android.keyguard.KeyguardViewController
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntrySourceInteractor
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
@@ -56,6 +57,7 @@ constructor(
private val sceneContainerFlags: SceneContainerFlags,
private val keyguardViewController: Lazy<KeyguardViewController>,
private val deviceEntryInteractor: DeviceEntryInteractor,
+ private val deviceEntrySourceInteractor: DeviceEntrySourceInteractor,
) {
private val intEvaluator = IntEvaluator()
private val floatEvaluator = FloatEvaluator()
@@ -208,14 +210,13 @@ constructor(
}
}
- fun onLongPress() {
- // TODO (b/309804148): play auth ripple via an interactor
-
+ suspend fun onLongPress() {
if (sceneContainerFlags.isEnabled()) {
deviceEntryInteractor.attemptDeviceEntry()
} else {
keyguardViewController.get().showPrimaryBouncer(/* scrim */ true)
}
+ deviceEntrySourceInteractor.attemptEnterDeviceFromDeviceEntryIcon()
}
private fun DeviceEntryIconView.IconType.toAccessibilityHintType():
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
index d7e062fb926f..7d13397b374c 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
@@ -41,6 +41,7 @@ import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
+import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.Settings;
@@ -55,12 +56,14 @@ import android.view.accessibility.AccessibilityManager;
import androidx.annotation.NonNull;
+import com.android.internal.accessibility.common.ShortcutConstants;
import com.android.systemui.Dumpable;
import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver;
import com.android.systemui.accessibility.SystemActions;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
import com.android.systemui.recents.OverviewProxyService;
@@ -79,6 +82,7 @@ import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
+import java.util.concurrent.Executor;
import javax.inject.Inject;
@@ -101,6 +105,7 @@ public final class NavBarHelper implements
private static final String TAG = NavBarHelper.class.getSimpleName();
private final Handler mHandler = new Handler(Looper.getMainLooper());
+ private final Executor mMainExecutor;
private final AccessibilityManager mAccessibilityManager;
private final Lazy<AssistManager> mAssistManagerLazy;
private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
@@ -185,7 +190,12 @@ public final class NavBarHelper implements
DisplayTracker displayTracker,
NotificationShadeWindowController notificationShadeWindowController,
DumpManager dumpManager,
- CommandQueue commandQueue) {
+ CommandQueue commandQueue,
+ @Main Executor mainExecutor) {
+ // b/319489709: This component shouldn't be running for a non-primary user
+ if (!Process.myUserHandle().equals(UserHandle.SYSTEM)) {
+ Log.wtf(TAG, "Unexpected initialization for non-primary user", new Throwable());
+ }
mContext = context;
mNotificationShadeWindowController = notificationShadeWindowController;
mCommandQueue = commandQueue;
@@ -201,6 +211,7 @@ public final class NavBarHelper implements
mWm = wm;
mDefaultDisplayId = displayTracker.getDefaultDisplayId();
mEdgeBackGestureHandler = edgeBackGestureHandlerFactory.create(context);
+ mMainExecutor = mainExecutor;
mNavBarMode = navigationModeController.addListener(this);
mCommandQueue.addCallback(this);
@@ -370,7 +381,7 @@ public final class NavBarHelper implements
// permission
final List<String> a11yButtonTargets =
mAccessibilityManager.getAccessibilityShortcutTargets(
- AccessibilityManager.ACCESSIBILITY_BUTTON);
+ ShortcutConstants.UserShortcutType.SOFTWARE);
final int requestingServices = a11yButtonTargets.size();
clickable = requestingServices >= 1;
@@ -418,7 +429,11 @@ public final class NavBarHelper implements
@Override
public void onConnectionChanged(boolean isConnected) {
if (isConnected) {
- updateAssistantAvailability();
+ // We add the OPS callback during construction, so if the service is already connected
+ // then we will try to get the AssistManager dependency which itself has an indirect
+ // dependency on NavBarHelper leading to a cycle. For now, we can defer updating the
+ // assistant availability.
+ mMainExecutor.execute(this::updateAssistantAvailability);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
index fa03dc245745..b3d848c2d23b 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
@@ -34,6 +34,8 @@ import androidx.annotation.VisibleForTesting
import androidx.core.os.postDelayed
import androidx.core.view.isVisible
import androidx.dynamicanimation.animation.DynamicAnimation
+import com.android.internal.jank.Cuj.CUJ_BACK_PANEL_ARROW
+import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.util.LatencyTracker
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.NavigationEdgeBackPlugin
@@ -86,6 +88,7 @@ internal constructor(
private val vibratorHelper: VibratorHelper,
private val configurationController: ConfigurationController,
private val latencyTracker: LatencyTracker,
+ private val interactionJankMonitor: InteractionJankMonitor,
) : ViewController<BackPanel>(BackPanel(context, latencyTracker)), NavigationEdgeBackPlugin {
/**
@@ -103,6 +106,7 @@ internal constructor(
private val vibratorHelper: VibratorHelper,
private val configurationController: ConfigurationController,
private val latencyTracker: LatencyTracker,
+ private val interactionJankMonitor: InteractionJankMonitor,
) {
/** Construct a [BackPanelController]. */
fun create(context: Context): BackPanelController {
@@ -115,6 +119,7 @@ internal constructor(
vibratorHelper,
configurationController,
latencyTracker,
+ interactionJankMonitor
)
backPanelController.init()
return backPanelController
@@ -183,7 +188,7 @@ internal constructor(
/* Arrow is animating in */
ENTRY,
- /* could be entry, neutral, or stretched, releasing will commit back */
+ /* releasing will commit back */
ACTIVE,
/* releasing will cancel back */
@@ -813,7 +818,7 @@ internal constructor(
scale =
when (currentState) {
GestureState.ACTIVE,
- GestureState.FLUNG, -> params.activeIndicator.scale
+ GestureState.FLUNG -> params.activeIndicator.scale
GestureState.COMMITTED -> params.committedIndicator.scale
else -> params.preThresholdIndicator.scale
},
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/BaseAutoAddableModule.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/BaseAutoAddableModule.kt
index adea26e5aa26..e1ec338cec6f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/BaseAutoAddableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/BaseAutoAddableModule.kt
@@ -18,6 +18,8 @@ package com.android.systemui.qs.pipeline.dagger
import android.content.res.Resources
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.pipeline.domain.autoaddable.A11yShortcutAutoAddable
+import com.android.systemui.qs.pipeline.domain.autoaddable.A11yShortcutAutoAddableList
import com.android.systemui.qs.pipeline.domain.autoaddable.AutoAddableSetting
import com.android.systemui.qs.pipeline.domain.autoaddable.AutoAddableSettingList
import com.android.systemui.qs.pipeline.domain.autoaddable.CastAutoAddable
@@ -51,6 +53,16 @@ interface BaseAutoAddableModule {
)
.toSet()
}
+
+ @Provides
+ @ElementsIntoSet
+ fun providesA11yShortcutAutoAddable(
+ a11yShortcutAutoAddableFactory: A11yShortcutAutoAddable.Factory
+ ): Set<AutoAddable> {
+ return A11yShortcutAutoAddableList.getA11yShortcutAutoAddables(
+ a11yShortcutAutoAddableFactory
+ )
+ }
}
@Binds @IntoSet fun bindCastAutoAddable(impl: CastAutoAddable): AutoAddable
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt
index bcd09bd877fd..dc39c97bc9ab 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt
@@ -25,6 +25,7 @@ import android.content.pm.PackageManager
import android.content.pm.PackageManager.ResolveInfoFlags
import android.os.UserHandle
import android.service.quicksettings.TileService
+import androidx.annotation.GuardedBy
import com.android.systemui.common.data.repository.PackageChangeRepository
import com.android.systemui.common.data.shared.model.PackageChangeModel
import com.android.systemui.dagger.SysUISingleton
@@ -32,12 +33,13 @@ import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.util.kotlin.isComponentActuallyEnabled
import javax.inject.Inject
-import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.shareIn
interface InstalledTilesComponentRepository {
@@ -49,33 +51,39 @@ class InstalledTilesComponentRepositoryImpl
@Inject
constructor(
@Application private val applicationContext: Context,
- @Background private val backgroundDispatcher: CoroutineDispatcher,
+ @Background private val backgroundScope: CoroutineScope,
private val packageChangeRepository: PackageChangeRepository
) : InstalledTilesComponentRepository {
- override fun getInstalledTilesComponents(userId: Int): Flow<Set<ComponentName>> {
- /*
- * In order to query [PackageManager] for different users, this implementation will call
- * [Context.createContextAsUser] and retrieve the [PackageManager] from that context.
- */
- val packageManager =
- if (applicationContext.userId == userId) {
- applicationContext.packageManager
- } else {
- applicationContext
- .createContextAsUser(
- UserHandle.of(userId),
- /* flags */ 0,
- )
- .packageManager
+ @GuardedBy("userMap") private val userMap = mutableMapOf<Int, Flow<Set<ComponentName>>>()
+
+ override fun getInstalledTilesComponents(userId: Int): Flow<Set<ComponentName>> =
+ synchronized(userMap) {
+ userMap.getOrPut(userId) {
+ /*
+ * In order to query [PackageManager] for different users, this implementation will
+ * call [Context.createContextAsUser] and retrieve the [PackageManager] from that
+ * context.
+ */
+ val packageManager =
+ if (applicationContext.userId == userId) {
+ applicationContext.packageManager
+ } else {
+ applicationContext
+ .createContextAsUser(
+ UserHandle.of(userId),
+ /* flags */ 0,
+ )
+ .packageManager
+ }
+ packageChangeRepository
+ .packageChanged(UserHandle.of(userId))
+ .onStart { emit(PackageChangeModel.Empty) }
+ .map { reloadComponents(userId, packageManager) }
+ .distinctUntilChanged()
+ .shareIn(backgroundScope, SharingStarted.WhileSubscribed(), replay = 1)
}
- return packageChangeRepository
- .packageChanged(UserHandle.of(userId))
- .onStart { emit(PackageChangeModel.Empty) }
- .map { reloadComponents(userId, packageManager) }
- .distinctUntilChanged()
- .flowOn(backgroundDispatcher)
- }
+ }
@WorkerThread
private fun reloadComponents(userId: Int, packageManager: PackageManager): Set<ComponentName> {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddable.kt
new file mode 100644
index 000000000000..2cebbe3f372c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddable.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import android.content.ComponentName
+import android.provider.Settings
+import com.android.systemui.accessibility.data.repository.AccessibilityQsShortcutsRepository
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.domain.model.AutoAddable
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import java.util.Objects
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+
+/**
+ * [A11yShortcutAutoAddable] will auto add/remove qs tile of the accessibility framework feature
+ * based on the user's choices in the Settings app.
+ *
+ * The a11y feature component is added to [Settings.Secure.ACCESSIBILITY_QS_TARGETS] when the user
+ * selects to use qs tile as a shortcut for the a11 feature in the Settings app. The accessibility
+ * feature component is removed from [Settings.Secure.ACCESSIBILITY_QS_TARGETS] when the user
+ * doesn't want to use qs tile as a shortcut for the a11y feature in the Settings app.
+ *
+ * [A11yShortcutAutoAddable] tracks a [Settings.Secure.ACCESSIBILITY_QS_TARGETS] and when its value
+ * changes, it will emit a [AutoAddSignal.Add] for the [spec] if the [componentName] is a substring
+ * of the value; it will emit a [AutoAddSignal.Remove] for the [spec] if the [componentName] is not
+ * a substring of the value.
+ */
+class A11yShortcutAutoAddable
+@AssistedInject
+constructor(
+ private val a11yQsShortcutsRepository: AccessibilityQsShortcutsRepository,
+ @Background private val bgDispatcher: CoroutineDispatcher,
+ @Assisted private val spec: TileSpec,
+ @Assisted private val componentName: ComponentName
+) : AutoAddable {
+
+ override fun autoAddSignal(userId: Int): Flow<AutoAddSignal> {
+ return a11yQsShortcutsRepository
+ .a11yQsShortcutTargets(userId)
+ .map { it.contains(componentName.flattenToString()) }
+ .filterNotNull()
+ .distinctUntilChanged()
+ .map { if (it) AutoAddSignal.Add(spec) else AutoAddSignal.Remove(spec) }
+ .flowOn(bgDispatcher)
+ }
+
+ override val autoAddTracking = AutoAddTracking.Always
+
+ override val description =
+ "A11yShortcutAutoAddableSetting: $spec:$componentName ($autoAddTracking)"
+
+ override fun equals(other: Any?): Boolean {
+ return other is A11yShortcutAutoAddable &&
+ spec == other.spec &&
+ componentName == other.componentName
+ }
+
+ override fun hashCode(): Int {
+ return Objects.hash(spec, componentName)
+ }
+
+ override fun toString(): String {
+ return description
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(spec: TileSpec, componentName: ComponentName): A11yShortcutAutoAddable
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableList.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableList.kt
new file mode 100644
index 000000000000..08e39204386e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableList.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import android.view.accessibility.Flags
+import com.android.internal.accessibility.AccessibilityShortcutController
+import com.android.systemui.qs.pipeline.domain.model.AutoAddable
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.ColorCorrectionTile
+import com.android.systemui.qs.tiles.ColorInversionTile
+import com.android.systemui.qs.tiles.OneHandedModeTile
+import com.android.systemui.qs.tiles.ReduceBrightColorsTile
+
+object A11yShortcutAutoAddableList {
+
+ /**
+ * Generate a collection of [A11yShortcutAutoAddable] for the framework tiles related to
+ * accessibility features with shortcut options
+ */
+ fun getA11yShortcutAutoAddables(factory: A11yShortcutAutoAddable.Factory): Set<AutoAddable> {
+ return if (Flags.a11yQsShortcut()) {
+ setOf(
+ factory.create(
+ TileSpec.create(ColorCorrectionTile.TILE_SPEC),
+ AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME
+ ),
+ factory.create(
+ TileSpec.create(ColorInversionTile.TILE_SPEC),
+ AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME
+ ),
+ factory.create(
+ TileSpec.create(OneHandedModeTile.TILE_SPEC),
+ AccessibilityShortcutController.ONE_HANDED_COMPONENT_NAME
+ ),
+ factory.create(
+ TileSpec.create(ReduceBrightColorsTile.TILE_SPEC),
+ AccessibilityShortcutController.REDUCE_BRIGHT_COLORS_COMPONENT_NAME
+ ),
+ )
+ } else {
+ emptySet()
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
index d04e4f52ac4d..53f287b81be9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
@@ -33,11 +33,13 @@ import android.view.View;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
+import androidx.annotation.VisibleForTesting;
+
import com.android.settingslib.Utils;
-import com.android.systemui.res.R;
import com.android.systemui.plugins.qs.QSIconView;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.qs.QSTile.State;
+import com.android.systemui.res.R;
import java.util.Objects;
@@ -52,7 +54,10 @@ public class QSIconViewImpl extends QSIconView {
private boolean mDisabledByPolicy = false;
private int mTint;
@Nullable
- private QSTile.Icon mLastIcon;
+ @VisibleForTesting
+ QSTile.Icon mLastIcon;
+
+ private boolean mIconChangeScheduled;
private ValueAnimator mColorAnimator = new ValueAnimator();
@@ -112,6 +117,7 @@ public class QSIconViewImpl extends QSIconView {
}
protected void updateIcon(ImageView iv, State state, boolean allowAnimations) {
+ mIconChangeScheduled = false;
final QSTile.Icon icon = state.iconSupplier != null ? state.iconSupplier.get() : state.icon;
if (!Objects.equals(icon, iv.getTag(R.id.qs_icon_tag))) {
boolean shouldAnimate = allowAnimations && shouldAnimate(iv);
@@ -167,7 +173,12 @@ public class QSIconViewImpl extends QSIconView {
mState = state.state;
mDisabledByPolicy = state.disabledByPolicy;
if (mTint != 0 && allowAnimations && shouldAnimate(iv)) {
- animateGrayScale(mTint, color, iv, () -> updateIcon(iv, state, allowAnimations));
+ mIconChangeScheduled = true;
+ animateGrayScale(mTint, color, iv, () -> {
+ if (mIconChangeScheduled) {
+ updateIcon(iv, state, allowAnimations);
+ }
+ });
} else {
setTint(iv, color);
updateIcon(iv, state, allowAnimations);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
index 216d716b07a4..88863cbad1ee 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
@@ -17,6 +17,8 @@
package com.android.systemui.qs.tiles
import android.app.AlertDialog
+import android.app.BroadcastOptions
+import android.app.PendingIntent
import android.content.Intent
import android.os.Handler
import android.os.Looper
@@ -42,6 +44,8 @@ import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.qs.tileimpl.QSTileImpl
import com.android.systemui.recordissue.RecordIssueDialogDelegate
import com.android.systemui.res.R
+import com.android.systemui.screenrecord.RecordingService
+import com.android.systemui.settings.UserContextProvider
import com.android.systemui.statusbar.phone.KeyguardDismissUtil
import com.android.systemui.statusbar.policy.KeyguardStateController
import javax.inject.Inject
@@ -61,6 +65,7 @@ constructor(
private val keyguardDismissUtil: KeyguardDismissUtil,
private val keyguardStateController: KeyguardStateController,
private val dialogLaunchAnimator: DialogLaunchAnimator,
+ private val userContextProvider: UserContextProvider,
private val delegateFactory: RecordIssueDialogDelegate.Factory,
) :
QSTileImpl<QSTile.BooleanState>(
@@ -91,12 +96,22 @@ constructor(
public override fun handleClick(view: View?) {
if (isRecording) {
isRecording = false
+ stopScreenRecord()
} else {
mUiHandler.post { showPrompt(view) }
}
refreshState()
}
+ private fun stopScreenRecord() =
+ PendingIntent.getService(
+ userContextProvider.userContext,
+ RecordingService.REQUEST_CODE,
+ RecordingService.getStopIntent(userContextProvider.userContext),
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
+ )
+ .send(BroadcastOptions.makeBasic().apply { isInteractive = true }.toBundle())
+
private fun showPrompt(view: View?) {
val dialog: AlertDialog =
delegateFactory
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index cc53aabfcd25..4e89fbfeb6e3 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -16,6 +16,7 @@
package com.android.systemui.recents;
+import static android.app.Flags.keyguardPrivateNotifications;
import static android.content.Intent.ACTION_PACKAGE_ADDED;
import static android.content.Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST;
import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
@@ -81,12 +82,13 @@ import com.android.internal.logging.UiEventLogger;
import com.android.internal.util.ScreenshotHelper;
import com.android.internal.util.ScreenshotRequest;
import com.android.systemui.Dumpable;
+import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
+import com.android.systemui.keyguard.KeyguardWmStateRefactor;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.keyguard.ui.view.InWindowLauncherUnlockAnimationManager;
import com.android.systemui.model.SysUiState;
@@ -167,13 +169,16 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
private final Optional<UnfoldTransitionProgressForwarder> mUnfoldTransitionProgressForwarder;
private final UiEventLogger mUiEventLogger;
private final DisplayTracker mDisplayTracker;
-
private Region mActiveNavBarRegion;
+ private final BroadcastDispatcher mBroadcastDispatcher;
+
private IOverviewProxy mOverviewProxy;
private int mConnectionBackoffAttempts;
private boolean mBound;
private boolean mIsEnabled;
+
+ private boolean mIsNonPrimaryUser;
private int mCurrentBoundedUserId = -1;
private boolean mInputFocusTransferStarted;
private float mInputFocusTransferStartY;
@@ -419,6 +424,21 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
retryConnectionWithBackoff();
};
+ private final BroadcastReceiver mUserEventReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (Objects.equals(intent.getAction(), Intent.ACTION_USER_UNLOCKED)) {
+ if (keyguardPrivateNotifications()) {
+ // Start the overview connection to the launcher service
+ // Connect if user hasn't connected yet
+ if (getProxy() == null) {
+ startConnectionToCurrentUser();
+ }
+ }
+ }
+ }
+ };
+
private final BroadcastReceiver mLauncherStateChangedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -586,11 +606,13 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
FeatureFlags featureFlags,
SceneContainerFlags sceneContainerFlags,
DumpManager dumpManager,
- Optional<UnfoldTransitionProgressForwarder> unfoldTransitionProgressForwarder
+ Optional<UnfoldTransitionProgressForwarder> unfoldTransitionProgressForwarder,
+ BroadcastDispatcher broadcastDispatcher
) {
// b/241601880: This component shouldn't be running for a non-primary user
- if (!Process.myUserHandle().equals(UserHandle.SYSTEM)) {
- Log.e(TAG_OPS, "Unexpected initialization for non-primary user", new Throwable());
+ mIsNonPrimaryUser = !Process.myUserHandle().equals(UserHandle.SYSTEM);
+ if (mIsNonPrimaryUser) {
+ Log.wtf(TAG_OPS, "Unexpected initialization for non-primary user", new Throwable());
}
mContext = context;
@@ -615,8 +637,9 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
mUiEventLogger = uiEventLogger;
mDisplayTracker = displayTracker;
mUnfoldTransitionProgressForwarder = unfoldTransitionProgressForwarder;
+ mBroadcastDispatcher = broadcastDispatcher;
- if (!featureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ if (!KeyguardWmStateRefactor.isEnabled()) {
mSysuiUnlockAnimationController = sysuiUnlockAnimationController;
} else {
mSysuiUnlockAnimationController = inWindowLauncherUnlockAnimationManager;
@@ -635,6 +658,12 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
mContext.registerReceiver(mLauncherStateChangedReceiver, filter);
+ if (keyguardPrivateNotifications()) {
+ mBroadcastDispatcher.registerReceiver(mUserEventReceiver,
+ new IntentFilter(Intent.ACTION_USER_UNLOCKED),
+ null /* executor */, UserHandle.ALL);
+ }
+
// Listen for status bar state changes
statusBarWinController.registerCallback(mStatusBarWindowCallback);
mScreenshotHelper = new ScreenshotHelper(context);
@@ -772,6 +801,13 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
}
private void internalConnectToCurrentUser(String reason) {
+ if (mIsNonPrimaryUser) {
+ // This should not happen, but if any per-user SysUI component has a dependency on OPS,
+ // then this could get triggered
+ Log.w(TAG_OPS, "Skipping connection to overview service due to non-primary user "
+ + "caller");
+ return;
+ }
disconnectFromLauncherService(reason);
// If user has not setup yet or already connected, do not try to connect
diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
index 97ec3f98cf0c..1c7cc007cbc7 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
@@ -34,7 +34,7 @@ import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.util.kotlin.collectFlow
import javax.inject.Inject
-import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.Flow
/**
* Controller that's responsible for the glanceable hub container view and its touch handling.
@@ -110,10 +110,8 @@ constructor(
return communalInteractor.isCommunalEnabled && isComposeAvailable()
}
- /** Returns a {@link StateFlow} that tracks whether communal hub is enabled. */
- fun enabledState(): StateFlow<Boolean> {
- return communalInteractor.communalEnabledState
- }
+ /** Returns a {@link StateFlow} that tracks whether communal hub is available. */
+ fun communalAvailable(): Flow<Boolean> = communalInteractor.isCommunalAvailable
/**
* Creates the container view containing the glanceable hub UI.
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index c0ceba3c1d4f..530c124f4f24 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -3576,11 +3576,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
}
@Override
- public NotificationStackScrollLayoutController getNotificationStackScrollLayoutController() {
- return mNotificationStackScrollLayoutController;
- }
-
- @Override
public void disableHeader(int state1, int state2, boolean animated) {
mShadeHeaderController.disable(state1, state2, animated);
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 5ecc54b09806..19a58401cbec 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -607,7 +607,7 @@ public class NotificationShadeWindowViewController implements Dumpable {
public void setupCommunalHubLayout() {
collectFlow(
mView,
- mGlanceableHubContainerController.enabledState(),
+ mGlanceableHubContainerController.communalAvailable(),
isEnabled -> {
if (isEnabled) {
View communalPlaceholder = mView.findViewById(R.id.communal_ui_stub);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
index e54286fcf992..4f970b3923aa 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
@@ -17,7 +17,6 @@ package com.android.systemui.shade
import android.view.ViewPropertyAnimator
import com.android.systemui.statusbar.GestureRecorder
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.statusbar.phone.CentralSurfaces
import com.android.systemui.statusbar.policy.HeadsUpManager
@@ -44,9 +43,6 @@ interface ShadeSurface : ShadeViewController {
/** Animates the view from its current alpha to zero then runs the runnable. */
fun fadeOut(startDelayMs: Long, durationMs: Long, endAction: Runnable): ViewPropertyAnimator
- /** Returns the NSSL controller. */
- val notificationStackScrollLayoutController: NotificationStackScrollLayoutController
-
/** Set whether the bouncer is showing. */
fun setBouncerShowing(bouncerShowing: Boolean)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index 24ac70e63e46..2a4753def463 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -234,10 +234,12 @@ public class NotificationLockscreenUserManagerImpl implements
} else if (profileAvailabilityActions(action)) {
updateCurrentProfilesCache();
} else if (Objects.equals(action, Intent.ACTION_USER_UNLOCKED)) {
- // Start the overview connection to the launcher service
- // Connect if user hasn't connected yet
- if (mOverviewProxyServiceLazy.get().getProxy() == null) {
- mOverviewProxyServiceLazy.get().startConnectionToCurrentUser();
+ if (!keyguardPrivateNotifications()) {
+ // Start the overview connection to the launcher service
+ // Connect if user hasn't connected yet
+ if (mOverviewProxyServiceLazy.get().getProxy() == null) {
+ mOverviewProxyServiceLazy.get().startConnectionToCurrentUser();
+ }
}
} else if (Objects.equals(action, NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION)) {
final IntentSender intentSender = intent.getParcelableExtra(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
index 6e3b15da4423..c643238b7e30 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
@@ -52,8 +52,8 @@ class ConversationNotificationProcessor @Inject constructor(
entry: NotificationEntry,
recoveredBuilder: Notification.Builder,
logger: NotificationContentInflaterLogger
- ) {
- val messagingStyle = recoveredBuilder.style as? Notification.MessagingStyle ?: return
+ ): Notification.MessagingStyle? {
+ val messagingStyle = recoveredBuilder.style as? Notification.MessagingStyle ?: return null
messagingStyle.conversationType =
if (entry.ranking.channel.isImportantConversation)
Notification.MessagingStyle.CONVERSATION_TYPE_IMPORTANT
@@ -68,6 +68,7 @@ class ConversationNotificationProcessor @Inject constructor(
}
messagingStyle.unreadMessageCount =
conversationNotificationManager.getUnreadCount(entry, recoveredBuilder)
+ return messagingStyle
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
index 73decfc326a4..639e23ae0765 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
@@ -362,8 +362,12 @@ public class PreparationCoordinator implements Coordinator {
}
NotifInflater.Params getInflaterParams(NotifUiAdjustment adjustment, String reason) {
- return new NotifInflater.Params(adjustment.isMinimized(), reason,
- adjustment.isSnoozeEnabled());
+ return new NotifInflater.Params(
+ adjustment.isMinimized(),
+ reason,
+ adjustment.isSnoozeEnabled(),
+ adjustment.isChildInGroup()
+ );
}
private void abortInflation(NotificationEntry entry, String reason) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifInflater.kt
index 4483599d6857..c0b187be42f3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifInflater.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifInflater.kt
@@ -20,9 +20,9 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.render.NotifViewController
/**
- * Used by the [PreparationCoordinator]. When notifications are added or updated, the
- * NotifInflater is asked to (re)inflated and prepare their views. This inflation occurs off the
- * main thread. When the inflation is finished, NotifInflater will trigger its InflationCallback.
+ * Used by the [PreparationCoordinator]. When notifications are added or updated, the NotifInflater
+ * is asked to (re)inflated and prepare their views. This inflation occurs off the main thread. When
+ * the inflation is finished, NotifInflater will trigger its InflationCallback.
*/
interface NotifInflater {
/**
@@ -33,7 +33,7 @@ interface NotifInflater {
fun rebindViews(entry: NotificationEntry, params: Params, callback: InflationCallback)
/**
- * Called to inflate the views of an entry. Views are not considered inflated until all of its
+ * Called to inflate the views of an entry. Views are not considered inflated until all of its
* views are bound. Once all views are inflated, the InflationCallback is triggered.
*
* @param callback callback called after inflation finishes
@@ -41,25 +41,24 @@ interface NotifInflater {
fun inflateViews(entry: NotificationEntry, params: Params, callback: InflationCallback)
/**
- * Request to stop the inflation of an entry. For example, called when a notification is
- * removed and no longer needs to be inflated. Returns whether anything may have been aborted.
+ * Request to stop the inflation of an entry. For example, called when a notification is removed
+ * and no longer needs to be inflated. Returns whether anything may have been aborted.
*/
fun abortInflation(entry: NotificationEntry): Boolean
- /**
- * Called to let the system remove the content views from the notification row.
- */
+ /** Called to let the system remove the content views from the notification row. */
fun releaseViews(entry: NotificationEntry)
- /**
- * Callback once all the views are inflated and bound for a given NotificationEntry.
- */
+ /** Callback once all the views are inflated and bound for a given NotificationEntry. */
interface InflationCallback {
fun onInflationFinished(entry: NotificationEntry, controller: NotifViewController)
}
- /**
- * A class holding parameters used when inflating the notification row
- */
- class Params(val isLowPriority: Boolean, val reason: String, val showSnooze: Boolean)
+ /** A class holding parameters used when inflating the notification row */
+ class Params(
+ val isLowPriority: Boolean,
+ val reason: String,
+ val showSnooze: Boolean,
+ val isChildInGroup: Boolean = false,
+ )
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustment.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustment.kt
index ee0b00807e27..e1d2cdc65d5a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustment.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustment.kt
@@ -20,6 +20,7 @@ import android.app.Notification
import android.app.RemoteInput
import android.graphics.drawable.Icon
import android.text.TextUtils
+import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation
/**
* An immutable object which contains minimal state extracted from an entry that represents state
@@ -34,6 +35,7 @@ class NotifUiAdjustment internal constructor(
val isSnoozeEnabled: Boolean,
val isMinimized: Boolean,
val needsRedaction: Boolean,
+ val isChildInGroup: Boolean,
) {
companion object {
@JvmStatic
@@ -48,6 +50,11 @@ class NotifUiAdjustment internal constructor(
oldAdjustment.needsRedaction != newAdjustment.needsRedaction -> true
areDifferent(oldAdjustment.smartActions, newAdjustment.smartActions) -> true
newAdjustment.smartReplies != oldAdjustment.smartReplies -> true
+ // TODO(b/217799515): Here we decide whether to re-inflate the row on every group-status
+ // change if we want to keep the single-line view, the following line should be:
+ // !oldAdjustment.isChildInGroup && newAdjustment.isChildInGroup -> true
+ AsyncHybridViewInflation.isEnabled &&
+ oldAdjustment.isChildInGroup != newAdjustment.isChildInGroup -> true
else -> false
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt
index 058545689c01..6f44c13a3e71 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt
@@ -29,6 +29,7 @@ import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.notification.collection.GroupEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider
+import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager
import com.android.systemui.util.ListenerSet
import com.android.systemui.util.settings.SecureSettings
import javax.inject.Inject
@@ -43,7 +44,8 @@ class NotifUiAdjustmentProvider @Inject constructor(
private val secureSettings: SecureSettings,
private val lockscreenUserManager: NotificationLockscreenUserManager,
private val sectionStyleProvider: SectionStyleProvider,
- private val userTracker: UserTracker
+ private val userTracker: UserTracker,
+ private val groupMembershipManager: GroupMembershipManager,
) {
private val dirtyListeners = ListenerSet<Runnable>()
private var isSnoozeSettingsEnabled = false
@@ -121,5 +123,6 @@ class NotifUiAdjustmentProvider @Inject constructor(
isSnoozeEnabled = isSnoozeSettingsEnabled && !entry.isCanceled,
isMinimized = isEntryMinimized(entry),
needsRedaction = lockscreenUserManager.needsRedaction(entry),
+ isChildInGroup = groupMembershipManager.isChildInGroup(entry),
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
index 80ef14bb4673..cd816aea452b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
@@ -20,6 +20,7 @@ import static com.android.systemui.Flags.screenshareNotificationHiding;
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED;
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED;
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC;
+import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_SINGLE_LINE;
import static java.util.Objects.requireNonNull;
@@ -49,6 +50,7 @@ import com.android.systemui.statusbar.notification.row.RowContentBindParams;
import com.android.systemui.statusbar.notification.row.RowContentBindStage;
import com.android.systemui.statusbar.notification.row.RowInflaterTask;
import com.android.systemui.statusbar.notification.row.dagger.ExpandableNotificationRowComponent;
+import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import javax.inject.Inject;
@@ -127,6 +129,8 @@ public class NotificationRowBinderImpl implements NotificationRowBinder {
@NonNull NotifInflater.Params params,
NotificationRowContentBinder.InflationCallback callback)
throws InflationException {
+ //TODO(b/217799515): Remove the entry parameter from getViewParentForNotification(), this
+ // function returns the NotificationStackScrollLayout regardless of the entry.
ViewGroup parent = mListContainer.getViewParentForNotification(entry);
if (entry.rowExists()) {
@@ -174,6 +178,9 @@ public class NotificationRowBinderImpl implements NotificationRowBinder {
params.markContentViewsFreeable(FLAG_CONTENT_VIEW_CONTRACTED);
params.markContentViewsFreeable(FLAG_CONTENT_VIEW_EXPANDED);
params.markContentViewsFreeable(FLAG_CONTENT_VIEW_PUBLIC);
+ if (AsyncHybridViewInflation.isEnabled()) {
+ params.markContentViewsFreeable(FLAG_CONTENT_VIEW_SINGLE_LINE);
+ }
mRowContentBindStage.requestRebind(entry, null);
}
@@ -254,6 +261,16 @@ public class NotificationRowBinderImpl implements NotificationRowBinder {
params.markContentViewsFreeable(FLAG_CONTENT_VIEW_PUBLIC);
}
+ if (AsyncHybridViewInflation.isEnabled()) {
+ if (inflaterParams.isChildInGroup()) {
+ params.requireContentViews(FLAG_CONTENT_VIEW_SINGLE_LINE);
+ } else {
+ // TODO(b/217799515): here we decide whether to free the single-line view
+ // when the group status changes
+ params.markContentViewsFreeable(FLAG_CONTENT_VIEW_SINGLE_LINE);
+ }
+ }
+
params.rebindAllContentViews();
mLogger.logRequestingRebind(entry, inflaterParams);
mRowContentBindStage.requestRebind(entry, en -> {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
index 61e6f65b2bc2..8021d8f58ccc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
@@ -127,6 +127,9 @@ class ShadeViewDiffer(
}
}
+ /**
+ * Attach the Child Nodes to the parentNode using the structure from specMap
+ */
private fun attachChildren(
parentNode: ShadeNode,
specMap: Map<NodeController, NodeSpec>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BindStage.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BindStage.java
index d626c18e46f5..8ae324fa4ef8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BindStage.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BindStage.java
@@ -44,6 +44,7 @@ public abstract class BindStage<Params> extends BindRequester {
/**
* Execute the stage asynchronously.
*
+ * @param entry the NotificationEntry to bind
* @param row notification top-level view to bind views to
* @param callback callback after stage finishes
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java
index 43d99a0e03f2..6bc2b2f9e250 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java
@@ -16,19 +16,27 @@
package com.android.systemui.statusbar.notification.row;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
+import android.content.res.ColorStateList;
import android.graphics.drawable.Icon;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
+import android.view.ViewStub;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.widget.ConversationLayout;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.notification.NotificationFadeAware;
+import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation;
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.ConversationAvatar;
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.FacePile;
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.SingleIcon;
/**
* A hybrid view which may contain information about one ore more conversations.
@@ -37,6 +45,7 @@ public class HybridConversationNotificationView extends HybridNotificationView {
private ImageView mConversationIconView;
private TextView mConversationSenderName;
+ private ViewStub mConversationFacePileStub;
private View mConversationFacePile;
private int mSingleAvatarSize;
private int mFacePileSize;
@@ -65,7 +74,16 @@ public class HybridConversationNotificationView extends HybridNotificationView {
protected void onFinishInflate() {
super.onFinishInflate();
mConversationIconView = requireViewById(com.android.internal.R.id.conversation_icon);
- mConversationFacePile = requireViewById(com.android.internal.R.id.conversation_face_pile);
+ if (AsyncHybridViewInflation.isEnabled()) {
+ mConversationFacePileStub =
+ requireViewById(com.android.internal.R.id.conversation_face_pile);
+ } else {
+ // TODO(b/217799515): This usage is vague because mConversationFacePile represents both
+ // View and ViewStub at different stages of View inflation, should be removed when
+ // AsyncHybridViewInflation flag is removed
+ mConversationFacePile =
+ requireViewById(com.android.internal.R.id.conversation_face_pile);
+ }
mConversationSenderName = requireViewById(R.id.conversation_notification_sender);
applyTextColor(mConversationSenderName, mSecondaryTextColor);
mFacePileSize = getResources()
@@ -85,7 +103,8 @@ public class HybridConversationNotificationView extends HybridNotificationView {
@Override
public void bind(@Nullable CharSequence title, @Nullable CharSequence text,
- @Nullable View contentView) {
+ @Nullable View contentView) {
+ AsyncHybridViewInflation.assertInLegacyMode();
if (!(contentView instanceof ConversationLayout)) {
super.bind(title, text, contentView);
return;
@@ -137,6 +156,77 @@ public class HybridConversationNotificationView extends HybridNotificationView {
super.bind(conversationTitle, conversationText, conversationLayout);
}
+ /**
+ * Set the avatar using ConversationAvatar from SingleLineViewModel
+ *
+ * @param conversationAvatar the icon needed for a single-line conversation view, it should be
+ * either an instance of SingleIcon or FacePile
+ */
+ public void setAvatar(@NonNull ConversationAvatar conversationAvatar) {
+ if (AsyncHybridViewInflation.isUnexpectedlyInLegacyMode()) return;
+ if (conversationAvatar instanceof SingleIcon) {
+ SingleIcon avatar = (SingleIcon) conversationAvatar;
+ if (mConversationFacePile != null) mConversationFacePile.setVisibility(GONE);
+ mConversationIconView.setVisibility(VISIBLE);
+ mConversationIconView.setImageDrawable(avatar.getIconDrawable());
+ setSize(mConversationIconView, mSingleAvatarSize);
+ return;
+ }
+
+ // If conversationAvatar is not a SingleIcon, it should be a FacePile.
+ // Bind the face pile with it.
+ FacePile facePileModel = (FacePile) conversationAvatar;
+ mConversationIconView.setVisibility(GONE);
+ // Inflate mConversationFacePile from ViewStub
+ if (mConversationFacePile == null) {
+ mConversationFacePile = mConversationFacePileStub.inflate();
+ }
+ mConversationFacePile.setVisibility(VISIBLE);
+
+ ImageView facePileBottomBg = mConversationFacePile.requireViewById(
+ com.android.internal.R.id.conversation_face_pile_bottom_background);
+ ImageView facePileBottom = mConversationFacePile.requireViewById(
+ com.android.internal.R.id.conversation_face_pile_bottom);
+ ImageView facePileTop = mConversationFacePile.requireViewById(
+ com.android.internal.R.id.conversation_face_pile_top);
+
+ int bottomBackgroundColor = facePileModel.getBottomBackgroundColor();
+ facePileBottomBg.setImageTintList(ColorStateList.valueOf(bottomBackgroundColor));
+
+ facePileBottom.setImageDrawable(facePileModel.getBottomIconDrawable());
+ facePileTop.setImageDrawable(facePileModel.getTopIconDrawable());
+
+ setSize(mConversationFacePile, mFacePileSize);
+ setSize(facePileBottom, mFacePileAvatarSize);
+ setSize(facePileTop, mFacePileAvatarSize);
+ setSize(facePileBottomBg, mFacePileAvatarSize + 2 * mFacePileProtectionWidth);
+
+ mTransformationHelper.addViewTransformingToSimilar(facePileTop);
+ mTransformationHelper.addViewTransformingToSimilar(facePileBottom);
+ mTransformationHelper.addViewTransformingToSimilar(facePileBottomBg);
+
+ }
+
+ /**
+ * bind the text views
+ */
+ public void setText(
+ CharSequence titleText,
+ CharSequence contentText,
+ CharSequence conversationSenderName
+ ) {
+ if (AsyncHybridViewInflation.isUnexpectedlyInLegacyMode()) return;
+ if (conversationSenderName == null) {
+ mConversationSenderName.setVisibility(GONE);
+ } else {
+ mConversationSenderName.setVisibility(VISIBLE);
+ mConversationSenderName.setText(conversationSenderName);
+ }
+ // TODO (b/217799515): super.bind() doesn't use contentView, remove the contentView
+ // argument when the flag is removed
+ super.bind(/* title = */ titleText, /* text = */ contentText, /* contentView = */ null);
+ }
+
private static void setSize(View view, int size) {
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) view.getLayoutParams();
lp.width = size;
@@ -153,4 +243,9 @@ public class HybridConversationNotificationView extends HybridNotificationView {
super.setNotificationFaded(faded);
NotificationFadeAware.setLayerTypeForFaded(mConversationFacePile, faded);
}
+
+ @VisibleForTesting
+ TextView getConversationSenderNameView() {
+ return mConversationSenderName;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridGroupManager.java
index ddd9bddc7375..09c034978977 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridGroupManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridGroupManager.java
@@ -32,6 +32,7 @@ import android.widget.TextView;
import com.android.internal.widget.ConversationLayout;
import com.android.systemui.res.R;
+import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation;
/**
* A class managing hybrid groups that include {@link HybridNotificationView} and the notification
@@ -41,6 +42,8 @@ public class HybridGroupManager {
private final Context mContext;
+ private static final String TAG = "HybridGroupManager";
+
private float mOverflowNumberSize;
private int mOverflowNumberPadding;
@@ -93,21 +96,34 @@ public class HybridGroupManager {
public HybridNotificationView bindFromNotification(HybridNotificationView reusableView,
View contentView, StatusBarNotification notification,
ViewGroup parent) {
+ AsyncHybridViewInflation.assertInLegacyMode();
boolean isNewView = false;
if (reusableView == null) {
Trace.beginSection("HybridGroupManager#bindFromNotification");
reusableView = inflateHybridView(contentView, parent);
isNewView = true;
}
- CharSequence titleText = resolveTitle(notification.getNotification());
- CharSequence contentText = resolveText(notification.getNotification());
- reusableView.bind(titleText, contentText, contentView);
+
+ updateReusableView(reusableView, notification, contentView);
if (isNewView) {
Trace.endSection();
}
return reusableView;
}
+ /**
+ * Update the HybridNotificationView (single-line view)'s appearance
+ */
+ public void updateReusableView(HybridNotificationView reusableView,
+ StatusBarNotification notification, View contentView) {
+ AsyncHybridViewInflation.assertInLegacyMode();
+ final CharSequence titleText = resolveTitle(notification.getNotification());
+ final CharSequence contentText = resolveText(notification.getNotification());
+ if (reusableView != null) {
+ reusableView.bind(titleText, contentText, contentView);
+ }
+ }
+
@Nullable
public static CharSequence resolveText(Notification notification) {
CharSequence contentText = notification.extras.getCharSequence(Notification.EXTRA_TEXT);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index f186e665f773..913d5f6d3848 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -20,6 +20,7 @@ import static com.android.internal.annotations.VisibleForTesting.Visibility.PACK
import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_CONTRACTED;
import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_EXPANDED;
import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP;
+import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_SINGLELINE;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -41,15 +42,19 @@ import android.widget.RemoteViews;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.widget.ImageMessageConsumer;
-import com.android.systemui.res.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.media.controls.util.MediaFeatureFlag;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.InflationTask;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.notification.ConversationNotificationProcessor;
import com.android.systemui.statusbar.notification.InflationException;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation;
+import com.android.systemui.statusbar.notification.row.ui.viewbinder.SingleLineConversationViewBinder;
+import com.android.systemui.statusbar.notification.row.ui.viewbinder.SingleLineViewBinder;
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.SingleLineViewModel;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.policy.InflatedSmartReplyState;
@@ -135,7 +140,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
AsyncInflationTask task = new AsyncInflationTask(
mBgExecutor,
mInflateSynchronously,
- contentToBind,
+ /* reInflateFlags = */ contentToBind,
mRemoteViewCache,
entry,
mConversationProcessor,
@@ -145,7 +150,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
bindParams.usesIncreasedHeadsUpHeight,
callback,
mRemoteInputManager.getRemoteViewsOnClickHandler(),
- mIsMediaInQS,
+ /* isMediaFlagEnabled = */ mIsMediaInQS,
mSmartReplyStateInflater,
mNotifLayoutInflaterFactoryProvider,
mLogger);
@@ -178,6 +183,29 @@ public class NotificationContentInflater implements NotificationRowContentBinder
result = inflateSmartReplyViews(result, reInflateFlags, entry, row.getContext(),
packageContext, row.getExistingSmartReplyState(), smartRepliesInflater, mLogger);
+ if (AsyncHybridViewInflation.isEnabled()) {
+ boolean isConversation = entry.getRanking().isConversation();
+ Notification.MessagingStyle messagingStyle = null;
+ if (isConversation) {
+ messagingStyle = mConversationProcessor
+ .processNotification(entry, builder, mLogger);
+ }
+ result.mInflatedSingleLineViewModel = SingleLineViewInflater
+ .inflateSingleLineViewModel(
+ entry.getSbn().getNotification(),
+ messagingStyle,
+ builder,
+ row.getContext()
+ );
+ result.mInflatedSingleLineViewHolder =
+ SingleLineViewInflater.inflateSingleLineViewHolder(
+ isConversation,
+ reInflateFlags,
+ entry,
+ row.getContext(),
+ mLogger
+ );
+ }
apply(
mBgExecutor,
@@ -255,6 +283,15 @@ public class NotificationContentInflater implements NotificationRowContentBinder
mRemoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC);
});
break;
+ case FLAG_CONTENT_VIEW_SINGLE_LINE: {
+ if (AsyncHybridViewInflation.isEnabled()) {
+ row.getPrivateLayout().performWhenContentInactive(
+ VISIBLE_TYPE_SINGLELINE,
+ () -> row.getPrivateLayout().setSingleLineView(null)
+ );
+ }
+ break;
+ }
default:
break;
}
@@ -282,6 +319,10 @@ public class NotificationContentInflater implements NotificationRowContentBinder
if ((contentViews & FLAG_CONTENT_VIEW_PUBLIC) != 0) {
row.getPublicLayout().removeContentInactiveRunnable(VISIBLE_TYPE_CONTRACTED);
}
+ if (AsyncHybridViewInflation.isEnabled()
+ && (contentViews & FLAG_CONTENT_VIEW_SINGLE_LINE) != 0) {
+ row.getPrivateLayout().removeContentInactiveRunnable(VISIBLE_TYPE_SINGLELINE);
+ }
}
private static InflationProgress inflateSmartReplyViews(
@@ -772,6 +813,25 @@ public class NotificationContentInflater implements NotificationRowContentBinder
}
setRepliesAndActions = true;
}
+
+ if (AsyncHybridViewInflation.isEnabled()
+ && (reInflateFlags & FLAG_CONTENT_VIEW_SINGLE_LINE) != 0) {
+ HybridNotificationView viewHolder = result.mInflatedSingleLineViewHolder;
+ SingleLineViewModel viewModel = result.mInflatedSingleLineViewModel;
+ if (viewHolder != null && viewModel != null) {
+ if (viewModel.isConversation()) {
+ SingleLineConversationViewBinder.bind(
+ result.mInflatedSingleLineViewModel,
+ result.mInflatedSingleLineViewHolder
+ );
+ } else {
+ SingleLineViewBinder.bind(result.mInflatedSingleLineViewModel,
+ result.mInflatedSingleLineViewHolder);
+ }
+ privateLayout.setSingleLineView(result.mInflatedSingleLineViewHolder);
+ }
+ }
+
if (setRepliesAndActions) {
privateLayout.setInflatedSmartReplyState(result.inflatedSmartReplyState);
}
@@ -941,19 +1001,23 @@ public class NotificationContentInflater implements NotificationRowContentBinder
// For all of our templates, we want it to be RTL
packageContext = new RtlEnabledContext(packageContext);
}
- if (mEntry.getRanking().isConversation()) {
- mConversationProcessor.processNotification(mEntry, recoveredBuilder, mLogger);
+ boolean isConversation = mEntry.getRanking().isConversation();
+ Notification.MessagingStyle messagingStyle = null;
+ if (isConversation) {
+ messagingStyle = mConversationProcessor.processNotification(
+ mEntry, recoveredBuilder, mLogger);
}
InflationProgress inflationProgress = createRemoteViews(mReInflateFlags,
recoveredBuilder, mIsLowPriority, mUsesIncreasedHeight,
mUsesIncreasedHeadsUpHeight, packageContext, mRow,
mNotifLayoutInflaterFactoryProvider, mLogger);
+
mLogger.logAsyncTaskProgress(mEntry,
"getting existing smart reply state (on wrong thread!)");
InflatedSmartReplyState previousSmartReplyState = mRow.getExistingSmartReplyState();
mLogger.logAsyncTaskProgress(mEntry, "inflating smart reply views");
InflationProgress result = inflateSmartReplyViews(
- inflationProgress,
+ /* result = */ inflationProgress,
mReInflateFlags,
mEntry,
mContext,
@@ -962,6 +1026,27 @@ public class NotificationContentInflater implements NotificationRowContentBinder
mSmartRepliesInflater,
mLogger);
+ if (AsyncHybridViewInflation.isEnabled()) {
+ // Inflate the single-line content view's ViewModel and ViewHolder from the
+ // background thread, the ViewHolder needs to be bind with ViewModel later from
+ // the main thread.
+ result.mInflatedSingleLineViewModel = SingleLineViewInflater
+ .inflateSingleLineViewModel(
+ mEntry.getSbn().getNotification(),
+ messagingStyle,
+ recoveredBuilder,
+ mContext
+ );
+ result.mInflatedSingleLineViewHolder =
+ SingleLineViewInflater.inflateSingleLineViewHolder(
+ isConversation,
+ mReInflateFlags,
+ mEntry,
+ mContext,
+ mLogger
+ );
+ }
+
mLogger.logAsyncTaskProgress(mEntry,
"getting row image resolver (on wrong thread!)");
final NotificationInlineImageResolver imageResolver = mRow.getImageResolver();
@@ -1078,6 +1163,11 @@ public class NotificationContentInflater implements NotificationRowContentBinder
private InflatedSmartReplyState inflatedSmartReplyState;
private InflatedSmartReplyViewHolder expandedInflatedSmartReplies;
private InflatedSmartReplyViewHolder headsUpInflatedSmartReplies;
+
+ // ViewModel for SingleLineView, holds the UI State
+ SingleLineViewModel mInflatedSingleLineViewModel;
+ // Inflated SingleLineViewHolder, SingleLineView that lacks the UI State
+ HybridNotificationView mInflatedSingleLineViewHolder;
}
@VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterLogger.kt
index 4f5455dc455f..ee9462c60674 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterLogger.kt
@@ -26,6 +26,7 @@ import com.android.systemui.statusbar.notification.row.NotificationRowContentBin
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_SINGLE_LINE
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag
import javax.inject.Inject
@@ -99,6 +100,26 @@ constructor(@NotifInflationLog private val buffer: LogBuffer) {
)
}
+ fun logInflateSingleLine(
+ entry: NotificationEntry,
+ @InflationFlag inflationFlags: Int,
+ isConversation: Boolean
+ ) {
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = entry.logKey
+ int1 = inflationFlags
+ bool1 = isConversation
+ },
+ {
+ "inflateSingleLineView, inflationFlags: ${flagToString(int1)} for $str1, " +
+ "isConversation: $bool1"
+ }
+ )
+ }
+
companion object {
fun flagToString(@InflationFlag flag: Int): String {
if (flag == 0) {
@@ -121,6 +142,9 @@ constructor(@NotifInflationLog private val buffer: LogBuffer) {
if (flag and FLAG_CONTENT_VIEW_PUBLIC != 0) {
l.add("PUBLIC")
}
+ if (flag and FLAG_CONTENT_VIEW_SINGLE_LINE != 0) {
+ l.add("SINGLE_LINE")
+ }
return l.joinToString("|")
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index a1718b9fbb02..402ea51bebb6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -57,6 +57,7 @@ import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
+import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationCustomViewWrapper;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
import com.android.systemui.statusbar.policy.InflatedSmartReplyState;
@@ -86,7 +87,7 @@ public class NotificationContentView extends FrameLayout implements Notification
public static final int VISIBLE_TYPE_CONTRACTED = 0;
public static final int VISIBLE_TYPE_EXPANDED = 1;
public static final int VISIBLE_TYPE_HEADSUP = 2;
- private static final int VISIBLE_TYPE_SINGLELINE = 3;
+ public static final int VISIBLE_TYPE_SINGLELINE = 3;
/**
* Used when there is no content on the view such as when we're a public layout but don't
* need to show.
@@ -98,6 +99,7 @@ public class NotificationContentView extends FrameLayout implements Notification
private final Rect mClipBounds = new Rect();
private int mMinContractedHeight;
+ private int mMinSingleLineHeight;
private View mContractedChild;
private View mExpandedChild;
private View mHeadsUpChild;
@@ -234,6 +236,11 @@ public class NotificationContentView extends FrameLayout implements Notification
public void reinflate() {
mMinContractedHeight = getResources().getDimensionPixelSize(
R.dimen.min_notification_layout_height);
+ if (AsyncHybridViewInflation.isEnabled()) {
+ //TODO: set the height with a more reasonable min single-line height
+ mMinSingleLineHeight = getResources().getDimensionPixelSize(
+ R.dimen.conversation_single_line_face_pile_size);
+ }
}
public void setHeights(int smallHeight, int headsUpMaxHeight, int maxHeight) {
@@ -540,6 +547,28 @@ public class NotificationContentView extends FrameLayout implements Notification
updateShownWrapper(mVisibleType);
}
+ /**
+ * Sets the single-line view. Child may be null to remove the view.
+ * @param child single-line content view to set
+ */
+ public void setSingleLineView(@Nullable HybridNotificationView child) {
+ if (AsyncHybridViewInflation.isUnexpectedlyInLegacyMode()) return;
+ if (mSingleLineView != null) {
+ mOnContentViewInactiveListeners.remove(mSingleLineView);
+ mSingleLineView.animate().cancel();
+ removeView(mSingleLineView);
+ }
+ if (child == null) {
+ mSingleLineView = null;
+ if (mTransformationStartVisibleType == VISIBLE_TYPE_SINGLELINE) {
+ mTransformationStartVisibleType = VISIBLE_TYPE_NONE;
+ }
+ return;
+ }
+ addView(child);
+ mSingleLineView = child;
+ }
+
@Override
public void onViewAdded(View child) {
super.onViewAdded(child);
@@ -809,7 +838,17 @@ public class NotificationContentView extends FrameLayout implements Notification
return mContractedChild != null
? getViewHeight(VISIBLE_TYPE_CONTRACTED) : mMinContractedHeight;
} else {
- return mSingleLineView.getHeight();
+ if (AsyncHybridViewInflation.isEnabled()) {
+ if (mSingleLineView != null) {
+ return getViewHeight(VISIBLE_TYPE_SINGLELINE);
+ } else {
+ Log.wtf(TAG, "getMinHeight: mSingleLineView == null");
+ return mMinSingleLineHeight;
+ }
+ } else {
+ AsyncHybridViewInflation.assertInLegacyMode();
+ return mSingleLineView.getHeight();
+ }
}
}
@@ -1264,19 +1303,30 @@ public class NotificationContentView extends FrameLayout implements Notification
}
private void updateSingleLineView() {
- if (mIsChildInGroup) {
+ try {
Trace.beginSection("NotifContentView#updateSingleLineView");
- boolean isNewView = mSingleLineView == null;
- mSingleLineView = mHybridGroupManager.bindFromNotification(
- mSingleLineView, mContractedChild, mNotificationEntry.getSbn(), this);
- if (isNewView) {
- updateViewVisibility(mVisibleType, VISIBLE_TYPE_SINGLELINE,
- mSingleLineView, mSingleLineView);
+ if (AsyncHybridViewInflation.isEnabled()) {
+ return;
+ }
+ AsyncHybridViewInflation.assertInLegacyMode();
+ if (mIsChildInGroup) {
+ boolean isNewView = mSingleLineView == null;
+ mSingleLineView = mHybridGroupManager.bindFromNotification(
+ /* reusableView = */ mSingleLineView,
+ /* contentView = */ mContractedChild,
+ /* notification = */ mNotificationEntry.getSbn(),
+ /* parent = */ this
+ );
+ if (isNewView && mSingleLineView != null) {
+ updateViewVisibility(mVisibleType, VISIBLE_TYPE_SINGLELINE,
+ mSingleLineView, mSingleLineView);
+ }
+ } else if (mSingleLineView != null) {
+ removeView(mSingleLineView);
+ mSingleLineView = null;
}
+ } finally {
Trace.endSection();
- } else if (mSingleLineView != null) {
- removeView(mSingleLineView);
- mSingleLineView = null;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java
index d7b7aa210257..736140c44dfd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java
@@ -80,6 +80,7 @@ public interface NotificationRowContentBinder {
FLAG_CONTENT_VIEW_EXPANDED,
FLAG_CONTENT_VIEW_HEADS_UP,
FLAG_CONTENT_VIEW_PUBLIC,
+ FLAG_CONTENT_VIEW_SINGLE_LINE,
FLAG_CONTENT_VIEW_ALL})
@interface InflationFlag {}
/**
@@ -102,7 +103,12 @@ public interface NotificationRowContentBinder {
*/
int FLAG_CONTENT_VIEW_PUBLIC = 1 << 3;
- int FLAG_CONTENT_VIEW_ALL = (1 << 4) - 1;
+ /**
+ * The single line notification view. Show when the notification is shown as a child in group.
+ */
+ int FLAG_CONTENT_VIEW_SINGLE_LINE = 1 << 4;
+
+ int FLAG_CONTENT_VIEW_ALL = (1 << 5) - 1;
/**
* Parameters for content view binding
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java
index a52f638e7c26..1494c275d061 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java
@@ -102,9 +102,9 @@ public final class RowContentBindParams {
* @see InflationFlag
*/
public void markContentViewsFreeable(@InflationFlag int contentViews) {
- @InflationFlag int existingContentViews = contentViews &= mContentViews;
+ @InflationFlag int existingFreeableContentViews = contentViews &= mContentViews;
mContentViews &= ~contentViews;
- mDirtyContentViews |= existingContentViews;
+ mDirtyContentViews |= existingFreeableContentViews;
}
public @InflationFlag int getContentViews() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java
index b70da00ad517..f4f8374d0a9f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java
@@ -63,7 +63,10 @@ public class RowContentBindStage extends BindStage<RowContentBindParams> {
@InflationFlag int inflationFlags = params.getContentViews();
@InflationFlag int invalidatedFlags = params.getDirtyContentViews();
+ // Rebind the content views which are needed now, and the corresponding old views are
+ // invalidated
@InflationFlag int contentToBind = invalidatedFlags & inflationFlags;
+ // Unbind the content views that are not needed
@InflationFlag int contentToUnbind = inflationFlags ^ FLAG_CONTENT_VIEW_ALL;
// Bind/unbind with parameters
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt
new file mode 100644
index 000000000000..d6118a0b3865
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt
@@ -0,0 +1,390 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row
+
+import android.app.Notification
+import android.app.Notification.MessagingStyle
+import android.app.Person
+import android.content.Context
+import android.graphics.drawable.Icon
+import android.util.Log
+import android.view.LayoutInflater
+import com.android.app.tracing.traceSection
+import com.android.internal.R
+import com.android.internal.widget.MessagingMessage
+import com.android.internal.widget.PeopleHelper
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.logKey
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_SINGLE_LINE
+import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.ConversationAvatar
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.ConversationData
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.FacePile
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.SingleIcon
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.SingleLineViewModel
+
+/** The inflater of SingleLineViewModel and SingleLineViewHolder */
+internal object SingleLineViewInflater {
+ const val TAG = "SingleLineViewInflater"
+
+ /**
+ * Inflate an instance of SingleLineViewModel.
+ *
+ * @param notification the notification to show
+ * @param messagingStyle the MessagingStyle information is only provided for conversation
+ * notification, not for legacy messaging notifications
+ * @param builder the recovered Notification Builder
+ * @param systemUiContext the context of Android System UI
+ * @return the inflated SingleLineViewModel
+ */
+ @JvmStatic
+ fun inflateSingleLineViewModel(
+ notification: Notification,
+ messagingStyle: MessagingStyle?,
+ builder: Notification.Builder,
+ systemUiContext: Context,
+ ): SingleLineViewModel {
+ if (AsyncHybridViewInflation.isUnexpectedlyInLegacyMode()) {
+ return SingleLineViewModel(null, null, null)
+ }
+ peopleHelper.init(systemUiContext)
+ var titleText = HybridGroupManager.resolveTitle(notification)
+ var contentText = HybridGroupManager.resolveText(notification)
+
+ if (messagingStyle == null) {
+ return SingleLineViewModel(
+ titleText = titleText,
+ contentText = contentText,
+ conversationData = null,
+ )
+ }
+
+ val isGroupConversation = messagingStyle.isGroupConversation
+
+ val conversationTextData = messagingStyle.loadConversationTextData(systemUiContext)
+ if (conversationTextData?.conversationTitle?.isNotEmpty() == true) {
+ titleText = conversationTextData.conversationTitle
+ }
+ if (conversationTextData?.conversationText?.isNotEmpty() == true) {
+ contentText = conversationTextData.conversationText
+ }
+
+ val conversationAvatar =
+ messagingStyle.loadConversationAvatar(
+ notification = notification,
+ isGroupConversation = isGroupConversation,
+ builder = builder,
+ systemUiContext = systemUiContext
+ )
+
+ val conversationData =
+ ConversationData(
+ // We don't show the sender's name for one-to-one conversation
+ conversationSenderName =
+ if (isGroupConversation) conversationTextData?.senderName else null,
+ avatar = conversationAvatar
+ )
+
+ return SingleLineViewModel(
+ titleText = titleText,
+ contentText = contentText,
+ conversationData = conversationData,
+ )
+ }
+
+ /** load conversation text data from the MessagingStyle of conversation notifications */
+ private fun MessagingStyle.loadConversationTextData(
+ systemUiContext: Context
+ ): ConversationTextData? {
+ if (AsyncHybridViewInflation.isUnexpectedlyInLegacyMode()) {
+ return null
+ }
+ var conversationText: CharSequence?
+
+ if (messages.isEmpty()) {
+ return null
+ }
+
+ // load the conversation text
+ val lastMessage = messages[messages.lastIndex]
+ conversationText = lastMessage.text
+ if (conversationText == null && lastMessage.isImageMessage()) {
+ conversationText = findBackUpConversationText(lastMessage, systemUiContext)
+ }
+
+ // load the sender's name to display
+ val name = lastMessage.senderPerson?.name
+ val senderName =
+ systemUiContext.resources.getString(
+ R.string.conversation_single_line_name_display,
+ name
+ )
+
+ // We need to find back-up values for those texts if they are needed and empty
+ return ConversationTextData(
+ conversationTitle = conversationTitle
+ ?: findBackUpConversationTitle(senderName, systemUiContext),
+ conversationText = conversationText,
+ senderName = senderName,
+ )
+ }
+
+ private fun MessagingStyle.Message.isImageMessage(): Boolean = MessagingMessage.hasImage(this)
+
+ /** find a back-up conversation title when the conversation title is null. */
+ private fun MessagingStyle.findBackUpConversationTitle(
+ senderName: CharSequence?,
+ systemUiContext: Context,
+ ): CharSequence {
+ if (AsyncHybridViewInflation.isUnexpectedlyInLegacyMode()) {
+ return ""
+ }
+ return if (isGroupConversation) {
+ systemUiContext.resources.getString(R.string.conversation_title_fallback_group_chat)
+ } else {
+ // Is one-to-one, let's try to use the last sender's name
+ // The last back-up is the value of resource: conversation_title_fallback_one_to_one
+ senderName
+ ?: systemUiContext.resources.getString(
+ R.string.conversation_title_fallback_one_to_one
+ )
+ }
+ }
+
+ /**
+ * find a back-up conversation text when the conversation has null text and is image message.
+ */
+ private fun findBackUpConversationText(
+ message: MessagingStyle.Message,
+ context: Context,
+ ): CharSequence? {
+ if (AsyncHybridViewInflation.isUnexpectedlyInLegacyMode()) {
+ return null
+ }
+ // If the message is not an image message, just return empty, the back-up text for showing
+ // will be SingleLineViewModel.contentText
+ if (!message.isImageMessage()) return null
+ // If is image message, return a placeholder
+ return context.resources.getString(R.string.conversation_single_line_image_placeholder)
+ }
+
+ /**
+ * The text data that we load from a conversation notification to show in the single-line views.
+ *
+ * Group conversation single-line view should be formatted as:
+ * [conversationTitle, senderName, conversationText]
+ *
+ * One-to-one single-line view should be formatted as:
+ * [conversationTitle (which is equal to the senderName), conversationText]
+ *
+ * @property conversationTitle the title of the conversation, not necessarily the title of the
+ * notification row. conversationTitle is non-null, though may be empty, in which case we need
+ * to show the notification title instead.
+ * @property conversationText the text content of the conversation, single-line will use the
+ * notification's text when conversationText is null
+ * @property senderName the sender's name to be shown in the row when needed. senderName can be
+ * null
+ */
+ data class ConversationTextData(
+ val conversationTitle: CharSequence,
+ val conversationText: CharSequence?,
+ val senderName: CharSequence?,
+ )
+
+ private fun groupMessages(
+ messages: List<MessagingStyle.Message>,
+ historicMessages: List<MessagingStyle.Message>,
+ ): List<MutableList<MessagingStyle.Message>> {
+ if (AsyncHybridViewInflation.isUnexpectedlyInLegacyMode()) {
+ return listOf()
+ }
+ if (messages.isEmpty() && historicMessages.isEmpty()) return listOf()
+ var currentGroup: MutableList<MessagingStyle.Message>? = null
+ var currentSenderKey: CharSequence? = null
+ val groups = mutableListOf<MutableList<MessagingStyle.Message>>()
+ for (i in 0 until (historicMessages.size + messages.size)) {
+ val message = if (i < historicMessages.size) historicMessages[i] else messages[i]
+
+ val sender = message.senderPerson
+ val senderKey = sender?.getKeyOrName()
+ val isNewGroup = (currentGroup == null) || senderKey != currentSenderKey
+ if (isNewGroup) {
+ currentGroup = mutableListOf()
+ groups.add(currentGroup)
+ currentSenderKey = senderKey
+ }
+ currentGroup?.add(message)
+ }
+ return groups
+ }
+
+ private fun MessagingStyle.loadConversationAvatar(
+ builder: Notification.Builder,
+ notification: Notification,
+ isGroupConversation: Boolean,
+ systemUiContext: Context,
+ ): ConversationAvatar {
+ if (AsyncHybridViewInflation.isUnexpectedlyInLegacyMode()) {
+ return SingleIcon(null)
+ }
+ val userKey = user.getKeyOrName()
+ var conversationIcon: Icon? = null
+ var conversationText: CharSequence? = conversationTitle
+
+ val groups = groupMessages(messages, historicMessages)
+ val uniqueNames = peopleHelper.mapUniqueNamesToPrefixWithGroupList(groups)
+
+ if (!isGroupConversation) {
+ // Conversation is one-to-one, load the single icon
+ // Let's resolve the icon / text from the last sender
+ if (shortcutIcon != null) {
+ conversationIcon = shortcutIcon
+ }
+
+ for (i in messages.lastIndex downTo 0) {
+ val message = messages[i]
+ val sender = message.senderPerson
+ val senderKey = sender?.getKeyOrName()
+ if ((sender != null && senderKey != userKey) || i == 0) {
+ if (conversationText.isNullOrEmpty()) {
+ // We use the senderName as header text if no conversation title is provided
+ // (This usually happens for most 1:1 conversations)
+ conversationText = sender?.name ?: ""
+ }
+ if (conversationIcon == null) {
+ var avatarIcon = sender?.icon
+ if (avatarIcon == null) {
+ avatarIcon = builder.getDefaultAvatar(name = conversationText)
+ }
+ conversationIcon = avatarIcon
+ }
+ break
+ }
+ }
+ }
+
+ if (conversationIcon == null) {
+ conversationIcon = notification.getLargeIcon()
+ }
+
+ // If is one-to-one or the conversation has an icon, return a single icon
+ if (!isGroupConversation || conversationIcon != null) {
+ return SingleIcon(conversationIcon?.loadDrawable(systemUiContext))
+ }
+
+ // Otherwise, let's find the two last conversations to build a face pile:
+ var secondLastIcon: Icon? = null
+ var lastIcon: Icon? = null
+ var lastKey: CharSequence? = null
+
+ for (i in groups.lastIndex downTo 0) {
+ val message = groups[i][0]
+ val sender = message.senderPerson ?: user
+ val senderKey = sender.getKeyOrName()
+ val notUser = senderKey != userKey
+ val notIncluded = senderKey != lastKey
+
+ if ((notUser && notIncluded) || (i == 0 && lastKey == null)) {
+ if (lastIcon == null) {
+ lastIcon =
+ sender.icon
+ ?: builder.getDefaultAvatar(
+ name = sender.name,
+ uniqueNames = uniqueNames
+ )
+ lastKey = senderKey
+ } else {
+ secondLastIcon =
+ sender.icon
+ ?: builder.getDefaultAvatar(
+ name = sender.name,
+ uniqueNames = uniqueNames
+ )
+ break
+ }
+ }
+ }
+
+ if (lastIcon == null) {
+ lastIcon = builder.getDefaultAvatar(name = "")
+ }
+
+ if (secondLastIcon == null) {
+ secondLastIcon = builder.getDefaultAvatar(name = "")
+ }
+
+ return FacePile(
+ topIconDrawable = secondLastIcon.loadDrawable(systemUiContext),
+ bottomIconDrawable = lastIcon.loadDrawable(systemUiContext),
+ bottomBackgroundColor = builder.getBackgroundColor(/* isHeader = */ false),
+ )
+ }
+
+ @JvmStatic
+ fun inflateSingleLineViewHolder(
+ isConversation: Boolean,
+ reinflateFlags: Int,
+ entry: NotificationEntry,
+ context: Context,
+ logger: NotificationContentInflaterLogger,
+ ): HybridNotificationView? {
+ if (AsyncHybridViewInflation.isUnexpectedlyInLegacyMode()) return null
+ if (reinflateFlags and FLAG_CONTENT_VIEW_SINGLE_LINE == 0) {
+ return null
+ }
+
+ logger.logInflateSingleLine(entry, reinflateFlags, isConversation)
+ logger.logAsyncTaskProgress(entry, "inflating single-line content view")
+
+ var view: HybridNotificationView? = null
+
+ traceSection("NotificationContentInflater#inflateSingleLineView") {
+ val inflater = LayoutInflater.from(context)
+ val layoutRes: Int =
+ if (isConversation)
+ com.android.systemui.res.R.layout.hybrid_conversation_notification
+ else com.android.systemui.res.R.layout.hybrid_notification
+ view = inflater.inflate(layoutRes, /* root = */ null) as HybridNotificationView
+ if (view == null) {
+ Log.wtf(TAG, "Single-line view inflation result is null for entry: ${entry.logKey}")
+ }
+ }
+ return view
+ }
+
+ private fun Notification.Builder.getDefaultAvatar(
+ name: CharSequence?,
+ uniqueNames: PeopleHelper.NameToPrefixMap? = null
+ ): Icon {
+ val layoutColor = getSmallIconColor(/* isHeader = */ false)
+ if (!name.isNullOrEmpty()) {
+ val symbol = uniqueNames?.getPrefix(name) ?: ""
+ return peopleHelper.createAvatarSymbol(
+ /* name = */ name,
+ /* symbol = */ symbol,
+ /* layoutColor = */ layoutColor
+ )
+ }
+ // If name is null, create default avatar with background color
+ // TODO(b/319829062): Investigate caching default icon for color
+ return peopleHelper.createAvatarSymbol(/* name = */ "", /* symbol = */ "", layoutColor)
+ }
+
+ private fun Person.getKeyOrName(): CharSequence? = if (key == null) name else key
+
+ private val peopleHelper = PeopleHelper()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/SingleLineConversationViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/SingleLineConversationViewBinder.kt
new file mode 100644
index 000000000000..69284bd7ef48
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/SingleLineConversationViewBinder.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row.ui.viewbinder
+
+import com.android.systemui.statusbar.notification.row.HybridConversationNotificationView
+import com.android.systemui.statusbar.notification.row.HybridNotificationView
+import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.SingleLineViewModel
+
+object SingleLineConversationViewBinder {
+ @JvmStatic
+ fun bind(viewModel: SingleLineViewModel, view: HybridNotificationView?) {
+ if (AsyncHybridViewInflation.isUnexpectedlyInLegacyMode()) return
+ if (view !is HybridConversationNotificationView || !viewModel.isConversation()) {
+ SingleLineViewBinder.bind(viewModel, view)
+ return
+ }
+
+ viewModel.conversationData?.avatar?.let { view.setAvatar(it) }
+ view.setText(
+ viewModel.titleText,
+ viewModel.contentText,
+ viewModel.conversationData?.conversationSenderName
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/SingleLineViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/SingleLineViewBinder.kt
new file mode 100644
index 000000000000..22e10c165521
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/SingleLineViewBinder.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row.ui.viewbinder
+
+import com.android.systemui.statusbar.notification.row.HybridNotificationView
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.SingleLineViewModel
+
+object SingleLineViewBinder {
+ @JvmStatic
+ fun bind(viewModel: SingleLineViewModel?, view: HybridNotificationView?) {
+ // bind the title and content text views
+ view?.apply {
+ bind(
+ /* title = */ viewModel?.titleText,
+ /* text = */ viewModel?.contentText,
+ /* contentView = */ null
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/SingleLineViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/SingleLineViewModel.kt
new file mode 100644
index 000000000000..d583fa5d97ed
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/SingleLineViewModel.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row.ui.viewmodel
+
+import android.annotation.ColorInt
+import android.graphics.drawable.Drawable
+
+/**
+ * ViewModel for SingleLine Notification View.
+ *
+ * @property titleText the text of notification view title
+ * @property contentText the text of view content
+ * @property conversationData the data that is needed specifically for conversation single-line
+ * views. Null conversationData shows that the notification is not conversation. Legacy
+ * MessagingStyle Notifications doesn't have this member.
+ */
+data class SingleLineViewModel(
+ var titleText: CharSequence?,
+ var contentText: CharSequence?,
+ var conversationData: ConversationData?,
+) {
+ fun isConversation(): Boolean {
+ return conversationData != null
+ }
+}
+
+/**
+ * @property conversationSenderName the name of sender to show in the single-line view. Only group
+ * conversation single-line views show the sender name.
+ * @property avatar the avatar to show for the conversation
+ */
+data class ConversationData(
+ val conversationSenderName: CharSequence?,
+ val avatar: ConversationAvatar,
+)
+
+/**
+ * An avatar to show for a single-line conversation notification, it can be either a single icon or
+ * a face pile.
+ */
+sealed class ConversationAvatar
+
+data class SingleIcon(val iconDrawable: Drawable?) : ConversationAvatar()
+
+/**
+ * A kind of avatar to show for a group conversation notification view. It consists of two avatars
+ * of the last two senders.
+ */
+data class FacePile(
+ val topIconDrawable: Drawable?,
+ val bottomIconDrawable: Drawable?,
+ @ColorInt val bottomBackgroundColor: Int
+) : ConversationAvatar()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
index 45b9c269b61c..abf6c27c68ac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
@@ -1295,8 +1295,8 @@ public class NotificationChildrenContainer extends ViewGroup
if (singleLineView != null) {
minExpandHeight += singleLineView.getHeight();
} else {
- Log.e(TAG, "getMinHeight: child " + child + " single line view is null",
- new Exception());
+ Log.e(TAG, "getMinHeight: child " + child.getEntry().getKey()
+ + " single line view is null", new Exception());
}
visibleChildren++;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 04db653282ac..dd04531b6b34 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -4917,30 +4917,12 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
public void removeContainerView(View v) {
Assert.isMainThread();
removeView(v);
- if (!FooterViewRefactor.isEnabled()) {
- // A notification was removed, and we're not currently showing the empty shade view.
- if (v instanceof ExpandableNotificationRow && !mController.isShowingEmptyShadeView()) {
- mController.updateShowEmptyShadeView();
- updateFooter();
- mController.updateImportantForAccessibility();
- }
- }
-
updateSpeedBumpIndex();
}
public void addContainerView(View v) {
Assert.isMainThread();
addView(v);
- if (!FooterViewRefactor.isEnabled()) {
- // A notification was added, and we're currently showing the empty shade view.
- if (v instanceof ExpandableNotificationRow && mController.isShowingEmptyShadeView()) {
- mController.updateShowEmptyShadeView();
- updateFooter();
- mController.updateImportantForAccessibility();
- }
- }
-
updateSpeedBumpIndex();
}
@@ -4948,14 +4930,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
Assert.isMainThread();
ensureRemovedFromTransientContainer(v);
addView(v, index);
- // A notification was added, and we're currently showing the empty shade view.
- if (!FooterViewRefactor.isEnabled() && v instanceof ExpandableNotificationRow
- && mController.isShowingEmptyShadeView()) {
- mController.updateShowEmptyShadeView();
- updateFooter();
- mController.updateImportantForAccessibility();
- }
-
updateSpeedBumpIndex();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 1143481863f5..49fde3984acc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -2137,6 +2137,7 @@ public class NotificationStackScrollLayoutController implements Dumpable {
if (!FooterViewRefactor.isEnabled()) {
updateShowEmptyShadeView();
+ updateImportantForAccessibility();
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
index f842e304ffdf..fe5bdd41a94f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
@@ -18,9 +18,12 @@ package com.android.systemui.statusbar.notification.stack.ui.viewbinder
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
+import android.view.View
+import android.view.WindowInsets
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.scene.shared.flag.SceneContainerFlags
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
@@ -30,6 +33,9 @@ import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificat
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
/** Binds the shared notification container to its view-model. */
@@ -65,6 +71,8 @@ object SharedNotificationContainerBinder {
}
}
+ val burnInParams = MutableStateFlow(BurnInParameters())
+
/*
* For animation sensitive coroutines, immediately run just like applicationScope does
* instead of doing a post() to the main thread. This extra delay can cause visible jitter.
@@ -122,7 +130,11 @@ object SharedNotificationContainerBinder {
}
}
- launch { viewModel.translationY.collect { controller.setTranslationY(it) } }
+ launch {
+ burnInParams
+ .flatMapLatest { params -> viewModel.translationY(params) }
+ .collect { y -> controller.setTranslationY(y) }
+ }
launch {
viewModel.expansionAlpha.collect { controller.setMaxAlphaForExpansion(it) }
@@ -137,11 +149,20 @@ object SharedNotificationContainerBinder {
controller.setOnHeightChangedRunnable(Runnable { viewModel.notificationStackChanged() })
+ view.setOnApplyWindowInsetsListener { v: View, insets: WindowInsets ->
+ val insetTypes = WindowInsets.Type.systemBars() or WindowInsets.Type.displayCutout()
+ burnInParams.update { current ->
+ current.copy(topInset = insets.getInsetsIgnoringVisibility(insetTypes).top)
+ }
+ insets
+ }
+
return object : DisposableHandle {
override fun dispose() {
disposableHandle.dispose()
disposableHandleMainImmediate.dispose()
controller.setOnHeightChangedRunnable(null)
+ view.setOnApplyWindowInsetsListener(null)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index 99cd89b84c14..4617ce49f44a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -28,6 +28,8 @@ import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.StatusBarState.SHADE_LOCKED
import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
+import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
+import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.LockscreenToGlanceableHubTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel
@@ -65,10 +67,11 @@ constructor(
keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val shadeInteractor: ShadeInteractor,
communalInteractor: CommunalInteractor,
- occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel,
+ private val occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel,
lockscreenToOccludedTransitionViewModel: LockscreenToOccludedTransitionViewModel,
glanceableHubToLockscreenTransitionViewModel: GlanceableHubToLockscreenTransitionViewModel,
- lockscreenToGlanceableHubTransitionViewModel: LockscreenToGlanceableHubTransitionViewModel
+ lockscreenToGlanceableHubTransitionViewModel: LockscreenToGlanceableHubTransitionViewModel,
+ private val aodBurnInViewModel: AodBurnInViewModel,
) {
private val statesForConstrainedNotifications =
setOf(
@@ -313,20 +316,22 @@ constructor(
* Under certain scenarios, such as swiping up on the lockscreen, the container will need to be
* translated as the keyguard fades out.
*/
- val translationY: Flow<Float> =
- combine(
+ fun translationY(params: BurnInParameters): Flow<Float> {
+ return combine(
+ aodBurnInViewModel.translationY(params).onStart { emit(0f) },
isOnLockscreenWithoutShade,
merge(
keyguardInteractor.keyguardTranslationY,
occludedToLockscreenTransitionViewModel.lockscreenTranslationY,
)
- ) { isOnLockscreenWithoutShade, translationY ->
+ ) { burnInY, isOnLockscreenWithoutShade, translationY ->
if (isOnLockscreenWithoutShade) {
- translationY
+ burnInY + translationY
} else {
0f
}
}
+ }
/**
* When on keyguard, there is limited space to display notifications so calculate how many could
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 266c19c09941..7952511addfe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -2587,8 +2587,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
// So if AOD is off or unsupported we need to trigger these updates at screen on
// when the keyguard is occluded.
mLockscreenUserManager.updatePublicMode();
- mShadeSurface.getNotificationStackScrollLayoutController()
- .updateSensitivenessForOccludedWakeup();
+ mStackScrollerController.updateSensitivenessForOccludedWakeup();
}
if (mLaunchCameraWhenFinishedWaking) {
mCameraLauncherLazy.get().launchCamera(mLastCameraLaunchSource,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index aabe4a0d66f9..3f20eaf45260 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -763,10 +763,15 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
// see: b/186644628
mNotificationsScrim.setDrawableBounds(left - 1, top, right + 1, bottom);
mScrimBehind.setBottomEdgePosition((int) top);
+ } else {
+ mNotificationsScrim.setDrawableBounds(left, top, right, bottom);
+ }
+
+ // Only clip if the notif scrim is visible
+ if (mNotificationsAlpha > 0f) {
mKeyguardInteractor.setTopClippingBounds((int) top);
} else {
mKeyguardInteractor.setTopClippingBounds(null);
- mNotificationsScrim.setDrawableBounds(left, top, right, bottom);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
index e3b65ab27f48..61bd112121bc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -20,6 +20,7 @@ import android.graphics.Color;
import android.os.Trace;
import com.android.systemui.dock.DockManager;
+import com.android.systemui.res.R;
import com.android.systemui.scrim.ScrimView;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
@@ -39,8 +40,8 @@ public enum ScrimState {
OFF {
@Override
public void prepare(ScrimState previousState) {
- mFrontTint = Color.BLACK;
- mBehindTint = Color.BLACK;
+ mFrontTint = mBackgroundColor;
+ mBehindTint = mBackgroundColor;
mFrontAlpha = 1f;
mBehindAlpha = 1f;
@@ -74,15 +75,15 @@ public enum ScrimState {
} else {
mAnimationDuration = ScrimController.ANIMATION_DURATION;
}
- mFrontTint = Color.BLACK;
- mBehindTint = Color.BLACK;
- mNotifTint = mClipQsScrim ? Color.BLACK : Color.TRANSPARENT;
+ mFrontTint = mBackgroundColor;
+ mBehindTint = mBackgroundColor;
+ mNotifTint = mClipQsScrim ? mBackgroundColor : Color.TRANSPARENT;
mFrontAlpha = 0;
mBehindAlpha = mClipQsScrim ? 1 : mScrimBehindAlphaKeyguard;
mNotifAlpha = mClipQsScrim ? mScrimBehindAlphaKeyguard : 0;
if (mClipQsScrim) {
- updateScrimColor(mScrimBehind, 1f /* alpha */, Color.BLACK);
+ updateScrimColor(mScrimBehind, 1f /* alpha */, mBackgroundColor);
}
}
},
@@ -93,10 +94,10 @@ public enum ScrimState {
// notif scrim alpha values are determined by ScrimController#applyState
// based on the shade expansion
- mFrontTint = Color.BLACK;
+ mFrontTint = mBackgroundColor;
mFrontAlpha = .66f;
- mBehindTint = Color.BLACK;
+ mBehindTint = mBackgroundColor;
mBehindAlpha = 1f;
}
},
@@ -110,7 +111,7 @@ public enum ScrimState {
mBehindTint = previousState.mBehindTint;
mBehindAlpha = previousState.mBehindAlpha;
- mFrontTint = Color.BLACK;
+ mFrontTint = mBackgroundColor;
mFrontAlpha = .66f;
}
},
@@ -122,7 +123,7 @@ public enum ScrimState {
@Override
public void prepare(ScrimState previousState) {
mBehindAlpha = mClipQsScrim ? 1 : mDefaultScrimAlpha;
- mBehindTint = mClipQsScrim ? Color.BLACK : mSurfaceColor;
+ mBehindTint = mClipQsScrim ? mBackgroundColor : mSurfaceColor;
mNotifAlpha = mClipQsScrim ? mDefaultScrimAlpha : 0;
mNotifTint = Color.TRANSPARENT;
mFrontAlpha = 0f;
@@ -154,10 +155,10 @@ public enum ScrimState {
mBehindAlpha = mClipQsScrim ? 1 : mDefaultScrimAlpha;
mNotifAlpha = 1f;
mFrontAlpha = 0f;
- mBehindTint = mClipQsScrim ? Color.TRANSPARENT : Color.BLACK;
+ mBehindTint = mClipQsScrim ? Color.TRANSPARENT : mBackgroundColor;
if (mClipQsScrim) {
- updateScrimColor(mScrimBehind, 1f /* alpha */, Color.BLACK);
+ updateScrimColor(mScrimBehind, 1f /* alpha */, mBackgroundColor);
}
}
},
@@ -184,11 +185,11 @@ public enum ScrimState {
final boolean isDocked = mDockManager.isDocked();
mBlankScreen = mDisplayRequiresBlanking;
- mFrontTint = Color.BLACK;
+ mFrontTint = mBackgroundColor;
mFrontAlpha = (alwaysOnEnabled || isDocked || quickPickupEnabled)
? mAodFrontScrimAlpha : 1f;
- mBehindTint = Color.BLACK;
+ mBehindTint = mBackgroundColor;
mBehindAlpha = ScrimController.TRANSPARENT;
mAnimationDuration = ScrimController.ANIMATION_DURATION_LONG;
@@ -222,8 +223,8 @@ public enum ScrimState {
@Override
public void prepare(ScrimState previousState) {
mFrontAlpha = mAodFrontScrimAlpha;
- mBehindTint = Color.BLACK;
- mFrontTint = Color.BLACK;
+ mBehindTint = mBackgroundColor;
+ mFrontTint = mBackgroundColor;
mBlankScreen = mDisplayRequiresBlanking;
mAnimationDuration = mWakeLockScreenSensorActive
? ScrimController.ANIMATION_DURATION_LONG : ScrimController.ANIMATION_DURATION;
@@ -231,7 +232,7 @@ public enum ScrimState {
@Override
public float getMaxLightRevealScrimAlpha() {
return mWakeLockScreenSensorActive ? ScrimController.WAKE_SENSOR_SCRIM_ALPHA
- : AOD.getMaxLightRevealScrimAlpha();
+ : AOD.getMaxLightRevealScrimAlpha();
}
},
@@ -245,7 +246,6 @@ public enum ScrimState {
mBehindAlpha = mClipQsScrim ? 1 : 0;
mNotifAlpha = 0;
mFrontAlpha = 0;
-
mAnimationDuration = mKeyguardFadingAway
? mKeyguardFadingAwayDuration
: CentralSurfaces.FADE_KEYGUARD_DURATION;
@@ -259,22 +259,22 @@ public enum ScrimState {
&& !fromAod;
mFrontTint = Color.TRANSPARENT;
- mBehindTint = Color.BLACK;
+ mBehindTint = mBackgroundColor;
mBlankScreen = false;
if (mDisplayRequiresBlanking && previousState == ScrimState.AOD) {
// Set all scrims black, before they fade transparent.
- updateScrimColor(mScrimInFront, 1f /* alpha */, Color.BLACK /* tint */);
- updateScrimColor(mScrimBehind, 1f /* alpha */, Color.BLACK /* tint */);
+ updateScrimColor(mScrimInFront, 1f /* alpha */, mBackgroundColor /* tint */);
+ updateScrimColor(mScrimBehind, 1f /* alpha */, mBackgroundColor /* tint */);
// Scrims should still be black at the end of the transition.
- mFrontTint = Color.BLACK;
- mBehindTint = Color.BLACK;
+ mFrontTint = mBackgroundColor;
+ mBehindTint = mBackgroundColor;
mBlankScreen = true;
}
if (mClipQsScrim) {
- updateScrimColor(mScrimBehind, 1f /* alpha */, Color.BLACK);
+ updateScrimColor(mScrimBehind, 1f /* alpha */, mBackgroundColor);
}
}
},
@@ -283,8 +283,8 @@ public enum ScrimState {
@Override
public void prepare(ScrimState previousState) {
mFrontTint = Color.TRANSPARENT;
- mBehindTint = Color.BLACK;
- mNotifTint = mClipQsScrim ? Color.BLACK : Color.TRANSPARENT;
+ mBehindTint = mBackgroundColor;
+ mNotifTint = mClipQsScrim ? mBackgroundColor : Color.TRANSPARENT;
mFrontAlpha = 0;
mBehindAlpha = mClipQsScrim ? 1 : 0;
@@ -293,7 +293,7 @@ public enum ScrimState {
mBlankScreen = false;
if (mClipQsScrim) {
- updateScrimColor(mScrimBehind, 1f /* alpha */, Color.BLACK);
+ updateScrimColor(mScrimBehind, 1f /* alpha */, mBackgroundColor);
}
}
};
@@ -327,9 +327,11 @@ public enum ScrimState {
boolean mKeyguardFadingAway;
long mKeyguardFadingAwayDuration;
boolean mClipQsScrim;
+ int mBackgroundColor;
public void init(ScrimView scrimInFront, ScrimView scrimBehind, DozeParameters dozeParameters,
DockManager dockManager) {
+ mBackgroundColor = scrimBehind.getContext().getColor(R.color.shade_scrim_background_dark);
mScrimInFront = scrimInFront;
mScrimBehind = scrimBehind;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 88347ab90606..4c83ca28b3cb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -69,6 +69,7 @@ import com.android.systemui.dock.DockManager;
import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
+import com.android.systemui.keyguard.KeyguardWmStateRefactor;
import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor;
@@ -474,7 +475,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
mIsDocked = mDockManager.isDocked();
}
- if (mFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ if (KeyguardWmStateRefactor.isEnabled()) {
// Show the keyguard views whenever we've told WM that the lockscreen is visible.
mShadeViewController.postToView(() ->
collectFlow(
@@ -1428,7 +1429,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
executeAfterKeyguardGoneAction();
}
- if (mFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ if (KeyguardWmStateRefactor.isEnabled()) {
mKeyguardTransitionInteractor.startDismissKeyguardTransition();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/BooleanFlowOperators.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/BooleanFlowOperators.kt
new file mode 100644
index 000000000000..693a835e25d2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/BooleanFlowOperators.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.kotlin
+
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.map
+
+object BooleanFlowOperators {
+ /**
+ * Logical AND operator for boolean flows. Will collect all flows and [combine] them to
+ * determine the result.
+ *
+ * Usage:
+ * ```
+ * val result = and(flow1, flow2)
+ * ```
+ */
+ fun and(vararg flows: Flow<Boolean>): Flow<Boolean> =
+ combine(flows.asIterable()) { values -> values.all { it } }
+
+ /**
+ * Logical NOT operator for a boolean flow.
+ *
+ * Usage:
+ * ```
+ * val negatedFlow = not(flow)
+ * ```
+ */
+ fun not(flow: Flow<Boolean>) = flow.map { !it }
+
+ /**
+ * Logical OR operator for a boolean flow. Will collect all flows and [combine] them to
+ * determine the result.
+ */
+ fun or(vararg flows: Flow<Boolean>): Flow<Boolean> =
+ combine(flows.asIterable()) { values -> values.any { it } }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/Dagger.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/Dagger.kt
index c587f2edd601..5150389930a9 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/Dagger.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/Dagger.kt
@@ -17,6 +17,7 @@
package com.android.systemui.util.kotlin
import dagger.Lazy
+import java.util.Optional
import kotlin.reflect.KProperty
/**
@@ -30,3 +31,16 @@ import kotlin.reflect.KProperty
* ```
*/
operator fun <T> Lazy<T>.getValue(thisRef: Any?, property: KProperty<*>): T = get()
+
+/**
+ * Extension operator that allows developers to use [java.util.Optional] as a nullable property
+ * delegate:
+ * ```kotlin
+ * class MyClass @Inject constructor(
+ * optionalDependency: Optional<Foo>,
+ * ) {
+ * val dependency: Foo? by optionalDependency
+ * }
+ * ```
+ */
+operator fun <T> Optional<T>.getValue(thisRef: Any?, property: KProperty<*>): T? = getOrNull()
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
new file mode 100644
index 000000000000..8d5e55a2917e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dagger
+
+import android.media.AudioManager
+import com.android.settingslib.volume.data.repository.AudioRepository
+import com.android.settingslib.volume.data.repository.AudioRepositoryImpl
+import com.android.settingslib.volume.domain.interactor.AudioModeInteractor
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import dagger.Module
+import dagger.Provides
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+
+/** Dagger module for audio code in the volume package */
+@Module
+interface AudioModule {
+
+ companion object {
+
+ @Provides
+ fun provideAudioRepository(
+ audioManager: AudioManager,
+ @Background coroutineContext: CoroutineContext,
+ @Application coroutineScope: CoroutineScope,
+ ): AudioRepository = AudioRepositoryImpl(audioManager, coroutineContext, coroutineScope)
+
+ @Provides
+ fun provideAudioModeInteractor(repository: AudioRepository): AudioModeInteractor =
+ AudioModeInteractor(repository)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
index b1bfbe0016e1..c842e5f295b9 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
@@ -60,6 +60,9 @@ import kotlinx.coroutines.CoroutineScope;
/** Dagger Module for code in the volume package. */
@Module(
+ includes = {
+ AudioModule.class,
+ },
subcomponents = {
VolumePanelComponent.class
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java
index 9bcab57bec87..90878169c201 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java
@@ -16,10 +16,12 @@
package com.android.systemui.accessibility.floatingmenu;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import android.annotation.NonNull;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.WindowManager;
@@ -27,10 +29,12 @@ import android.view.accessibility.AccessibilityManager;
import androidx.test.filters.SmallTest;
+import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.accessibility.utils.TestUtils;
import com.android.systemui.util.settings.SecureSettings;
-import com.android.wm.shell.bubbles.DismissViewUtils;
import com.android.wm.shell.common.bubbles.DismissView;
+import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
import org.junit.Before;
import org.junit.Rule;
@@ -46,6 +50,7 @@ import org.mockito.junit.MockitoRule;
@TestableLooper.RunWithLooper
public class DragToInteractAnimationControllerTest extends SysuiTestCase {
private DragToInteractAnimationController mDragToInteractAnimationController;
+ private DragToInteractView mInteractView;
private DismissView mDismissView;
@Rule
@@ -57,29 +62,72 @@ public class DragToInteractAnimationControllerTest extends SysuiTestCase {
@Before
public void setUp() throws Exception {
final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
+ final SecureSettings mockSecureSettings = TestUtils.mockSecureSettings();
final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager,
- mock(SecureSettings.class));
+ mockSecureSettings);
final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext,
stubWindowManager);
- final MenuView stubMenuView = new MenuView(mContext, stubMenuViewModel,
- stubMenuViewAppearance);
+ final MenuView stubMenuView = spy(new MenuView(mContext, stubMenuViewModel,
+ stubMenuViewAppearance, mockSecureSettings));
+ mInteractView = spy(new DragToInteractView(mContext));
mDismissView = spy(new DismissView(mContext));
- DismissViewUtils.setup(mDismissView);
- mDragToInteractAnimationController = new DragToInteractAnimationController(
- mDismissView, stubMenuView);
+
+ if (Flags.floatingMenuDragToEdit()) {
+ mDragToInteractAnimationController = new DragToInteractAnimationController(
+ mInteractView, stubMenuView);
+ } else {
+ mDragToInteractAnimationController = new DragToInteractAnimationController(
+ mDismissView, stubMenuView);
+ }
+
+ mDragToInteractAnimationController.setMagnetListener(new MagnetizedObject.MagnetListener() {
+ @Override
+ public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target) {
+
+ }
+
+ @Override
+ public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
+ float velX, float velY, boolean wasFlungOut) {
+
+ }
+
+ @Override
+ public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
+
+ }
+ });
}
@Test
- public void showDismissView_success() {
- mDragToInteractAnimationController.showDismissView(true);
+ @DisableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
+ public void showDismissView_success_old() {
+ mDragToInteractAnimationController.showInteractView(true);
verify(mDismissView).show();
}
@Test
- public void hideDismissView_success() {
- mDragToInteractAnimationController.showDismissView(false);
+ @DisableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
+ public void hideDismissView_success_old() {
+ mDragToInteractAnimationController.showInteractView(false);
verify(mDismissView).hide();
}
+
+ @Test
+ @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
+ public void showDismissView_success() {
+ mDragToInteractAnimationController.showInteractView(true);
+
+ verify(mInteractView).show();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
+ public void hideDismissView_success() {
+ mDragToInteractAnimationController.showInteractView(false);
+
+ verify(mInteractView).hide();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
index 215f93d1e163..e0df1e0e5586 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
@@ -17,6 +17,7 @@
package com.android.systemui.accessibility.floatingmenu;
import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
@@ -42,6 +43,7 @@ import androidx.test.filters.SmallTest;
import com.android.systemui.Flags;
import com.android.systemui.Prefs;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.accessibility.utils.TestUtils;
import com.android.systemui.util.settings.SecureSettings;
import org.junit.After;
@@ -79,10 +81,12 @@ public class MenuAnimationControllerTest extends SysuiTestCase {
final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext,
stubWindowManager);
+ final SecureSettings secureSettings = TestUtils.mockSecureSettings();
final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager,
- mock(SecureSettings.class));
+ secureSettings);
- mMenuView = spy(new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance));
+ mMenuView = spy(new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance,
+ secureSettings));
mViewPropertyAnimator = spy(mMenuView.animate());
doReturn(mViewPropertyAnimator).when(mMenuView).animate();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
index 9c8de302c5e1..c2ed7d4a9d3c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
@@ -22,10 +22,13 @@ import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.ACTIO
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import android.graphics.Rect;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.WindowManager;
@@ -37,7 +40,9 @@ import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate;
import androidx.test.filters.SmallTest;
+import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.accessibility.utils.TestUtils;
import com.android.systemui.res.R;
import com.android.systemui.util.settings.SecureSettings;
@@ -49,6 +54,8 @@ import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
+import java.util.concurrent.atomic.AtomicBoolean;
+
/** Tests for {@link MenuItemAccessibilityDelegate}. */
@SmallTest
@TestableLooper.RunWithLooper
@@ -59,17 +66,16 @@ public class MenuItemAccessibilityDelegateTest extends SysuiTestCase {
@Mock
private AccessibilityManager mAccessibilityManager;
- @Mock
- private SecureSettings mSecureSettings;
- @Mock
- private DragToInteractAnimationController.DismissCallback mStubDismissCallback;
-
+ private final SecureSettings mSecureSettings = TestUtils.mockSecureSettings();
private RecyclerView mStubListView;
private MenuView mMenuView;
+ private MenuViewLayer mMenuViewLayer;
private MenuItemAccessibilityDelegate mMenuItemAccessibilityDelegate;
private MenuAnimationController mMenuAnimationController;
private final Rect mDraggableBounds = new Rect(100, 200, 300, 400);
+ private final AtomicBoolean mEditReceived = new AtomicBoolean(false);
+
@Before
public void setUp() {
final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
@@ -80,20 +86,28 @@ public class MenuItemAccessibilityDelegateTest extends SysuiTestCase {
final int halfScreenHeight =
stubWindowManager.getCurrentWindowMetrics().getBounds().height() / 2;
- mMenuView = spy(new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance));
+ mMenuView = spy(new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance,
+ mSecureSettings));
mMenuView.setTranslationY(halfScreenHeight);
+ mMenuViewLayer = spy(new MenuViewLayer(
+ mContext, stubWindowManager, mAccessibilityManager,
+ stubMenuViewModel, stubMenuViewAppearance, mMenuView,
+ mock(IAccessibilityFloatingMenu.class), mSecureSettings));
+
doReturn(mDraggableBounds).when(mMenuView).getMenuDraggableBounds();
mStubListView = new RecyclerView(mContext);
mMenuAnimationController = spy(new MenuAnimationController(mMenuView,
stubMenuViewAppearance));
mMenuItemAccessibilityDelegate =
new MenuItemAccessibilityDelegate(new RecyclerViewAccessibilityDelegate(
- mStubListView), mMenuAnimationController);
+ mStubListView), mMenuAnimationController, mMenuViewLayer);
+ mEditReceived.set(false);
}
@Test
- public void getAccessibilityActionList_matchSize() {
+ @DisableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
+ public void getAccessibilityActionList_matchSize_withoutEdit() {
final AccessibilityNodeInfoCompat info =
new AccessibilityNodeInfoCompat(new AccessibilityNodeInfo());
@@ -103,6 +117,17 @@ public class MenuItemAccessibilityDelegateTest extends SysuiTestCase {
}
@Test
+ @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
+ public void getAccessibilityActionList_matchSize() {
+ final AccessibilityNodeInfoCompat info =
+ new AccessibilityNodeInfoCompat(new AccessibilityNodeInfo());
+
+ mMenuItemAccessibilityDelegate.onInitializeAccessibilityNodeInfo(mStubListView, info);
+
+ assertThat(info.getActionList().size()).isEqualTo(7);
+ }
+
+ @Test
public void performMoveTopLeftAction_matchPosition() {
final boolean moveTopLeftAction =
mMenuItemAccessibilityDelegate.performAccessibilityAction(mStubListView,
@@ -169,13 +194,22 @@ public class MenuItemAccessibilityDelegateTest extends SysuiTestCase {
@Test
public void performRemoveMenuAction_success() {
- mMenuAnimationController.setDismissCallback(mStubDismissCallback);
final boolean removeMenuAction =
mMenuItemAccessibilityDelegate.performAccessibilityAction(mStubListView,
R.id.action_remove_menu, null);
assertThat(removeMenuAction).isTrue();
- verify(mMenuAnimationController).removeMenu();
+ verify(mMenuViewLayer).dispatchAccessibilityAction(R.id.action_remove_menu);
+ }
+
+ @Test
+ public void performEditAction_success() {
+ final boolean editAction =
+ mMenuItemAccessibilityDelegate.performAccessibilityAction(mStubListView,
+ R.id.action_edit, null);
+
+ assertThat(editAction).isTrue();
+ verify(mMenuViewLayer).dispatchAccessibilityAction(R.id.action_edit);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
index e1522f5f6751..9e8c6b3395e2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
@@ -16,6 +16,7 @@
package com.android.systemui.accessibility.floatingmenu;
+import static android.R.id.empty;
import static android.view.View.OVER_SCROLL_NEVER;
import static com.google.common.truth.Truth.assertThat;
@@ -27,6 +28,8 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.MotionEvent;
@@ -38,10 +41,11 @@ import androidx.recyclerview.widget.RecyclerView;
import androidx.test.filters.SmallTest;
import com.android.internal.accessibility.dialog.AccessibilityTarget;
+import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.accessibility.MotionEventHelper;
+import com.android.systemui.accessibility.utils.TestUtils;
import com.android.systemui.util.settings.SecureSettings;
-import com.android.wm.shell.bubbles.DismissViewUtils;
import com.android.wm.shell.common.bubbles.DismissView;
import org.junit.After;
@@ -71,6 +75,7 @@ public class MenuListViewTouchHandlerTest extends SysuiTestCase {
private DragToInteractAnimationController mDragToInteractAnimationController;
private RecyclerView mStubListView;
private DismissView mDismissView;
+ private DragToInteractView mInteractView;
@Rule
public MockitoRule mockito = MockitoJUnit.rule();
@@ -81,19 +86,28 @@ public class MenuListViewTouchHandlerTest extends SysuiTestCase {
@Before
public void setUp() throws Exception {
final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
+ final SecureSettings secureSettings = TestUtils.mockSecureSettings();
final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager,
- mock(SecureSettings.class));
+ secureSettings);
final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext,
windowManager);
- mStubMenuView = new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance);
+ mStubMenuView = new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance,
+ secureSettings);
mStubMenuView.setTranslationX(0);
mStubMenuView.setTranslationY(0);
mMenuAnimationController = spy(new MenuAnimationController(
mStubMenuView, stubMenuViewAppearance));
+ mInteractView = spy(new DragToInteractView(mContext));
mDismissView = spy(new DismissView(mContext));
- DismissViewUtils.setup(mDismissView);
- mDragToInteractAnimationController =
- spy(new DragToInteractAnimationController(mDismissView, mStubMenuView));
+
+ if (Flags.floatingMenuDragToEdit()) {
+ mDragToInteractAnimationController = spy(new DragToInteractAnimationController(
+ mInteractView, mStubMenuView));
+ } else {
+ mDragToInteractAnimationController = spy(new DragToInteractAnimationController(
+ mDismissView, mStubMenuView));
+ }
+
mTouchHandler = new MenuListViewTouchHandler(mMenuAnimationController,
mDragToInteractAnimationController);
final AccessibilityTargetAdapter stubAdapter = new AccessibilityTargetAdapter(mStubTargets);
@@ -115,7 +129,7 @@ public class MenuListViewTouchHandlerTest extends SysuiTestCase {
@Test
public void onActionMoveEvent_notConsumedEvent_shouldMoveToPosition() {
- doReturn(false).when(mDragToInteractAnimationController).maybeConsumeMoveMotionEvent(
+ doReturn(empty).when(mDragToInteractAnimationController).maybeConsumeMoveMotionEvent(
any(MotionEvent.class));
final int offset = 100;
final MotionEvent stubDownEvent =
@@ -136,6 +150,7 @@ public class MenuListViewTouchHandlerTest extends SysuiTestCase {
}
@Test
+ @DisableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
public void onActionMoveEvent_shouldShowDismissView() {
final int offset = 100;
final MotionEvent stubDownEvent =
@@ -154,6 +169,25 @@ public class MenuListViewTouchHandlerTest extends SysuiTestCase {
}
@Test
+ @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
+ public void onActionMoveEvent_shouldShowInteractView() {
+ final int offset = 100;
+ final MotionEvent stubDownEvent =
+ mMotionEventHelper.obtainMotionEvent(/* downTime= */ 0, /* eventTime= */ 1,
+ MotionEvent.ACTION_DOWN, mStubMenuView.getTranslationX(),
+ mStubMenuView.getTranslationY());
+ final MotionEvent stubMoveEvent =
+ mMotionEventHelper.obtainMotionEvent(/* downTime= */ 0, /* eventTime= */ 3,
+ MotionEvent.ACTION_MOVE, mStubMenuView.getTranslationX() + offset,
+ mStubMenuView.getTranslationY() + offset);
+
+ mTouchHandler.onInterceptTouchEvent(mStubListView, stubDownEvent);
+ mTouchHandler.onInterceptTouchEvent(mStubListView, stubMoveEvent);
+
+ verify(mInteractView).show();
+ }
+
+ @Test
public void dragAndDrop_shouldFlingMenuThenSpringToEdge() {
final int offset = 100;
final MotionEvent stubDownEvent =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
index bc9a0a5484ac..ca3eb3e077d4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
@@ -30,6 +30,7 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
@@ -68,10 +69,13 @@ import androidx.dynamicanimation.animation.DynamicAnimation;
import androidx.dynamicanimation.animation.SpringAnimation;
import androidx.test.filters.SmallTest;
+import com.android.internal.accessibility.common.ShortcutConstants;
import com.android.internal.messages.nano.SystemMessageProto;
import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.SysuiTestableContext;
+import com.android.systemui.accessibility.utils.TestUtils;
+import com.android.systemui.res.R;
import com.android.systemui.util.settings.SecureSettings;
import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
@@ -81,6 +85,7 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.junit.MockitoJUnit;
@@ -122,18 +127,17 @@ public class MenuViewLayerTest extends SysuiTestCase {
private SysuiTestableContext mSpyContext = getContext();
@Mock
private IAccessibilityFloatingMenu mFloatingMenu;
-
- @Mock
- private SecureSettings mSecureSettings;
-
@Mock
private WindowManager mStubWindowManager;
-
@Mock
private AccessibilityManager mStubAccessibilityManager;
+ private final SecureSettings mSecureSettings = TestUtils.mockSecureSettings();
private final NotificationManager mMockNotificationManager = mock(NotificationManager.class);
+ private final ArgumentMatcher<IntentFilter> mNotificationMatcher =
+ (arg) -> arg.hasAction(ACTION_UNDO) && arg.hasAction(ACTION_DELETE);
+
@Before
public void setUp() throws Exception {
mSpyContext.addMockSystemService(Context.NOTIFICATION_SERVICE, mMockNotificationManager);
@@ -145,8 +149,16 @@ public class MenuViewLayerTest extends SysuiTestCase {
new WindowMetrics(mDisplayBounds, fakeDisplayInsets(), /* density = */ 0.0f));
doReturn(mWindowMetrics).when(mStubWindowManager).getCurrentWindowMetrics();
- mMenuViewLayer = new MenuViewLayer(mSpyContext, mStubWindowManager,
- mStubAccessibilityManager, mFloatingMenu, mSecureSettings);
+ MenuViewModel menuViewModel = new MenuViewModel(
+ mSpyContext, mStubAccessibilityManager, mSecureSettings);
+ MenuViewAppearance menuViewAppearance = new MenuViewAppearance(
+ mSpyContext, mStubWindowManager);
+ mMenuView = spy(
+ new MenuView(mSpyContext, menuViewModel, menuViewAppearance, mSecureSettings));
+
+ mMenuViewLayer = spy(new MenuViewLayer(mSpyContext, mStubWindowManager,
+ mStubAccessibilityManager, menuViewModel, menuViewAppearance, mMenuView,
+ mFloatingMenu, mSecureSettings));
mMenuView = (MenuView) mMenuViewLayer.getChildAt(LayerIndex.MENU_VIEW);
mMenuAnimationController = mMenuView.getMenuAnimationController();
@@ -226,7 +238,7 @@ public class MenuViewLayerTest extends SysuiTestCase {
final List<String> stubShortcutTargets = new ArrayList<>();
stubShortcutTargets.add(TEST_SELECT_TO_SPEAK_COMPONENT_NAME.flattenToString());
when(mStubAccessibilityManager.getAccessibilityShortcutTargets(
- AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY)).thenReturn(stubShortcutTargets);
+ ShortcutConstants.UserShortcutType.HARDWARE)).thenReturn(stubShortcutTargets);
mMenuViewLayer.mDismissMenuAction.run();
final String value = Settings.Secure.getString(mSpyContext.getContentResolver(),
@@ -236,6 +248,27 @@ public class MenuViewLayerTest extends SysuiTestCase {
}
@Test
+ @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
+ public void onEditAction_gotoEditScreen_isCalled() {
+ mMenuViewLayer.dispatchAccessibilityAction(R.id.action_edit);
+ verify(mMenuView).gotoEditScreen();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_HIDE)
+ public void onDismissAction_hideMenuAndShowNotification() {
+ mMenuViewLayer.dispatchAccessibilityAction(R.id.action_remove_menu);
+ verify(mMenuViewLayer).hideMenuAndShowNotification();
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_HIDE)
+ public void onDismissAction_hideMenuAndShowMessage() {
+ mMenuViewLayer.dispatchAccessibilityAction(R.id.action_remove_menu);
+ verify(mMenuViewLayer).hideMenuAndShowMessage();
+ }
+
+ @Test
public void showingImeInsetsChange_notOverlapOnIme_menuKeepOriginalPosition() {
final float menuTop = STATUS_BAR_HEIGHT + 100;
mMenuAnimationController.moveAndPersistPosition(new PointF(0, menuTop));
@@ -307,19 +340,13 @@ public class MenuViewLayerTest extends SysuiTestCase {
@Test
@EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_HIDE)
public void onReleasedInTarget_hideMenuAndShowNotificationWithExpectedActions() {
- dragMenuThenReleasedInTarget();
+ dragMenuThenReleasedInTarget(R.id.action_remove_menu);
verify(mMockNotificationManager).notify(
eq(SystemMessageProto.SystemMessage.NOTE_A11Y_FLOATING_MENU_HIDDEN),
any(Notification.class));
- ArgumentCaptor<IntentFilter> intentFilterCaptor = ArgumentCaptor.forClass(
- IntentFilter.class);
verify(mSpyContext).registerReceiver(
- any(BroadcastReceiver.class),
- intentFilterCaptor.capture(),
- anyInt());
- assertThat(intentFilterCaptor.getValue().matchAction(ACTION_UNDO)).isTrue();
- assertThat(intentFilterCaptor.getValue().matchAction(ACTION_DELETE)).isTrue();
+ any(BroadcastReceiver.class), argThat(mNotificationMatcher), anyInt());
}
@Test
@@ -327,10 +354,10 @@ public class MenuViewLayerTest extends SysuiTestCase {
public void receiveActionUndo_dismissNotificationAndMenuVisible() {
ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor = ArgumentCaptor.forClass(
BroadcastReceiver.class);
- dragMenuThenReleasedInTarget();
+ dragMenuThenReleasedInTarget(R.id.action_remove_menu);
verify(mSpyContext).registerReceiver(broadcastReceiverCaptor.capture(),
- any(IntentFilter.class), anyInt());
+ argThat(mNotificationMatcher), anyInt());
broadcastReceiverCaptor.getValue().onReceive(mSpyContext, new Intent(ACTION_UNDO));
verify(mSpyContext).unregisterReceiver(broadcastReceiverCaptor.getValue());
@@ -344,10 +371,10 @@ public class MenuViewLayerTest extends SysuiTestCase {
public void receiveActionDelete_dismissNotificationAndHideMenu() {
ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor = ArgumentCaptor.forClass(
BroadcastReceiver.class);
- dragMenuThenReleasedInTarget();
+ dragMenuThenReleasedInTarget(R.id.action_remove_menu);
verify(mSpyContext).registerReceiver(broadcastReceiverCaptor.capture(),
- any(IntentFilter.class), anyInt());
+ argThat(mNotificationMatcher), anyInt());
broadcastReceiverCaptor.getValue().onReceive(mSpyContext, new Intent(ACTION_DELETE));
verify(mSpyContext).unregisterReceiver(broadcastReceiverCaptor.getValue());
@@ -423,10 +450,12 @@ public class MenuViewLayerTest extends SysuiTestCase {
});
}
- private void dragMenuThenReleasedInTarget() {
+ private void dragMenuThenReleasedInTarget(int id) {
MagnetizedObject.MagnetListener magnetListener =
- mMenuViewLayer.getDragToInteractAnimationController().getMagnetListener();
+ mMenuViewLayer.getDragToInteractAnimationController().getMagnetListener(id);
+ View view = mock(View.class);
+ when(view.getId()).thenReturn(id);
magnetListener.onReleasedInTarget(
- new MagnetizedObject.MagneticTarget(mock(View.class), 200));
+ new MagnetizedObject.MagneticTarget(view, 200));
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
index 8da6cf98d76f..7c97f53d539d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
@@ -17,15 +17,19 @@
package com.android.systemui.accessibility.floatingmenu;
import static android.app.UiModeManager.MODE_NIGHT_YES;
+
import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.Mockito.mock;
+
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import android.app.UiModeManager;
+import android.content.Intent;
import android.graphics.Rect;
import android.graphics.drawable.GradientDrawable;
import android.platform.test.annotations.EnableFlags;
+import android.provider.Settings;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.WindowManager;
@@ -36,6 +40,8 @@ import androidx.test.filters.SmallTest;
import com.android.systemui.Flags;
import com.android.systemui.Prefs;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.SysuiTestableContext;
+import com.android.systemui.accessibility.utils.TestUtils;
import com.android.systemui.util.settings.SecureSettings;
import org.junit.After;
@@ -65,17 +71,23 @@ public class MenuViewTest extends SysuiTestCase {
@Mock
private AccessibilityManager mAccessibilityManager;
+ private SysuiTestableContext mSpyContext;
+
@Before
public void setUp() throws Exception {
mUiModeManager = mContext.getSystemService(UiModeManager.class);
mNightMode = mUiModeManager.getNightMode();
mUiModeManager.setNightMode(MODE_NIGHT_YES);
+
+ mSpyContext = spy(mContext);
+ final SecureSettings secureSettings = TestUtils.mockSecureSettings();
final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager,
- mock(SecureSettings.class));
+ secureSettings);
final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
- mStubMenuViewAppearance = new MenuViewAppearance(mContext, stubWindowManager);
- mMenuView = spy(new MenuView(mContext, stubMenuViewModel, mStubMenuViewAppearance));
- mLastPosition = Prefs.getString(mContext,
+ mStubMenuViewAppearance = new MenuViewAppearance(mSpyContext, stubWindowManager);
+ mMenuView = spy(new MenuView(mSpyContext, stubMenuViewModel, mStubMenuViewAppearance,
+ secureSettings));
+ mLastPosition = Prefs.getString(mSpyContext,
Prefs.Key.ACCESSIBILITY_FLOATING_MENU_POSITION, /* defaultValue= */ null);
}
@@ -154,6 +166,25 @@ public class MenuViewTest extends SysuiTestCase {
assertThat(radiiAnimator.isStarted()).isTrue();
}
+ @Test
+ public void getIntentForEditScreen_validate() {
+ Intent intent = mMenuView.getIntentForEditScreen();
+ String[] targets = intent.getBundleExtra(
+ ":settings:show_fragment_args").getStringArray("targets");
+
+ assertThat(intent.getAction()).isEqualTo(Settings.ACTION_ACCESSIBILITY_SHORTCUT_SETTINGS);
+ assertThat(targets).asList().containsExactlyElementsIn(TestUtils.TEST_BUTTON_TARGETS);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
+ public void gotoEditScreen_sendsIntent() {
+ // Notably, this shouldn't crash the settings app,
+ // because the button target args are configured.
+ mMenuView.gotoEditScreen();
+ verify(mSpyContext).startActivity(any());
+ }
+
private InstantInsetLayerDrawable getMenuViewInsetLayer() {
return (InstantInsetLayerDrawable) mMenuView.getBackground();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/utils/TestUtils.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/utils/TestUtils.java
index 10c8caa4fd27..8399fa85bfb1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/utils/TestUtils.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/utils/TestUtils.java
@@ -16,11 +16,27 @@
package com.android.systemui.accessibility.utils;
+import static com.android.internal.accessibility.common.ShortcutConstants.SERVICES_SEPARATOR;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
import android.os.SystemClock;
+import android.os.UserHandle;
+import android.provider.Settings;
+
+import com.android.systemui.util.settings.SecureSettings;
+import java.util.Set;
+import java.util.StringJoiner;
import java.util.function.BooleanSupplier;
public class TestUtils {
+ private static final ComponentName TEST_COMPONENT_A = new ComponentName("pkg", "A");
+ private static final ComponentName TEST_COMPONENT_B = new ComponentName("pkg", "B");
+ public static final String[] TEST_BUTTON_TARGETS = {
+ TEST_COMPONENT_A.flattenToString(), TEST_COMPONENT_B.flattenToString()};
public static long DEFAULT_CONDITION_DURATION = 5_000;
/**
@@ -55,4 +71,28 @@ public class TestUtils {
SystemClock.sleep(sleepMs);
}
}
+
+ /**
+ * Returns a mock secure settings configured to return information needed for tests.
+ * Currently, this only includes button targets.
+ */
+ public static SecureSettings mockSecureSettings() {
+ SecureSettings secureSettings = mock(SecureSettings.class);
+
+ final String targets = getShortcutTargets(
+ Set.of(TEST_COMPONENT_A, TEST_COMPONENT_B));
+ when(secureSettings.getStringForUser(
+ Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
+ UserHandle.USER_CURRENT)).thenReturn(targets);
+
+ return secureSettings;
+ }
+
+ private static String getShortcutTargets(Set<ComponentName> components) {
+ final StringJoiner stringJoiner = new StringJoiner(String.valueOf(SERVICES_SEPARATOR));
+ for (ComponentName target : components) {
+ stringJoiner.add(target.flattenToString());
+ }
+ return stringJoiner.toString();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
index a47e28801709..7c03d7899398 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
@@ -27,12 +27,13 @@ import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.keyguard.logging.KeyguardLogger
+import com.android.systemui.Flags
import com.android.systemui.Flags.FLAG_LIGHT_REVEAL_MIGRATION
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository
-import com.android.systemui.log.logcatLogBuffer
-import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.deviceentry.domain.interactor.AuthRippleInteractor
import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.LightRevealScrim
import com.android.systemui.statusbar.NotificationShadeWindowController
@@ -42,7 +43,7 @@ import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.leak.RotationUtils
import com.android.systemui.util.mockito.any
-import javax.inject.Provider
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import org.junit.After
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
@@ -61,8 +62,10 @@ import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
import org.mockito.MockitoSession
import org.mockito.quality.Strictness
+import javax.inject.Provider
+@ExperimentalCoroutinesApi
@SmallTest
@RunWith(AndroidTestingRunner::class)
class AuthRippleControllerTest : SysuiTestCase() {
@@ -74,6 +77,7 @@ class AuthRippleControllerTest : SysuiTestCase() {
@Mock private lateinit var configurationController: ConfigurationController
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Mock private lateinit var authController: AuthController
+ @Mock private lateinit var authRippleInteractor: AuthRippleInteractor
@Mock private lateinit var keyguardStateController: KeyguardStateController
@Mock
private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
@@ -88,8 +92,6 @@ class AuthRippleControllerTest : SysuiTestCase() {
@Mock
private lateinit var statusBarStateController: StatusBarStateController
@Mock
- private lateinit var featureFlags: FeatureFlags
- @Mock
private lateinit var lightRevealScrim: LightRevealScrim
@Mock
private lateinit var fpSensorProp: FingerprintSensorPropertiesInternal
@@ -103,6 +105,7 @@ class AuthRippleControllerTest : SysuiTestCase() {
@Before
fun setUp() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
MockitoAnnotations.initMocks(this)
staticMockSession = mockitoSession()
.mockStatic(RotationUtils::class.java)
@@ -128,6 +131,7 @@ class AuthRippleControllerTest : SysuiTestCase() {
KeyguardLogger(logcatLogBuffer(AuthRippleController.TAG)),
biometricUnlockController,
lightRevealScrim,
+ authRippleInteractor,
facePropertyRepository,
rippleView,
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index 8127bb1f0465..6a9c88151dd0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -1226,6 +1226,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
@Test
fun descriptionOverriddenByContentView() =
runGenericTest(contentView = promptContentView, description = "test description") {
+ mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
val contentView by collectLastValue(viewModel.contentView)
val description by collectLastValue(viewModel.description)
@@ -1236,6 +1237,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
@Test
fun descriptionWithoutContentView() =
runGenericTest(description = "test description") {
+ mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
val contentView by collectLastValue(viewModel.contentView)
val description by collectLastValue(viewModel.description)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt
index 0dfdeca60fcd..bdf0e06ce410 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.deviceentry.data.repository
+package com.android.systemui.deviceentry.domain.interactor
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -23,12 +23,13 @@ import com.android.systemui.biometrics.data.repository.fingerprintPropertyReposi
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.android.systemui.biometrics.shared.model.SensorStrength
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.deviceentry.domain.interactor.deviceEntryHapticsInteractor
import com.android.systemui.deviceentry.shared.model.FailedFaceAuthenticationStatus
import com.android.systemui.keyevent.data.repository.fakeKeyEventRepository
import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
@@ -158,9 +159,10 @@ class DeviceEntryHapticsInteractorTest : SysuiTestCase() {
}
private suspend fun enterDeviceFromBiometricUnlock() {
- kosmos.fakeDeviceEntryRepository.enteringDeviceFromBiometricUnlock(
+ kosmos.fakeKeyguardRepository.setBiometricUnlockSource(
BiometricUnlockSource.FINGERPRINT_SENSOR
)
+ kosmos.fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK)
}
private fun fingerprintFailure() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepositoryImplTest.kt
new file mode 100644
index 000000000000..ed80a869dda8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepositoryImplTest.kt
@@ -0,0 +1,98 @@
+package com.android.systemui.keyboard.stickykeys.data.repository
+
+import android.content.pm.UserInfo
+import android.hardware.input.InputManager
+import android.provider.Settings.Secure.ACCESSIBILITY_STICKY_KEYS
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.keyboard.stickykeys.StickyKeysLogger
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class StickyKeysRepositoryImplTest : SysuiTestCase() {
+
+ private val dispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(dispatcher)
+ private val secureSettings = FakeSettings()
+ private val userRepository = Kosmos().fakeUserRepository
+ private lateinit var stickyKeysRepository: StickyKeysRepositoryImpl
+
+ @Before
+ fun setup() {
+ stickyKeysRepository = StickyKeysRepositoryImpl(
+ mock<InputManager>(),
+ dispatcher,
+ secureSettings,
+ userRepository,
+ mock<StickyKeysLogger>()
+ )
+ userRepository.setUserInfos(USER_INFOS)
+ setStickyKeySettingForUser(enabled = true, userInfo = SETTING_ENABLED_USER)
+ setStickyKeySettingForUser(enabled = false, userInfo = SETTING_DISABLED_USER)
+ }
+
+ @Test
+ fun settingEnabledEmitsValueForCurrentUser() {
+ testScope.runTest {
+ userRepository.setSelectedUserInfo(SETTING_ENABLED_USER)
+
+ val enabled by collectLastValue(stickyKeysRepository.settingEnabled)
+
+ assertThat(enabled).isTrue()
+ }
+ }
+
+ @Test
+ fun settingEnabledEmitsNewValueWhenSettingChanges() {
+ testScope.runTest {
+ userRepository.setSelectedUserInfo(SETTING_ENABLED_USER)
+ val enabled by collectValues(stickyKeysRepository.settingEnabled)
+ runCurrent()
+
+ setStickyKeySettingForUser(enabled = false, userInfo = SETTING_ENABLED_USER)
+
+ assertThat(enabled).containsExactly(true, false).inOrder()
+ }
+ }
+
+ @Test
+ fun settingEnabledEmitsValueForNewUserWhenUserChanges() {
+ testScope.runTest {
+ userRepository.setSelectedUserInfo(SETTING_ENABLED_USER)
+ val enabled by collectLastValue(stickyKeysRepository.settingEnabled)
+ runCurrent()
+
+ userRepository.setSelectedUserInfo(SETTING_DISABLED_USER)
+
+ assertThat(enabled).isFalse()
+ }
+ }
+
+ private fun setStickyKeySettingForUser(enabled: Boolean, userInfo: UserInfo) {
+ val newValue = if (enabled) "1" else "0"
+ secureSettings.putStringForUser(ACCESSIBILITY_STICKY_KEYS, newValue, userInfo.id)
+ }
+
+ private companion object {
+ val SETTING_ENABLED_USER = UserInfo(/* id= */ 0, "user1", /* flags= */ 0)
+ val SETTING_DISABLED_USER = UserInfo(/* id= */ 1, "user2", /* flags= */ 0)
+ val USER_INFOS = listOf(SETTING_ENABLED_USER, SETTING_DISABLED_USER)
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt
index 8a713688acf9..6eebb6d19e5e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt
@@ -18,6 +18,7 @@ package com.android.systemui.keyboard.stickykeys.ui.viewmodel
import android.hardware.input.InputManager
import android.hardware.input.StickyModifierState
+import android.provider.Settings.Secure.ACCESSIBILITY_STICKY_KEYS
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
@@ -31,8 +32,11 @@ import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.ALT_GR
import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.CTRL
import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.META
import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.SHIFT
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.user.data.repository.fakeUserRepository
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
@@ -57,6 +61,8 @@ class StickyKeysIndicatorViewModelTest : SysuiTestCase() {
private lateinit var viewModel: StickyKeysIndicatorViewModel
private val inputManager = mock<InputManager>()
private val keyboardRepository = FakeKeyboardRepository()
+ private val secureSettings = FakeSettings()
+ private val userRepository = Kosmos().fakeUserRepository
private val captor =
ArgumentCaptor.forClass(InputManager.StickyModifierStateListener::class.java)
@@ -65,8 +71,11 @@ class StickyKeysIndicatorViewModelTest : SysuiTestCase() {
val stickyKeysRepository = StickyKeysRepositoryImpl(
inputManager,
dispatcher,
+ secureSettings,
+ userRepository,
mock<StickyKeysLogger>()
)
+ setStickyKeySetting(enabled = false)
viewModel =
StickyKeysIndicatorViewModel(
stickyKeysRepository = stickyKeysRepository,
@@ -76,13 +85,24 @@ class StickyKeysIndicatorViewModelTest : SysuiTestCase() {
}
@Test
- fun startsListeningToStickyKeysOnlyWhenKeyboardIsConnected() {
+ fun doesntListenToStickyKeysOnlyWhenKeyboardIsConnected() {
testScope.runTest {
collectLastValue(viewModel.indicatorContent)
+
+ keyboardRepository.setIsAnyKeyboardConnected(true)
runCurrent()
+
verifyZeroInteractions(inputManager)
+ }
+ }
+ @Test
+ fun startsListeningToStickyKeysOnlyWhenKeyboardIsConnectedAndSettingIsOn() {
+ testScope.runTest {
+ collectLastValue(viewModel.indicatorContent)
keyboardRepository.setIsAnyKeyboardConnected(true)
+
+ setStickyKeySetting(enabled = true)
runCurrent()
verify(inputManager)
@@ -93,11 +113,31 @@ class StickyKeysIndicatorViewModelTest : SysuiTestCase() {
}
}
+ private fun setStickyKeySetting(enabled: Boolean) {
+ val newValue = if (enabled) "1" else "0"
+ val defaultUser = userRepository.getSelectedUserInfo().id
+ secureSettings.putStringForUser(ACCESSIBILITY_STICKY_KEYS, newValue, defaultUser)
+ }
+
+ @Test
+ fun stopsListeningToStickyKeysWhenStickyKeySettingsIsTurnedOff() {
+ testScope.runTest {
+ collectLastValue(viewModel.indicatorContent)
+ setStickyKeysActive()
+ runCurrent()
+
+ setStickyKeySetting(enabled = false)
+ runCurrent()
+
+ verify(inputManager).unregisterStickyModifierStateListener(any())
+ }
+ }
+
@Test
fun stopsListeningToStickyKeysWhenKeyboardDisconnects() {
testScope.runTest {
collectLastValue(viewModel.indicatorContent)
- keyboardRepository.setIsAnyKeyboardConnected(true)
+ setStickyKeysActive()
runCurrent()
keyboardRepository.setIsAnyKeyboardConnected(false)
@@ -111,7 +151,7 @@ class StickyKeysIndicatorViewModelTest : SysuiTestCase() {
fun emitsStickyKeysListWhenStickyKeyIsPressed() {
testScope.runTest {
val stickyKeys by collectLastValue(viewModel.indicatorContent)
- keyboardRepository.setIsAnyKeyboardConnected(true)
+ setStickyKeysActive()
setStickyKeys(mapOf(ALT to false))
@@ -123,7 +163,7 @@ class StickyKeysIndicatorViewModelTest : SysuiTestCase() {
fun emitsEmptyListWhenNoStickyKeysAreActive() {
testScope.runTest {
val stickyKeys by collectLastValue(viewModel.indicatorContent)
- keyboardRepository.setIsAnyKeyboardConnected(true)
+ setStickyKeysActive()
setStickyKeys(emptyMap())
@@ -135,7 +175,7 @@ class StickyKeysIndicatorViewModelTest : SysuiTestCase() {
fun passesAllStickyKeysToDialog() {
testScope.runTest {
val stickyKeys by collectLastValue(viewModel.indicatorContent)
- keyboardRepository.setIsAnyKeyboardConnected(true)
+ setStickyKeysActive()
setStickyKeys(mapOf(
ALT to false,
@@ -154,7 +194,7 @@ class StickyKeysIndicatorViewModelTest : SysuiTestCase() {
fun showsOnlyLockedStateIfKeyIsStickyAndLocked() {
testScope.runTest {
val stickyKeys by collectLastValue(viewModel.indicatorContent)
- keyboardRepository.setIsAnyKeyboardConnected(true)
+ setStickyKeysActive()
setStickyKeys(mapOf(
ALT to false,
@@ -168,7 +208,7 @@ class StickyKeysIndicatorViewModelTest : SysuiTestCase() {
fun doesNotChangeOrderOfKeysIfTheyBecomeLocked() {
testScope.runTest {
val stickyKeys by collectLastValue(viewModel.indicatorContent)
- keyboardRepository.setIsAnyKeyboardConnected(true)
+ setStickyKeysActive()
setStickyKeys(mapOf(
META to false,
@@ -186,6 +226,11 @@ class StickyKeysIndicatorViewModelTest : SysuiTestCase() {
}
}
+ private fun setStickyKeysActive() {
+ keyboardRepository.setIsAnyKeyboardConnected(true)
+ setStickyKeySetting(enabled = true)
+ }
+
private fun TestScope.setStickyKeys(keys: Map<ModifierKey, Boolean>) {
runCurrent()
verify(inputManager).registerStickyModifierStateListener(any(), captor.capture())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 8a3a4342915b..14cae0b89a49 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -25,6 +25,7 @@ import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STR
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
import static com.android.systemui.Flags.FLAG_REFACTOR_GET_CURRENT_USER;
+import static com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR;
import static com.android.systemui.keyguard.KeyguardViewMediator.DELAYED_KEYGUARD_ACTION;
import static com.android.systemui.keyguard.KeyguardViewMediator.KEYGUARD_LOCK_AFTER_DELAY_DEFAULT;
import static com.android.systemui.keyguard.KeyguardViewMediator.REBOOT_MAINLINE_UPDATE;
@@ -270,8 +271,8 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
mSceneContainerFlags,
mKosmos::getCommunalInteractor);
mFeatureFlags = new FakeFeatureFlags();
- mFeatureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false);
mSetFlagsRule.enableFlags(FLAG_REFACTOR_GET_CURRENT_USER);
+ mSetFlagsRule.disableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR);
DejankUtils.setImmediate(true);
@@ -356,6 +357,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
mock(FoldGracePeriodProvider.class);
mViewMediator.mFoldGracePeriodProvider = mockedFoldGracePeriodProvider;
when(mockedFoldGracePeriodProvider.isEnabled()).thenReturn(true);
+ when(mUpdateMonitor.isDeviceProvisioned()).thenReturn(true);
// GIVEN keyguard is not enabled and isn't showing
mViewMediator.onSystemReady();
@@ -374,12 +376,40 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
@Test
@TestableLooper.RunWithLooper(setAsMainLooper = true)
+ public void doNotShowKeyguard_deviceNotProvisioned() {
+ // GIVEN feature is enabled
+ final FoldGracePeriodProvider mockedFoldGracePeriodProvider =
+ mock(FoldGracePeriodProvider.class);
+ mViewMediator.mFoldGracePeriodProvider = mockedFoldGracePeriodProvider;
+ when(mockedFoldGracePeriodProvider.isEnabled()).thenReturn(true);
+
+ // GIVEN keyguard is not enabled and isn't showing
+ mViewMediator.onSystemReady();
+ mViewMediator.setKeyguardEnabled(false);
+ TestableLooper.get(this).processAllMessages();
+ captureKeyguardUpdateMonitorCallback();
+ assertFalse(mViewMediator.isShowingAndNotOccluded());
+
+ // WHEN device is NOT provisioned
+ when(mUpdateMonitor.isDeviceProvisioned()).thenReturn(false);
+
+ // WHEN showKeyguard is requested
+ mViewMediator.showDismissibleKeyguard();
+
+ // THEN keyguard is NOT shown
+ TestableLooper.get(this).processAllMessages();
+ assertFalse(mViewMediator.isShowingAndNotOccluded());
+ }
+
+ @Test
+ @TestableLooper.RunWithLooper(setAsMainLooper = true)
public void showKeyguardAfterKeyguardNotEnabled_featureNotEnabled() {
// GIVEN feature is NOT enabled
final FoldGracePeriodProvider mockedFoldGracePeriodProvider =
mock(FoldGracePeriodProvider.class);
mViewMediator.mFoldGracePeriodProvider = mockedFoldGracePeriodProvider;
when(mockedFoldGracePeriodProvider.isEnabled()).thenReturn(false);
+ when(mUpdateMonitor.isDeviceProvisioned()).thenReturn(true);
// GIVEN keyguard is not enabled and isn't showing
mViewMediator.onSystemReady();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index 4f3a63dd2829..e93ad0be3e85 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -21,6 +21,7 @@ import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardSecurityModel
import com.android.keyguard.KeyguardSecurityModel.SecurityMode.PIN
import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
+import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR
import com.android.systemui.SysuiTestCase
import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
@@ -29,7 +30,6 @@ import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.FakeCommandQueue
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardSurfaceBehindRepository
@@ -137,8 +137,8 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
whenever(keyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(PIN)
- featureFlags = FakeFeatureFlags().apply { set(Flags.KEYGUARD_WM_STATE_REFACTOR, false) }
mSetFlagsRule.enableFlags(FLAG_COMMUNAL_HUB)
+ featureFlags = FakeFeatureFlags()
keyguardInteractor = createKeyguardInteractor()
@@ -299,6 +299,10 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
powerInteractor = powerInteractor,
)
.apply { start() }
+
+ mSetFlagsRule.disableFlags(
+ FLAG_KEYGUARD_WM_STATE_REFACTOR,
+ )
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
index c864704f6997..699284e29ce3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
@@ -39,6 +39,7 @@ import com.android.systemui.shade.NotificationPanelView
import com.android.systemui.statusbar.VibratorHelper
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -71,6 +72,7 @@ class DefaultDeviceEntrySectionTest : SysuiTestCase() {
FakeFeatureFlagsClassic().apply { set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false) }
underTest =
DefaultDeviceEntrySection(
+ TestScope().backgroundScope,
keyguardUpdateMonitor,
authController,
windowManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
index 8d306cceeaa7..28d35ce24f4b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java
@@ -41,6 +41,7 @@ import android.view.accessibility.AccessibilityManager;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.accessibility.common.ShortcutConstants;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver;
@@ -56,6 +57,8 @@ import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import dagger.Lazy;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -65,8 +68,7 @@ import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
-
-import dagger.Lazy;
+import java.util.concurrent.Executor;
/**
* Tests for {@link NavBarHelper}.
@@ -123,6 +125,8 @@ public class NavBarHelperTest extends SysuiTestCase {
SYSUI_STATE_A11Y_BUTTON_CLICKABLE | SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
private NavBarHelper mNavBarHelper;
+ private final Executor mSynchronousExecutor = runnable -> runnable.run();
+
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
@@ -140,7 +144,8 @@ public class NavBarHelperTest extends SysuiTestCase {
mSystemActions, mOverviewProxyService, mAssistManagerLazy,
() -> Optional.of(mock(CentralSurfaces.class)), mock(KeyguardStateController.class),
mNavigationModeController, mEdgeBackGestureHandlerFactory, mWm, mUserTracker,
- mDisplayTracker, mNotificationShadeWindowController, mDumpManager, mCommandQueue);
+ mDisplayTracker, mNotificationShadeWindowController, mDumpManager, mCommandQueue,
+ mSynchronousExecutor);
}
@@ -266,7 +271,8 @@ public class NavBarHelperTest extends SysuiTestCase {
@Test
public void initNavBarHelper_buttonModeNavBar_a11yButtonClickableState() {
when(mAccessibilityManager.getAccessibilityShortcutTargets(
- AccessibilityManager.ACCESSIBILITY_BUTTON)).thenReturn(createFakeShortcutTargets());
+ ShortcutConstants.UserShortcutType.SOFTWARE)).thenReturn(
+ createFakeShortcutTargets());
mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
@@ -291,7 +297,8 @@ public class NavBarHelperTest extends SysuiTestCase {
ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR);
when(mAccessibilityManager.getAccessibilityShortcutTargets(
- AccessibilityManager.ACCESSIBILITY_BUTTON)).thenReturn(createFakeShortcutTargets());
+ ShortcutConstants.UserShortcutType.SOFTWARE)).thenReturn(
+ createFakeShortcutTargets());
mAccessibilityServicesStateChangeListener.onAccessibilityServicesStateChanged(
mAccessibilityManager);
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 ddceed62fdeb..db5bd9b13dd0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
@@ -228,6 +228,8 @@ public class NavigationBarTest extends SysuiTestCase {
@Rule
public final LeakCheckedTest.SysuiLeakCheck mLeakCheck = new LeakCheckedTest.SysuiLeakCheck();
+ private final Executor mSynchronousExecutor = runnable -> runnable.run();
+
@Before
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
@@ -269,7 +271,7 @@ public class NavigationBarTest extends SysuiTestCase {
mEdgeBackGestureHandlerFactory, mock(IWindowManager.class),
mock(UserTracker.class), mock(DisplayTracker.class),
mNotificationShadeWindowController, mock(DumpManager.class),
- mock(CommandQueue.class)));
+ mock(CommandQueue.class), mSynchronousExecutor));
mNavigationBar = createNavBar(mContext);
mExternalDisplayNavigationBar = createNavBar(mSysuiTestableContextExternal);
});
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt
index f93d52b2c35c..aa54565c2aa0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt
@@ -28,6 +28,7 @@ import android.view.MotionEvent.ACTION_UP
import android.view.ViewConfiguration
import android.view.WindowManager
import androidx.test.filters.SmallTest
+import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.util.LatencyTracker
import com.android.systemui.SysuiTestCase
import com.android.systemui.plugins.NavigationEdgeBackPlugin
@@ -40,8 +41,10 @@ import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
+import org.mockito.Mockito.anyInt
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@SmallTest
@@ -59,12 +62,16 @@ class BackPanelControllerTest : SysuiTestCase() {
@Mock private lateinit var windowManager: WindowManager
@Mock private lateinit var configurationController: ConfigurationController
@Mock private lateinit var latencyTracker: LatencyTracker
+ @Mock private lateinit var interactionJankMonitor: InteractionJankMonitor
@Mock private lateinit var layoutParams: WindowManager.LayoutParams
@Mock private lateinit var backCallback: NavigationEdgeBackPlugin.BackCallback
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
+ `when`(interactionJankMonitor.begin(any(), anyInt())).thenReturn(true)
+ `when`(interactionJankMonitor.end(anyInt())).thenReturn(true)
+ `when`(interactionJankMonitor.cancel(anyInt())).thenReturn(true)
mBackPanelController =
BackPanelController(
context,
@@ -74,6 +81,7 @@ class BackPanelControllerTest : SysuiTestCase() {
vibratorHelper,
configurationController,
latencyTracker,
+ interactionJankMonitor,
)
mBackPanelController.setLayoutParams(layoutParams)
mBackPanelController.setBackCallback(backCallback)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/LeftRightArrowPressedListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/LeftRightArrowPressedListenerTest.kt
index 60eb3aec190f..40eccad94340 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/LeftRightArrowPressedListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/LeftRightArrowPressedListenerTest.kt
@@ -37,7 +37,7 @@ class LeftRightArrowPressedListenerTest : SysuiTestCase() {
object : Consumer<Int> {
var lastValue: Int? = null
- override fun accept(keyCode: Int?) {
+ override fun accept(keyCode: Int) {
lastValue = keyCode
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt
new file mode 100644
index 000000000000..e8aa8f0bdc5d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tileimpl
+
+import android.animation.AnimatorTestRule
+import android.content.Context
+import android.service.quicksettings.Tile
+import android.testing.AndroidTestingRunner
+import android.testing.UiThreadTest
+import android.view.ContextThemeWrapper
+import android.view.View
+import android.widget.ImageView
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.connectivity.WifiIcons
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import org.junit.Rule
+import org.junit.runner.RunWith
+
+/** Test for regression b/311121830 */
+@RunWith(AndroidTestingRunner::class)
+@UiThreadTest
+@SmallTest
+class QSIconViewImplTest_311121830 : SysuiTestCase() {
+
+ @get:Rule val animatorRule = AnimatorTestRule()
+
+ @Test
+ fun alwaysLastIcon() {
+ // Need to inflate with the correct theme so the colors can be retrieved and the animations
+ // are run
+ val iconView =
+ AnimateQSIconViewImpl(
+ ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings)
+ )
+
+ val initialState =
+ QSTile.State().apply {
+ state = Tile.STATE_INACTIVE
+ icon = QSTileImpl.ResourceIcon.get(R.drawable.ic_qs_no_internet_available)
+ }
+ val firstState =
+ QSTile.State().apply {
+ state = Tile.STATE_ACTIVE
+ icon = QSTileImpl.ResourceIcon.get(WifiIcons.WIFI_NO_INTERNET_ICONS[4])
+ }
+ val secondState =
+ QSTile.State().apply {
+ state = Tile.STATE_ACTIVE
+ icon = QSTileImpl.ResourceIcon.get(WifiIcons.WIFI_FULL_ICONS[4])
+ }
+
+ // Start with the initial state
+ iconView.setIcon(initialState, /* allowAnimations= */ false)
+
+ // Set the first state to animate, and advance time to half the time of the animation
+ iconView.setIcon(firstState, /* allowAnimations= */ true)
+ animatorRule.advanceTimeBy(QSIconViewImpl.QS_ANIM_LENGTH / 2)
+
+ // Set the second state to animate (it shouldn't, because `State.state` is the same) and
+ // advance time to 2 animations length
+ iconView.setIcon(secondState, /* allowAnimations= */ true)
+ animatorRule.advanceTimeBy(QSIconViewImpl.QS_ANIM_LENGTH * 2)
+
+ assertThat(iconView.mLastIcon).isEqualTo(secondState.icon)
+ }
+
+ private class AnimateQSIconViewImpl(context: Context) : QSIconViewImpl(context) {
+ override fun createIcon(): View {
+ return object : ImageView(context) {
+ override fun isShown(): Boolean {
+ return true
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
index c7479fd50db1..1ed8c3cdf0ba 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
@@ -32,6 +32,7 @@ import com.android.systemui.qs.QsEventLogger
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.recordissue.RecordIssueDialogDelegate
import com.android.systemui.res.R
+import com.android.systemui.settings.UserContextProvider
import com.android.systemui.statusbar.phone.KeyguardDismissUtil
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -65,6 +66,7 @@ class RecordIssueTileTest : SysuiTestCase() {
@Mock private lateinit var keyguardDismissUtil: KeyguardDismissUtil
@Mock private lateinit var keyguardStateController: KeyguardStateController
@Mock private lateinit var dialogLauncherAnimator: DialogLaunchAnimator
+ @Mock private lateinit var userContextProvider: UserContextProvider
@Mock private lateinit var delegateFactory: RecordIssueDialogDelegate.Factory
@Mock private lateinit var dialogDelegate: RecordIssueDialogDelegate
@Mock private lateinit var dialog: SystemUIDialog
@@ -94,6 +96,7 @@ class RecordIssueTileTest : SysuiTestCase() {
keyguardDismissUtil,
keyguardStateController,
dialogLauncherAnimator,
+ userContextProvider,
delegateFactory,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
index 70a48f574949..bcb64aaca3b3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
@@ -17,16 +17,21 @@
package com.android.systemui.recents
import android.content.ComponentName
+import android.content.Context
import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
import android.os.PowerManager
+import android.os.Process;
+import android.os.UserHandle
import android.testing.AndroidTestingRunner
import android.testing.TestableContext
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito
import com.android.internal.app.AssistUtils
import com.android.internal.logging.UiEventLogger
import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FakeFeatureFlags
@@ -66,10 +71,14 @@ import org.mockito.ArgumentMatchers
import org.mockito.Mock
import org.mockito.Mockito.any
import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.atLeast
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.intThat
import org.mockito.Mockito.mock
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.times
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@SmallTest
@@ -80,7 +89,7 @@ class OverviewProxyServiceTest : SysuiTestCase() {
@Main private val executor: Executor = MoreExecutors.directExecutor()
private lateinit var subject: OverviewProxyService
- private val dumpManager = DumpManager()
+ @Mock private val dumpManager = DumpManager()
private val displayTracker = FakeDisplayTracker(mContext)
private val fakeSystemClock = FakeSystemClock()
private val sysUiState = SysUiState(displayTracker)
@@ -109,6 +118,8 @@ class OverviewProxyServiceTest : SysuiTestCase() {
@Mock
private lateinit var unfoldTransitionProgressForwarder:
Optional<UnfoldTransitionProgressForwarder>
+ @Mock
+ private lateinit var broadcastDispatcher: BroadcastDispatcher
@Before
fun setUp() {
@@ -131,32 +142,11 @@ class OverviewProxyServiceTest : SysuiTestCase() {
whenever(packageManager.resolveServiceAsUser(any(), anyInt(), anyInt()))
.thenReturn(mock(ResolveInfo::class.java))
- featureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false)
- subject =
- OverviewProxyService(
- context,
- executor,
- commandQueue,
- shellInterface,
- { navBarController },
- { shadeViewController },
- screenPinningRequest,
- navModeController,
- statusBarWinController,
- sysUiState,
- mock(),
- userTracker,
- wakefulnessLifecycle,
- uiEventLogger,
- displayTracker,
- sysuiUnlockAnimationController,
- inWindowLauncherUnlockAnimationManager,
- assistUtils,
- featureFlags,
- FakeSceneContainerFlags(),
- dumpManager,
- unfoldTransitionProgressForwarder
- )
+ mSetFlagsRule.disableFlags(
+ com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR,
+ )
+
+ subject = createOverviewProxyService(context)
}
@After
@@ -209,4 +199,66 @@ class OverviewProxyServiceTest : SysuiTestCase() {
intThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_GOING_TO_SLEEP }
)
}
+
+ @Test
+ fun connectToOverviewService_primaryUser_expectBindService() {
+ val mockitoSession = ExtendedMockito.mockitoSession()
+ .spyStatic(Process::class.java)
+ .startMocking()
+ try {
+ `when`(Process.myUserHandle()).thenReturn(UserHandle.SYSTEM)
+ val spyContext = spy(context)
+ val ops = createOverviewProxyService(spyContext)
+ ops.startConnectionToCurrentUser()
+ verify(spyContext, atLeast(1)).bindServiceAsUser(any(), any(),
+ anyInt(), any())
+ } finally {
+ mockitoSession.finishMocking()
+ }
+ }
+
+ @Test
+ fun connectToOverviewService_nonPrimaryUser_expectNoBindService() {
+ val mockitoSession = ExtendedMockito.mockitoSession()
+ .spyStatic(Process::class.java)
+ .startMocking()
+ try {
+ `when`(Process.myUserHandle()).thenReturn(UserHandle.of(12345))
+ val spyContext = spy(context)
+ val ops = createOverviewProxyService(spyContext)
+ ops.startConnectionToCurrentUser()
+ verify(spyContext, times(0)).bindServiceAsUser(any(), any(),
+ anyInt(), any())
+ } finally {
+ mockitoSession.finishMocking()
+ }
+ }
+
+ private fun createOverviewProxyService(ctx: Context) : OverviewProxyService {
+ return OverviewProxyService(
+ ctx,
+ executor,
+ commandQueue,
+ shellInterface,
+ { navBarController },
+ { shadeViewController },
+ screenPinningRequest,
+ navModeController,
+ statusBarWinController,
+ sysUiState,
+ mock(),
+ userTracker,
+ wakefulnessLifecycle,
+ uiEventLogger,
+ displayTracker,
+ sysuiUnlockAnimationController,
+ inWindowLauncherUnlockAnimationManager,
+ assistUtils,
+ featureFlags,
+ FakeSceneContainerFlags(),
+ dumpManager,
+ unfoldTransitionProgressForwarder,
+ broadcastDispatcher
+ )
+ }
}
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 6681ceee3d09..22b05be507c6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -514,7 +514,7 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
}
whenever(mGlanceableHubContainerController.isEnabled()).thenReturn(false)
- whenever(mGlanceableHubContainerController.enabledState())
+ whenever(mGlanceableHubContainerController.communalAvailable())
.thenReturn(MutableStateFlow(false))
val mockCommunalPlaceholder = mock(View::class.java)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
index 58eec2e71d32..4519ba6d3590 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
@@ -65,6 +65,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider;
+import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
import com.android.systemui.statusbar.notification.collection.render.NotifViewBarn;
import com.android.systemui.statusbar.notification.row.NotifInflationErrorManager;
import com.android.systemui.util.settings.SecureSettings;
@@ -111,6 +112,7 @@ public class PreparationCoordinatorTest extends SysuiTestCase {
@Spy private FakeNotifInflater mNotifInflater = new FakeNotifInflater();
private final SectionStyleProvider mSectionStyleProvider = new SectionStyleProvider();
@Mock private UserTracker mUserTracker;
+ @Mock private GroupMembershipManager mGroupMembershipManager;
private NotifUiAdjustmentProvider mAdjustmentProvider;
@@ -127,7 +129,9 @@ public class PreparationCoordinatorTest extends SysuiTestCase {
mSecureSettings,
mLockscreenUserManager,
mSectionStyleProvider,
- mUserTracker);
+ mUserTracker,
+ mGroupMembershipManager
+ );
mEntry = getNotificationEntryBuilder().setParent(ROOT_ENTRY).build();
mInflationError = new Exception(TEST_MESSAGE);
mErrorManager = new NotifInflationErrorManager();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt
index f9f8d8a2cfc6..73c49c023dd5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt
@@ -17,6 +17,8 @@ package com.android.systemui.statusbar.notification.collection.inflation
import android.database.ContentObserver
import android.os.Handler
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.provider.Settings.Secure.SHOW_NOTIFICATION_SNOOZE
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
@@ -28,6 +30,8 @@ import com.android.systemui.statusbar.notification.collection.GroupEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection
import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider
+import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager
+import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
@@ -35,6 +39,8 @@ import com.android.systemui.util.mockito.withArgCaptor
import com.android.systemui.util.settings.FakeSettings
import com.android.systemui.util.settings.SecureSettings
import com.google.common.truth.Truth.assertThat
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -55,6 +61,7 @@ class NotifUiAdjustmentProviderTest : SysuiTestCase() {
private val uri = FakeSettings().getUriFor(SHOW_NOTIFICATION_SNOOZE)
private val dirtyListener: Runnable = mock()
private val userTracker: UserTracker = mock()
+ private val groupMembershipManager: GroupMembershipManager = mock()
private val section = NotifSection(mock(), 0)
private val entry = NotificationEntryBuilder()
@@ -69,7 +76,8 @@ class NotifUiAdjustmentProviderTest : SysuiTestCase() {
secureSettings,
lockscreenUserManager,
sectionStyleProvider,
- userTracker
+ userTracker,
+ groupMembershipManager,
)
@Before
@@ -127,4 +135,42 @@ class NotifUiAdjustmentProviderTest : SysuiTestCase() {
assertThat(withSnoozing.isSnoozeEnabled).isTrue()
assertThat(withSnoozing).isNotEqualTo(original)
}
+
+ @Test
+ @EnableFlags(AsyncHybridViewInflation.FLAG_NAME)
+ fun changeIsChildInGroup_asyncHybirdFlagEnabled_needReInflation() {
+ // Given: an Entry that is not child in group
+ // AsyncHybridViewInflation flag is enabled
+ whenever(groupMembershipManager.isChildInGroup(entry)).thenReturn(false)
+ val oldAdjustment = adjustmentProvider.calculateAdjustment(entry)
+ assertThat(oldAdjustment.isChildInGroup).isFalse()
+
+ // When: the Entry becomes a group child
+ whenever(groupMembershipManager.isChildInGroup(entry)).thenReturn(true)
+ val newAdjustment = adjustmentProvider.calculateAdjustment(entry)
+ assertThat(newAdjustment.isChildInGroup).isTrue()
+ assertThat(newAdjustment).isNotEqualTo(oldAdjustment)
+
+ // Then: need re-inflation
+ assertTrue(NotifUiAdjustment.needReinflate(oldAdjustment, newAdjustment))
+ }
+
+ @Test
+ @DisableFlags(AsyncHybridViewInflation.FLAG_NAME)
+ fun changeIsChildInGroup_asyncHybirdFlagDisabled_noNeedForReInflation() {
+ // Given: an Entry that is not child in group
+ // AsyncHybridViewInflation flag is disabled
+ whenever(groupMembershipManager.isChildInGroup(entry)).thenReturn(false)
+ val oldAdjustment = adjustmentProvider.calculateAdjustment(entry)
+ assertThat(oldAdjustment.isChildInGroup).isFalse()
+
+ // When: the Entry becomes a group child
+ whenever(groupMembershipManager.isChildInGroup(entry)).thenReturn(true)
+ val newAdjustment = adjustmentProvider.calculateAdjustment(entry)
+ assertThat(newAdjustment.isChildInGroup).isTrue()
+ assertThat(newAdjustment).isNotEqualTo(oldAdjustment)
+
+ // Then: need no re-inflation
+ assertFalse(NotifUiAdjustment.needReinflate(oldAdjustment, newAdjustment))
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
index b0996ad48d0a..a0d10759ba56 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
@@ -88,6 +88,8 @@ public class NotificationContentInflaterTest extends SysuiTestCase {
private Notification.Builder mBuilder;
private ExpandableNotificationRow mRow;
+ private NotificationTestHelper mHelper;
+
@Mock private NotifRemoteViewCache mCache;
@Mock private ConversationNotificationProcessor mConversationNotificationProcessor;
@Mock private InflatedSmartReplyState mInflatedSmartReplyState;
@@ -119,11 +121,11 @@ public class NotificationContentInflaterTest extends SysuiTestCase {
.setContentTitle("Title")
.setContentText("Text")
.setStyle(new Notification.BigTextStyle().bigText("big text"));
- NotificationTestHelper helper = new NotificationTestHelper(
+ mHelper = new NotificationTestHelper(
mContext,
mDependency,
TestableLooper.get(this));
- ExpandableNotificationRow row = helper.createRow(mBuilder.build());
+ ExpandableNotificationRow row = mHelper.createRow(mBuilder.build());
mRow = spy(row);
when(mNotifLayoutInflaterFactoryProvider.provide(any(), any()))
.thenReturn(mNotifLayoutInflaterFactory);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineConversationViewBinderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineConversationViewBinderTest.kt
new file mode 100644
index 000000000000..1c959af6ec3f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineConversationViewBinderTest.kt
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.notification.row
+
+import android.app.Notification
+import android.app.Person
+import android.platform.test.annotations.EnableFlags
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.internal.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_SINGLE_LINE
+import com.android.systemui.statusbar.notification.row.SingleLineViewInflater.inflateSingleLineViewHolder
+import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation
+import com.android.systemui.statusbar.notification.row.ui.viewbinder.SingleLineConversationViewBinder
+import com.android.systemui.util.mockito.mock
+import kotlin.test.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class SingleLineConversationViewBinderTest : SysuiTestCase() {
+ private lateinit var notificationBuilder: Notification.Builder
+ private lateinit var helper: NotificationTestHelper
+
+ @Before
+ fun setUp() {
+ allowTestableLooperAsMainThread()
+ helper = NotificationTestHelper(context, mDependency, TestableLooper.get(this))
+ notificationBuilder = Notification.Builder(context, CHANNEL_ID)
+ notificationBuilder
+ .setSmallIcon(R.drawable.ic_corp_icon)
+ .setContentTitle(CONTENT_TITLE)
+ .setContentText(CONTENT_TEXT)
+ }
+
+ @Test
+ @EnableFlags(AsyncHybridViewInflation.FLAG_NAME)
+ fun bindGroupConversationSingleLineView() {
+ // GIVEN a row with a group conversation notification
+ val user =
+ Person.Builder()
+ // .setIcon(Icon.createWithResource(mContext,
+ // R.drawable.ic_account_circle))
+ .setName(USER_NAME)
+ .build()
+ val style =
+ Notification.MessagingStyle(user)
+ .addMessage(MESSAGE_TEXT, System.currentTimeMillis(), user)
+ .addMessage(
+ "How about lunch?",
+ System.currentTimeMillis(),
+ Person.Builder().setName("user2").build()
+ )
+ .setGroupConversation(true)
+ notificationBuilder.setStyle(style).setShortcutId(SHORTCUT_ID)
+ val notification = notificationBuilder.build()
+ val row = helper.createRow(notification)
+
+ val viewHolder =
+ inflateSingleLineViewHolder(
+ isConversation = true,
+ reinflateFlags = FLAG_CONTENT_VIEW_SINGLE_LINE,
+ entry = row.entry,
+ context = context,
+ logger = mock()
+ )
+ as HybridConversationNotificationView
+ val viewModel =
+ SingleLineViewInflater.inflateSingleLineViewModel(
+ notification = notification,
+ messagingStyle = style,
+ builder = notificationBuilder,
+ systemUiContext = context,
+ )
+ // WHEN: binds the viewHolder
+ SingleLineConversationViewBinder.bind(
+ viewModel,
+ viewHolder,
+ )
+
+ // THEN: the single-line conversation view should be bind with view model's corresponding
+ // fields
+ assertEquals(viewModel.titleText, viewHolder.titleView.text)
+ assertEquals(viewModel.contentText, viewHolder.textView.text)
+ assertEquals(
+ viewModel.conversationData?.conversationSenderName,
+ viewHolder.conversationSenderNameView.text
+ )
+ }
+
+ private companion object {
+ const val CHANNEL_ID = "CHANNEL_ID"
+ const val CONTENT_TITLE = "CONTENT_TITLE"
+ const val CONTENT_TEXT = "CONTENT_TEXT"
+ const val USER_NAME = "USER_NAME"
+ const val MESSAGE_TEXT = "MESSAGE_TEXT"
+ const val SHORTCUT_ID = "Shortcut"
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewBinderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewBinderTest.kt
new file mode 100644
index 000000000000..f0fc349777b2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewBinderTest.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.notification.row
+
+import android.app.Notification
+import android.platform.test.annotations.EnableFlags
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.internal.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_SINGLE_LINE
+import com.android.systemui.statusbar.notification.row.SingleLineViewInflater.inflateSingleLineViewHolder
+import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation
+import com.android.systemui.statusbar.notification.row.ui.viewbinder.SingleLineViewBinder
+import com.android.systemui.util.mockito.mock
+import org.junit.Assert
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class SingleLineViewBinderTest : SysuiTestCase() {
+ private lateinit var notificationBuilder: Notification.Builder
+ private lateinit var helper: NotificationTestHelper
+
+ @Before
+ fun setUp() {
+ allowTestableLooperAsMainThread()
+ helper = NotificationTestHelper(mContext, mDependency, TestableLooper.get(this))
+ notificationBuilder = Notification.Builder(mContext, CHANNEL_ID)
+ notificationBuilder
+ .setSmallIcon(R.drawable.ic_corp_icon)
+ .setContentTitle(CONTENT_TITLE)
+ .setContentText(CONTENT_TEXT)
+ }
+
+ @Test
+ @EnableFlags(AsyncHybridViewInflation.FLAG_NAME)
+ fun bindNonConversationSingleLineView() {
+ // GIVEN: a row with bigText style notification
+ val style = Notification.BigTextStyle().bigText(CONTENT_TEXT)
+ notificationBuilder.setStyle(style)
+ val notification = notificationBuilder.build()
+ val row: ExpandableNotificationRow = helper.createRow(notification)
+
+ val viewHolder =
+ inflateSingleLineViewHolder(
+ isConversation = false,
+ reinflateFlags = FLAG_CONTENT_VIEW_SINGLE_LINE,
+ entry = row.entry,
+ context = context,
+ logger = mock()
+ )
+ val viewModel =
+ SingleLineViewInflater.inflateSingleLineViewModel(
+ notification = notification,
+ messagingStyle = null,
+ builder = notificationBuilder,
+ systemUiContext = context,
+ )
+
+ // WHEN: binds the viewHolder
+ SingleLineViewBinder.bind(viewModel, viewHolder)
+
+ // THEN: the single-line view should be bind with viewModel's title and content text
+ Assert.assertEquals(viewModel.titleText, viewHolder?.titleView?.text)
+ Assert.assertEquals(viewModel.contentText, viewHolder?.textView?.text)
+ }
+
+ private companion object {
+ const val CHANNEL_ID = "CHANNEL_ID"
+ const val CONTENT_TITLE = "A Cool New Feature"
+ const val CONTENT_TEXT = "Checkout out new feature!"
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflaterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflaterTest.kt
new file mode 100644
index 000000000000..b67153a842ac
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflaterTest.kt
@@ -0,0 +1,463 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row
+
+import android.app.Notification
+import android.app.Person
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.Canvas
+import android.graphics.Paint
+import android.graphics.PorterDuff
+import android.graphics.PorterDuffXfermode
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.Icon
+import android.platform.test.annotations.EnableFlags
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.core.graphics.drawable.toBitmap
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.ConversationAvatar
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.FacePile
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.SingleIcon
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.SingleLineViewModel
+import kotlin.test.assertEquals
+import kotlin.test.assertIs
+import kotlin.test.assertIsNot
+import kotlin.test.assertNull
+import kotlin.test.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+@EnableFlags(AsyncHybridViewInflation.FLAG_NAME)
+class SingleLineViewInflaterTest : SysuiTestCase() {
+ private lateinit var helper: NotificationTestHelper
+ // Non-group MessagingStyles only have firstSender
+ private lateinit var firstSender: Person
+ private lateinit var lastSender: Person
+ private lateinit var firstSenderIcon: Icon
+ private lateinit var lastSenderIcon: Icon
+ private var firstSenderIconDrawable: Drawable? = null
+ private var lastSenderIconDrawable: Drawable? = null
+ private val currentUser: Person? = null
+
+ private companion object {
+ const val FIRST_SENDER_NAME = "First Sender"
+ const val LAST_SENDER_NAME = "Second Sender"
+ const val LAST_MESSAGE = "How about lunch?"
+
+ const val CONVERSATION_TITLE = "The Sender Family"
+ const val CONTENT_TITLE = "A Cool Group"
+ const val CONTENT_TEXT = "This is an amazing group chat"
+
+ const val SHORTCUT_ID = "Shortcut"
+ }
+
+ @Before
+ fun setUp() {
+ helper = NotificationTestHelper(mContext, mDependency, TestableLooper.get(this))
+ firstSenderIcon = Icon.createWithBitmap(getBitmap(context, R.drawable.ic_person))
+ firstSenderIconDrawable = firstSenderIcon.loadDrawable(context)
+ lastSenderIcon =
+ Icon.createWithBitmap(
+ getBitmap(context, com.android.internal.R.drawable.ic_account_circle)
+ )
+ lastSenderIconDrawable = lastSenderIcon.loadDrawable(context)
+ firstSender = Person.Builder().setName(FIRST_SENDER_NAME).setIcon(firstSenderIcon).build()
+ lastSender = Person.Builder().setName(LAST_SENDER_NAME).setIcon(lastSenderIcon).build()
+ }
+
+ @Test
+ fun createViewModelForNonConversationSingleLineView() {
+ // Given: a non-conversation notification
+ val notificationType = NonMessaging()
+ val notification = getNotification(NonMessaging())
+
+ // When: inflate the SingleLineViewModel
+ val singleLineViewModel = notification.makeSingleLineViewModel(notificationType)
+
+ // Then: the inflated SingleLineViewModel should be as expected
+ // conversationData: null, because it's not a conversation notification
+ assertEquals(SingleLineViewModel(CONTENT_TITLE, CONTENT_TEXT, null), singleLineViewModel)
+ }
+
+ @Test
+ fun createViewModelForNonGroupConversationNotification() {
+ // Given: a non-group conversation notification
+ val notificationType = OneToOneConversation()
+ val notification = getNotification(notificationType)
+
+ // When: inflate the SingleLineViewModel
+ val singleLineViewModel = notification.makeSingleLineViewModel(notificationType)
+
+ // Then: the inflated SingleLineViewModel should be as expected
+ // titleText: Notification.ConversationTitle
+ // contentText: the last message text
+ // conversationSenderName: null, because it's not a group conversation
+ // conversationData.avatar: a single icon of the last sender
+ assertEquals(CONVERSATION_TITLE, singleLineViewModel.titleText)
+ assertEquals(LAST_MESSAGE, singleLineViewModel.contentText)
+ assertNull(
+ singleLineViewModel.conversationData?.conversationSenderName,
+ "Sender name should be null for one-on-one conversation"
+ )
+ assertTrue {
+ singleLineViewModel.conversationData
+ ?.avatar
+ ?.equalsTo(SingleIcon(firstSenderIcon.loadDrawable(context))) == true
+ }
+ }
+
+ @Test
+ fun createViewModelForNonGroupLegacyMessagingStyleNotification() {
+ // Given: a non-group legacy messaging style notification
+ val notificationType = LegacyMessaging()
+ val notification = getNotification(notificationType)
+
+ // When: inflate the SingleLineViewModel
+ val singleLineViewModel = notification.makeSingleLineViewModel(notificationType)
+
+ // Then: the inflated SingleLineViewModel should be as expected
+ // titleText: CONVERSATION_TITLE: SENDER_NAME
+ // contentText: the last message text
+ // conversationData: null, because it's not a conversation notification
+ assertEquals("$CONVERSATION_TITLE: $FIRST_SENDER_NAME", singleLineViewModel.titleText)
+ assertEquals(LAST_MESSAGE, singleLineViewModel.contentText)
+ assertNull(
+ singleLineViewModel.conversationData,
+ "conversationData should be null for legacy messaging conversation"
+ )
+ }
+
+ @Test
+ fun createViewModelForGroupLegacyMessagingStyleNotification() {
+ // Given: a non-group legacy messaging style notification
+ val notificationType = LegacyMessagingGroup()
+ val notification = getNotification(notificationType)
+
+ // When: inflate the SingleLineViewModel
+ val singleLineViewModel = notification.makeSingleLineViewModel(notificationType)
+
+ // Then: the inflated SingleLineViewModel should be as expected
+ // titleText: CONVERSATION_TITLE: LAST_SENDER_NAME
+ // contentText: the last message text
+ // conversationData: null, because it's not a conversation notification
+ assertEquals("$CONVERSATION_TITLE: $LAST_SENDER_NAME", singleLineViewModel.titleText)
+ assertEquals(LAST_MESSAGE, singleLineViewModel.contentText)
+ assertNull(
+ singleLineViewModel.conversationData,
+ "conversationData should be null for legacy messaging conversation"
+ )
+ }
+
+ @Test
+ fun createViewModelForNonGroupConversationNotificationWithShortcutIcon() {
+ // Given: a non-group conversation notification with a shortcut icon
+ val shortcutIcon =
+ Icon.createWithResource(context, com.android.internal.R.drawable.ic_account_circle)
+ val notificationType = OneToOneConversation(shortcutIcon = shortcutIcon)
+ val notification = getNotification(notificationType)
+
+ // When: inflate the SingleLineViewModel
+ val singleLineViewModel = notification.makeSingleLineViewModel(notificationType)
+
+ // Then: the inflated SingleLineViewModel should be expected
+ // titleText: Notification.ConversationTitle
+ // contentText: the last message text
+ // conversationSenderName: null, because it's not a group conversation
+ // conversationData.avatar: a single icon of the shortcut icon
+ assertEquals(CONVERSATION_TITLE, singleLineViewModel.titleText)
+ assertEquals(LAST_MESSAGE, singleLineViewModel.contentText)
+ assertNull(
+ singleLineViewModel.conversationData?.conversationSenderName,
+ "Sender name should be null for one-on-one conversation"
+ )
+ assertTrue {
+ singleLineViewModel.conversationData
+ ?.avatar
+ ?.equalsTo(SingleIcon(shortcutIcon.loadDrawable(context))) == true
+ }
+ }
+
+ @Test
+ fun createViewModelForGroupConversationNotificationWithLargeIcon() {
+ // Given: a group conversation notification with a large icon
+ val largeIcon =
+ Icon.createWithResource(context, com.android.internal.R.drawable.ic_account_circle)
+ val notificationType = GroupConversation(largeIcon = largeIcon)
+ val notification = getNotification(notificationType)
+
+ // When: inflate the SingleLineViewModel
+ val singleLineViewModel = notification.makeSingleLineViewModel(notificationType)
+
+ // Then: the inflated SingleLineViewModel should be expected
+ // titleText: Notification.ConversationTitle
+ // contentText: the last message text
+ // conversationSenderName: the last non-user sender's name
+ // conversationData.avatar: a single icon
+ assertEquals(CONVERSATION_TITLE, singleLineViewModel.titleText)
+ assertEquals(LAST_MESSAGE, singleLineViewModel.contentText)
+ assertEquals(
+ context.resources.getString(
+ com.android.internal.R.string.conversation_single_line_name_display,
+ LAST_SENDER_NAME
+ ),
+ singleLineViewModel.conversationData?.conversationSenderName
+ )
+ assertTrue {
+ singleLineViewModel.conversationData
+ ?.avatar
+ ?.equalsTo(SingleIcon(largeIcon.loadDrawable(context))) == true
+ }
+ }
+
+ @Test
+ fun createViewModelForGroupConversationWithNoIcon() {
+ // Given: a group conversation notification
+ val notificationType = GroupConversation()
+ val notification = getNotification(notificationType)
+
+ // When: inflate the SingleLineViewModel
+ val singleLineViewModel = notification.makeSingleLineViewModel(notificationType)
+
+ // Then: the inflated SingleLineViewModel should be expected
+ // titleText: Notification.ConversationTitle
+ // contentText: the last message text
+ // conversationSenderName: the last non-user sender's name
+ // conversationData.avatar: a face-pile consists the last sender's icon
+ assertEquals(CONVERSATION_TITLE, singleLineViewModel.titleText)
+ assertEquals(LAST_MESSAGE, singleLineViewModel.contentText)
+ assertEquals(
+ context.resources.getString(
+ com.android.internal.R.string.conversation_single_line_name_display,
+ LAST_SENDER_NAME
+ ),
+ singleLineViewModel.conversationData?.conversationSenderName
+ )
+
+ val backgroundColor =
+ Notification.Builder.recoverBuilder(context, notification)
+ .getBackgroundColor(/* isHeader = */ false)
+ assertTrue {
+ singleLineViewModel.conversationData
+ ?.avatar
+ ?.equalsTo(
+ FacePile(
+ firstSenderIconDrawable,
+ lastSenderIconDrawable,
+ backgroundColor,
+ )
+ ) == true
+ }
+ }
+
+ sealed class NotificationType(val largeIcon: Icon? = null)
+
+ class NonMessaging(largeIcon: Icon? = null) : NotificationType(largeIcon)
+
+ class LegacyMessaging(largeIcon: Icon? = null) : NotificationType(largeIcon)
+
+ class LegacyMessagingGroup(largeIcon: Icon? = null) : NotificationType(largeIcon)
+
+ class OneToOneConversation(largeIcon: Icon? = null, val shortcutIcon: Icon? = null) :
+ NotificationType(largeIcon)
+
+ class GroupConversation(largeIcon: Icon? = null) : NotificationType(largeIcon)
+
+ private fun getNotification(type: NotificationType): Notification {
+ val notificationBuilder: Notification.Builder =
+ Notification.Builder(mContext, "channelId")
+ .setSmallIcon(R.drawable.ic_person)
+ .setContentTitle(CONTENT_TITLE)
+ .setContentText(CONTENT_TEXT)
+ .setLargeIcon(type.largeIcon)
+
+ val user = Person.Builder().setName("User").build()
+
+ val buildMessagingStyle =
+ Notification.MessagingStyle(user)
+ .setConversationTitle(CONVERSATION_TITLE)
+ .addMessage("Hi", 0, currentUser)
+
+ return when (type) {
+ is NonMessaging ->
+ notificationBuilder
+ .setStyle(Notification.BigTextStyle().bigText("Big Text"))
+ .build()
+ is LegacyMessaging -> {
+ buildMessagingStyle
+ .addMessage("What's up?", 0, firstSender)
+ .addMessage("Not much", 0, currentUser)
+ .addMessage(LAST_MESSAGE, 0, firstSender)
+
+ val notification = notificationBuilder.setStyle(buildMessagingStyle).build()
+
+ assertNull(notification.shortcutId)
+ notification
+ }
+ is LegacyMessagingGroup -> {
+ buildMessagingStyle
+ .addMessage("What's up?", 0, firstSender)
+ .addMessage("Check out my new hover board!", 0, lastSender)
+ .setGroupConversation(true)
+ .addMessage(LAST_MESSAGE, 0, lastSender)
+
+ val notification = notificationBuilder.setStyle(buildMessagingStyle).build()
+
+ assertNull(notification.shortcutId)
+ notification
+ }
+ is OneToOneConversation -> {
+ buildMessagingStyle
+ .addMessage("What's up?", 0, firstSender)
+ .addMessage("Not much", 0, currentUser)
+ .addMessage(LAST_MESSAGE, 0, firstSender)
+ .setShortcutIcon(type.shortcutIcon)
+ notificationBuilder.setShortcutId(SHORTCUT_ID).setStyle(buildMessagingStyle).build()
+ }
+ is GroupConversation -> {
+ buildMessagingStyle
+ .addMessage("What's up?", 0, firstSender)
+ .addMessage("Check out my new hover board!", 0, lastSender)
+ .setGroupConversation(true)
+ .addMessage(LAST_MESSAGE, 0, lastSender)
+ notificationBuilder.setShortcutId(SHORTCUT_ID).setStyle(buildMessagingStyle).build()
+ }
+ }
+ }
+
+ private fun Notification.makeSingleLineViewModel(type: NotificationType): SingleLineViewModel {
+ val builder = Notification.Builder.recoverBuilder(context, this)
+
+ // Validate the recovered builder has the right type of style
+ val expectMessagingStyle =
+ when (type) {
+ is LegacyMessaging,
+ is LegacyMessagingGroup,
+ is OneToOneConversation,
+ is GroupConversation -> true
+ else -> false
+ }
+ if (expectMessagingStyle) {
+ assertIs<Notification.MessagingStyle>(
+ builder.style,
+ "Notification style should be MessagingStyle"
+ )
+ } else {
+ assertIsNot<Notification.MessagingStyle>(
+ builder.style,
+ message = "Notification style should not be MessagingStyle"
+ )
+ }
+
+ // Inflate the SingleLineViewModel
+ // Mock the behavior of NotificationContentInflater.doInBackground
+ val messagingStyle = builder.getMessagingStyle()
+ val isConversation = type is OneToOneConversation || type is GroupConversation
+ return SingleLineViewInflater.inflateSingleLineViewModel(
+ this,
+ if (isConversation) messagingStyle else null,
+ builder,
+ context
+ )
+ }
+
+ private fun Notification.Builder.getMessagingStyle(): Notification.MessagingStyle? {
+ return style as? Notification.MessagingStyle
+ }
+
+ private fun getBitmap(context: Context, resId: Int): Bitmap {
+ val largeIconDimension =
+ context.resources.getDimension(R.dimen.conversation_single_line_avatar_size)
+ val d = context.resources.getDrawable(resId)
+ val b =
+ Bitmap.createBitmap(
+ largeIconDimension.toInt(),
+ largeIconDimension.toInt(),
+ Bitmap.Config.ARGB_8888
+ )
+ val c = Canvas(b)
+ val paint = Paint()
+ c.drawCircle(
+ largeIconDimension / 2,
+ largeIconDimension / 2,
+ largeIconDimension.coerceAtMost(largeIconDimension) / 2,
+ paint
+ )
+ d.setBounds(0, 0, largeIconDimension.toInt(), largeIconDimension.toInt())
+ paint.setXfermode(PorterDuffXfermode(PorterDuff.Mode.SRC_IN))
+ c.saveLayer(0F, 0F, largeIconDimension, largeIconDimension, paint, Canvas.ALL_SAVE_FLAG)
+ d.draw(c)
+ c.restore()
+ return b
+ }
+
+ fun ConversationAvatar.equalsTo(other: ConversationAvatar?): Boolean =
+ when {
+ this === other -> true
+ this is SingleIcon && other is SingleIcon -> equalsTo(other)
+ this is FacePile && other is FacePile -> equalsTo(other)
+ else -> false
+ }
+
+ private fun SingleIcon.equalsTo(other: SingleIcon): Boolean =
+ iconDrawable?.equalsTo(other.iconDrawable) == true
+
+ private fun FacePile.equalsTo(other: FacePile): Boolean =
+ when {
+ bottomBackgroundColor != other.bottomBackgroundColor -> false
+ topIconDrawable?.equalsTo(other.topIconDrawable) != true -> false
+ bottomIconDrawable?.equalsTo(other.bottomIconDrawable) != true -> false
+ else -> true
+ }
+
+ fun Drawable.equalsTo(other: Drawable?): Boolean =
+ when {
+ this === other -> true
+ this.pixelsEqualTo(other) -> true
+ else -> false
+ }
+
+ private fun <T : Drawable> T.pixelsEqualTo(t: T?) =
+ toBitmap().pixelsEqualTo(t?.toBitmap(), false)
+
+ private fun Bitmap.pixelsEqualTo(otherBitmap: Bitmap?, shouldRecycle: Boolean = false) =
+ otherBitmap?.let { other ->
+ if (width == other.width && height == other.height) {
+ val res = toPixels().contentEquals(other.toPixels())
+ if (shouldRecycle) {
+ doRecycle().also { otherBitmap.doRecycle() }
+ }
+ res
+ } else false
+ }
+ ?: kotlin.run { false }
+
+ private fun Bitmap.toPixels() =
+ IntArray(width * height).apply { getPixels(this, 0, width, 0, 0, width, height) }
+
+ fun Bitmap.doRecycle() {
+ if (!isRecycled) recycle()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index 1ab4c32c7d08..dbe63f290407 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -860,9 +860,6 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
when(mNotificationStackScrollLayout.onKeyguard()).thenReturn(true);
mController.getNotifStackController().setNotifStats(NotifStats.getEmpty());
- // WHEN: call updateImportantForAccessibility
- mController.updateImportantForAccessibility();
-
// THEN: mNotificationStackScrollLayout should not be important for A11y
verify(mNotificationStackScrollLayout)
.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
@@ -884,9 +881,6 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
/* hasClearableSilentNotifs = */ false)
);
- // WHEN: call updateImportantForAccessibility
- mController.updateImportantForAccessibility();
-
// THEN: mNotificationStackScrollLayout should be important for A11y
verify(mNotificationStackScrollLayout)
.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
@@ -908,9 +902,6 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
/* hasClearableSilentNotifs = */ false)
);
- // WHEN: call updateImportantForAccessibility
- mController.updateImportantForAccessibility();
-
// THEN: mNotificationStackScrollLayout should be important for A11y
verify(mNotificationStackScrollLayout)
.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
@@ -925,9 +916,6 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
when(mNotificationStackScrollLayout.onKeyguard()).thenReturn(false);
mController.getNotifStackController().setNotifStats(NotifStats.getEmpty());
- // WHEN: call updateImportantForAccessibility
- mController.updateImportantForAccessibility();
-
// THEN: mNotificationStackScrollLayout should be important for A11y
verify(mNotificationStackScrollLayout)
.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index 06298b78ae57..32c727c70172 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -38,6 +38,9 @@ import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
+import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
+import com.android.systemui.keyguard.ui.viewmodel.aodBurnInViewModel
import com.android.systemui.keyguard.ui.viewmodel.keyguardRootViewModel
import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
@@ -45,6 +48,7 @@ import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.shade.mockLargeScreenHeaderHelper
import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -55,15 +59,22 @@ import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
@SmallTest
@RunWith(AndroidJUnit4::class)
class SharedNotificationContainerViewModelTest : SysuiTestCase() {
+ val aodBurnInViewModel = mock(AodBurnInViewModel::class.java)
+ lateinit var translationYFlow: MutableStateFlow<Float>
val kosmos =
testKosmos().apply {
fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
}
+
+ init {
+ kosmos.aodBurnInViewModel = aodBurnInViewModel
+ }
val testScope = kosmos.testScope
val configurationRepository = kosmos.fakeConfigurationRepository
val keyguardRepository = kosmos.fakeKeyguardRepository
@@ -75,11 +86,14 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() {
val sharedNotificationContainerInteractor = kosmos.sharedNotificationContainerInteractor
val largeScreenHeaderHelper = kosmos.mockLargeScreenHeaderHelper
- val underTest = kosmos.sharedNotificationContainerViewModel
+ lateinit var underTest: SharedNotificationContainerViewModel
@Before
fun setUp() {
overrideResource(R.bool.config_use_split_notification_shade, false)
+ translationYFlow = MutableStateFlow(0f)
+ whenever(aodBurnInViewModel.translationY(any())).thenReturn(translationYFlow)
+ underTest = kosmos.sharedNotificationContainerViewModel
}
@Test
@@ -579,9 +593,21 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() {
}
@Test
+ fun translationYUpdatesOnKeyguardForBurnIn() =
+ testScope.runTest {
+ val translationY by collectLastValue(underTest.translationY(BurnInParameters()))
+
+ showLockscreen()
+ assertThat(translationY).isEqualTo(0)
+
+ translationYFlow.value = 150f
+ assertThat(translationY).isEqualTo(150f)
+ }
+
+ @Test
fun translationYUpdatesOnKeyguard() =
testScope.runTest {
- val translationY by collectLastValue(underTest.translationY)
+ val translationY by collectLastValue(underTest.translationY(BurnInParameters()))
configurationRepository.setDimensionPixelSize(
R.dimen.keyguard_translate_distance_on_swipe_up,
@@ -601,7 +627,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() {
@Test
fun translationYDoesNotUpdateWhenShadeIsExpanded() =
testScope.runTest {
- val translationY by collectLastValue(underTest.translationY)
+ val translationY by collectLastValue(underTest.translationY(BurnInParameters()))
configurationRepository.setDimensionPixelSize(
R.dimen.keyguard_translate_distance_on_swipe_up,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index b3a47d77a307..423cc8478dda 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -31,6 +31,7 @@ import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -1683,6 +1684,26 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ public void notificationBoundsTopGetsPassedToKeyguard() {
+ mScrimController.transitionTo(SHADE_LOCKED);
+ mScrimController.setQsPosition(1f, 0);
+ finishAnimationsImmediately();
+
+ mScrimController.setNotificationsBounds(0f, 100f, 0f, 0f);
+ verify(mKeyguardInteractor).setTopClippingBounds(eq(100));
+ }
+
+ @Test
+ public void notificationBoundsTopDoesNotGetPassedToKeyguardWhenNotifScrimIsNotVisible() {
+ mScrimController.setKeyguardOccluded(true);
+ mScrimController.transitionTo(ScrimState.KEYGUARD);
+ finishAnimationsImmediately();
+
+ mScrimController.setNotificationsBounds(0f, 100f, 0f, 0f);
+ verify(mKeyguardInteractor).setTopClippingBounds(eq(null));
+ }
+
+ @Test
public void transitionToDreaming() {
mScrimController.setRawPanelExpansionFraction(0f);
mScrimController.setBouncerHiddenFraction(KeyguardBouncerConstants.EXPANSION_HIDDEN);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 8dde9359bdfc..cb4531567e86 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -182,8 +182,10 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
when(mBouncerViewDelegate.getBackCallback()).thenReturn(mBouncerViewDelegateBackCallback);
mFeatureFlags = new FakeFeatureFlags();
mFeatureFlags.set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false);
- mFeatureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false);
- mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR);
+ mSetFlagsRule.disableFlags(
+ com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR,
+ com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR
+ );
when(mNotificationShadeWindowController.getWindowRootView())
.thenReturn(mNotificationShadeWindowView);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt
index 0e0d4897d667..5b5819d649b4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt
@@ -125,22 +125,6 @@ class DeviceBasedSatelliteRepositoryImplTest : SysuiTestCase() {
}
@Test
- fun satelliteManagerThrows_doesNotCrash() =
- testScope.runTest {
- setupDefaultRepo()
-
- whenever(satelliteManager.registerForNtnSignalStrengthChanged(any(), any()))
- .thenThrow(SatelliteException(13))
-
- val conn by collectLastValue(underTest.connectionState)
- val strength by collectLastValue(underTest.signalStrength)
-
- // Flows have not emitted, we haven't crashed
- assertThat(conn).isNull()
- assertThat(strength).isNull()
- }
-
- @Test
fun connectionState_mapsFromSatelliteModemState() =
testScope.runTest {
setupDefaultRepo()
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
index af7f4c894c7c..b62b3a211e58 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
@@ -21,17 +21,25 @@ import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import android.app.Instrumentation;
+import android.content.Context;
+import android.content.res.Resources;
import android.os.Build;
import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.Looper;
import android.os.MessageQueue;
import android.os.ParcelFileDescriptor;
+import android.platform.test.annotations.DisabledOnRavenwood;
import android.platform.test.flag.junit.SetFlagsRule;
+import android.platform.test.ravenwood.RavenwoodClassRule;
+import android.platform.test.ravenwood.RavenwoodRule;
+import android.test.mock.MockContext;
import android.testing.DexmakerShareClassLoaderRule;
import android.testing.LeakCheck;
import android.testing.TestWithLooperRule;
import android.testing.TestableLooper;
import android.util.Log;
+import android.util.Singleton;
import androidx.annotation.NonNull;
import androidx.core.animation.AndroidXAnimatorIsolationRule;
@@ -44,17 +52,24 @@ import com.android.systemui.flags.SceneContainerRule;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
+import org.junit.ClassRule;
import org.junit.Rule;
import org.mockito.Mockito;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
import java.util.concurrent.Future;
/**
* Base class that does System UI specific setup.
*/
+// NOTE: This @DisabledOnRavenwood annotation is inherited to all subclasses (unless overridden
+// via a more-specific @EnabledOnRavenwood annotation); this means that by default all
+// subclasses will be "ignored" when executed on the Ravenwood testing environment; more
+// background on Ravenwood is available at go/ravenwood-docs
+@DisabledOnRavenwood
public abstract class SysuiTestCase {
private static final String TAG = "SysuiTestCase";
@@ -66,6 +81,23 @@ public abstract class SysuiTestCase {
public AndroidXAnimatorIsolationRule mAndroidXAnimatorIsolationRule =
new AndroidXAnimatorIsolationRule();
+ /**
+ * Rule that respects class-level annotations such as {@code @DisabledOnRavenwood} when tests
+ * are running on Ravenwood; on all other test environments this rule is a no-op passthrough.
+ */
+ @ClassRule(order = Integer.MIN_VALUE + 1)
+ public static final RavenwoodClassRule sRavenwood = new RavenwoodClassRule();
+
+ /**
+ * Rule that defines and prepares the Ravenwood environment when tests are running on
+ * Ravenwood; on all other test environments this rule is a no-op passthrough.
+ */
+ @Rule(order = Integer.MIN_VALUE + 1)
+ public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+ .setProcessApp()
+ .setProvideMainThread(true)
+ .build();
+
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
@@ -78,7 +110,7 @@ public abstract class SysuiTestCase {
@NonNull
private SysuiTestableContext createTestableContext() {
SysuiTestableContext context = new SysuiTestableContext(
- InstrumentationRegistry.getContext(), getLeakCheck());
+ getTestableContextBase(), getLeakCheck());
if (isRobolectricTest()) {
// Manually associate a Display to context for Robolectric test. Similar to b/214297409
return context.createDefaultDisplayContext();
@@ -87,6 +119,43 @@ public abstract class SysuiTestCase {
}
}
+ @NonNull
+ private Context getTestableContextBase() {
+ if (isRavenwoodTest()) {
+ // TODO(b/292141694): build out Ravenwood support for Context
+ // Ravenwood doesn't yet provide a Context, but many SysUI tests assume one exists;
+ // so here we construct just enough of a Context to be useful; this will be replaced
+ // as more of the Ravenwood environment is built out
+ return new MockContext() {
+ @Override
+ public void setTheme(int resid) {
+ // TODO(b/318393625): build out Ravenwood support for Resources
+ // until then, ignored as no-op
+ }
+
+ @Override
+ public Resources getResources() {
+ // TODO(b/318393625): build out Ravenwood support for Resources
+ return Mockito.mock(Resources.class);
+ }
+
+ private Singleton<Executor> mMainExecutor = new Singleton<>() {
+ @Override
+ protected Executor create() {
+ return new HandlerExecutor(new Handler(Looper.getMainLooper()));
+ }
+ };
+
+ @Override
+ public Executor getMainExecutor() {
+ return mMainExecutor.get();
+ }
+ };
+ } else {
+ return InstrumentationRegistry.getContext();
+ }
+ }
+
@Rule
public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule =
new DexmakerShareClassLoaderRule();
@@ -103,17 +172,22 @@ public abstract class SysuiTestCase {
public void SysuiSetup() throws Exception {
mSysuiDependency = new SysuiTestDependency(mContext, shouldFailOnLeakedReceiver());
mDependency = mSysuiDependency.install();
- mRealInstrumentation = InstrumentationRegistry.getInstrumentation();
- Instrumentation inst = spy(mRealInstrumentation);
- when(inst.getContext()).thenAnswer(invocation -> {
- throw new RuntimeException(
- "SysUI Tests should use SysuiTestCase#getContext or SysuiTestCase#mContext");
- });
- when(inst.getTargetContext()).thenAnswer(invocation -> {
- throw new RuntimeException(
- "SysUI Tests should use SysuiTestCase#getContext or SysuiTestCase#mContext");
- });
- InstrumentationRegistry.registerInstance(inst, InstrumentationRegistry.getArguments());
+ // TODO(b/292141694): build out Ravenwood support for Instrumentation
+ // Ravenwood doesn't yet provide Instrumentation, so we sidestep this global configuration
+ // step; any tests that rely on it are already being excluded on Ravenwood
+ if (!isRavenwoodTest()) {
+ mRealInstrumentation = InstrumentationRegistry.getInstrumentation();
+ Instrumentation inst = spy(mRealInstrumentation);
+ when(inst.getContext()).thenAnswer(invocation -> {
+ throw new RuntimeException(
+ "Tests should use SysuiTestCase#getContext or SysuiTestCase#mContext");
+ });
+ when(inst.getTargetContext()).thenAnswer(invocation -> {
+ throw new RuntimeException(
+ "Tests should use SysuiTestCase#getContext or SysuiTestCase#mContext");
+ });
+ InstrumentationRegistry.registerInstance(inst, InstrumentationRegistry.getArguments());
+ }
}
protected boolean shouldFailOnLeakedReceiver() {
@@ -209,7 +283,11 @@ public abstract class SysuiTestCase {
}
public static boolean isRobolectricTest() {
- return Build.FINGERPRINT.contains("robolectric");
+ return !isRavenwoodTest() && Build.FINGERPRINT.contains("robolectric");
+ }
+
+ public static boolean isRavenwoodTest() {
+ return RavenwoodRule.isOnRavenwood();
}
private static final void validateThread(Looper l) {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestDependency.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestDependency.kt
index d89d7b00592e..364d3b236ef5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestDependency.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestDependency.kt
@@ -34,10 +34,16 @@ class SysuiTestDependency(
// is missing (constructing the actual one would throw).
// TODO(b/219008720): Remove this.
dependency.injectMockDependency(SystemUIDialogManager::class.java)
- dependency.injectTestDependency(
- DialogLaunchAnimator::class.java,
- fakeDialogLaunchAnimator()
- )
+
+ // TODO(b/292141694): build out Ravenwood support for UI animations
+ // Ravenwood doesn't yet provide UI animations, so we sidestep this global configuration
+ // step; any tests that rely on it are already being excluded under Ravenwood
+ if (!SysuiTestCase.isRavenwoodTest()) {
+ dependency.injectTestDependency(
+ DialogLaunchAnimator::class.java,
+ fakeDialogLaunchAnimator()
+ )
+ }
// Many tests end up creating a BroadcastDispatcher. Instead, give them a fake that will
// record receivers registered. They are not actually leaked as they are kept just as a weak
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeAccessibilityQsShortcutsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeAccessibilityQsShortcutsRepository.kt
new file mode 100644
index 000000000000..e547da1b92dd
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeAccessibilityQsShortcutsRepository.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility.data.repository
+
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.flow.asSharedFlow
+
+class FakeAccessibilityQsShortcutsRepository : AccessibilityQsShortcutsRepository {
+
+ private val targetsPerUser = mutableMapOf<Int, MutableSharedFlow<Set<String>>>()
+
+ override fun a11yQsShortcutTargets(userId: Int): SharedFlow<Set<String>> {
+ return getFlow(userId).asSharedFlow()
+ }
+
+ /**
+ * Set the a11y qs shortcut targets. In real world, the A11y QS Shortcut targets are set by the
+ * Settings app not in SysUi
+ */
+ suspend fun setA11yQsShortcutTargets(userId: Int, targets: Set<String>) {
+ getFlow(userId).emit(targets)
+ }
+
+ private fun getFlow(userId: Int): MutableSharedFlow<Set<String>> =
+ targetsPerUser.getOrPut(userId) { MutableSharedFlow(replay = 1) }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/UdfpsUtilsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/UdfpsUtilsKosmos.kt
new file mode 100644
index 000000000000..4849fec14a88
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/UdfpsUtilsKosmos.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.biometrics
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+var Kosmos.udfpsUtils by Kosmos.Fixture { mock<UdfpsUtils>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryKosmos.kt
new file mode 100644
index 000000000000..961022f0f426
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryKosmos.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.biometrics.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+
+val Kosmos.biometricStatusRepository by Fixture { FakeBiometricStatusRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/PromptRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/PromptRepositoryKosmos.kt
new file mode 100644
index 000000000000..31fa5d2ed715
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/PromptRepositoryKosmos.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.biometrics.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+
+var Kosmos.promptRepository by Fixture { FakePromptRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractorKosmos.kt
new file mode 100644
index 000000000000..1493f14d9aa7
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractorKosmos.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.biometrics.domain.interactor
+
+import com.android.app.activityTaskManager
+import com.android.systemui.biometrics.data.repository.biometricStatusRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+
+val Kosmos.biometricStatusInteractor by Fixture {
+ BiometricStatusInteractorImpl(
+ activityTaskManager = activityTaskManager,
+ biometricStatusRepository = biometricStatusRepository
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorKosmos.kt
new file mode 100644
index 000000000000..7f9a71cd149e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorKosmos.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.interactor
+
+import com.android.internal.widget.lockPatternUtils
+import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
+import com.android.systemui.biometrics.data.repository.promptRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+
+val Kosmos.promptSelectorInteractor by Fixture {
+ PromptSelectorInteractorImpl(
+ fingerprintPropertyRepository = fingerprintPropertyRepository,
+ promptRepository = promptRepository,
+ lockPatternUtils = lockPatternUtils
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelKosmos.kt
new file mode 100644
index 000000000000..9cbe6337befe
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelKosmos.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.biometrics.ui.viewmodel
+
+import android.content.applicationContext
+import com.android.systemui.biometrics.domain.interactor.displayStateInteractor
+import com.android.systemui.biometrics.domain.interactor.promptSelectorInteractor
+import com.android.systemui.biometrics.domain.interactor.udfpsOverlayInteractor
+import com.android.systemui.biometrics.udfpsUtils
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+
+val Kosmos.promptViewModel by Fixture {
+ PromptViewModel(
+ displayStateInteractor = displayStateInteractor,
+ promptSelectorInteractor = promptSelectorInteractor,
+ context = applicationContext,
+ udfpsOverlayInteractor = udfpsOverlayInteractor,
+ udfpsUtils = udfpsUtils
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
index e25e8c099c21..bc7e7af245a6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
@@ -39,13 +39,4 @@ class FakeCommunalWidgetRepository(private val coroutineScope: CoroutineScope) :
private fun onConfigured(id: Int, providerInfo: AppWidgetProviderInfo, priority: Int) {
_communalWidgets.value += listOf(CommunalWidgetContentModel(id, providerInfo, priority))
}
-
- private var isHostActive = false
- override fun updateAppWidgetHostActive(active: Boolean) {
- isHostActive = active
- }
-
- fun isHostActive(): Boolean {
- return isHostActive
- }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
index 1abf71fde14c..c818e9c8971c 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
@@ -24,14 +24,12 @@ import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.smartspace.data.repository.smartspaceRepository
import com.android.systemui.user.data.repository.userRepository
import com.android.systemui.util.mockito.mock
val Kosmos.communalInteractor by Fixture {
CommunalInteractor(
- applicationScope = applicationCoroutineScope,
communalRepository = communalRepository,
widgetRepository = communalWidgetRepository,
mediaRepository = communalMediaRepository,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt
index 6436a382eb7f..77caeaa6da4d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt
@@ -16,25 +16,16 @@
package com.android.systemui.deviceentry.data.repository
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
import dagger.Binds
import dagger.Module
import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
/** Fake implementation of [DeviceEntryRepository] */
@SysUISingleton
class FakeDeviceEntryRepository @Inject constructor() : DeviceEntryRepository {
- private val _enteringDeviceFromBiometricUnlock: MutableSharedFlow<BiometricUnlockSource> =
- MutableSharedFlow()
- override val enteringDeviceFromBiometricUnlock: Flow<BiometricUnlockSource> =
- _enteringDeviceFromBiometricUnlock.asSharedFlow()
-
private var isLockscreenEnabled = true
private val _isBypassEnabled = MutableStateFlow(false)
@@ -62,10 +53,6 @@ class FakeDeviceEntryRepository @Inject constructor() : DeviceEntryRepository {
fun setBypassEnabled(isBypassEnabled: Boolean) {
_isBypassEnabled.value = isBypassEnabled
}
-
- suspend fun enteringDeviceFromBiometricUnlock(sourceType: BiometricUnlockSource) {
- _enteringDeviceFromBiometricUnlock.emit(sourceType)
- }
}
@Module
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractorKosmos.kt
new file mode 100644
index 000000000000..3070cf4c06ad
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractorKosmos.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.deviceentry.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+@OptIn(ExperimentalCoroutinesApi::class)
+val Kosmos.authRippleInteractor by
+ Kosmos.Fixture {
+ AuthRippleInteractor(
+ deviceEntrySourceInteractor = deviceEntrySourceInteractor,
+ deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt
index de58ae5e9452..878e38594fe1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt
@@ -30,7 +30,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
val Kosmos.deviceEntryHapticsInteractor by
Kosmos.Fixture {
DeviceEntryHapticsInteractor(
- deviceEntryInteractor = deviceEntryInteractor,
+ deviceEntrySourceInteractor = deviceEntrySourceInteractor,
deviceEntryFingerprintAuthInteractor = deviceEntryFingerprintAuthInteractor,
deviceEntryBiometricAuthInteractor = deviceEntryBiometricAuthInteractor,
fingerprintPropertyRepository = fingerprintPropertyRepository,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt
index 8dcdd3a9425c..0d1a31f9605e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-@file:OptIn(ExperimentalCoroutinesApi::class)
-
package com.android.systemui.deviceentry.domain.interactor
import com.android.systemui.authentication.domain.interactor.authenticationInteractor
@@ -28,6 +26,7 @@ import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.flag.sceneContainerFlags
import kotlinx.coroutines.ExperimentalCoroutinesApi
+@ExperimentalCoroutinesApi
val Kosmos.deviceEntryInteractor by
Kosmos.Fixture {
DeviceEntryInteractor(
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractorKosmos.kt
new file mode 100644
index 000000000000..0b9ec92af2b5
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractorKosmos.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.deviceentry.domain.interactor
+
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.kosmos.Kosmos
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+@ExperimentalCoroutinesApi
+val Kosmos.deviceEntrySourceInteractor by
+ Kosmos.Fixture {
+ DeviceEntrySourceInteractor(
+ keyguardInteractor = keyguardInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 5766f7a9028c..793e2d7efcda 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -65,7 +65,7 @@ class FakeKeyguardRepository @Inject constructor() : KeyguardRepository {
override val isKeyguardShowing: Flow<Boolean> = _isKeyguardShowing
private val _isKeyguardUnlocked = MutableStateFlow(false)
- override val isKeyguardUnlocked: StateFlow<Boolean> = _isKeyguardUnlocked.asStateFlow()
+ override val isKeyguardDismissible: StateFlow<Boolean> = _isKeyguardUnlocked.asStateFlow()
private val _isKeyguardOccluded = MutableStateFlow(false)
override val isKeyguardOccluded: Flow<Boolean> = _isKeyguardOccluded
@@ -165,7 +165,7 @@ class FakeKeyguardRepository @Inject constructor() : KeyguardRepository {
_isKeyguardOccluded.value = isOccluded
}
- fun setKeyguardUnlocked(isUnlocked: Boolean) {
+ fun setKeyguardDismissible(isUnlocked: Boolean) {
_isKeyguardUnlocked.value = isUnlocked
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelKosmos.kt
index 35cfa89e56ed..a8f45b0974c4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelKosmos.kt
@@ -26,7 +26,7 @@ import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import kotlinx.coroutines.ExperimentalCoroutinesApi
-val Kosmos.aodBurnInViewModel by Fixture {
+var Kosmos.aodBurnInViewModel by Fixture {
AodBurnInViewModel(
burnInInteractor = burnInInteractor,
configurationInteractor = configurationInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt
index 5ceefde32d2a..73fd9991945c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.ui.viewmodel
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.deviceentry.domain.interactor.deviceEntrySourceInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor
import com.android.systemui.keyguard.domain.interactor.burnInInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
@@ -27,6 +28,7 @@ import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.scene.shared.flag.sceneContainerFlags
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.statusbar.phone.statusBarKeyguardViewManager
+import kotlinx.coroutines.ExperimentalCoroutinesApi
val Kosmos.fakeDeviceEntryIconViewModelTransition by Fixture { FakeDeviceEntryIconTransition() }
@@ -34,6 +36,7 @@ val Kosmos.deviceEntryIconViewModelTransitionsMock by Fixture {
setOf<DeviceEntryIconTransition>(fakeDeviceEntryIconViewModelTransition)
}
+@ExperimentalCoroutinesApi
val Kosmos.deviceEntryIconViewModel by Fixture {
DeviceEntryIconViewModel(
transitions = deviceEntryIconViewModelTransitionsMock,
@@ -46,5 +49,6 @@ val Kosmos.deviceEntryIconViewModel by Fixture {
sceneContainerFlags = sceneContainerFlags,
keyguardViewController = { statusBarKeyguardViewManager },
deviceEntryInteractor = deviceEntryInteractor,
+ deviceEntrySourceInteractor = deviceEntrySourceInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
index db4050905200..7c398cd45f90 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.stack.ui.viewmodel
import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.keyguard.ui.viewmodel.aodBurnInViewModel
import com.android.systemui.keyguard.ui.viewmodel.glanceableHubToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.lockscreenToGlanceableHubTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.lockscreenToOccludedTransitionViewModel
@@ -40,6 +41,7 @@ val Kosmos.sharedNotificationContainerViewModel by Fixture {
occludedToLockscreenTransitionViewModel = occludedToLockscreenTransitionViewModel,
lockscreenToOccludedTransitionViewModel = lockscreenToOccludedTransitionViewModel,
glanceableHubToLockscreenTransitionViewModel = glanceableHubToLockscreenTransitionViewModel,
- lockscreenToGlanceableHubTransitionViewModel = lockscreenToGlanceableHubTransitionViewModel
+ lockscreenToGlanceableHubTransitionViewModel = lockscreenToGlanceableHubTransitionViewModel,
+ aodBurnInViewModel = aodBurnInViewModel,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ConfigurationControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ConfigurationControllerKosmos.kt
index 18a2f9482df8..33ed7e61de4d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ConfigurationControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ConfigurationControllerKosmos.kt
@@ -20,3 +20,5 @@ import com.android.systemui.kosmos.Kosmos
import com.android.systemui.util.mockito.mock
val Kosmos.configurationController by Kosmos.Fixture { mock<ConfigurationController>() }
+val Kosmos.fakeConfigurationController: FakeConfigurationController by
+ Kosmos.Fixture { FakeConfigurationController() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/VolumePanelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/VolumePanelKosmos.kt
new file mode 100644
index 000000000000..d3410737a432
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/VolumePanelKosmos.kt
@@ -0,0 +1,72 @@
+/*
+ * 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
+
+import android.content.res.mainResources
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.policy.fakeConfigurationController
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.volume.panel.dagger.factory.KosmosVolumePanelComponentFactory
+import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria
+import com.android.systemui.volume.panel.domain.TestComponentAvailabilityCriteria
+import com.android.systemui.volume.panel.domain.interactor.ComponentsInteractor
+import com.android.systemui.volume.panel.domain.interactor.ComponentsInteractorImpl
+import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey
+import com.android.systemui.volume.panel.shared.model.VolumePanelUiComponent
+import com.android.systemui.volume.panel.ui.composable.ComponentsFactory
+import com.android.systemui.volume.panel.ui.layout.ComponentsLayoutManager
+import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
+import javax.inject.Provider
+
+val Kosmos.mockVolumePanelUiComponent: VolumePanelUiComponent by Kosmos.Fixture { mock {} }
+val Kosmos.mockVolumePanelUiComponentProvider: Provider<VolumePanelUiComponent> by
+ Kosmos.Fixture { Provider { mockVolumePanelUiComponent } }
+var Kosmos.componentByKey: Map<VolumePanelComponentKey, Provider<VolumePanelUiComponent>> by
+ Kosmos.Fixture { emptyMap() }
+val Kosmos.componentsFactory: ComponentsFactory by
+ Kosmos.Fixture { ComponentsFactory(componentByKey) }
+
+var Kosmos.componentsLayoutManager: ComponentsLayoutManager by Kosmos.Fixture()
+var Kosmos.enabledComponents: Collection<VolumePanelComponentKey> by
+ Kosmos.Fixture { componentByKey.keys }
+val Kosmos.unavailableCriteria: Provider<ComponentAvailabilityCriteria> by
+ Kosmos.Fixture { Provider { TestComponentAvailabilityCriteria(false) } }
+val Kosmos.availableCriteria: Provider<ComponentAvailabilityCriteria> by
+ Kosmos.Fixture { Provider { TestComponentAvailabilityCriteria(true) } }
+var Kosmos.defaultCriteria: Provider<ComponentAvailabilityCriteria> by
+ Kosmos.Fixture { availableCriteria }
+var Kosmos.criteriaByKey: Map<VolumePanelComponentKey, Provider<ComponentAvailabilityCriteria>> by
+ Kosmos.Fixture { emptyMap() }
+var Kosmos.componentsInteractor: ComponentsInteractor by
+ Kosmos.Fixture {
+ ComponentsInteractorImpl(
+ enabledComponents,
+ defaultCriteria,
+ testScope.backgroundScope,
+ criteriaByKey,
+ )
+ }
+
+var Kosmos.volumePanelViewModel: VolumePanelViewModel by
+ Kosmos.Fixture {
+ VolumePanelViewModel(
+ mainResources,
+ KosmosVolumePanelComponentFactory(this),
+ fakeConfigurationController,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/dagger/factory/KosmosVolumePanelComponentFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/dagger/factory/KosmosVolumePanelComponentFactory.kt
new file mode 100644
index 000000000000..49041ed0d652
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/dagger/factory/KosmosVolumePanelComponentFactory.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.volume.panel.dagger.factory
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.volume.panel.componentsFactory
+import com.android.systemui.volume.panel.componentsInteractor
+import com.android.systemui.volume.panel.componentsLayoutManager
+import com.android.systemui.volume.panel.dagger.VolumePanelComponent
+import com.android.systemui.volume.panel.domain.interactor.ComponentsInteractor
+import com.android.systemui.volume.panel.ui.composable.ComponentsFactory
+import com.android.systemui.volume.panel.ui.layout.ComponentsLayoutManager
+import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
+import kotlinx.coroutines.CoroutineScope
+
+class KosmosVolumePanelComponentFactory(private val kosmos: Kosmos) : VolumePanelComponentFactory {
+
+ override fun create(viewModel: VolumePanelViewModel): VolumePanelComponent =
+ object : VolumePanelComponent {
+
+ override fun coroutineScope(): CoroutineScope = kosmos.testScope.backgroundScope
+
+ override fun componentsInteractor(): ComponentsInteractor = kosmos.componentsInteractor
+
+ override fun componentsFactory(): ComponentsFactory = kosmos.componentsFactory
+
+ override fun componentsLayoutManager(): ComponentsLayoutManager =
+ kosmos.componentsLayoutManager
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/domain/TestComponentAvailabilityCriteria.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/domain/TestComponentAvailabilityCriteria.kt
new file mode 100644
index 000000000000..5ab92745ae69
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/domain/TestComponentAvailabilityCriteria.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.domain
+
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+
+class TestComponentAvailabilityCriteria(val isAvailable: Boolean) : ComponentAvailabilityCriteria {
+
+ override fun isAvailable(): Flow<Boolean> = flowOf(isAvailable)
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/ui/layout/FakeComponentsLayoutManager.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/ui/layout/FakeComponentsLayoutManager.kt
new file mode 100644
index 000000000000..655d8f7ad952
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/ui/layout/FakeComponentsLayoutManager.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.systemui.volume.panel.ui.layout
+
+import com.android.systemui.volume.panel.ui.viewmodel.ComponentState
+import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelState
+
+class FakeComponentsLayoutManager(
+ private val isBottomBar: (components: ComponentState) -> Boolean
+) : ComponentsLayoutManager {
+
+ override fun layout(
+ volumePanelState: VolumePanelState,
+ components: Collection<ComponentState>
+ ): ComponentsLayout {
+ return ComponentsLayout(
+ components
+ .filter { componentState -> !isBottomBar(componentState) }
+ .sortedBy { it.key },
+ components.find(isBottomBar)!!,
+ )
+ }
+}
diff --git a/ravenwood/framework-minus-apex-ravenwood-policies.txt b/ravenwood/framework-minus-apex-ravenwood-policies.txt
index e33fff117d41..16f99e9289db 100644
--- a/ravenwood/framework-minus-apex-ravenwood-policies.txt
+++ b/ravenwood/framework-minus-apex-ravenwood-policies.txt
@@ -138,6 +138,9 @@ class com.android.modules.utils.TypedXmlSerializer stubclass
class android.net.Uri stubclass
class android.net.UriCodec stubclass
+# Telephony
+class android.telephony.PinResult stubclass
+
# Just enough to support mocking, no further functionality
class android.content.Context stub
method <init> ()V stub
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
index 91c522e82cce..b3dbcde9d324 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
@@ -22,7 +22,7 @@ import android.os.Looper;
public class RavenwoodRuleImpl {
private static final String MAIN_THREAD_NAME = "RavenwoodMain";
- public static boolean isUnderRavenwood() {
+ public static boolean isOnRavenwood() {
return true;
}
diff --git a/ravenwood/junit-src/android/platform/test/annotations/IncludeUnderRavenwood.java b/ravenwood/junit-src/android/platform/test/annotations/DisabledOnRavenwood.java
index 0b2e32f67960..4bf09807c360 100644
--- a/ravenwood/junit-src/android/platform/test/annotations/IncludeUnderRavenwood.java
+++ b/ravenwood/junit-src/android/platform/test/annotations/DisabledOnRavenwood.java
@@ -23,15 +23,16 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
- * Tests marked with this annotation are included when running under a Ravenwood test environment.
+ * Tests marked with this annotation are disabled when running under a Ravenwood test environment.
*
* A more specific method-level annotation always takes precedence over any class-level
- * annotation, and an {@link IncludeUnderRavenwood} annotation always takes precedence over
- * an {@link ExcludeUnderRavenwood} annotation.
+ * annotation, and an {@link EnabledOnRavenwood} annotation always takes precedence over
+ * an {@link DisabledOnRavenwood} annotation.
*
* This annotation only takes effect when the containing class has a {@code
- * RavenwoodRule} configured. Ignoring is accomplished by throwing an {@code org.junit
- * .AssumptionViolatedException} which test infrastructure treats as being ignored.
+ * RavenwoodRule} or {@code RavenwoodClassRule} configured. Ignoring is accomplished by
+ * throwing an {@code org.junit.AssumptionViolatedException} which test infrastructure treats as
+ * being ignored.
*
* This annotation has no effect on any other non-Ravenwood test environments.
*
@@ -40,5 +41,5 @@ import java.lang.annotation.Target;
@Inherited
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
-public @interface IncludeUnderRavenwood {
+public @interface DisabledOnRavenwood {
}
diff --git a/ravenwood/junit-src/android/platform/test/annotations/ExcludeUnderRavenwood.java b/ravenwood/junit-src/android/platform/test/annotations/EnabledOnRavenwood.java
index 2ef381e8682c..9dd0a58a9db1 100644
--- a/ravenwood/junit-src/android/platform/test/annotations/ExcludeUnderRavenwood.java
+++ b/ravenwood/junit-src/android/platform/test/annotations/EnabledOnRavenwood.java
@@ -23,15 +23,16 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
- * Tests marked with this annotation are excluded when running under a Ravenwood test environment.
+ * Tests marked with this annotation are enabled when running under a Ravenwood test environment.
*
* A more specific method-level annotation always takes precedence over any class-level
- * annotation, and an {@link IncludeUnderRavenwood} annotation always takes precedence over
- * an {@link ExcludeUnderRavenwood} annotation.
+ * annotation, and an {@link EnabledOnRavenwood} annotation always takes precedence over
+ * an {@link DisabledOnRavenwood} annotation.
*
* This annotation only takes effect when the containing class has a {@code
- * RavenwoodRule} configured. Ignoring is accomplished by throwing an {@code org.junit
- * .AssumptionViolatedException} which test infrastructure treats as being ignored.
+ * RavenwoodRule} or {@code RavenwoodClassRule} configured. Ignoring is accomplished by
+ * throwing an {@code org.junit.AssumptionViolatedException} which test infrastructure treats as
+ * being ignored.
*
* This annotation has no effect on any other non-Ravenwood test environments.
*
@@ -40,5 +41,5 @@ import java.lang.annotation.Target;
@Inherited
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
-public @interface ExcludeUnderRavenwood {
+public @interface EnabledOnRavenwood {
}
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodClassRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodClassRule.java
new file mode 100644
index 000000000000..68b163ede89c
--- /dev/null
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodClassRule.java
@@ -0,0 +1,63 @@
+/*
+ * 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.platform.test.ravenwood.RavenwoodRule.ENABLE_PROBE_IGNORED;
+import static android.platform.test.ravenwood.RavenwoodRule.IS_ON_RAVENWOOD;
+import static android.platform.test.ravenwood.RavenwoodRule.shouldEnableOnRavenwood;
+
+import android.platform.test.annotations.DisabledOnRavenwood;
+import android.platform.test.annotations.EnabledOnRavenwood;
+
+import org.junit.Assume;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+/**
+ * {@code @ClassRule} that respects Ravenwood-specific class annotations. This rule has no effect
+ * when tests are run on non-Ravenwood test environments.
+ *
+ * By default, all tests are executed on Ravenwood, but annotations such as
+ * {@link DisabledOnRavenwood} and {@link EnabledOnRavenwood} can be used at both the method
+ * and class level to "ignore" tests that may not be ready.
+ */
+public class RavenwoodClassRule implements TestRule {
+ @Override
+ public Statement apply(Statement base, Description description) {
+ // No special treatment when running outside Ravenwood; run tests as-is
+ if (!IS_ON_RAVENWOOD) {
+ return base;
+ }
+
+ if (ENABLE_PROBE_IGNORED) {
+ // Pass through to possible underlying RavenwoodRule for both environment
+ // configuration and handling method-level annotations
+ return base;
+ } else {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ Assume.assumeTrue(shouldEnableOnRavenwood(description));
+ // Pass through to possible underlying RavenwoodRule for both environment
+ // configuration and handling method-level annotations
+ base.evaluate();
+ }
+ };
+ }
+ }
+}
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
index dd442f08321f..fef77f9d5ed7 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
@@ -18,9 +18,9 @@ package android.platform.test.ravenwood;
import static org.junit.Assert.fail;
-import android.platform.test.annotations.ExcludeUnderRavenwood;
+import android.platform.test.annotations.DisabledOnRavenwood;
import android.platform.test.annotations.IgnoreUnderRavenwood;
-import android.platform.test.annotations.IncludeUnderRavenwood;
+import android.platform.test.annotations.EnabledOnRavenwood;
import org.junit.Assume;
import org.junit.rules.TestRule;
@@ -30,29 +30,36 @@ import org.junit.runners.model.Statement;
import java.util.concurrent.atomic.AtomicInteger;
/**
- * THIS RULE IS EXPERIMENTAL. REACH OUT TO g/ravenwood BEFORE USING IT, OR YOU HAVE ANY
- * QUESTIONS ABOUT IT.
+ * {@code @Rule} that configures the Ravenwood test environment. This rule has no effect when
+ * tests are run on non-Ravenwood test environments.
*
- * @hide
+ * This rule initializes and resets the Ravenwood environment between each test method to offer a
+ * hermetic testing environment.
+ *
+ * By default, all tests are executed on Ravenwood, but annotations such as
+ * {@link DisabledOnRavenwood} and {@link EnabledOnRavenwood} can be used at both the method
+ * and class level to "ignore" tests that may not be ready. When needed, a
+ * {@link RavenwoodClassRule} can be used in addition to a {@link RavenwoodRule} to ignore tests
+ * before a test class is fully initialized.
*/
public class RavenwoodRule implements TestRule {
- private static AtomicInteger sNextPid = new AtomicInteger(100);
-
- private static final boolean IS_UNDER_RAVENWOOD = RavenwoodRuleImpl.isUnderRavenwood();
+ static final boolean IS_ON_RAVENWOOD = RavenwoodRuleImpl.isOnRavenwood();
/**
- * When probing is enabled, all tests will be unconditionally run under Ravenwood to detect
+ * When probing is enabled, all tests will be unconditionally run on Ravenwood to detect
* cases where a test is able to pass despite being marked as {@code IgnoreUnderRavenwood}.
*
* This is typically helpful for internal maintainers discovering tests that had previously
* been ignored, but now have enough Ravenwood-supported functionality to be enabled.
*/
- private static final boolean ENABLE_PROBE_IGNORED = false; // DO NOT SUBMIT WITH TRUE
+ static final boolean ENABLE_PROBE_IGNORED = false; // DO NOT SUBMIT WITH TRUE
private static final int SYSTEM_UID = 1000;
private static final int NOBODY_UID = 9999;
private static final int FIRST_APPLICATION_UID = 10000;
+ private static final AtomicInteger sNextPid = new AtomicInteger(100);
+
/**
* Unless the test author requests differently, run as "nobody", and give each collection of
* tests its own unique PID.
@@ -75,7 +82,7 @@ public class RavenwoodRule implements TestRule {
/**
* Configure the identity of this process to be the system UID for the duration of the
- * test. Has no effect under non-Ravenwood environments.
+ * test. Has no effect on non-Ravenwood environments.
*/
public Builder setProcessSystem() {
mRule.mUid = SYSTEM_UID;
@@ -84,7 +91,7 @@ public class RavenwoodRule implements TestRule {
/**
* Configure the identity of this process to be an app UID for the duration of the
- * test. Has no effect under non-Ravenwood environments.
+ * test. Has no effect on non-Ravenwood environments.
*/
public Builder setProcessApp() {
mRule.mUid = FIRST_APPLICATION_UID;
@@ -93,7 +100,7 @@ public class RavenwoodRule implements TestRule {
/**
* Configure a "main" thread to be available for the duration of the test, as defined
- * by {@code Looper.getMainLooper()}. Has no effect under non-Ravenwood environments.
+ * by {@code Looper.getMainLooper()}. Has no effect on non-Ravenwood environments.
*/
public Builder setProvideMainThread(boolean provideMainThread) {
mRule.mProvideMainThread = provideMainThread;
@@ -108,7 +115,7 @@ public class RavenwoodRule implements TestRule {
* All properties in the {@code debug.*} namespace are automatically mutable, with no
* developer action required.
*
- * Has no effect under non-Ravenwood environments.
+ * Has no effect on non-Ravenwood environments.
*/
public Builder setSystemPropertyImmutable(/* @NonNull */ String key,
/* @Nullable */ Object value) {
@@ -125,7 +132,7 @@ public class RavenwoodRule implements TestRule {
* All properties in the {@code debug.*} namespace are automatically mutable, with no
* developer action required.
*
- * Has no effect under non-Ravenwood environments.
+ * Has no effect on non-Ravenwood environments.
*/
public Builder setSystemPropertyMutable(/* @NonNull */ String key,
/* @Nullable */ Object value) {
@@ -140,42 +147,51 @@ public class RavenwoodRule implements TestRule {
}
/**
- * Return if the current process is running under a Ravenwood test environment.
+ * @deprecated replaced by {@link #isOnRavenwood()}
*/
+ @Deprecated
public static boolean isUnderRavenwood() {
- return IS_UNDER_RAVENWOOD;
+ return IS_ON_RAVENWOOD;
+ }
+
+ /**
+ * Return if the current process is running on a Ravenwood test environment.
+ */
+ public static boolean isOnRavenwood() {
+ return IS_ON_RAVENWOOD;
}
/**
- * Determine if the given {@link Description} should be included when running under the
+ * Determine if the given {@link Description} should be enabled when running on the
* Ravenwood test environment.
*
* A more specific method-level annotation always takes precedence over any class-level
- * annotation, and an {@link IncludeUnderRavenwood} annotation always takes precedence over
- * an {@link ExcludeUnderRavenwood} annotation.
+ * annotation, and an {@link EnabledOnRavenwood} annotation always takes precedence over
+ * an {@link DisabledOnRavenwood} annotation.
*/
- private boolean shouldIncludeUnderRavenwood(Description description) {
- // Stopgap for http://g/ravenwood/EPAD-N5ntxM
- if (description.getMethodName().endsWith("$noRavenwood")) {
- return false;
- }
-
+ static boolean shouldEnableOnRavenwood(Description description) {
// First, consult any method-level annotations
- if (description.getAnnotation(IncludeUnderRavenwood.class) != null) {
- return true;
- }
- if (description.getAnnotation(ExcludeUnderRavenwood.class) != null) {
- return false;
- }
- if (description.getAnnotation(IgnoreUnderRavenwood.class) != null) {
- return false;
+ if (description.isTest()) {
+ // Stopgap for http://g/ravenwood/EPAD-N5ntxM
+ if (description.getMethodName().endsWith("$noRavenwood")) {
+ return false;
+ }
+ if (description.getAnnotation(EnabledOnRavenwood.class) != null) {
+ return true;
+ }
+ if (description.getAnnotation(DisabledOnRavenwood.class) != null) {
+ return false;
+ }
+ if (description.getAnnotation(IgnoreUnderRavenwood.class) != null) {
+ return false;
+ }
}
// Otherwise, consult any class-level annotations
- if (description.getTestClass().getAnnotation(IncludeUnderRavenwood.class) != null) {
+ if (description.getTestClass().getAnnotation(EnabledOnRavenwood.class) != null) {
return true;
}
- if (description.getTestClass().getAnnotation(ExcludeUnderRavenwood.class) != null) {
+ if (description.getTestClass().getAnnotation(DisabledOnRavenwood.class) != null) {
return false;
}
if (description.getTestClass().getAnnotation(IgnoreUnderRavenwood.class) != null) {
@@ -189,7 +205,7 @@ public class RavenwoodRule implements TestRule {
@Override
public Statement apply(Statement base, Description description) {
// No special treatment when running outside Ravenwood; run tests as-is
- if (!IS_UNDER_RAVENWOOD) {
+ if (!IS_ON_RAVENWOOD) {
return base;
}
@@ -207,7 +223,7 @@ public class RavenwoodRule implements TestRule {
return new Statement() {
@Override
public void evaluate() throws Throwable {
- Assume.assumeTrue(shouldIncludeUnderRavenwood(description));
+ Assume.assumeTrue(shouldEnableOnRavenwood(description));
RavenwoodRuleImpl.init(RavenwoodRule.this);
try {
@@ -221,7 +237,7 @@ public class RavenwoodRule implements TestRule {
/**
* Run the given {@link Statement} with probing enabled. All tests will be unconditionally
- * run under Ravenwood to detect cases where a test is able to pass despite being marked as
+ * run on Ravenwood to detect cases where a test is able to pass despite being marked as
* {@code IgnoreUnderRavenwood}.
*/
private Statement applyProbeIgnored(Statement base, Description description) {
@@ -234,13 +250,13 @@ public class RavenwoodRule implements TestRule {
} catch (Throwable t) {
// If the test isn't included, eat the exception and report the
// assumption failure that test authors expect; otherwise throw
- Assume.assumeTrue(shouldIncludeUnderRavenwood(description));
+ Assume.assumeTrue(shouldEnableOnRavenwood(description));
throw t;
} finally {
RavenwoodRuleImpl.reset(RavenwoodRule.this);
}
- if (!shouldIncludeUnderRavenwood(description)) {
+ if (!shouldEnableOnRavenwood(description)) {
fail("Test wasn't included under Ravenwood, but it actually "
+ "passed under Ravenwood; consider updating annotations");
}
diff --git a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
index 0ff6a1ad846b..d0c2e18be8df 100644
--- a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
+++ b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
@@ -17,7 +17,7 @@
package android.platform.test.ravenwood;
public class RavenwoodRuleImpl {
- public static boolean isUnderRavenwood() {
+ public static boolean isOnRavenwood() {
return false;
}
diff --git a/ravenwood/ravenwood-annotation-allowed-classes.txt b/ravenwood/ravenwood-annotation-allowed-classes.txt
index 5700f000cbc5..eaf01a32592e 100644
--- a/ravenwood/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/ravenwood-annotation-allowed-classes.txt
@@ -1,5 +1,6 @@
# Only classes listed here can use the Ravenwood annotations.
+com.android.internal.display.BrightnessSynchronizer
com.android.internal.util.ArrayUtils
com.android.internal.logging.MetricsLogger
com.android.internal.logging.testing.FakeMetricsLogger
@@ -31,6 +32,7 @@ android.util.LruCache
android.util.MonthDisplayHelper
android.util.RecurrenceRule
android.util.RotationUtils
+android.util.Singleton
android.util.Slog
android.util.SparseDoubleArray
android.util.SparseSetArray
@@ -76,6 +78,7 @@ android.os.Trace
android.os.UidBatteryConsumer
android.os.UidBatteryConsumer$Builder
android.os.UserHandle
+android.os.UserManager
android.os.WorkSource
android.content.ClipData
@@ -89,16 +92,22 @@ android.content.Intent
android.content.IntentFilter
android.content.UriMatcher
-android.content.pm.PackageInfo
+android.content.pm.ActivityInfo
android.content.pm.ApplicationInfo
-android.content.pm.PackageItemInfo
android.content.pm.ComponentInfo
-android.content.pm.ActivityInfo
-android.content.pm.ServiceInfo
+android.content.pm.PackageInfo
+android.content.pm.PackageItemInfo
+android.content.pm.PackageManager$Flags
+android.content.pm.PackageManager$PackageInfoFlags
+android.content.pm.PackageManager$ApplicationInfoFlags
+android.content.pm.PackageManager$ComponentInfoFlags
+android.content.pm.PackageManager$ResolveInfoFlags
android.content.pm.PathPermission
android.content.pm.ProviderInfo
android.content.pm.ResolveInfo
+android.content.pm.ServiceInfo
android.content.pm.Signature
+android.content.pm.UserInfo
android.database.AbstractCursor
android.database.CharArrayBuffer
@@ -134,6 +143,10 @@ android.content.ContentProvider
android.metrics.LogMaker
+android.view.Display$HdrCapabilities
+android.view.Display$Mode
+android.view.DisplayInfo
+
com.android.server.LocalServices
com.android.server.power.stats.BatteryStatsImpl
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index d656892062d1..57c05396992e 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -30,10 +30,7 @@ import static android.companion.virtual.VirtualDeviceManager.ACTION_VIRTUAL_DEVI
import static android.companion.virtual.VirtualDeviceManager.EXTRA_VIRTUAL_DEVICE_ID;
import static android.content.Context.DEVICE_ID_DEFAULT;
import static android.provider.Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED;
-import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
-import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
import static android.view.accessibility.AccessibilityManager.FlashNotificationReason;
-import static android.view.accessibility.AccessibilityManager.ShortcutType;
import static com.android.internal.accessibility.AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME;
import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_COMPONENT_NAME;
@@ -142,6 +139,7 @@ import com.android.internal.R;
import com.android.internal.accessibility.AccessibilityShortcutController;
import com.android.internal.accessibility.AccessibilityShortcutController.FrameworkFeatureInfo;
import com.android.internal.accessibility.AccessibilityShortcutController.LaunchableFrameworkFeatureInfo;
+import com.android.internal.accessibility.common.ShortcutConstants;
import com.android.internal.accessibility.dialog.AccessibilityButtonChooserActivity;
import com.android.internal.accessibility.dialog.AccessibilityShortcutChooserActivity;
import com.android.internal.accessibility.util.AccessibilityUtils;
@@ -1721,7 +1719,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
mMainHandler.sendMessage(obtainMessage(
AccessibilityManagerService::performAccessibilityShortcutInternal, this,
- displayId, ACCESSIBILITY_BUTTON, targetName));
+ displayId, ShortcutConstants.UserShortcutType.SOFTWARE, targetName));
}
/**
@@ -2200,11 +2198,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
private void showAccessibilityTargetsSelection(int displayId,
- @ShortcutType int shortcutType) {
+ @ShortcutConstants.UserShortcutType int shortcutType) {
final Intent intent = new Intent(AccessibilityManager.ACTION_CHOOSE_ACCESSIBILITY_BUTTON);
- final String chooserClassName = (shortcutType == ACCESSIBILITY_SHORTCUT_KEY)
- ? AccessibilityShortcutChooserActivity.class.getName()
- : AccessibilityButtonChooserActivity.class.getName();
+ final String chooserClassName =
+ (shortcutType == ShortcutConstants.UserShortcutType.HARDWARE)
+ ? AccessibilityShortcutChooserActivity.class.getName()
+ : AccessibilityButtonChooserActivity.class.getName();
intent.setClassName(CHOOSER_PACKAGE_NAME, chooserClassName);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
final Bundle bundle = ActivityOptions.makeBasic().setLaunchDisplayId(displayId).toBundle();
@@ -3275,7 +3274,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
final Set<String> currentTargets =
- userState.getShortcutTargetsLocked(ACCESSIBILITY_SHORTCUT_KEY);
+ userState.getShortcutTargetsLocked(ShortcutConstants.UserShortcutType.HARDWARE);
if (targetsFromSetting.equals(currentTargets)) {
return false;
}
@@ -3291,7 +3290,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
userState.mUserId, str -> str, targetsFromSetting);
final Set<String> currentTargets =
- userState.getShortcutTargetsLocked(ACCESSIBILITY_BUTTON);
+ userState.getShortcutTargetsLocked(ShortcutConstants.UserShortcutType.SOFTWARE);
if (targetsFromSetting.equals(currentTargets)) {
return false;
}
@@ -3346,7 +3345,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
*/
private void updateAccessibilityShortcutKeyTargetsLocked(AccessibilityUserState userState) {
final Set<String> currentTargets =
- userState.getShortcutTargetsLocked(ACCESSIBILITY_SHORTCUT_KEY);
+ userState.getShortcutTargetsLocked(ShortcutConstants.UserShortcutType.HARDWARE);
final int lastSize = currentTargets.size();
if (lastSize == 0) {
return;
@@ -3531,7 +3530,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
final Set<String> currentTargets =
- userState.getShortcutTargetsLocked(ACCESSIBILITY_BUTTON);
+ userState.getShortcutTargetsLocked(ShortcutConstants.UserShortcutType.SOFTWARE);
final int lastSize = currentTargets.size();
if (lastSize == 0) {
return;
@@ -3571,7 +3570,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
return;
}
final Set<String> buttonTargets =
- userState.getShortcutTargetsLocked(ACCESSIBILITY_BUTTON);
+ userState.getShortcutTargetsLocked(ShortcutConstants.UserShortcutType.SOFTWARE);
int lastSize = buttonTargets.size();
buttonTargets.removeIf(name -> {
if (packageName != null && name != null && !name.contains(packageName)) {
@@ -3608,7 +3607,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
lastSize = buttonTargets.size();
final Set<String> shortcutKeyTargets =
- userState.getShortcutTargetsLocked(ACCESSIBILITY_SHORTCUT_KEY);
+ userState.getShortcutTargetsLocked(ShortcutConstants.UserShortcutType.HARDWARE);
userState.mEnabledServices.forEach(componentName -> {
if (packageName != null && componentName != null
&& !packageName.equals(componentName.getPackageName())) {
@@ -3665,16 +3664,18 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
return;
}
final ComponentName serviceName = service.getComponentName();
- if (userState.removeShortcutTargetLocked(ACCESSIBILITY_SHORTCUT_KEY, serviceName)) {
+ if (userState.removeShortcutTargetLocked(
+ ShortcutConstants.UserShortcutType.HARDWARE, serviceName)) {
final Set<String> currentTargets = userState.getShortcutTargetsLocked(
- ACCESSIBILITY_SHORTCUT_KEY);
+ ShortcutConstants.UserShortcutType.HARDWARE);
persistColonDelimitedSetToSettingLocked(
Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
userState.mUserId, currentTargets, str -> str);
}
- if (userState.removeShortcutTargetLocked(ACCESSIBILITY_BUTTON, serviceName)) {
+ if (userState.removeShortcutTargetLocked(
+ ShortcutConstants.UserShortcutType.SOFTWARE, serviceName)) {
final Set<String> currentTargets = userState.getShortcutTargetsLocked(
- ACCESSIBILITY_BUTTON);
+ ShortcutConstants.UserShortcutType.SOFTWARE);
persistColonDelimitedSetToSettingLocked(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
userState.mUserId, currentTargets, str -> str);
}
@@ -3750,7 +3751,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
mMainHandler.sendMessage(obtainMessage(
AccessibilityManagerService::performAccessibilityShortcutInternal, this,
- Display.DEFAULT_DISPLAY, ACCESSIBILITY_SHORTCUT_KEY, targetName));
+ Display.DEFAULT_DISPLAY, ShortcutConstants.UserShortcutType.HARDWARE, targetName));
}
/**
@@ -3763,7 +3764,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
* specified target.
*/
private void performAccessibilityShortcutInternal(int displayId,
- @ShortcutType int shortcutType, @Nullable String targetName) {
+ @ShortcutConstants.UserShortcutType int shortcutType, @Nullable String targetName) {
final List<String> shortcutTargets = getAccessibilityShortcutTargetsInternal(shortcutType);
if (shortcutTargets.isEmpty()) {
Slog.d(LOG_TAG, "No target to perform shortcut, shortcutType=" + shortcutType);
@@ -3814,7 +3815,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
private boolean performAccessibilityFrameworkFeature(int displayId,
- ComponentName assignedTarget, @ShortcutType int shortcutType) {
+ ComponentName assignedTarget, @ShortcutConstants.UserShortcutType int shortcutType) {
final Map<ComponentName, FrameworkFeatureInfo> frameworkFeatureMap =
AccessibilityShortcutController.getFrameworkShortcutFeaturesMap();
if (!frameworkFeatureMap.containsKey(assignedTarget)) {
@@ -3863,12 +3864,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
/**
* Perform accessibility service shortcut action.
*
- * 1) For {@link AccessibilityManager#ACCESSIBILITY_BUTTON} type and services targeting sdk
+ * 1) For {@link ShortcutConstants.UserShortcutType.SOFTWARE} type and services targeting sdk
* version <= Q: callbacks to accessibility service if service is bounded and requests
* accessibility button.
- * 2) For {@link AccessibilityManager#ACCESSIBILITY_SHORTCUT_KEY} type and service targeting sdk
+ * 2) For {@link ShortcutConstants.UserShortcutType.HARDWARE} type and service targeting sdk
* version <= Q: turns on / off the accessibility service.
- * 3) For {@link AccessibilityManager#ACCESSIBILITY_SHORTCUT_KEY} type and service targeting sdk
+ * 3) For {@link ShortcutConstants.UserShortcutType.HARDWARE} type and service targeting sdk
* version > Q and request accessibility button: turn on the accessibility service if it's
* not in the enabled state.
* (It'll happen when a service is disabled and assigned to shortcut then upgraded.)
@@ -3879,7 +3880,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
* button.
*/
private boolean performAccessibilityShortcutTargetService(int displayId,
- @ShortcutType int shortcutType, ComponentName assignedTarget) {
+ @ShortcutConstants.UserShortcutType int shortcutType, ComponentName assignedTarget) {
synchronized (mLock) {
final AccessibilityUserState userState = getCurrentUserStateLocked();
final AccessibilityServiceInfo installedServiceInfo =
@@ -3897,7 +3898,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
final boolean requestA11yButton = (installedServiceInfo.flags
& FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0;
// Turns on / off the accessibility service
- if ((targetSdk <= Build.VERSION_CODES.Q && shortcutType == ACCESSIBILITY_SHORTCUT_KEY)
+ if ((targetSdk <= Build.VERSION_CODES.Q
+ && shortcutType == ShortcutConstants.UserShortcutType.HARDWARE)
|| (targetSdk > Build.VERSION_CODES.Q && !requestA11yButton)) {
if (serviceConnection == null) {
logAccessibilityShortcutActivated(mContext, assignedTarget, shortcutType,
@@ -3911,7 +3913,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
return true;
}
- if (shortcutType == ACCESSIBILITY_SHORTCUT_KEY && targetSdk > Build.VERSION_CODES.Q
+ if (shortcutType == ShortcutConstants.UserShortcutType.HARDWARE
+ && targetSdk > Build.VERSION_CODES.Q
&& requestA11yButton) {
if (!userState.getEnabledServicesLocked().contains(assignedTarget)) {
enableAccessibilityServiceLocked(assignedTarget, mCurrentUserId);
@@ -3941,7 +3944,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
@Override
- public List<String> getAccessibilityShortcutTargets(@ShortcutType int shortcutType) {
+ public List<String> getAccessibilityShortcutTargets(
+ @ShortcutConstants.UserShortcutType int shortcutType) {
if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(LOG_TAG + ".getAccessibilityShortcutTargets",
FLAGS_ACCESSIBILITY_MANAGER, "shortcutType=" + shortcutType);
@@ -3955,12 +3959,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
return getAccessibilityShortcutTargetsInternal(shortcutType);
}
- private List<String> getAccessibilityShortcutTargetsInternal(@ShortcutType int shortcutType) {
+ private List<String> getAccessibilityShortcutTargetsInternal(
+ @ShortcutConstants.UserShortcutType int shortcutType) {
synchronized (mLock) {
final AccessibilityUserState userState = getCurrentUserStateLocked();
final ArrayList<String> shortcutTargets = new ArrayList<>(
userState.getShortcutTargetsLocked(shortcutType));
- if (shortcutType != ACCESSIBILITY_BUTTON) {
+ if (shortcutType != ShortcutConstants.UserShortcutType.SOFTWARE) {
return shortcutTargets;
}
// Adds legacy a11y services requesting a11y button into the list.
@@ -4423,7 +4428,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
}
// Warning is not required if the service is already assigned to a shortcut.
- for (int shortcutType : AccessibilityManager.SHORTCUT_TYPES) {
+ for (int shortcutType : ShortcutConstants.USER_SHORTCUT_TYPES) {
if (getAccessibilityShortcutTargets(shortcutType).contains(
componentName.flattenToString())) {
return false;
@@ -4603,8 +4608,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
public void onShellCommand(FileDescriptor in, FileDescriptor out,
FileDescriptor err, String[] args, ShellCallback callback,
ResultReceiver resultReceiver) {
- new AccessibilityShellCommand(this, mSystemActionPerformer).exec(this, in, out, err, args,
- callback, resultReceiver);
+ new AccessibilityShellCommand(this, mSystemActionPerformer)
+ .exec(this, in, out, err, args, callback, resultReceiver);
}
private final class InteractionBridge {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
index 68ee78076f3d..41165b6aed5a 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
@@ -24,9 +24,6 @@ import static android.accessibilityservice.AccessibilityService.SHOW_MODE_IGNORE
import static android.accessibilityservice.AccessibilityService.SHOW_MODE_MASK;
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_NONE;
-import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
-import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
-import static android.view.accessibility.AccessibilityManager.ShortcutType;
import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
@@ -51,6 +48,7 @@ import android.view.accessibility.IAccessibilityManagerClient;
import com.android.internal.R;
import com.android.internal.accessibility.AccessibilityShortcutController;
+import com.android.internal.accessibility.common.ShortcutConstants;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -757,13 +755,21 @@ class AccessibilityUserState {
* @param shortcutType The shortcut type.
* @return The array set of the strings
*/
- public ArraySet<String> getShortcutTargetsLocked(@ShortcutType int shortcutType) {
- if (shortcutType == ACCESSIBILITY_SHORTCUT_KEY) {
+ public ArraySet<String> getShortcutTargetsLocked(
+ @ShortcutConstants.UserShortcutType int shortcutType) {
+ if (shortcutType == ShortcutConstants.UserShortcutType.HARDWARE) {
return mAccessibilityShortcutKeyTargets;
- } else if (shortcutType == ACCESSIBILITY_BUTTON) {
+ } else if (shortcutType == ShortcutConstants.UserShortcutType.SOFTWARE) {
return mAccessibilityButtonTargets;
+ } else if ((shortcutType == ShortcutConstants.UserShortcutType.TRIPLETAP
+ && isMagnificationSingleFingerTripleTapEnabledLocked()) || (
+ shortcutType == ShortcutConstants.UserShortcutType.TWO_FINGERS_TRIPLE_TAP
+ && isMagnificationTwoFingerTripleTapEnabledLocked())) {
+ ArraySet<String> targets = new ArraySet<>();
+ targets.add(MAGNIFICATION_CONTROLLER_NAME);
+ return targets;
}
- return null;
+ return new ArraySet<>();
}
/**
@@ -802,12 +808,22 @@ class AccessibilityUserState {
/**
* Removes given shortcut target in the list.
*
- * @param shortcutType The shortcut type.
- * @param target The component name of the shortcut target.
+ * @param shortcutType one of {@link ShortcutConstants.UserShortcutType.HARDWARE} or
+ * {@link ShortcutConstants.UserShortcutType.SOFTWARE}. Other types are not
+ * implemented.
+ * @param target The component name of the shortcut target.
* @return true if the shortcut target is removed.
*/
- public boolean removeShortcutTargetLocked(@ShortcutType int shortcutType,
+ public boolean removeShortcutTargetLocked(@ShortcutConstants.UserShortcutType int shortcutType,
ComponentName target) {
+ if (shortcutType == ShortcutConstants.UserShortcutType.TRIPLETAP
+ || shortcutType == ShortcutConstants.UserShortcutType.TWO_FINGERS_TRIPLE_TAP) {
+ throw new UnsupportedOperationException(
+ "removeShortcutTargetLocked only support shortcut type: "
+ + "software and hardware for now"
+ );
+ }
+
return getShortcutTargetsLocked(shortcutType).removeIf(name -> {
ComponentName componentName;
if (name == null
diff --git a/services/autofill/java/com/android/server/autofill/RemoteFillService.java b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
index 5c93991bef8c..f914ed54fbb1 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteFillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
@@ -31,9 +31,12 @@ import android.os.Handler;
import android.os.ICancellationSignal;
import android.os.RemoteException;
import android.service.autofill.AutofillService;
+import android.service.autofill.ConvertCredentialRequest;
+import android.service.autofill.ConvertCredentialResponse;
import android.service.autofill.FillRequest;
import android.service.autofill.FillResponse;
import android.service.autofill.IAutoFillService;
+import android.service.autofill.IConvertCredentialCallback;
import android.service.autofill.IFillCallback;
import android.service.autofill.ISaveCallback;
import android.service.autofill.SaveRequest;
@@ -69,6 +72,7 @@ final class RemoteFillService extends ServiceConnector.Impl<IAutoFillService> {
private int mPendingFillRequestId = INVALID_REQUEST_ID;
private AtomicReference<IFillCallback> mFillCallback;
private AtomicReference<ISaveCallback> mSaveCallback;
+ private AtomicReference<IConvertCredentialCallback> mConvertCredentialCallback;
private final ComponentName mComponentName;
private final boolean mIsCredentialAutofillService;
@@ -81,13 +85,20 @@ final class RemoteFillService extends ServiceConnector.Impl<IAutoFillService> {
extends AbstractRemoteService.VultureCallback<RemoteFillService> {
void onFillRequestSuccess(int requestId, @Nullable FillResponse response,
@NonNull String servicePackageName, int requestFlags);
+
void onFillRequestFailure(int requestId, @Nullable CharSequence message);
+
void onFillRequestTimeout(int requestId);
+
void onSaveRequestSuccess(@NonNull String servicePackageName,
@Nullable IntentSender intentSender);
+
// TODO(b/80093094): add timeout here too?
void onSaveRequestFailure(@Nullable CharSequence message,
@NonNull String servicePackageName);
+
+ void onConvertCredentialRequestSuccess(@NonNull ConvertCredentialResponse
+ convertCredentialResponse);
}
RemoteFillService(Context context, ComponentName componentName, int userId,
@@ -211,6 +222,32 @@ final class RemoteFillService extends ServiceConnector.Impl<IAutoFillService> {
}
}
+ static class IConvertCredentialCallbackDelegate extends IConvertCredentialCallback.Stub {
+
+ private WeakReference<IConvertCredentialCallback> mCallbackWeakRef;
+
+ IConvertCredentialCallbackDelegate(IConvertCredentialCallback callback) {
+ mCallbackWeakRef = new WeakReference(callback);
+ }
+
+ @Override
+ public void onSuccess(ConvertCredentialResponse convertCredentialResponse)
+ throws RemoteException {
+ IConvertCredentialCallback callback = mCallbackWeakRef.get();
+ if (callback != null) {
+ callback.onSuccess(convertCredentialResponse);
+ }
+ }
+
+ @Override
+ public void onFailure(CharSequence message) throws RemoteException {
+ IConvertCredentialCallback callback = mCallbackWeakRef.get();
+ if (callback != null) {
+ callback.onFailure(message);
+ }
+ }
+ }
+
/**
* Wraps an {@link IFillCallback} object using weak reference.
*
@@ -237,6 +274,18 @@ final class RemoteFillService extends ServiceConnector.Impl<IAutoFillService> {
return callback;
}
+ /**
+ * Wraps an {@link IConvertCredentialCallback} object using weak reference
+ */
+ private IConvertCredentialCallback maybeWrapWithWeakReference(
+ IConvertCredentialCallback callback) {
+ if (remoteFillServiceUseWeakReference()) {
+ mConvertCredentialCallback = new AtomicReference<>(callback);
+ return new IConvertCredentialCallbackDelegate(callback);
+ }
+ return callback;
+ }
+
public void onFillCredentialRequest(@NonNull FillRequest request,
IAutoFillManagerClient autofillCallback) {
if (sVerbose) {
@@ -378,6 +427,52 @@ final class RemoteFillService extends ServiceConnector.Impl<IAutoFillService> {
}));
}
+ public void onConvertCredentialRequest(
+ @NonNull ConvertCredentialRequest convertCredentialRequest) {
+ if (sVerbose) Slog.v(TAG, "calling onConvertCredentialRequest()");
+ CompletableFuture<ConvertCredentialResponse>
+ connectThenConvertCredentialRequest = postAsync(
+ remoteService -> {
+ if (sVerbose) {
+ Slog.v(TAG, "calling onConvertCredentialRequest()");
+ }
+ CompletableFuture<ConvertCredentialResponse>
+ convertCredentialCompletableFuture = new CompletableFuture<>();
+ remoteService.onConvertCredentialRequest(convertCredentialRequest,
+ maybeWrapWithWeakReference(
+ new IConvertCredentialCallback.Stub() {
+ @Override
+ public void onSuccess(ConvertCredentialResponse
+ convertCredentialResponse) {
+ convertCredentialCompletableFuture
+ .complete(convertCredentialResponse);
+ }
+
+ @Override
+ public void onFailure(CharSequence message) {
+ String errorMessage =
+ message == null ? "" :
+ String.valueOf(message);
+ convertCredentialCompletableFuture
+ .completeExceptionally(
+ new RuntimeException(errorMessage));
+ }
+ })
+ );
+ return convertCredentialCompletableFuture;
+ }).orTimeout(TIMEOUT_REMOTE_REQUEST_MILLIS, TimeUnit.MILLISECONDS);
+
+ connectThenConvertCredentialRequest.whenComplete(
+ (res, err) -> Handler.getMain().post(() -> {
+ if (err == null) {
+ mCallbacks.onConvertCredentialRequestSuccess(res);
+ } else {
+ // TODO: Add a callback function to log this failure
+ Slog.e(TAG, "Error calling on convert credential request", err);
+ }
+ }));
+ }
+
public void onSaveRequest(@NonNull SaveRequest request) {
postAsync(service -> {
if (sVerbose) Slog.v(TAG, "calling onSaveRequest()");
diff --git a/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java b/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java
index 123470304783..0af703e415ff 100644
--- a/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java
+++ b/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java
@@ -25,6 +25,7 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.IntentSender;
import android.os.Bundle;
+import android.service.autofill.ConvertCredentialResponse;
import android.service.autofill.FillRequest;
import android.service.autofill.FillResponse;
import android.util.Slog;
@@ -98,6 +99,12 @@ final class SecondaryProviderHandler implements RemoteFillService.FillServiceCal
}
+ @Override
+ public void onConvertCredentialRequestSuccess(@NonNull ConvertCredentialResponse
+ convertCredentialResponse) {
+
+ }
+
/**
* Requests a new fill response.
*/
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index f3b74ea00a58..049feeed2fa1 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -24,6 +24,7 @@ import static android.service.autofill.Dataset.PICK_REASON_PCC_DETECTION_PREFERR
import static android.service.autofill.Dataset.PICK_REASON_PROVIDER_DETECTION_ONLY;
import static android.service.autofill.Dataset.PICK_REASON_PROVIDER_DETECTION_PREFERRED_WITH_PCC;
import static android.service.autofill.Dataset.PICK_REASON_UNKNOWN;
+import static android.service.autofill.FillEventHistory.Event.UI_TYPE_CREDMAN_BOTTOM_SHEET;
import static android.service.autofill.FillEventHistory.Event.UI_TYPE_DIALOG;
import static android.service.autofill.FillEventHistory.Event.UI_TYPE_INLINE;
import static android.service.autofill.FillEventHistory.Event.UI_TYPE_MENU;
@@ -44,6 +45,7 @@ import static android.view.autofill.AutofillManager.ACTION_VIEW_ENTERED;
import static android.view.autofill.AutofillManager.ACTION_VIEW_EXITED;
import static android.view.autofill.AutofillManager.COMMIT_REASON_SESSION_DESTROYED;
import static android.view.autofill.AutofillManager.COMMIT_REASON_UNKNOWN;
+import static android.view.autofill.AutofillManager.EXTRA_AUTOFILL_REQUEST_ID;
import static android.view.autofill.AutofillManager.FLAG_SMART_SUGGESTION_SYSTEM;
import static android.view.autofill.AutofillManager.getSmartSuggestionModeToString;
@@ -130,6 +132,7 @@ import android.service.assist.classification.FieldClassificationResponse;
import android.service.autofill.AutofillFieldClassificationService.Scores;
import android.service.autofill.AutofillService;
import android.service.autofill.CompositeUserData;
+import android.service.autofill.ConvertCredentialResponse;
import android.service.autofill.Dataset;
import android.service.autofill.Dataset.DatasetEligibleReason;
import android.service.autofill.Field;
@@ -2429,6 +2432,29 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
removeFromService();
}
+ // FillServiceCallbacks
+ @Override
+ public void onConvertCredentialRequestSuccess(@NonNull ConvertCredentialResponse
+ convertCredentialResponse) {
+ Dataset dataset = convertCredentialResponse.getDataset();
+ Bundle clientState = convertCredentialResponse.getClientState();
+ if (dataset != null) {
+ int requestId = -1;
+ if (clientState != null) {
+ requestId = clientState.getInt(EXTRA_AUTOFILL_REQUEST_ID);
+ } else {
+ Slog.e(TAG, "onConvertCredentialRequestSuccess(): client state is null, this "
+ + "would cause loss in logging.");
+ }
+ // TODO: Add autofill related logging; consider whether to log the index
+ fill(requestId, /* datasetIndex=*/ -1, dataset, UI_TYPE_CREDMAN_BOTTOM_SHEET);
+ } else {
+ // TODO: Add logging to log this error case
+ Slog.e(TAG, "onConvertCredentialRequestSuccess(): dataset inside response is "
+ + "null");
+ }
+ }
+
/**
* Gets the {@link FillContext} for a request.
*
diff --git a/services/backup/flags.aconfig b/services/backup/flags.aconfig
index 4022e3378954..1416c888f790 100644
--- a/services/backup/flags.aconfig
+++ b/services/backup/flags.aconfig
@@ -11,7 +11,7 @@ flag {
flag {
name: "enable_metrics_system_backup_agents"
- namespace: "backup"
+ namespace: "onboarding"
description: "Enable SystemBackupAgent to collect B&R agent metrics by passing an instance of "
"the logger to each BackupHelper."
bug: "296844513"
diff --git a/services/companion/TEST_MAPPING b/services/companion/TEST_MAPPING
index 37c47baa813b..ae6d59129adb 100644
--- a/services/companion/TEST_MAPPING
+++ b/services/companion/TEST_MAPPING
@@ -9,5 +9,10 @@
{
"name": "CtsCompanionDeviceManagerNoCompanionServicesTestCases"
}
+ ],
+ "postsubmit": [
+ {
+ "name": "CtsCompanionDeviceManagerMultiProcessTestCases"
+ }
]
}
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 2e01ced2022b..5019428c5323 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -368,8 +368,10 @@ public class CompanionDeviceManagerService extends SystemService {
if (blueToothDevices != null) {
for (BluetoothDevice bluetoothDevice : blueToothDevices) {
- final List<ParcelUuid> deviceUuids = bluetoothDevice.getUuids() == null
- ? Collections.emptyList() : Arrays.asList(bluetoothDevice.getUuids());
+ final ParcelUuid[] bluetoothDeviceUuids = bluetoothDevice.getUuids();
+
+ final List<ParcelUuid> deviceUuids = ArrayUtils.isEmpty(bluetoothDeviceUuids)
+ ? Collections.emptyList() : Arrays.asList(bluetoothDeviceUuids);
for (AssociationInfo ai:
mAssociationStore.getAssociationsByAddress(bluetoothDevice.getAddress())) {
diff --git a/services/companion/java/com/android/server/companion/InactiveAssociationsRemovalService.java b/services/companion/java/com/android/server/companion/InactiveAssociationsRemovalService.java
index 3482863032f9..aac628cab403 100644
--- a/services/companion/java/com/android/server/companion/InactiveAssociationsRemovalService.java
+++ b/services/companion/java/com/android/server/companion/InactiveAssociationsRemovalService.java
@@ -36,7 +36,8 @@ import com.android.server.LocalServices;
* will be killed if association/role are revoked.
*/
public class InactiveAssociationsRemovalService extends JobService {
- private static final int JOB_ID = InactiveAssociationsRemovalService.class.hashCode();
+ private static final String JOB_NAMESPACE = "companion";
+ private static final int JOB_ID = 1;
private static final long ONE_DAY_INTERVAL = DAYS.toMillis(1);
@Override
@@ -61,7 +62,8 @@ public class InactiveAssociationsRemovalService extends JobService {
static void schedule(Context context) {
Slog.i(TAG, "Scheduling the Association Removal job");
- final JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
+ final JobScheduler jobScheduler =
+ context.getSystemService(JobScheduler.class).forNamespace(JOB_NAMESPACE);
final JobInfo job = new JobInfo.Builder(JOB_ID,
new ComponentName(context, InactiveAssociationsRemovalService.class))
.setRequiresCharging(true)
@@ -71,4 +73,3 @@ public class InactiveAssociationsRemovalService extends JobService {
jobScheduler.schedule(job);
}
}
-
diff --git a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
index 7eca1193ca12..c514f3ef29d0 100644
--- a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
+++ b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
@@ -38,6 +38,7 @@ import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.ArrayUtils;
import com.android.server.companion.AssociationStore;
import com.android.server.companion.ObservableUuid;
import com.android.server.companion.ObservableUuidStore;
@@ -172,8 +173,10 @@ public class BluetoothCompanionDeviceConnectionListener
mAssociationStore.getAssociationsByAddress(device.getAddress());
final List<ObservableUuid> observableUuids =
mObservableUuidStore.getObservableUuidsForUser(userId);
- final List<ParcelUuid> deviceUuids = device.getUuids() == null
- ? Collections.emptyList() : Arrays.asList(device.getUuids());
+ final ParcelUuid[] bluetoothDeviceUuids = device.getUuids();
+
+ final List<ParcelUuid> deviceUuids = ArrayUtils.isEmpty(bluetoothDeviceUuids)
+ ? Collections.emptyList() : Arrays.asList(bluetoothDeviceUuids);
if (DEBUG) {
Log.d(TAG, "onDevice_ConnectivityChanged() " + btDeviceToString(device)
diff --git a/services/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java
index 3b9d92dc3d02..8962bf02ff2e 100644
--- a/services/companion/java/com/android/server/companion/virtual/InputController.java
+++ b/services/companion/java/com/android/server/companion/virtual/InputController.java
@@ -163,7 +163,7 @@ class InputController {
createDeviceInternal(InputDeviceDescriptor.TYPE_MOUSE, deviceName, vendorId, productId,
deviceToken, displayId, phys,
() -> mNativeWrapper.openUinputMouse(deviceName, vendorId, productId, phys));
- mInputManagerInternal.setVirtualMousePointerDisplayId(displayId);
+ setVirtualMousePointerDisplayId(displayId);
}
void createTouchscreen(@NonNull String deviceName, int vendorId, int productId,
@@ -235,8 +235,7 @@ class InputController {
// id if there's another mouse (choose the most recent). The inputDeviceDescriptor must be
// removed from the mInputDeviceDescriptors instance variable prior to this point.
if (inputDeviceDescriptor.isMouse()) {
- if (mInputManagerInternal.getVirtualMousePointerDisplayId()
- == inputDeviceDescriptor.getDisplayId()) {
+ if (getVirtualMousePointerDisplayId() == inputDeviceDescriptor.getDisplayId()) {
updateActivePointerDisplayIdLocked();
}
}
@@ -271,6 +270,7 @@ class InputController {
mWindowManager.setDisplayImePolicy(displayId, policy);
}
+ // TODO(b/293587049): Remove after pointer icon refactor is complete.
@GuardedBy("mLock")
private void updateActivePointerDisplayIdLocked() {
InputDeviceDescriptor mostRecentlyCreatedMouse = null;
@@ -285,11 +285,11 @@ class InputController {
}
}
if (mostRecentlyCreatedMouse != null) {
- mInputManagerInternal.setVirtualMousePointerDisplayId(
+ setVirtualMousePointerDisplayId(
mostRecentlyCreatedMouse.getDisplayId());
} else {
// All mice have been unregistered
- mInputManagerInternal.setVirtualMousePointerDisplayId(Display.INVALID_DISPLAY);
+ setVirtualMousePointerDisplayId(Display.INVALID_DISPLAY);
}
}
@@ -349,10 +349,8 @@ class InputController {
if (inputDeviceDescriptor == null) {
return false;
}
- if (inputDeviceDescriptor.getDisplayId()
- != mInputManagerInternal.getVirtualMousePointerDisplayId()) {
- mInputManagerInternal.setVirtualMousePointerDisplayId(
- inputDeviceDescriptor.getDisplayId());
+ if (inputDeviceDescriptor.getDisplayId() != getVirtualMousePointerDisplayId()) {
+ setVirtualMousePointerDisplayId(inputDeviceDescriptor.getDisplayId());
}
return mNativeWrapper.writeButtonEvent(inputDeviceDescriptor.getNativePointer(),
event.getButtonCode(), event.getAction(), event.getEventTimeNanos());
@@ -380,10 +378,8 @@ class InputController {
if (inputDeviceDescriptor == null) {
return false;
}
- if (inputDeviceDescriptor.getDisplayId()
- != mInputManagerInternal.getVirtualMousePointerDisplayId()) {
- mInputManagerInternal.setVirtualMousePointerDisplayId(
- inputDeviceDescriptor.getDisplayId());
+ if (inputDeviceDescriptor.getDisplayId() != getVirtualMousePointerDisplayId()) {
+ setVirtualMousePointerDisplayId(inputDeviceDescriptor.getDisplayId());
}
return mNativeWrapper.writeRelativeEvent(inputDeviceDescriptor.getNativePointer(),
event.getRelativeX(), event.getRelativeY(), event.getEventTimeNanos());
@@ -397,10 +393,8 @@ class InputController {
if (inputDeviceDescriptor == null) {
return false;
}
- if (inputDeviceDescriptor.getDisplayId()
- != mInputManagerInternal.getVirtualMousePointerDisplayId()) {
- mInputManagerInternal.setVirtualMousePointerDisplayId(
- inputDeviceDescriptor.getDisplayId());
+ if (inputDeviceDescriptor.getDisplayId() != getVirtualMousePointerDisplayId()) {
+ setVirtualMousePointerDisplayId(inputDeviceDescriptor.getDisplayId());
}
return mNativeWrapper.writeScrollEvent(inputDeviceDescriptor.getNativePointer(),
event.getXAxisMovement(), event.getYAxisMovement(), event.getEventTimeNanos());
@@ -415,12 +409,11 @@ class InputController {
throw new IllegalArgumentException(
"Could not get cursor position for input device for given token");
}
- if (inputDeviceDescriptor.getDisplayId()
- != mInputManagerInternal.getVirtualMousePointerDisplayId()) {
- mInputManagerInternal.setVirtualMousePointerDisplayId(
- inputDeviceDescriptor.getDisplayId());
+ if (inputDeviceDescriptor.getDisplayId() != getVirtualMousePointerDisplayId()) {
+ setVirtualMousePointerDisplayId(inputDeviceDescriptor.getDisplayId());
}
- return LocalServices.getService(InputManagerInternal.class).getCursorPosition();
+ return LocalServices.getService(InputManagerInternal.class).getCursorPosition(
+ inputDeviceDescriptor.getDisplayId());
}
}
@@ -847,4 +840,22 @@ class InputController {
/** Returns true if the calling thread is a valid thread for device creation. */
boolean isValidThread();
}
+
+ // TODO(b/293587049): Remove after pointer icon refactor is complete.
+ private void setVirtualMousePointerDisplayId(int displayId) {
+ if (com.android.input.flags.Flags.enablePointerChoreographer()) {
+ // We no longer need to set the pointer display when pointer choreographer is enabled.
+ return;
+ }
+ mInputManagerInternal.setVirtualMousePointerDisplayId(displayId);
+ }
+
+ // TODO(b/293587049): Remove after pointer icon refactor is complete.
+ private int getVirtualMousePointerDisplayId() {
+ if (com.android.input.flags.Flags.enablePointerChoreographer()) {
+ // We no longer need to get the pointer display when pointer choreographer is enabled.
+ return Display.INVALID_DISPLAY;
+ }
+ return mInputManagerInternal.getVirtualMousePointerDisplayId();
+ }
}
diff --git a/services/core/Android.bp b/services/core/Android.bp
index a54a48a7e84e..8e35b7455d02 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -139,6 +139,7 @@ java_library_static {
libs: [
"services.net",
+ "android.frameworks.location.altitude-V2-java",
"android.hardware.common-V2-java",
"android.hardware.light-V2.0-java",
"android.hardware.gnss-V2-java",
@@ -160,7 +161,6 @@ java_library_static {
],
static_libs: [
- "android.frameworks.location.altitude-V2-java", // AIDL
"android.frameworks.vibrator-V1-java", // AIDL
"android.hardware.authsecret-V1.0-java",
"android.hardware.authsecret-V1-java",
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index afb8345249b1..adc0255743c2 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -181,7 +181,6 @@ import android.content.pm.ParceledListSlice;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.pm.ServiceInfo.ForegroundServiceType;
-import android.os.Binder;
import android.os.Build;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
@@ -306,6 +305,57 @@ public final class ActiveServices {
@interface FgsStopReason {}
/**
+ * The policy to be applied to the service bindings; this one means it follows the legacy
+ * behavior.
+ */
+ static final int SERVICE_BIND_OOMADJ_POLICY_LEGACY = 0;
+
+ /**
+ * The policy to be applied to the service bindings; this one means we'll skip
+ * updating the target process's oom adj score / process state for its {@link Service#onCreate}.
+ */
+ static final int SERVICE_BIND_OOMADJ_POLICY_SKIP_OOM_UPDATE_ON_CREATE = 1;
+
+ /**
+ * The policy to be applied to the service bindings; this one means we'll skip
+ * updating the target process's oom adj score / process state for its {@link Service#onBind}.
+ */
+ static final int SERVICE_BIND_OOMADJ_POLICY_SKIP_OOM_UPDATE_ON_BIND = 1 << 1;
+
+ /**
+ * The policy to be applied to the service bindings; this one means we'll skip
+ * updating the target process's oom adj score / process state on setting up the service
+ * connection between the client and the service host process.
+ */
+ static final int SERVICE_BIND_OOMADJ_POLICY_SKIP_OOM_UPDATE_ON_CONNECT = 1 << 2;
+ /**
+ * The policy to be applied to the service bindings; this one means the caller
+ * will be frozen upon calling the bindService APIs.
+ */
+ static final int SERVICE_BIND_OOMADJ_POLICY_FREEZE_CALLER = 1 << 3;
+
+ @IntDef(flag = true, prefix = { "SERVICE_BIND_OOMADJ_POLICY_" }, value = {
+ SERVICE_BIND_OOMADJ_POLICY_LEGACY,
+ SERVICE_BIND_OOMADJ_POLICY_SKIP_OOM_UPDATE_ON_CREATE,
+ SERVICE_BIND_OOMADJ_POLICY_SKIP_OOM_UPDATE_ON_BIND,
+ SERVICE_BIND_OOMADJ_POLICY_SKIP_OOM_UPDATE_ON_CONNECT,
+ SERVICE_BIND_OOMADJ_POLICY_FREEZE_CALLER,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface ServiceBindingOomAdjPolicy {}
+
+ @ServiceBindingOomAdjPolicy
+ static final int DEFAULT_SERVICE_NO_BUMP_BIND_POLICY_FLAG =
+ SERVICE_BIND_OOMADJ_POLICY_SKIP_OOM_UPDATE_ON_CREATE
+ | SERVICE_BIND_OOMADJ_POLICY_SKIP_OOM_UPDATE_ON_BIND
+ | SERVICE_BIND_OOMADJ_POLICY_SKIP_OOM_UPDATE_ON_CONNECT;
+
+ @ServiceBindingOomAdjPolicy
+ static final int DEFAULT_SERVICE_CACHED_BIND_POLICY_FLAG =
+ SERVICE_BIND_OOMADJ_POLICY_SKIP_OOM_UPDATE_ON_CREATE
+ | SERVICE_BIND_OOMADJ_POLICY_SKIP_OOM_UPDATE_ON_BIND;
+
+ /**
* Disables foreground service background starts from BOOT_COMPLETED broadcasts for all types
* except:
* <ul>
@@ -1244,7 +1294,7 @@ public final class ActiveServices {
@Override
public void onResult(Bundle result) {
synchronized (mAm) {
- final long identity = Binder.clearCallingIdentity();
+ final long identity = mAm.mInjector.clearCallingIdentity();
try {
if (!mPendingServices.contains(r)) {
return;
@@ -1263,7 +1313,8 @@ public final class ActiveServices {
false /* whileRestarting */,
false /* permissionsReviewRequired */,
false /* packageFrozen */,
- true /* enqueueOomAdj */);
+ true /* enqueueOomAdj */,
+ SERVICE_BIND_OOMADJ_POLICY_LEGACY);
} catch (RemoteException e) {
/* ignore - local call */
} finally {
@@ -1275,7 +1326,7 @@ public final class ActiveServices {
unbindServiceLocked(connection);
}
} finally {
- Binder.restoreCallingIdentity(identity);
+ mAm.mInjector.restoreCallingIdentity(identity);
}
}
}
@@ -1353,7 +1404,8 @@ public final class ActiveServices {
false /* whileRestarting */,
false /* permissionsReviewRequired */,
false /* packageFrozen */,
- true /* enqueueOomAdj */);
+ true /* enqueueOomAdj */,
+ SERVICE_BIND_OOMADJ_POLICY_LEGACY);
} catch (TransactionTooLargeException e) {
/* ignore - local call */
} finally {
@@ -1431,7 +1483,8 @@ public final class ActiveServices {
false /* whileRestarting */,
false /* permissionsReviewRequired */,
false /* packageFrozen */,
- true /* enqueueOomAdj */);
+ true /* enqueueOomAdj */,
+ SERVICE_BIND_OOMADJ_POLICY_LEGACY);
/* Will be a no-op if nothing pending */
mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_SERVICE);
if (error != null) {
@@ -1550,22 +1603,22 @@ public final class ActiveServices {
if (caller != null && callerApp == null) {
throw new SecurityException(
"Unable to find app for caller " + caller
- + " (pid=" + Binder.getCallingPid()
+ + " (pid=" + mAm.mInjector.getCallingPid()
+ ") when stopping service " + service);
}
// If this service is active, make sure it is stopped.
ServiceLookupResult r = retrieveServiceLocked(service, instanceName, isSdkSandboxService,
sdkSandboxClientAppUid, sdkSandboxClientAppPackage, resolvedType, null,
- Binder.getCallingPid(), Binder.getCallingUid(), userId, false, false, false, false,
- null, false, false);
+ mAm.mInjector.getCallingPid(), mAm.mInjector.getCallingUid(),
+ userId, false, false, false, false, null, false, false);
if (r != null) {
if (r.record != null) {
- final long origId = Binder.clearCallingIdentity();
+ final long origId = mAm.mInjector.clearCallingIdentity();
try {
stopServiceLocked(r.record, false);
} finally {
- Binder.restoreCallingIdentity(origId);
+ mAm.mInjector.restoreCallingIdentity(origId);
}
return 1;
}
@@ -1649,7 +1702,7 @@ public final class ActiveServices {
IBinder peekServiceLocked(Intent service, String resolvedType, String callingPackage) {
ServiceLookupResult r = retrieveServiceLocked(service, null, resolvedType, callingPackage,
- Binder.getCallingPid(), Binder.getCallingUid(),
+ mAm.mInjector.getCallingPid(), mAm.mInjector.getCallingUid(),
UserHandle.getCallingUserId(), false, false, false, false, false, false);
IBinder ret = null;
@@ -1658,8 +1711,8 @@ public final class ActiveServices {
if (r.record == null) {
throw new SecurityException(
"Permission Denial: Accessing service"
- + " from pid=" + Binder.getCallingPid()
- + ", uid=" + Binder.getCallingUid()
+ + " from pid=" + mAm.mInjector.getCallingPid()
+ + ", uid=" + mAm.mInjector.getCallingUid()
+ " requires " + r.permission);
}
IntentBindRecord ib = r.record.bindings.get(r.record.intent);
@@ -1719,9 +1772,9 @@ public final class ActiveServices {
}
}
r.callStart = false;
- final long origId = Binder.clearCallingIdentity();
+ final long origId = mAm.mInjector.clearCallingIdentity();
bringDownServiceIfNeededLocked(r, false, false, false, "stopServiceToken");
- Binder.restoreCallingIdentity(origId);
+ mAm.mInjector.restoreCallingIdentity(origId);
return true;
}
return false;
@@ -1734,14 +1787,14 @@ public final class ActiveServices {
public void setServiceForegroundLocked(ComponentName className, IBinder token,
int id, Notification notification, int flags, int foregroundServiceType) {
final int userId = UserHandle.getCallingUserId();
- final long origId = Binder.clearCallingIdentity();
+ final long origId = mAm.mInjector.clearCallingIdentity();
try {
ServiceRecord r = findServiceLocked(className, token, userId);
if (r != null) {
setServiceForegroundInnerLocked(r, id, notification, flags, foregroundServiceType);
}
} finally {
- Binder.restoreCallingIdentity(origId);
+ mAm.mInjector.restoreCallingIdentity(origId);
}
}
@@ -1753,7 +1806,7 @@ public final class ActiveServices {
*/
public int getForegroundServiceTypeLocked(ComponentName className, IBinder token) {
final int userId = UserHandle.getCallingUserId();
- final long origId = Binder.clearCallingIdentity();
+ final long origId = mAm.mInjector.clearCallingIdentity();
int ret = ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE;
try {
ServiceRecord r = findServiceLocked(className, token, userId);
@@ -1761,7 +1814,7 @@ public final class ActiveServices {
ret = r.foregroundServiceType;
}
} finally {
- Binder.restoreCallingIdentity(origId);
+ mAm.mInjector.restoreCallingIdentity(origId);
}
return ret;
}
@@ -3483,7 +3536,7 @@ public final class ActiveServices {
boolean shouldServiceTimeOutLocked(ComponentName className, IBinder token) {
final int userId = UserHandle.getCallingUserId();
- final long ident = Binder.clearCallingIdentity();
+ final long ident = mAm.mInjector.clearCallingIdentity();
try {
ServiceRecord sr = findServiceLocked(className, token, userId);
if (sr == null) {
@@ -3492,7 +3545,7 @@ public final class ActiveServices {
final long nowUptime = SystemClock.uptimeMillis();
return sr.shouldTriggerShortFgsTimeout(nowUptime);
} finally {
- Binder.restoreCallingIdentity(ident);
+ mAm.mInjector.restoreCallingIdentity(ident);
}
}
@@ -3636,8 +3689,8 @@ public final class ActiveServices {
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "bindService: " + service
+ " type=" + resolvedType + " conn=" + connection.asBinder()
+ " flags=0x" + Long.toHexString(flags));
- final int callingPid = Binder.getCallingPid();
- final int callingUid = Binder.getCallingUid();
+ final int callingPid = mAm.mInjector.getCallingPid();
+ final int callingUid = mAm.mInjector.getCallingUid();
final ProcessRecord callerApp = mAm.getRecordForAppLOSP(caller);
if (callerApp == null) {
throw new SecurityException(
@@ -3778,7 +3831,7 @@ public final class ActiveServices {
&& !requestStartTargetPermissionsReviewIfNeededLocked(s, callingPackage, null,
callingUid, service, callerFg, userId, true, connection);
- final long origId = Binder.clearCallingIdentity();
+ final long origId = mAm.mInjector.clearCallingIdentity();
try {
if (unscheduleServiceRestartLocked(s, callerApp.info.uid, false)) {
@@ -3859,12 +3912,34 @@ public final class ActiveServices {
}
clist.add(c);
+ final boolean isolated = (s.serviceInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) != 0;
+ final ProcessRecord hostApp = isolated
+ ? null
+ : mAm.getProcessRecordLocked(s.processName, s.appInfo.uid);
+ final int serviceBindingOomAdjPolicy = hostApp != null
+ ? getServiceBindingOomAdjPolicyForAddLocked(b.client, hostApp, c)
+ : SERVICE_BIND_OOMADJ_POLICY_LEGACY;
+
+ final boolean shouldFreezeCaller = !packageFrozen && !permissionsReviewRequired
+ && (serviceBindingOomAdjPolicy & SERVICE_BIND_OOMADJ_POLICY_FREEZE_CALLER) != 0
+ && callerApp.isFreezable();
+
+ if (shouldFreezeCaller) {
+ // Freeze the caller immediately, so the following #onBind/#onConnected will be
+ // queued up in the app side as they're one way calls. And we'll also hold off
+ // the service timeout timer until the process is unfrozen.
+ mAm.mOomAdjuster.updateAppFreezeStateLSP(callerApp, OOM_ADJ_REASON_BIND_SERVICE,
+ true);
+ }
+
boolean needOomAdj = false;
if (c.hasFlag(Context.BIND_AUTO_CREATE)) {
s.lastActivity = SystemClock.uptimeMillis();
- needOomAdj = true;
+ needOomAdj = (serviceBindingOomAdjPolicy
+ & SERVICE_BIND_OOMADJ_POLICY_SKIP_OOM_UPDATE_ON_CREATE) == 0;
if (bringUpServiceLocked(s, service.getFlags(), callerFg, false,
- permissionsReviewRequired, packageFrozen, true) != null) {
+ permissionsReviewRequired, packageFrozen, true, serviceBindingOomAdjPolicy)
+ != null) {
mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_BIND_SERVICE);
return 0;
}
@@ -3886,8 +3961,11 @@ public final class ActiveServices {
|| (callerApp.mState.getCurProcState() <= PROCESS_STATE_TOP
&& c.hasFlag(Context.BIND_TREAT_LIKE_ACTIVITY)),
b.client);
- needOomAdj = true;
- mAm.enqueueOomAdjTargetLocked(s.app);
+ if ((serviceBindingOomAdjPolicy
+ & SERVICE_BIND_OOMADJ_POLICY_SKIP_OOM_UPDATE_ON_CONNECT) == 0) {
+ needOomAdj = true;
+ mAm.enqueueOomAdjTargetLocked(s.app);
+ }
}
if (needOomAdj) {
mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_BIND_SERVICE);
@@ -3937,10 +4015,12 @@ public final class ActiveServices {
// and the service had previously asked to be told when
// rebound, then do so.
if (b.intent.apps.size() == 1 && b.intent.doRebind) {
- requestServiceBindingLocked(s, b.intent, callerFg, true);
+ requestServiceBindingLocked(s, b.intent, callerFg, true,
+ serviceBindingOomAdjPolicy);
}
} else if (!b.intent.requested) {
- requestServiceBindingLocked(s, b.intent, callerFg, false);
+ requestServiceBindingLocked(s, b.intent, callerFg, false,
+ serviceBindingOomAdjPolicy);
}
maybeLogBindCrossProfileService(userId, callingPackage, callerApp.info.uid);
@@ -3948,7 +4028,7 @@ public final class ActiveServices {
getServiceMapLocked(s.userId).ensureNotStartingBackgroundLocked(s);
} finally {
- Binder.restoreCallingIdentity(origId);
+ mAm.mInjector.restoreCallingIdentity(origId);
}
notifyBindingServiceEventLocked(callerApp, callingPackage);
@@ -3982,7 +4062,7 @@ public final class ActiveServices {
}
void publishServiceLocked(ServiceRecord r, Intent intent, IBinder service) {
- final long origId = Binder.clearCallingIdentity();
+ final long origId = mAm.mInjector.clearCallingIdentity();
try {
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "PUBLISHING " + r
+ " " + intent + ": " + service);
@@ -4025,10 +4105,11 @@ public final class ActiveServices {
}
serviceDoneExecutingLocked(r, mDestroyingServices.contains(r), false, false,
- OOM_ADJ_REASON_EXECUTING_SERVICE);
+ !Flags.serviceBindingOomAdjPolicy() || b == null || !b.mSkippedOomAdj
+ ? OOM_ADJ_REASON_EXECUTING_SERVICE : OOM_ADJ_REASON_NONE);
}
} finally {
- Binder.restoreCallingIdentity(origId);
+ mAm.mInjector.restoreCallingIdentity(origId);
}
}
@@ -4078,8 +4159,8 @@ public final class ActiveServices {
return false;
}
- final int callingPid = Binder.getCallingPid();
- final long origId = Binder.clearCallingIdentity();
+ final int callingPid = mAm.mInjector.getCallingPid();
+ final long origId = mAm.mInjector.clearCallingIdentity();
try {
if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
String info;
@@ -4092,9 +4173,10 @@ public final class ActiveServices {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "unbindServiceLocked: " + info);
}
+ boolean needOomAdj = false;
while (clist.size() > 0) {
ConnectionRecord r = clist.get(0);
- removeConnectionLocked(r, null, null, true);
+ int serviceBindingOomAdjPolicy = removeConnectionLocked(r, null, null, true);
if (clist.size() > 0 && clist.get(0) == r) {
// In case it didn't get removed above, do it now.
Slog.wtf(TAG, "Connection " + r + " not removed for binder " + binder);
@@ -4112,22 +4194,28 @@ public final class ActiveServices {
psr.setTreatLikeActivity(true);
mAm.updateLruProcessLocked(app, true, null);
}
- mAm.enqueueOomAdjTargetLocked(app);
+ // If the bindee is more important than the binder, we may skip the OomAdjuster.
+ if (serviceBindingOomAdjPolicy == SERVICE_BIND_OOMADJ_POLICY_LEGACY) {
+ mAm.enqueueOomAdjTargetLocked(app);
+ needOomAdj = true;
+ }
}
}
- mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_UNBIND_SERVICE);
+ if (needOomAdj) {
+ mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_UNBIND_SERVICE);
+ }
} finally {
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- Binder.restoreCallingIdentity(origId);
+ mAm.mInjector.restoreCallingIdentity(origId);
}
return true;
}
void unbindFinishedLocked(ServiceRecord r, Intent intent, boolean doRebind) {
- final long origId = Binder.clearCallingIdentity();
+ final long origId = mAm.mInjector.clearCallingIdentity();
try {
if (r != null) {
Intent.FilterComparison filter
@@ -4138,6 +4226,7 @@ public final class ActiveServices {
+ (b != null ? b.apps.size() : 0));
boolean inDestroying = mDestroyingServices.contains(r);
+ boolean skipOomAdj = false;
if (b != null) {
if (b.apps.size() > 0 && !inDestroying) {
// Applications have already bound since the last
@@ -4152,7 +4241,8 @@ public final class ActiveServices {
}
}
try {
- requestServiceBindingLocked(r, b, inFg, true);
+ requestServiceBindingLocked(r, b, inFg, true,
+ SERVICE_BIND_OOMADJ_POLICY_LEGACY);
} catch (TransactionTooLargeException e) {
// Don't pass this back to ActivityThread, it's unrelated.
}
@@ -4161,13 +4251,14 @@ public final class ActiveServices {
// a new client.
b.doRebind = true;
}
+ skipOomAdj = Flags.serviceBindingOomAdjPolicy() && b.mSkippedOomAdj;
}
serviceDoneExecutingLocked(r, inDestroying, false, false,
- OOM_ADJ_REASON_UNBIND_SERVICE);
+ skipOomAdj ? OOM_ADJ_REASON_NONE : OOM_ADJ_REASON_UNBIND_SERVICE);
}
} finally {
- Binder.restoreCallingIdentity(origId);
+ mAm.mInjector.restoreCallingIdentity(origId);
}
}
@@ -4503,7 +4594,7 @@ public final class ActiveServices {
userId = 0;
smap = getServiceMapLocked(0);
// Bypass INTERACT_ACROSS_USERS permission check
- final long token = Binder.clearCallingIdentity();
+ final long token = mAm.mInjector.clearCallingIdentity();
try {
ResolveInfo rInfoForUserId0 =
mAm.getPackageManagerInternal().resolveService(service,
@@ -4516,7 +4607,7 @@ public final class ActiveServices {
}
sInfo = rInfoForUserId0.serviceInfo;
} finally {
- Binder.restoreCallingIdentity(token);
+ mAm.mInjector.restoreCallingIdentity(token);
}
}
sInfo = new ServiceInfo(sInfo);
@@ -4645,7 +4736,8 @@ public final class ActiveServices {
* @return {@code true} if it performed oomAdjUpdate.
*/
private boolean bumpServiceExecutingLocked(
- ServiceRecord r, boolean fg, String why, @OomAdjReason int oomAdjReason) {
+ ServiceRecord r, boolean fg, String why, @OomAdjReason int oomAdjReason,
+ boolean skipTimeoutIfPossible) {
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, ">>> EXECUTING "
+ why + " of " + r + " in app " + r.app);
else if (DEBUG_SERVICE_EXECUTING) Slog.v(TAG_SERVICE_EXECUTING, ">>> EXECUTING "
@@ -4669,6 +4761,10 @@ public final class ActiveServices {
timeoutNeeded = false;
}
+ // If the process is frozen or to be frozen, and we want to skip the timeout, skip it.
+ final boolean shouldSkipTimeout = skipTimeoutIfPossible && r.app != null
+ && (r.app.mOptRecord.isPendingFreeze() || r.app.mOptRecord.isFrozen());
+
ProcessServiceRecord psr;
if (r.executeNesting == 0) {
r.executeFg = fg;
@@ -4684,7 +4780,11 @@ public final class ActiveServices {
psr.startExecutingService(r);
psr.setExecServicesFg(psr.shouldExecServicesFg() || fg);
if (timeoutNeeded && psr.numberOfExecutingServices() == 1) {
- scheduleServiceTimeoutLocked(r.app);
+ if (!shouldSkipTimeout) {
+ scheduleServiceTimeoutLocked(r.app);
+ } else {
+ r.app.mServices.noteScheduleServiceTimeoutPending(true);
+ }
}
}
} else if (r.app != null && fg) {
@@ -4692,7 +4792,11 @@ public final class ActiveServices {
if (!psr.shouldExecServicesFg()) {
psr.setExecServicesFg(true);
if (timeoutNeeded) {
- scheduleServiceTimeoutLocked(r.app);
+ if (!shouldSkipTimeout) {
+ scheduleServiceTimeoutLocked(r.app);
+ } else {
+ r.app.mServices.noteScheduleServiceTimeoutPending(true);
+ }
}
}
}
@@ -4712,16 +4816,22 @@ public final class ActiveServices {
}
private final boolean requestServiceBindingLocked(ServiceRecord r, IntentBindRecord i,
- boolean execInFg, boolean rebind) throws TransactionTooLargeException {
+ boolean execInFg, boolean rebind,
+ @ServiceBindingOomAdjPolicy int serviceBindingOomAdjPolicy)
+ throws TransactionTooLargeException {
if (r.app == null || r.app.getThread() == null) {
// If service is not currently running, can't yet bind.
return false;
}
if (DEBUG_SERVICE) Slog.d(TAG_SERVICE, "requestBind " + i + ": requested=" + i.requested
+ " rebind=" + rebind);
+ final boolean skipOomAdj = (serviceBindingOomAdjPolicy
+ & SERVICE_BIND_OOMADJ_POLICY_SKIP_OOM_UPDATE_ON_BIND) != 0;
if ((!i.requested || rebind) && i.apps.size() > 0) {
try {
- bumpServiceExecutingLocked(r, execInFg, "bind", OOM_ADJ_REASON_BIND_SERVICE);
+ i.mSkippedOomAdj = !bumpServiceExecutingLocked(r, execInFg, "bind",
+ skipOomAdj ? OOM_ADJ_REASON_NONE : OOM_ADJ_REASON_BIND_SERVICE,
+ skipOomAdj /* skipTimeoutIfPossible */);
if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
Trace.instant(Trace.TRACE_TAG_ACTIVITY_MANAGER, "requestServiceBinding="
+ i.intent.getIntent() + ". bindSeq=" + mBindServiceSeqCounter);
@@ -4738,14 +4848,14 @@ public final class ActiveServices {
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Crashed while binding " + r, e);
final boolean inDestroying = mDestroyingServices.contains(r);
serviceDoneExecutingLocked(r, inDestroying, inDestroying, false,
- OOM_ADJ_REASON_UNBIND_SERVICE);
+ skipOomAdj ? OOM_ADJ_REASON_NONE : OOM_ADJ_REASON_UNBIND_SERVICE);
throw e;
} catch (RemoteException e) {
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Crashed while binding " + r);
// Keep the executeNesting count accurate.
final boolean inDestroying = mDestroyingServices.contains(r);
serviceDoneExecutingLocked(r, inDestroying, inDestroying, false,
- OOM_ADJ_REASON_UNBIND_SERVICE);
+ skipOomAdj ? OOM_ADJ_REASON_NONE : OOM_ADJ_REASON_UNBIND_SERVICE);
return false;
}
}
@@ -5117,7 +5227,7 @@ public final class ActiveServices {
}
try {
bringUpServiceLocked(r, r.intent.getIntent().getFlags(), r.createdFromFg, true, false,
- false, true);
+ false, true, SERVICE_BIND_OOMADJ_POLICY_LEGACY);
} catch (TransactionTooLargeException e) {
// Ignore, it's been logged and nothing upstack cares.
} finally {
@@ -5217,7 +5327,7 @@ public final class ActiveServices {
private String bringUpServiceLocked(ServiceRecord r, int intentFlags, boolean execInFg,
boolean whileRestarting, boolean permissionsReviewRequired, boolean packageFrozen,
- boolean enqueueOomAdj)
+ boolean enqueueOomAdj, @ServiceBindingOomAdjPolicy int serviceBindingOomAdjPolicy)
throws TransactionTooLargeException {
try {
if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
@@ -5225,7 +5335,8 @@ public final class ActiveServices {
"bringUpServiceLocked: " + r.shortInstanceName);
}
return bringUpServiceInnerLocked(r, intentFlags, execInFg, whileRestarting,
- permissionsReviewRequired, packageFrozen, enqueueOomAdj);
+ permissionsReviewRequired, packageFrozen, enqueueOomAdj,
+ serviceBindingOomAdjPolicy);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
@@ -5233,7 +5344,7 @@ public final class ActiveServices {
private String bringUpServiceInnerLocked(ServiceRecord r, int intentFlags, boolean execInFg,
boolean whileRestarting, boolean permissionsReviewRequired, boolean packageFrozen,
- boolean enqueueOomAdj)
+ boolean enqueueOomAdj, @ServiceBindingOomAdjPolicy int serviceBindingOomAdjPolicy)
throws TransactionTooLargeException {
if (r.app != null && r.app.isThreadReady()) {
sendServiceArgsLocked(r, execInFg, false);
@@ -5317,7 +5428,7 @@ public final class ActiveServices {
app.addPackage(r.appInfo.packageName, r.appInfo.longVersionCode,
mAm.mProcessStats);
realStartServiceLocked(r, app, thread, pid, uidRecord, execInFg,
- enqueueOomAdj);
+ enqueueOomAdj, serviceBindingOomAdjPolicy);
return null;
} catch (TransactionTooLargeException e) {
throw e;
@@ -5347,7 +5458,7 @@ public final class ActiveServices {
"realStartServiceLocked: " + r.shortInstanceName);
}
realStartServiceLocked(r, app, thread, pid, uidRecord, execInFg,
- enqueueOomAdj);
+ enqueueOomAdj, SERVICE_BIND_OOMADJ_POLICY_LEGACY);
return null;
} catch (TransactionTooLargeException e) {
throw e;
@@ -5452,16 +5563,61 @@ public final class ActiveServices {
return HostingRecord.TRIGGER_TYPE_UNKNOWN;
}
- private final void requestServiceBindingsLocked(ServiceRecord r, boolean execInFg)
+ private void requestServiceBindingsLocked(ServiceRecord r, boolean execInFg,
+ @ServiceBindingOomAdjPolicy int serviceBindingOomAdjPolicy)
throws TransactionTooLargeException {
for (int i=r.bindings.size()-1; i>=0; i--) {
IntentBindRecord ibr = r.bindings.valueAt(i);
- if (!requestServiceBindingLocked(r, ibr, execInFg, false)) {
+ if (!requestServiceBindingLocked(r, ibr, execInFg, false, serviceBindingOomAdjPolicy)) {
break;
}
}
}
+ @ServiceBindingOomAdjPolicy
+ private int getServiceBindingOomAdjPolicyForAddLocked(ProcessRecord clientApp,
+ ProcessRecord hostApp, ConnectionRecord cr) {
+ @ServiceBindingOomAdjPolicy int policy = SERVICE_BIND_OOMADJ_POLICY_LEGACY;
+ if (Flags.serviceBindingOomAdjPolicy() && clientApp != null && hostApp != null) {
+ if (clientApp == hostApp) {
+ policy = DEFAULT_SERVICE_NO_BUMP_BIND_POLICY_FLAG;
+ } else if (clientApp.isCached()) {
+ policy = DEFAULT_SERVICE_NO_BUMP_BIND_POLICY_FLAG;
+ if (clientApp.isFreezable()) {
+ policy |= SERVICE_BIND_OOMADJ_POLICY_FREEZE_CALLER;
+ }
+ }
+ if ((policy & SERVICE_BIND_OOMADJ_POLICY_SKIP_OOM_UPDATE_ON_CONNECT) == 0) {
+ // Binding between two different processes.
+ // Check if the caller has a better process state, oom adj score,
+ // or if the caller has more capabilities.
+ if (!mAm.mOomAdjuster.evaluateServiceConnectionAdd(clientApp, hostApp, cr)) {
+ // Running an oom adjuster won't be give the host app a better score, skip it.
+ policy = DEFAULT_SERVICE_NO_BUMP_BIND_POLICY_FLAG;
+ }
+ }
+ }
+ return policy;
+ }
+
+ @ServiceBindingOomAdjPolicy
+ private int getServiceBindingOomAdjPolicyForRemovalLocked(ProcessRecord clientApp,
+ ProcessRecord hostApp, ConnectionRecord cr) {
+ @ServiceBindingOomAdjPolicy int policy = SERVICE_BIND_OOMADJ_POLICY_LEGACY;
+ if (Flags.serviceBindingOomAdjPolicy() && clientApp != null && hostApp != null
+ && cr != null) {
+ if (clientApp == hostApp) {
+ policy = DEFAULT_SERVICE_NO_BUMP_BIND_POLICY_FLAG;
+ } else {
+ if (!mAm.mOomAdjuster.evaluateServiceConnectionRemoval(clientApp, hostApp, cr)) {
+ // Running an oom adjuster won't be give the host app a better score, skip it.
+ policy = DEFAULT_SERVICE_NO_BUMP_BIND_POLICY_FLAG;
+ }
+ }
+ }
+ return policy;
+ }
+
/**
* Note the name of this method should not be confused with the started services concept.
* The "start" here means bring up the instance in the client, and this method is called
@@ -5469,7 +5625,8 @@ public final class ActiveServices {
*/
private void realStartServiceLocked(ServiceRecord r, ProcessRecord app,
IApplicationThread thread, int pid, UidRecord uidRecord, boolean execInFg,
- boolean enqueueOomAdj) throws RemoteException {
+ boolean enqueueOomAdj, @ServiceBindingOomAdjPolicy int serviceBindingOomAdjPolicy)
+ throws RemoteException {
if (thread == null) {
throw new RemoteException();
}
@@ -5478,17 +5635,28 @@ public final class ActiveServices {
+ ", ProcessRecord.uid = " + app.uid);
r.setProcess(app, thread, pid, uidRecord);
r.restartTime = r.lastActivity = SystemClock.uptimeMillis();
-
+ final boolean skipOomAdj = (serviceBindingOomAdjPolicy
+ & SERVICE_BIND_OOMADJ_POLICY_SKIP_OOM_UPDATE_ON_CREATE) != 0;
final ProcessServiceRecord psr = app.mServices;
final boolean newService = psr.startService(r);
bumpServiceExecutingLocked(r, execInFg, "create",
- OOM_ADJ_REASON_NONE /* use "none" to avoid extra oom adj */);
+ OOM_ADJ_REASON_NONE /* use "none" to avoid extra oom adj */,
+ skipOomAdj /* skipTimeoutIfPossible */);
mAm.updateLruProcessLocked(app, false, null);
updateServiceForegroundLocked(psr, /* oomAdj= */ false);
- // Force an immediate oomAdjUpdate, so the client app could be in the correct process state
- // before doing any service related transactions
- mAm.enqueueOomAdjTargetLocked(app);
- mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_SERVICE);
+ // Skip the oom adj update if it's a self-binding, the Service#onCreate() will be running
+ // at its current adj score.
+ if (!skipOomAdj) {
+ // Force an immediate oomAdjUpdate, so the host app could be in the correct
+ // process state before doing any service related transactions
+ mAm.enqueueOomAdjTargetLocked(app);
+ mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_SERVICE);
+ } else {
+ // Since we skipped the oom adj update, the Service#onCreate() might be running in
+ // the cached state, if the service process drops into the cached state after the call.
+ // But there is still a grace period before freezing it, so we should be fine
+ // in terms of not getting an ANR.
+ }
boolean created = false;
try {
@@ -5523,7 +5691,7 @@ public final class ActiveServices {
// Keep the executeNesting count accurate.
final boolean inDestroying = mDestroyingServices.contains(r);
serviceDoneExecutingLocked(r, inDestroying, inDestroying, false,
- OOM_ADJ_REASON_STOP_SERVICE);
+ skipOomAdj ? OOM_ADJ_REASON_NONE : OOM_ADJ_REASON_STOP_SERVICE);
// Cleanup.
if (newService) {
@@ -5542,7 +5710,7 @@ public final class ActiveServices {
psr.mAllowlistManager = true;
}
- requestServiceBindingsLocked(r, execInFg);
+ requestServiceBindingsLocked(r, execInFg, serviceBindingOomAdjPolicy);
updateServiceClientActivitiesLocked(psr, null, true);
@@ -5610,7 +5778,8 @@ public final class ActiveServices {
UserHandle.getAppId(r.appInfo.uid)
);
bumpServiceExecutingLocked(r, execInFg, "start",
- OOM_ADJ_REASON_NONE /* use "none" to avoid extra oom adj */);
+ OOM_ADJ_REASON_NONE /* use "none" to avoid extra oom adj */,
+ false /* skipTimeoutIfPossible */);
if (r.fgRequired && !r.fgWaiting) {
if (!r.isForeground) {
if (DEBUG_BACKGROUND_CHECK) {
@@ -5753,7 +5922,8 @@ public final class ActiveServices {
if (ibr.hasBound) {
try {
oomAdjusted |= bumpServiceExecutingLocked(r, false, "bring down unbind",
- OOM_ADJ_REASON_UNBIND_SERVICE);
+ OOM_ADJ_REASON_UNBIND_SERVICE,
+ false /* skipTimeoutIfPossible */);
ibr.hasBound = false;
ibr.requested = false;
r.app.getThread().scheduleUnbindService(r,
@@ -5909,7 +6079,8 @@ public final class ActiveServices {
} else {
try {
oomAdjusted |= bumpServiceExecutingLocked(r, false, "destroy",
- oomAdjusted ? 0 : OOM_ADJ_REASON_STOP_SERVICE);
+ oomAdjusted ? 0 : OOM_ADJ_REASON_STOP_SERVICE,
+ false /* skipTimeoutIfPossible */);
mDestroyingServices.add(r);
r.destroying = true;
r.app.getThread().scheduleStopService(r);
@@ -5992,11 +6163,17 @@ public final class ActiveServices {
}
}
- void removeConnectionLocked(ConnectionRecord c, ProcessRecord skipApp,
+ /**
+ * @return The ServiceBindingOomAdjPolicy used in this removal.
+ */
+ @ServiceBindingOomAdjPolicy
+ int removeConnectionLocked(ConnectionRecord c, ProcessRecord skipApp,
ActivityServiceConnectionsHolder skipAct, boolean enqueueOomAdj) {
IBinder binder = c.conn.asBinder();
AppBindRecord b = c.binding;
ServiceRecord s = b.service;
+ @ServiceBindingOomAdjPolicy int serviceBindingOomAdjPolicy =
+ SERVICE_BIND_OOMADJ_POLICY_LEGACY;
ArrayList<ConnectionRecord> clist = s.getConnections().get(binder);
if (clist != null) {
clist.remove(c);
@@ -6055,8 +6232,14 @@ public final class ActiveServices {
+ ": shouldUnbind=" + b.intent.hasBound);
if (s.app != null && s.app.isThreadReady() && b.intent.apps.size() == 0
&& b.intent.hasBound) {
+ serviceBindingOomAdjPolicy = getServiceBindingOomAdjPolicyForRemovalLocked(b.client,
+ s.app, c);
+ final boolean skipOomAdj = (serviceBindingOomAdjPolicy
+ & SERVICE_BIND_OOMADJ_POLICY_SKIP_OOM_UPDATE_ON_CONNECT) != 0;
try {
- bumpServiceExecutingLocked(s, false, "unbind", OOM_ADJ_REASON_UNBIND_SERVICE);
+ b.intent.mSkippedOomAdj = !bumpServiceExecutingLocked(s, false, "unbind",
+ skipOomAdj ? OOM_ADJ_REASON_NONE : OOM_ADJ_REASON_UNBIND_SERVICE,
+ skipOomAdj /* skipTimeoutIfPossible */);
if (b.client != s.app && c.notHasFlag(Context.BIND_WAIVE_PRIORITY)
&& s.app.mState.getSetProcState() <= PROCESS_STATE_HEAVY_WEIGHT) {
// If this service's process is not already in the cached list,
@@ -6096,12 +6279,14 @@ public final class ActiveServices {
"removeConnection");
}
}
+ return serviceBindingOomAdjPolicy;
}
void serviceDoneExecutingLocked(ServiceRecord r, int type, int startId, int res,
- boolean enqueueOomAdj) {
+ boolean enqueueOomAdj, Intent intent) {
boolean inDestroying = mDestroyingServices.contains(r);
if (r != null) {
+ boolean skipOomAdj = false;
if (type == ActivityThread.SERVICE_DONE_EXECUTING_START) {
// This is a call from a service start... take care of
// book-keeping.
@@ -6177,14 +6362,19 @@ public final class ActiveServices {
// Fake it to keep from ANR due to orphaned entry.
r.executeNesting = 1;
}
+ } else if (type == ActivityThread.SERVICE_DONE_EXECUTING_REBIND
+ || type == ActivityThread.SERVICE_DONE_EXECUTING_UNBIND) {
+ final Intent.FilterComparison filter = new Intent.FilterComparison(intent);
+ final IntentBindRecord b = r.bindings.get(filter);
+ skipOomAdj = Flags.serviceBindingOomAdjPolicy() && b != null && b.mSkippedOomAdj;
}
- final long origId = Binder.clearCallingIdentity();
+ final long origId = mAm.mInjector.clearCallingIdentity();
serviceDoneExecutingLocked(r, inDestroying, inDestroying, enqueueOomAdj,
- OOM_ADJ_REASON_EXECUTING_SERVICE);
- Binder.restoreCallingIdentity(origId);
+ skipOomAdj ? OOM_ADJ_REASON_NONE : OOM_ADJ_REASON_EXECUTING_SERVICE);
+ mAm.mInjector.restoreCallingIdentity(origId);
} else {
Slog.w(TAG, "Done executing unknown service from pid "
- + Binder.getCallingPid());
+ + mAm.mInjector.getCallingPid());
}
}
@@ -6236,10 +6426,17 @@ public final class ActiveServices {
mDestroyingServices.remove(r);
r.bindings.clear();
}
- if (enqueueOomAdj) {
- mAm.enqueueOomAdjTargetLocked(r.app);
+ boolean oomAdjusted = false;
+ if (oomAdjReason != OOM_ADJ_REASON_NONE) {
+ if (enqueueOomAdj) {
+ mAm.enqueueOomAdjTargetLocked(r.app);
+ } else {
+ mAm.updateOomAdjLocked(r.app, oomAdjReason);
+ }
+ oomAdjusted = true;
} else {
- mAm.updateOomAdjLocked(r.app, oomAdjReason);
+ // Skip oom adj if it wasn't bumped during the bumpServiceExecutingLocked()
+ oomAdjusted = false;
}
}
r.executeFg = false;
@@ -6296,7 +6493,7 @@ public final class ActiveServices {
"realStartServiceLocked: " + sr.shortInstanceName);
}
realStartServiceLocked(sr, proc, thread, pid, uidRecord, sr.createdFromFg,
- true);
+ true, SERVICE_BIND_OOMADJ_POLICY_LEGACY);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
@@ -6786,6 +6983,7 @@ public final class ActiveServices {
}
psr.stopAllExecutingServices();
+ psr.noteScheduleServiceTimeoutPending(false);
}
ActivityManager.RunningServiceInfo makeRunningServiceInfoLocked(ServiceRecord r) {
@@ -6836,7 +7034,7 @@ public final class ActiveServices {
ArrayList<ActivityManager.RunningServiceInfo> res
= new ArrayList<ActivityManager.RunningServiceInfo>();
- final long ident = Binder.clearCallingIdentity();
+ final long ident = mAm.mInjector.clearCallingIdentity();
try {
if (canInteractAcrossUsers) {
int[] users = mAm.mUserController.getUsers();
@@ -6878,14 +7076,14 @@ public final class ActiveServices {
}
}
} finally {
- Binder.restoreCallingIdentity(ident);
+ mAm.mInjector.restoreCallingIdentity(ident);
}
return res;
}
public PendingIntent getRunningServiceControlPanelLocked(ComponentName name) {
- int userId = UserHandle.getUserId(Binder.getCallingUid());
+ int userId = UserHandle.getUserId(mAm.mInjector.getCallingUid());
ServiceRecord r = getServiceByNameLocked(name, userId);
if (r != null) {
ArrayMap<IBinder, ArrayList<ConnectionRecord>> connections = r.getConnections();
@@ -7077,6 +7275,7 @@ public final class ActiveServices {
final long delay = proc.mServices.shouldExecServicesFg()
? mAm.mConstants.SERVICE_TIMEOUT : mAm.mConstants.SERVICE_BACKGROUND_TIMEOUT;
mActiveServiceAnrTimer.start(proc, delay);
+ proc.mServices.noteScheduleServiceTimeoutPending(false);
}
void scheduleServiceForegroundTransitionTimeoutLocked(ServiceRecord r) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index ca04e41769ee..74902f76b617 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1751,7 +1751,7 @@ public class ActivityManagerService extends IActivityManager.Stub
@GuardedBy("mProcLock")
private long mLastBinderHeavyHitterAutoSamplerStart = 0L;
- final AppProfiler mAppProfiler;
+ AppProfiler mAppProfiler;
private static final int INDEX_NATIVE_PSS = 0;
private static final int INDEX_NATIVE_SWAP_PSS = 1;
@@ -2496,7 +2496,7 @@ public class ActivityManagerService extends IActivityManager.Stub
mInjector = injector;
mContext = mInjector.getContext();
mUiContext = null;
- mAppErrors = null;
+ mAppErrors = injector.getAppErrors();
mPackageWatchdog = null;
mAppOpsService = mInjector.getAppOpsService(null /* recentAccessesFile */,
null /* storageFile */, null /* handler */);
@@ -2514,7 +2514,7 @@ public class ActivityManagerService extends IActivityManager.Stub
? new OomAdjusterModernImpl(this, mProcessList, activeUids, handlerThread)
: new OomAdjuster(this, mProcessList, activeUids, handlerThread);
- mIntentFirewall = null;
+ mIntentFirewall = injector.getIntentFirewall();
mProcessStats = new ProcessStatsService(this, mContext.getCacheDir());
mCpHelper = new ContentProviderHelper(this, false);
mServices = mInjector.getActiveServices(this);
@@ -13889,13 +13889,15 @@ public class ActivityManagerService extends IActivityManager.Stub
}
}
- public void serviceDoneExecuting(IBinder token, int type, int startId, int res) {
+ @Override
+ public void serviceDoneExecuting(IBinder token, int type, int startId, int res, Intent intent) {
synchronized(this) {
if (!(token instanceof ServiceRecord)) {
Slog.e(TAG, "serviceDoneExecuting: Invalid service token=" + token);
throw new IllegalArgumentException("Invalid service token");
}
- mServices.serviceDoneExecutingLocked((ServiceRecord) token, type, startId, res, false);
+ mServices.serviceDoneExecutingLocked((ServiceRecord) token, type, startId, res, false,
+ intent);
}
}
@@ -20236,6 +20238,36 @@ public class ActivityManagerService extends IActivityManager.Stub
}
return broadcastQueues;
}
+
+ /** @see Binder#getCallingUid */
+ public int getCallingUid() {
+ return Binder.getCallingUid();
+ }
+
+ /** @see Binder#getCallingPid */
+ public int getCallingPid() {
+ return Binder.getCallingUid();
+ }
+
+ /** @see Binder#clearCallingIdentity */
+ public long clearCallingIdentity() {
+ return Binder.clearCallingIdentity();
+ }
+
+ /** @see Binder#clearCallingIdentity */
+ public void restoreCallingIdentity(long ident) {
+ Binder.restoreCallingIdentity(ident);
+ }
+
+ /** @return the default instance of AppErrors */
+ public AppErrors getAppErrors() {
+ return null;
+ }
+
+ /** @return the default instance of intent firewall */
+ public IntentFirewall getIntentFirewall() {
+ return null;
+ }
}
@Override
diff --git a/services/core/java/com/android/server/am/AppBatteryExemptionTracker.java b/services/core/java/com/android/server/am/AppBatteryExemptionTracker.java
index b07d9a6b258c..9c2e69be7685 100644
--- a/services/core/java/com/android/server/am/AppBatteryExemptionTracker.java
+++ b/services/core/java/com/android/server/am/AppBatteryExemptionTracker.java
@@ -520,7 +520,7 @@ final class AppBatteryExemptionTracker
/**
* Default value to {@link #mTrackerEnabled}.
*/
- static final boolean DEFAULT_BG_BATTERY_EXEMPTION_ENABLED = true;
+ static final boolean DEFAULT_BG_BATTERY_EXEMPTION_ENABLED = false;
AppBatteryExemptionPolicy(@NonNull Injector injector,
@NonNull AppBatteryExemptionTracker tracker) {
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index 626b70b51093..d92a24b82764 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -1419,6 +1419,11 @@ public final class CachedAppOptimizer {
}
@GuardedBy({"mAm", "mProcLock"})
+ void freezeAppAsyncAtEarliestLSP(ProcessRecord app) {
+ freezeAppAsyncLSP(app, updateEarliestFreezableTime(app, 0));
+ }
+
+ @GuardedBy({"mAm", "mProcLock"})
void freezeAppAsyncInternalLSP(ProcessRecord app, @UptimeMillisLong long delayMillis,
boolean force) {
final ProcessCachedOptimizerRecord opt = app.mOptRecord;
@@ -1714,6 +1719,14 @@ public final class CachedAppOptimizer {
compactApp(frozenProc, CompactProfile.FULL, CompactSource.APP, false);
}
}
+ frozenProc.onProcessFrozen();
+ }
+
+ /**
+ * Callback received when an attempt to freeze a process is cancelled (failed).
+ */
+ void onProcessFrozenCancelled(ProcessRecord app) {
+ app.onProcessFrozenCancelled();
}
/**
@@ -2203,6 +2216,8 @@ public final class CachedAppOptimizer {
onProcessFrozen(proc);
removeMessages(DEADLOCK_WATCHDOG_MSG);
sendEmptyMessageDelayed(DEADLOCK_WATCHDOG_MSG, FREEZE_DEADLOCK_TIMEOUT_MS);
+ } else {
+ onProcessFrozenCancelled(proc);
}
} break;
case REPORT_UNFREEZE_MSG: {
@@ -2460,7 +2475,7 @@ public final class CachedAppOptimizer {
pr = mAm.mPidsSelfLocked.get(blocked);
}
if (pr != null
- && pr.mState.getCurAdj() < ProcessList.CACHED_APP_MIN_ADJ) {
+ && pr.mState.getCurAdj() < ProcessList.FREEZER_CUTOFF_ADJ) {
Slog.d(TAG_AM, app.processName + " (" + pid + ") blocks "
+ pr.processName + " (" + blocked + ")");
// Found at least one blocked non-cached process
diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java
index 30f21a65b5b1..cb7898d8b862 100644
--- a/services/core/java/com/android/server/am/ContentProviderHelper.java
+++ b/services/core/java/com/android/server/am/ContentProviderHelper.java
@@ -35,6 +35,7 @@ import static com.android.internal.util.FrameworkStatsLog.PROVIDER_ACQUISITION_E
import static com.android.internal.util.FrameworkStatsLog.PROVIDER_ACQUISITION_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU;
import static com.android.server.am.ActivityManagerService.TAG_MU;
+import static com.android.server.am.Flags.serviceBindingOomAdjPolicy;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
@@ -319,8 +320,10 @@ public class ContentProviderHelper {
checkTime(startTime, "getContentProviderImpl: before updateOomAdj");
final int verifiedAdj = cpr.proc.mState.getVerifiedAdj();
- boolean success = mService.updateOomAdjLocked(cpr.proc,
- OOM_ADJ_REASON_GET_PROVIDER);
+ boolean success = !serviceBindingOomAdjPolicy()
+ || mService.mOomAdjuster.evaluateProviderConnectionAdd(r, cpr.proc)
+ ? mService.updateOomAdjLocked(cpr.proc, OOM_ADJ_REASON_GET_PROVIDER)
+ : true;
// XXX things have changed so updateOomAdjLocked doesn't actually tell us
// if the process has been successfully adjusted. So to reduce races with
// it, we will check whether the process still exists. Note that this doesn't
@@ -1529,7 +1532,9 @@ public class ContentProviderHelper {
}
mService.stopAssociationLocked(conn.client.uid, conn.client.processName, cpr.uid,
cpr.appInfo.longVersionCode, cpr.name, cpr.info.processName);
- if (updateOomAdj) {
+ if (updateOomAdj && (!serviceBindingOomAdjPolicy()
+ || mService.mOomAdjuster.evaluateProviderConnectionRemoval(conn.client,
+ cpr.proc))) {
mService.updateOomAdjLocked(conn.provider.proc, OOM_ADJ_REASON_REMOVE_PROVIDER);
}
}
diff --git a/services/core/java/com/android/server/am/IntentBindRecord.java b/services/core/java/com/android/server/am/IntentBindRecord.java
index abc7ab110f89..db47e3f26a6d 100644
--- a/services/core/java/com/android/server/am/IntentBindRecord.java
+++ b/services/core/java/com/android/server/am/IntentBindRecord.java
@@ -46,9 +46,17 @@ final class IntentBindRecord {
boolean hasBound;
/** Set when the service's onUnbind() has asked to be told about new clients. */
boolean doRebind;
-
+
String stringName; // caching of toString
-
+
+ /**
+ * Mark if we've skipped oom adj update before calling into the {@link Service#onBind()}
+ * or {@link Service#onUnbind()}.
+ *
+ * <p>If it's true, we'll skip the oom adj update too during the serviceDoneExecuting.
+ */
+ boolean mSkippedOomAdj;
+
void dump(PrintWriter pw, String prefix) {
pw.print(prefix); pw.print("service="); pw.println(service);
dumpInService(pw, prefix);
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index ef7a0e058db0..862542e3d3d0 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -103,6 +103,7 @@ import static com.android.server.am.ProcessList.CACHED_APP_IMPORTANCE_LEVELS;
import static com.android.server.am.ProcessList.CACHED_APP_MAX_ADJ;
import static com.android.server.am.ProcessList.CACHED_APP_MIN_ADJ;
import static com.android.server.am.ProcessList.FOREGROUND_APP_ADJ;
+import static com.android.server.am.ProcessList.FREEZER_CUTOFF_ADJ;
import static com.android.server.am.ProcessList.HEAVY_WEIGHT_APP_ADJ;
import static com.android.server.am.ProcessList.HOME_APP_ADJ;
import static com.android.server.am.ProcessList.INVALID_ADJ;
@@ -2309,7 +2310,7 @@ public class OomAdjuster {
}
computeServiceHostOomAdjLSP(cr, app, cr.binding.client, now, topApp, doingAll,
- cycleReEval, computeClients, oomAdjReason, cachedAdj, true);
+ cycleReEval, computeClients, oomAdjReason, cachedAdj, true, false);
adj = state.getCurRawAdj();
procState = state.getCurRawProcState();
@@ -2341,7 +2342,7 @@ public class OomAdjuster {
ContentProviderConnection conn = cpr.connections.get(i);
ProcessRecord client = conn.client;
computeProviderHostOomAdjLSP(conn, app, client, now, topApp, doingAll,
- cycleReEval, computeClients, oomAdjReason, cachedAdj, true);
+ cycleReEval, computeClients, oomAdjReason, cachedAdj, true, false);
adj = state.getCurRawAdj();
procState = state.getCurRawProcState();
@@ -2558,17 +2559,18 @@ public class OomAdjuster {
}
@GuardedBy({"mService", "mProcLock"})
- protected void computeServiceHostOomAdjLSP(ConnectionRecord cr, ProcessRecord app,
+ protected boolean computeServiceHostOomAdjLSP(ConnectionRecord cr, ProcessRecord app,
ProcessRecord client, long now, ProcessRecord topApp, boolean doingAll,
boolean cycleReEval, boolean computeClients, int oomAdjReason, int cachedAdj,
- boolean couldRecurse) {
+ boolean couldRecurse, boolean dryRun) {
if (app.isPendingFinishAttach()) {
// We've set the attaching process state in the computeInitialOomAdjLSP. Skip it here.
- return;
+ return false;
}
final ProcessStateRecord state = app.mState;
ProcessStateRecord cstate = client.mState;
+ boolean updated = false;
if (couldRecurse) {
if (app.isSdkSandbox && cr.binding.attributedClient != null) {
@@ -2599,19 +2601,25 @@ public class OomAdjuster {
final int prevRawAdj = adj;
final int prevProcState = procState;
final int prevSchedGroup = schedGroup;
+ final int prevCapability = capability;
final int appUid = app.info.uid;
final int logUid = mService.mCurOomAdjUid;
- state.setCurBoundByNonBgRestrictedApp(state.isCurBoundByNonBgRestrictedApp()
- || cstate.isCurBoundByNonBgRestrictedApp()
- || clientProcState <= PROCESS_STATE_BOUND_TOP
- || (clientProcState == PROCESS_STATE_FOREGROUND_SERVICE
- && !cstate.isBackgroundRestricted()));
+ if (!dryRun) {
+ state.setCurBoundByNonBgRestrictedApp(state.isCurBoundByNonBgRestrictedApp()
+ || cstate.isCurBoundByNonBgRestrictedApp()
+ || clientProcState <= PROCESS_STATE_BOUND_TOP
+ || (clientProcState == PROCESS_STATE_FOREGROUND_SERVICE
+ && !cstate.isBackgroundRestricted()));
+ }
if (client.mOptRecord.shouldNotFreeze()) {
// Propagate the shouldNotFreeze flag down the bindings.
- app.mOptRecord.setShouldNotFreeze(true);
+ if (app.mOptRecord.setShouldNotFreeze(true, dryRun)) {
+ // Bail out early, as we only care about the return value for a dryrun.
+ return true;
+ }
}
boolean trackedProcState = false;
@@ -2653,7 +2661,7 @@ public class OomAdjuster {
}
if (couldRecurse && shouldSkipDueToCycle(app, cstate, procState, adj, cycleReEval)) {
- return;
+ return false;
}
if (clientProcState >= PROCESS_STATE_CACHED_ACTIVITY) {
@@ -2666,7 +2674,10 @@ public class OomAdjuster {
if (cr.hasFlag(Context.BIND_ALLOW_OOM_MANAGEMENT)) {
// Similar to BIND_WAIVE_PRIORITY, keep it unfrozen.
if (clientAdj < CACHED_APP_MIN_ADJ) {
- app.mOptRecord.setShouldNotFreeze(true);
+ if (app.mOptRecord.setShouldNotFreeze(true, dryRun)) {
+ // Bail out early, as we only care about the return value for a dryrun.
+ return true;
+ }
}
// Not doing bind OOM management, so treat
// this guy more like a started service.
@@ -2678,7 +2689,10 @@ public class OomAdjuster {
if (adj > clientAdj) {
adjType = "cch-bound-ui-services";
}
- state.setCached(false);
+ if (state.setCached(false, dryRun)) {
+ // Bail out early, as we only care about the return value for a dryrun.
+ return true;
+ }
clientAdj = adj;
clientProcState = procState;
} else {
@@ -2721,7 +2735,9 @@ public class OomAdjuster {
newAdj = PERSISTENT_SERVICE_ADJ;
schedGroup = SCHED_GROUP_DEFAULT;
procState = ActivityManager.PROCESS_STATE_PERSISTENT;
- cr.trackProcState(procState, mAdjSeq);
+ if (!dryRun) {
+ cr.trackProcState(procState, mAdjSeq);
+ }
trackedProcState = true;
}
} else if (cr.hasFlag(Context.BIND_NOT_PERCEPTIBLE)
@@ -2762,11 +2778,16 @@ public class OomAdjuster {
}
}
if (!cstate.isCached()) {
- state.setCached(false);
+ if (state.setCached(false, dryRun)) {
+ // Bail out early, as we only care about the return value for a dryrun.
+ return true;
+ }
}
if (adj > newAdj) {
adj = newAdj;
- state.setCurRawAdj(adj);
+ if (state.setCurRawAdj(adj, dryRun)) {
+ // Bail out early, as we only care about the return value for a dryrun.
+ }
adjType = "service";
}
}
@@ -2833,25 +2854,35 @@ public class OomAdjuster {
if (cr.hasFlag(Context.BIND_SCHEDULE_LIKE_TOP_APP) && clientIsSystem) {
schedGroup = SCHED_GROUP_TOP_APP;
- state.setScheduleLikeTopApp(true);
+ if (dryRun) {
+ if (prevSchedGroup < schedGroup) {
+ // Bail out early, as we only care about the return value for a dryrun.
+ return true;
+ }
+ } else {
+ state.setScheduleLikeTopApp(true);
+ }
}
- if (!trackedProcState) {
+ if (!trackedProcState && !dryRun) {
cr.trackProcState(clientProcState, mAdjSeq);
}
if (procState > clientProcState) {
procState = clientProcState;
- state.setCurRawProcState(procState);
+ if (state.setCurRawProcState(procState, dryRun)) {
+ // Bail out early, as we only care about the return value for a dryrun.
+ return true;
+ }
if (adjType == null) {
adjType = "service";
}
}
if (procState < PROCESS_STATE_IMPORTANT_BACKGROUND
- && cr.hasFlag(Context.BIND_SHOWING_UI)) {
+ && cr.hasFlag(Context.BIND_SHOWING_UI) && !dryRun) {
app.setPendingUiClean(true);
}
- if (adjType != null) {
+ if (adjType != null && !dryRun) {
state.setAdjType(adjType);
state.setAdjTypeCode(ActivityManager.RunningAppProcessInfo
.REASON_SERVICE_IN_USE);
@@ -2876,11 +2907,16 @@ public class OomAdjuster {
// bound by an unfrozen app via a WPRI binding has to remain
// unfrozen.
if (clientAdj < CACHED_APP_MIN_ADJ) {
- app.mOptRecord.setShouldNotFreeze(true);
+ if (app.mOptRecord.setShouldNotFreeze(true, dryRun)) {
+ // Bail out early, as we only care about the return value for a dryrun.
+ return true;
+ }
}
}
if (cr.hasFlag(Context.BIND_TREAT_LIKE_ACTIVITY)) {
- app.mServices.setTreatLikeActivity(true);
+ if (!dryRun) {
+ app.mServices.setTreatLikeActivity(true);
+ }
if (clientProcState <= PROCESS_STATE_CACHED_ACTIVITY
&& procState > PROCESS_STATE_CACHED_ACTIVITY) {
// This is a cached process, but somebody wants us to treat it like it has
@@ -2894,7 +2930,9 @@ public class OomAdjuster {
if (a != null && adj > FOREGROUND_APP_ADJ
&& a.isActivityVisible()) {
adj = FOREGROUND_APP_ADJ;
- state.setCurRawAdj(adj);
+ if (state.setCurRawAdj(adj, dryRun)) {
+ return true;
+ }
if (cr.notHasFlag(Context.BIND_NOT_FOREGROUND)) {
if (cr.hasFlag(Context.BIND_IMPORTANT)) {
schedGroup = SCHED_GROUP_TOP_APP_BOUND;
@@ -2902,16 +2940,18 @@ public class OomAdjuster {
schedGroup = SCHED_GROUP_DEFAULT;
}
}
- state.setCached(false);
- state.setAdjType("service");
- state.setAdjTypeCode(ActivityManager.RunningAppProcessInfo
- .REASON_SERVICE_IN_USE);
- state.setAdjSource(a);
- state.setAdjSourceProcState(procState);
- state.setAdjTarget(cr.binding.service.instanceName);
- if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
- reportOomAdjMessageLocked(TAG_OOM_ADJ,
- "Raise to service w/activity: " + app);
+ if (!dryRun) {
+ state.setCached(false);
+ state.setAdjType("service");
+ state.setAdjTypeCode(ActivityManager.RunningAppProcessInfo
+ .REASON_SERVICE_IN_USE);
+ state.setAdjSource(a);
+ state.setAdjSourceProcState(procState);
+ state.setAdjTarget(cr.binding.service.instanceName);
+ if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+ reportOomAdjMessageLocked(TAG_OOM_ADJ,
+ "Raise to service w/activity: " + app);
+ }
}
}
}
@@ -2922,7 +2962,15 @@ public class OomAdjuster {
if (procState > PROCESS_STATE_BOUND_FOREGROUND_SERVICE) {
capability &= ~PROCESS_CAPABILITY_BFSL;
}
+ if (!updated) {
+ updated = adj < prevRawAdj || procState < prevProcState || schedGroup > prevSchedGroup
+ || (capability != prevCapability
+ && (capability & prevCapability) == prevCapability);
+ }
+ if (dryRun) {
+ return updated;
+ }
if (adj < prevRawAdj) {
schedGroup = setIntermediateAdjLSP(app, adj, prevRawAdj, schedGroup);
}
@@ -2935,15 +2983,16 @@ public class OomAdjuster {
state.setCurCapability(capability);
state.setEmpty(false);
+ return updated;
}
- protected void computeProviderHostOomAdjLSP(ContentProviderConnection conn, ProcessRecord app,
- ProcessRecord client, long now, ProcessRecord topApp, boolean doingAll,
- boolean cycleReEval, boolean computeClients, int oomAdjReason, int cachedAdj,
- boolean couldRecurse) {
+ protected boolean computeProviderHostOomAdjLSP(ContentProviderConnection conn,
+ ProcessRecord app, ProcessRecord client, long now, ProcessRecord topApp,
+ boolean doingAll, boolean cycleReEval, boolean computeClients, int oomAdjReason,
+ int cachedAdj, boolean couldRecurse, boolean dryRun) {
if (app.isPendingFinishAttach()) {
// We've set the attaching process state in the computeInitialOomAdjLSP. Skip it here.
- return;
+ return false;
}
final ProcessStateRecord state = app.mState;
@@ -2951,7 +3000,7 @@ public class OomAdjuster {
if (client == app) {
// Being our own client is not interesting.
- return;
+ return false;
}
if (couldRecurse) {
if (computeClients) {
@@ -2964,7 +3013,7 @@ public class OomAdjuster {
if (shouldSkipDueToCycle(app, cstate, state.getCurRawProcState(), state.getCurRawAdj(),
cycleReEval)) {
- return;
+ return false;
}
}
@@ -2979,6 +3028,7 @@ public class OomAdjuster {
final int prevRawAdj = adj;
final int prevProcState = procState;
final int prevSchedGroup = schedGroup;
+ final int prevCapability = capability;
final int appUid = app.info.uid;
final int logUid = mService.mCurOomAdjUid;
@@ -2995,14 +3045,19 @@ public class OomAdjuster {
}
if (client.mOptRecord.shouldNotFreeze()) {
// Propagate the shouldNotFreeze flag down the bindings.
- app.mOptRecord.setShouldNotFreeze(true);
+ if (app.mOptRecord.setShouldNotFreeze(true, dryRun)) {
+ // Bail out early, as we only care about the return value for a dryrun.
+ return true;
+ }
}
- state.setCurBoundByNonBgRestrictedApp(state.isCurBoundByNonBgRestrictedApp()
- || cstate.isCurBoundByNonBgRestrictedApp()
- || clientProcState <= PROCESS_STATE_BOUND_TOP
- || (clientProcState == PROCESS_STATE_FOREGROUND_SERVICE
- && !cstate.isBackgroundRestricted()));
+ if (!dryRun) {
+ state.setCurBoundByNonBgRestrictedApp(state.isCurBoundByNonBgRestrictedApp()
+ || cstate.isCurBoundByNonBgRestrictedApp()
+ || clientProcState <= PROCESS_STATE_BOUND_TOP
+ || (clientProcState == PROCESS_STATE_FOREGROUND_SERVICE
+ && !cstate.isBackgroundRestricted()));
+ }
String adjType = null;
if (adj > clientAdj) {
@@ -3011,10 +3066,16 @@ public class OomAdjuster {
adjType = "cch-ui-provider";
} else {
adj = Math.max(clientAdj, FOREGROUND_APP_ADJ);
- state.setCurRawAdj(adj);
+ if (state.setCurRawAdj(adj, dryRun)) {
+ // Bail out early, as we only care about the return value for a dryrun.
+ return true;
+ }
adjType = "provider";
}
- state.setCached(state.isCached() & cstate.isCached());
+ if (state.setCached(state.isCached() & cstate.isCached(), dryRun)) {
+ // Bail out early, as we only care about the return value for a dryrun.
+ return true;
+ }
}
if (clientProcState <= PROCESS_STATE_FOREGROUND_SERVICE) {
@@ -3028,15 +3089,20 @@ public class OomAdjuster {
}
}
- conn.trackProcState(clientProcState, mAdjSeq);
+ if (!dryRun) {
+ conn.trackProcState(clientProcState, mAdjSeq);
+ }
if (procState > clientProcState) {
procState = clientProcState;
- state.setCurRawProcState(procState);
+ if (state.setCurRawProcState(procState, dryRun)) {
+ // Bail out early, as we only care about the return value for a dryrun.
+ return true;
+ }
}
if (cstate.getCurrentSchedulingGroup() > schedGroup) {
schedGroup = SCHED_GROUP_DEFAULT;
}
- if (adjType != null) {
+ if (adjType != null && !dryRun) {
state.setAdjType(adjType);
state.setAdjTypeCode(ActivityManager.RunningAppProcessInfo
.REASON_PROVIDER_IN_USE);
@@ -3056,6 +3122,12 @@ public class OomAdjuster {
capability &= ~PROCESS_CAPABILITY_BFSL;
}
+ if (dryRun && (adj < prevRawAdj || procState < prevProcState || schedGroup > prevSchedGroup
+ || (capability != prevCapability
+ && (capability & prevCapability) == prevCapability))) {
+ return true;
+ }
+
if (adj < prevRawAdj) {
schedGroup = setIntermediateAdjLSP(app, adj, prevRawAdj, schedGroup);
}
@@ -3068,6 +3140,7 @@ public class OomAdjuster {
state.setCurCapability(capability);
state.setEmpty(false);
+ return false;
}
protected int getDefaultCapability(ProcessRecord app, int procState) {
@@ -3342,7 +3415,7 @@ public class OomAdjuster {
changes |= ActivityManagerService.ProcessChangeItem.CHANGE_ACTIVITIES;
}
- updateAppFreezeStateLSP(app, oomAdjReson);
+ updateAppFreezeStateLSP(app, oomAdjReson, false);
if (state.getReportedProcState() != state.getCurProcState()) {
state.setReportedProcState(state.getCurProcState());
@@ -3727,7 +3800,8 @@ public class OomAdjuster {
}
@GuardedBy({"mService", "mProcLock"})
- private void updateAppFreezeStateLSP(ProcessRecord app, @OomAdjReason int oomAdjReason) {
+ void updateAppFreezeStateLSP(ProcessRecord app, @OomAdjReason int oomAdjReason,
+ boolean immediate) {
if (!mCachedAppOptimizer.useFreezer()) {
return;
}
@@ -3746,10 +3820,14 @@ public class OomAdjuster {
final ProcessStateRecord state = app.mState;
// Use current adjustment when freezing, set adjustment when unfreezing.
- if (state.getCurAdj() >= CACHED_APP_MIN_ADJ && !opt.isFrozen()
+ if (state.getCurAdj() >= FREEZER_CUTOFF_ADJ && !opt.isFrozen()
&& !opt.shouldNotFreeze()) {
- mCachedAppOptimizer.freezeAppAsyncLSP(app);
- } else if (state.getSetAdj() < CACHED_APP_MIN_ADJ) {
+ if (!immediate) {
+ mCachedAppOptimizer.freezeAppAsyncLSP(app);
+ } else {
+ mCachedAppOptimizer.freezeAppAsyncAtEarliestLSP(app);
+ }
+ } else if (state.getSetAdj() < FREEZER_CUTOFF_ADJ) {
mCachedAppOptimizer.unfreezeAppLSP(app,
CachedAppOptimizer.getUnfreezeReasonCodeFromOomAdjReason(oomAdjReason));
}
@@ -3826,4 +3904,89 @@ public class OomAdjuster {
// The caller will set the initial value in this implementation.
return app.mState.isCurBoundByNonBgRestrictedApp();
}
+
+ /**
+ * Evaluate the service connection, return {@code true} if the client will change the state
+ * of the service host process by the given connection.
+ */
+ @GuardedBy("mService")
+ boolean evaluateServiceConnectionAdd(ProcessRecord client, ProcessRecord app,
+ ConnectionRecord cr) {
+ if (evaluateConnectionPrelude(client, app)) {
+ return true;
+ }
+ if (app.getSetAdj() <= client.getSetAdj()
+ && app.getSetProcState() <= client.getSetProcState()
+ && ((app.getSetCapability() & client.getSetCapability())
+ == client.getSetCapability()
+ || cr.notHasFlag(Context.BIND_INCLUDE_CAPABILITIES
+ | Context.BIND_BYPASS_USER_NETWORK_RESTRICTIONS))) {
+ // The service host process has better states than the client, so no change.
+ return false;
+ }
+ // Take a dry run of the computeServiceHostOomAdjLSP, this would't be expensive
+ // since it's only evaluating one service connection.
+ return computeServiceHostOomAdjLSP(cr, app, client, SystemClock.uptimeMillis(),
+ mService.getTopApp(), false, false, false, OOM_ADJ_REASON_NONE,
+ CACHED_APP_MIN_ADJ, false, true /* dryRun */);
+ }
+
+ @GuardedBy("mService")
+ boolean evaluateServiceConnectionRemoval(ProcessRecord client, ProcessRecord app,
+ ConnectionRecord cr) {
+ if (evaluateConnectionPrelude(client, app)) {
+ return true;
+ }
+
+ if (app.getSetAdj() < client.getSetAdj()
+ && app.getSetProcState() < client.getSetProcState()) {
+ // The service host process has better states than the client.
+ if (((app.getSetCapability() & client.getSetCapability()) == PROCESS_CAPABILITY_NONE)
+ || cr.notHasFlag(Context.BIND_INCLUDE_CAPABILITIES
+ | Context.BIND_BYPASS_USER_NETWORK_RESTRICTIONS)) {
+ // The service host app doesn't get any capabilities from the client.
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @GuardedBy("mService")
+ boolean evaluateProviderConnectionAdd(ProcessRecord client, ProcessRecord app) {
+ if (evaluateConnectionPrelude(client, app)) {
+ return true;
+ }
+ if (app.getSetAdj() <= client.getSetAdj()
+ && app.getSetProcState() <= client.getSetProcState()) {
+ // The provider host process has better states than the client, so no change.
+ return false;
+ }
+ return computeProviderHostOomAdjLSP(null, app, client, SystemClock.uptimeMillis(),
+ mService.getTopApp(), false, false, false, OOM_ADJ_REASON_NONE, CACHED_APP_MIN_ADJ,
+ false, true /* dryRun */);
+ }
+
+ @GuardedBy("mService")
+ boolean evaluateProviderConnectionRemoval(ProcessRecord client, ProcessRecord app) {
+ if (evaluateConnectionPrelude(client, app)) {
+ return true;
+ }
+ if (app.getSetAdj() < client.getSetAdj()
+ && app.getSetProcState() < client.getSetProcState()) {
+ // The provider host process has better states than the client, so no change.
+ return false;
+ }
+ return true;
+ }
+
+ private boolean evaluateConnectionPrelude(ProcessRecord client, ProcessRecord app) {
+ if (client == null || app == null) {
+ return true;
+ }
+ if (app.isSdkSandbox || app.isolated || app.isKilledByAm() || app.isKilled()) {
+ // Let's always re-evaluate them for now.
+ return true;
+ }
+ return false;
+ }
}
diff --git a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
index 5a3fbe9a66ac..f85b03e8b4eb 100644
--- a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
+++ b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
@@ -1002,7 +1002,7 @@ public class OomAdjusterModernImpl extends OomAdjuster {
computeServiceHostOomAdjLSP(cr, service, app, now, topApp, fullUpdate, false, false,
- oomAdjReason, cachedAdj, false);
+ oomAdjReason, cachedAdj, false, false);
}
for (int i = psr.numberOfSdkSandboxConnections() - 1; i >= 0; i--) {
@@ -1018,7 +1018,7 @@ public class OomAdjusterModernImpl extends OomAdjuster {
}
computeServiceHostOomAdjLSP(cr, service, app, now, topApp, fullUpdate, false, false,
- oomAdjReason, cachedAdj, false);
+ oomAdjReason, cachedAdj, false, false);
}
final ProcessProviderRecord ppr = app.mProviders;
@@ -1035,7 +1035,7 @@ public class OomAdjusterModernImpl extends OomAdjuster {
}
computeProviderHostOomAdjLSP(cpc, provider, app, now, topApp, fullUpdate, false, false,
- oomAdjReason, cachedAdj, false);
+ oomAdjReason, cachedAdj, false, false);
}
}
}
diff --git a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
index f5c5ea8ae55a..a8fe734f59b7 100644
--- a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
+++ b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
@@ -274,7 +274,20 @@ final class ProcessCachedOptimizerRecord {
@GuardedBy("mProcLock")
void setShouldNotFreeze(boolean shouldNotFreeze) {
+ setShouldNotFreeze(shouldNotFreeze, false);
+ }
+
+ /**
+ * @return {@code true} if it's a dry run and it's going to unfreeze the process
+ * if it was a real run.
+ */
+ @GuardedBy("mProcLock")
+ boolean setShouldNotFreeze(boolean shouldNotFreeze, boolean dryRun) {
+ if (dryRun) {
+ return mFrozen && !shouldNotFreeze;
+ }
mShouldNotFreeze = shouldNotFreeze;
+ return false;
}
@GuardedBy("mProcLock")
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index f5c34a5da1c1..10cd6e5bb136 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -371,6 +371,12 @@ public final class ProcessList {
private static final long LMKD_RECONNECT_DELAY_MS = 1000;
/**
+ * The cuttoff adj for the freezer, app processes with adj greater than this value will be
+ * eligible for the freezer.
+ */
+ static final int FREEZER_CUTOFF_ADJ = CACHED_APP_MIN_ADJ;
+
+ /**
* Apps have no access to the private data directories of any other app, even if the other
* app has made them world-readable.
*/
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index e5c4a66562c3..de6f034b62ee 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -720,6 +720,11 @@ class ProcessRecord implements WindowProcessListener {
return mState.getSetProcState();
}
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ int getSetCapability() {
+ return mState.getSetCapability();
+ }
+
@GuardedBy({"mService", "mProcLock"})
public void makeActive(IApplicationThread thread, ProcessStatsService tracker) {
mProfile.onProcessActive(thread, tracker);
@@ -1401,8 +1406,12 @@ class ProcessRecord implements WindowProcessListener {
void onProcessUnfrozen() {
mProfile.onProcessUnfrozen();
+ mServices.onProcessUnfrozen();
}
+ void onProcessFrozenCancelled() {
+ mServices.onProcessFrozenCancelled();
+ }
/*
* Delete all packages from list except the package indicated in info
@@ -1644,6 +1653,13 @@ class ProcessRecord implements WindowProcessListener {
return mWasForceStopped;
}
+ boolean isFreezable() {
+ return mService.mOomAdjuster.mCachedAppOptimizer.useFreezer()
+ && !mOptRecord.isFreezeExempt()
+ && !mOptRecord.shouldNotFreeze()
+ && mState.getCurAdj() >= ProcessList.FREEZER_CUTOFF_ADJ;
+ }
+
/**
* Traverses all client processes and feed them to consumer.
*/
diff --git a/services/core/java/com/android/server/am/ProcessServiceRecord.java b/services/core/java/com/android/server/am/ProcessServiceRecord.java
index f5f2b102596b..57d233e7c503 100644
--- a/services/core/java/com/android/server/am/ProcessServiceRecord.java
+++ b/services/core/java/com/android/server/am/ProcessServiceRecord.java
@@ -19,6 +19,8 @@ package com.android.server.am;
import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_BOUND_SERVICE;
import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_FOREGROUND_SERVICE;
+import static com.android.server.am.Flags.serviceBindingOomAdjPolicy;
+
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.content.Context;
@@ -144,6 +146,11 @@ final class ProcessServiceRecord {
*/
private ArraySet<Integer> mBoundClientUids = new ArraySet<>();
+ /**
+ * The process should schedule a service timeout timer but haven't done so.
+ */
+ private boolean mScheduleServiceTimeoutPending;
+
final ProcessRecord mApp;
private final ActivityManagerService mService;
@@ -657,6 +664,41 @@ final class ProcessServiceRecord {
setHasClientActivities(false);
}
+ @GuardedBy("mService")
+ void noteScheduleServiceTimeoutPending(boolean pending) {
+ mScheduleServiceTimeoutPending = pending;
+ }
+
+ @GuardedBy("mService")
+ boolean isScheduleServiceTimeoutPending() {
+ return mScheduleServiceTimeoutPending;
+ }
+
+ @GuardedBy("mService")
+ void onProcessUnfrozen() {
+ scheduleServiceTimeoutIfNeededLocked();
+ }
+
+ @GuardedBy("mService")
+ void onProcessFrozenCancelled() {
+ scheduleServiceTimeoutIfNeededLocked();
+ }
+
+ @GuardedBy("mService")
+ private void scheduleServiceTimeoutIfNeededLocked() {
+ if (!serviceBindingOomAdjPolicy()) {
+ return;
+ }
+ if (mScheduleServiceTimeoutPending && mExecutingServices.size() > 0) {
+ mService.mServices.scheduleServiceTimeoutLocked(mApp);
+ // We'll need to reset the executingStart since the app was frozen.
+ final long now = SystemClock.uptimeMillis();
+ for (int i = 0, size = mExecutingServices.size(); i < size; i++) {
+ mExecutingServices.valueAt(i).executingStart = now;
+ }
+ }
+ }
+
void dump(PrintWriter pw, String prefix, long nowUptime) {
if (mHasForegroundServices || mApp.mState.getForcingToImportant() != null) {
pw.print(prefix); pw.print("mHasForegroundServices="); pw.print(mHasForegroundServices);
@@ -701,5 +743,10 @@ final class ProcessServiceRecord {
pw.print(prefix); pw.print(" - "); pw.println(mConnections.valueAt(i));
}
}
+ if (serviceBindingOomAdjPolicy()) {
+ pw.print(prefix);
+ pw.print("scheduleServiceTimeoutPending=");
+ pw.println(mScheduleServiceTimeoutPending);
+ }
}
}
diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java
index 3391ec7c4e68..8362eaf76d55 100644
--- a/services/core/java/com/android/server/am/ProcessStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessStateRecord.java
@@ -479,9 +479,22 @@ final class ProcessStateRecord {
@GuardedBy({"mService", "mProcLock"})
void setCurRawAdj(int curRawAdj) {
+ setCurRawAdj(curRawAdj, false);
+ }
+
+ /**
+ * @return {@code true} if it's a dry run and it's going to bump the adj score of the process
+ * if it was a real run.
+ */
+ @GuardedBy({"mService", "mProcLock"})
+ boolean setCurRawAdj(int curRawAdj, boolean dryRun) {
+ if (dryRun) {
+ return mCurRawAdj > curRawAdj;
+ }
mCurRawAdj = curRawAdj;
mApp.getWindowProcessController().setPerceptible(
curRawAdj <= ProcessList.PERCEPTIBLE_APP_ADJ);
+ return false;
}
@GuardedBy(anyOf = {"mService", "mProcLock"})
@@ -594,7 +607,20 @@ final class ProcessStateRecord {
@GuardedBy({"mService", "mProcLock"})
void setCurRawProcState(int curRawProcState) {
+ setCurRawProcState(curRawProcState, false);
+ }
+
+ /**
+ * @return {@code true} if it's a dry run and it's going to bump the procstate of the process
+ * if it was a real run.
+ */
+ @GuardedBy({"mService", "mProcLock"})
+ boolean setCurRawProcState(int curRawProcState, boolean dryRun) {
+ if (dryRun) {
+ return mCurRawProcState > curRawProcState;
+ }
mCurRawProcState = curRawProcState;
+ return false;
}
@GuardedBy(anyOf = {"mService", "mProcLock"})
@@ -900,7 +926,20 @@ final class ProcessStateRecord {
@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;
}
@GuardedBy("mService")
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index 654aebd89de2..31d9cc927223 100644
--- a/services/core/java/com/android/server/am/flags.aconfig
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -35,3 +35,10 @@ flag {
description: "Enable the new FGS restriction logic"
bug: "276963716"
}
+
+flag {
+ name: "service_binding_oom_adj_policy"
+ namespace: "backstage_power"
+ description: "Optimize the service bindings by different policies like skipping oom adjuster"
+ bug: "318717054"
+}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 57b19cda7c12..690c37a9349a 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -914,28 +914,27 @@ public class AudioDeviceInventory {
di.mDeviceCodecFormat = codec;
mConnectedDevices.replace(key, di);
codecChange = true;
- }
- final int res = mAudioSystem.handleDeviceConfigChange(
- btInfo.mAudioSystemDevice, address, BtHelper.getName(btDevice), codec);
-
- if (res != AudioSystem.AUDIO_STATUS_OK) {
- AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
- "APM handleDeviceConfigChange failed for A2DP device addr="
- + address + " codec="
- + AudioSystem.audioFormatToString(codec))
- .printLog(TAG));
-
- // force A2DP device disconnection in case of error so that AudioService
- // state is consistent with audio policy manager state
- setBluetoothActiveDevice(new AudioDeviceBroker.BtDeviceInfo(btInfo,
- BluetoothProfile.STATE_DISCONNECTED));
- } else {
- AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
- "APM handleDeviceConfigChange success for A2DP device addr="
- + address
- + " codec=" + AudioSystem.audioFormatToString(codec))
- .printLog(TAG));
-
+ final int res = mAudioSystem.handleDeviceConfigChange(
+ btInfo.mAudioSystemDevice, address,
+ BtHelper.getName(btDevice), codec);
+ if (res != AudioSystem.AUDIO_STATUS_OK) {
+ AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
+ "APM handleDeviceConfigChange failed for A2DP device addr="
+ + address + " codec="
+ + AudioSystem.audioFormatToString(codec))
+ .printLog(TAG));
+
+ // force A2DP device disconnection in case of error so that AudioService
+ // state is consistent with audio policy manager state
+ setBluetoothActiveDevice(new AudioDeviceBroker.BtDeviceInfo(btInfo,
+ BluetoothProfile.STATE_DISCONNECTED));
+ } else {
+ AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
+ "APM handleDeviceConfigChange success for A2DP device addr="
+ + address
+ + " codec=" + AudioSystem.audioFormatToString(codec))
+ .printLog(TAG));
+ }
}
}
if (!codecChange) {
diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java
index a30cdc47a461..9610034caf01 100644
--- a/services/core/java/com/android/server/audio/SoundDoseHelper.java
+++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java
@@ -1203,7 +1203,7 @@ public class SoundDoseHelper {
@GuardedBy("mCsdStateLock")
private void sanitizeDoseRecords_l() {
if (mDoseRecords.size() > MAX_NUMBER_OF_CACHED_RECORDS) {
- int nrToRemove = MAX_NUMBER_OF_CACHED_RECORDS - mDoseRecords.size();
+ int nrToRemove = mDoseRecords.size() - MAX_NUMBER_OF_CACHED_RECORDS;
Log.w(TAG,
"Removing " + nrToRemove + " records from the total of " + mDoseRecords.size());
// Remove older elements to fit into persisted settings max length
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index 21e6bac53cde..3f3540e2868c 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -20,7 +20,7 @@ package com.android.server.biometrics;
// TODO(b/141025588): Create separate internal and external permissions for AuthService.
// TODO(b/141025588): Get rid of the USE_FINGERPRINT permission.
-import static android.Manifest.permission.MANAGE_BIOMETRIC_DIALOG;
+import static android.Manifest.permission.SET_BIOMETRIC_DIALOG_LOGO;
import static android.Manifest.permission.TEST_BIOMETRIC;
import static android.Manifest.permission.USE_BIOMETRIC;
import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
@@ -305,7 +305,7 @@ public class AuthService extends SystemService {
if (promptInfo.containsPrivateApiConfigurations()) {
checkInternalPermission();
}
- if (promptInfo.containsManageBioApiConfigurations()) {
+ if (promptInfo.containsSetLogoApiConfigurations()) {
checkManageBiometricPermission();
}
@@ -997,8 +997,8 @@ public class AuthService extends SystemService {
}
private void checkManageBiometricPermission() {
- getContext().enforceCallingOrSelfPermission(MANAGE_BIOMETRIC_DIALOG,
- "Must have MANAGE_BIOMETRIC_DIALOG permission");
+ getContext().enforceCallingOrSelfPermission(SET_BIOMETRIC_DIALOG_LOGO,
+ "Must have SET_BIOMETRIC_DIALOG_LOGO permission");
}
private void checkPermission() {
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 9cf9119aff82..245fcea06eac 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -2776,17 +2776,17 @@ public final class DisplayManagerService extends SystemService {
}
private ScreenCapture.ScreenshotHardwareBuffer userScreenshotInternal(int displayId) {
+ final ScreenCapture.DisplayCaptureArgs captureArgs;
synchronized (mSyncRoot) {
final IBinder token = getDisplayToken(displayId);
if (token == null) {
return null;
}
- final ScreenCapture.DisplayCaptureArgs captureArgs =
- new ScreenCapture.DisplayCaptureArgs.Builder(token)
+ captureArgs = new ScreenCapture.DisplayCaptureArgs.Builder(token)
.build();
- return ScreenCapture.captureDisplay(captureArgs);
}
+ return ScreenCapture.captureDisplay(captureArgs);
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/input/InputManagerInternal.java b/services/core/java/com/android/server/input/InputManagerInternal.java
index 380106ba486d..b963a4b614e8 100644
--- a/services/core/java/com/android/server/input/InputManagerInternal.java
+++ b/services/core/java/com/android/server/input/InputManagerInternal.java
@@ -87,12 +87,16 @@ public abstract class InputManagerInternal {
* connected, the caller may be blocked for an arbitrary period of time.
*
* @return true if the pointer displayId was set successfully, or false if it fails.
+ *
+ * @deprecated TODO(b/293587049): Not needed - remove after Pointer Icon Refactor is complete.
*/
public abstract boolean setVirtualMousePointerDisplayId(int pointerDisplayId);
/**
* Gets the display id that the MouseCursorController is being forced to target. Returns
* {@link android.view.Display#INVALID_DISPLAY} if there is no override
+ *
+ * @deprecated TODO(b/293587049): Not needed - remove after Pointer Icon Refactor is complete.
*/
public abstract int getVirtualMousePointerDisplayId();
@@ -101,7 +105,7 @@ public abstract class InputManagerInternal {
*
* Returns NaN-s as the coordinates if the cursor is not available.
*/
- public abstract PointF getCursorPosition();
+ public abstract PointF getCursorPosition(int displayId);
/**
* Enables or disables pointer acceleration for mouse movements.
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 67c23fc4db12..687def05b1d7 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -1448,6 +1448,10 @@ public class InputManagerService extends IInputManager.Stub
}
private boolean setVirtualMousePointerDisplayIdBlocking(int overrideDisplayId) {
+ if (com.android.input.flags.Flags.enablePointerChoreographer()) {
+ throw new IllegalStateException(
+ "This must not be used when PointerChoreographer is enabled");
+ }
final boolean isRemovingOverride = overrideDisplayId == Display.INVALID_DISPLAY;
// Take care to not make calls to window manager while holding internal locks.
@@ -1486,6 +1490,10 @@ public class InputManagerService extends IInputManager.Stub
}
private int getVirtualMousePointerDisplayId() {
+ if (com.android.input.flags.Flags.enablePointerChoreographer()) {
+ throw new IllegalStateException(
+ "This must not be used when PointerChoreographer is enabled");
+ }
synchronized (mAdditionalDisplayInputPropertiesLock) {
return mOverriddenPointerDisplayId;
}
@@ -3332,8 +3340,8 @@ public class InputManagerService extends IInputManager.Stub
}
@Override
- public PointF getCursorPosition() {
- final float[] p = mNative.getMouseCursorPosition();
+ public PointF getCursorPosition(int displayId) {
+ final float[] p = mNative.getMouseCursorPosition(displayId);
if (p == null || p.length != 2) {
throw new IllegalStateException("Failed to get mouse cursor position");
}
@@ -3614,6 +3622,13 @@ public class InputManagerService extends IInputManager.Stub
}
/**
+ * Sets Accessibility slow keys threshold in milliseconds.
+ */
+ public void setAccessibilitySlowKeysThreshold(int thresholdTimeMs) {
+ mNative.setAccessibilitySlowKeysThreshold(thresholdTimeMs);
+ }
+
+ /**
* Sets whether Accessibility sticky keys is enabled.
*/
public void setAccessibilityStickyKeysEnabled(boolean enabled) {
diff --git a/services/core/java/com/android/server/input/InputSettingsObserver.java b/services/core/java/com/android/server/input/InputSettingsObserver.java
index 572d844d752d..165dfe445751 100644
--- a/services/core/java/com/android/server/input/InputSettingsObserver.java
+++ b/services/core/java/com/android/server/input/InputSettingsObserver.java
@@ -89,6 +89,8 @@ class InputSettingsObserver extends ContentObserver {
(reason) -> updateShowRotaryInput()),
Map.entry(Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_BOUNCE_KEYS),
(reason) -> updateAccessibilityBounceKeys()),
+ Map.entry(Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_SLOW_KEYS),
+ (reason) -> updateAccessibilitySlowKeys()),
Map.entry(Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_STICKY_KEYS),
(reason) -> updateAccessibilityStickyKeys()));
}
@@ -228,6 +230,11 @@ class InputSettingsObserver extends ContentObserver {
InputSettings.getAccessibilityBounceKeysThreshold(mContext));
}
+ private void updateAccessibilitySlowKeys() {
+ mService.setAccessibilitySlowKeysThreshold(
+ InputSettings.getAccessibilitySlowKeysThreshold(mContext));
+ }
+
private void updateAccessibilityStickyKeys() {
mService.setAccessibilityStickyKeysEnabled(
InputSettings.isAccessibilityStickyKeysEnabled(mContext));
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index 8aec8ca7a23f..bc8207835a6e 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -233,14 +233,15 @@ interface NativeInputManagerService {
void setStylusButtonMotionEventsEnabled(boolean enabled);
/**
- * Get the current position of the mouse cursor.
+ * Get the current position of the mouse cursor on the given display.
*
- * If the mouse cursor is not currently shown, the coordinate values will be NaN-s.
+ * If the mouse cursor is not currently shown, the coordinate values will be NaN-s. Use
+ * {@link android.view.Display#INVALID_DISPLAY} to get the position of the default mouse cursor.
*
* NOTE: This will grab the PointerController's lock, so we must be careful about calling this
* from the InputReader or Display threads, which may result in a deadlock.
*/
- float[] getMouseCursorPosition();
+ float[] getMouseCursorPosition(int displayId);
/** Set whether showing a pointer icon for styluses is enabled. */
void setStylusPointerIconEnabled(boolean enabled);
@@ -257,6 +258,11 @@ interface NativeInputManagerService {
void setAccessibilityBounceKeysThreshold(int thresholdTimeMs);
/**
+ * Notify if Accessibility slow keys threshold is changed from InputSettings.
+ */
+ void setAccessibilitySlowKeysThreshold(int thresholdTimeMs);
+
+ /**
* Notify if Accessibility sticky keys is enabled/disabled from InputSettings.
*/
void setAccessibilityStickyKeysEnabled(boolean enabled);
@@ -514,7 +520,7 @@ interface NativeInputManagerService {
public native void setStylusButtonMotionEventsEnabled(boolean enabled);
@Override
- public native float[] getMouseCursorPosition();
+ public native float[] getMouseCursorPosition(int displayId);
@Override
public native void setStylusPointerIconEnabled(boolean enabled);
@@ -526,6 +532,9 @@ interface NativeInputManagerService {
public native void setAccessibilityBounceKeysThreshold(int thresholdTimeMs);
@Override
+ public native void setAccessibilitySlowKeysThreshold(int thresholdTimeMs);
+
+ @Override
public native void setAccessibilityStickyKeysEnabled(boolean enabled);
}
}
diff --git a/services/core/java/com/android/server/inputmethod/ClientController.java b/services/core/java/com/android/server/inputmethod/ClientController.java
index 21b952bb7760..ece236a5f18c 100644
--- a/services/core/java/com/android/server/inputmethod/ClientController.java
+++ b/services/core/java/com/android/server/inputmethod/ClientController.java
@@ -21,8 +21,6 @@ import android.content.pm.PackageManagerInternal;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.ArrayMap;
-import android.util.SparseArray;
-import android.view.inputmethod.InputBinding;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -156,48 +154,4 @@ final class ClientController {
return InputMethodUtils.checkIfPackageBelongsToUid(
mPackageManagerInternal, cs.mUid, packageName);
}
-
- static final class ClientState {
- final IInputMethodClientInvoker mClient;
- final IRemoteInputConnection mFallbackInputConnection;
- final int mUid;
- final int mPid;
- final int mSelfReportedDisplayId;
- final InputBinding mBinding;
- final IBinder.DeathRecipient mClientDeathRecipient;
-
- @GuardedBy("ImfLock.class")
- boolean mSessionRequested;
-
- @GuardedBy("ImfLock.class")
- boolean mSessionRequestedForAccessibility;
-
- @GuardedBy("ImfLock.class")
- InputMethodManagerService.SessionState mCurSession;
-
- @GuardedBy("ImfLock.class")
- SparseArray<InputMethodManagerService.AccessibilitySessionState> mAccessibilitySessions =
- new SparseArray<>();
-
- @Override
- public String toString() {
- return "ClientState{" + Integer.toHexString(
- System.identityHashCode(this)) + " mUid=" + mUid
- + " mPid=" + mPid + " mSelfReportedDisplayId=" + mSelfReportedDisplayId + "}";
- }
-
- ClientState(IInputMethodClientInvoker client,
- IRemoteInputConnection fallbackInputConnection,
- int uid, int pid, int selfReportedDisplayId,
- IBinder.DeathRecipient clientDeathRecipient) {
- mClient = client;
- mFallbackInputConnection = fallbackInputConnection;
- mUid = uid;
- mPid = pid;
- mSelfReportedDisplayId = selfReportedDisplayId;
- mBinding = new InputBinding(null /*conn*/, mFallbackInputConnection.asBinder(), mUid,
- mPid);
- mClientDeathRecipient = clientDeathRecipient;
- }
- }
}
diff --git a/services/core/java/com/android/server/inputmethod/ClientState.java b/services/core/java/com/android/server/inputmethod/ClientState.java
new file mode 100644
index 000000000000..e98a5a73ab90
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/ClientState.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import android.os.IBinder;
+import android.util.SparseArray;
+import android.view.inputmethod.InputBinding;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.inputmethod.IRemoteInputConnection;
+
+final class ClientState {
+ final IInputMethodClientInvoker mClient;
+ final IRemoteInputConnection mFallbackInputConnection;
+ final int mUid;
+ final int mPid;
+ final int mSelfReportedDisplayId;
+ final InputBinding mBinding;
+ final IBinder.DeathRecipient mClientDeathRecipient;
+
+ @GuardedBy("ImfLock.class")
+ boolean mSessionRequested;
+
+ @GuardedBy("ImfLock.class")
+ boolean mSessionRequestedForAccessibility;
+
+ @GuardedBy("ImfLock.class")
+ InputMethodManagerService.SessionState mCurSession;
+
+ @GuardedBy("ImfLock.class")
+ SparseArray<InputMethodManagerService.AccessibilitySessionState> mAccessibilitySessions =
+ new SparseArray<>();
+
+ @Override
+ public String toString() {
+ return "ClientState{" + Integer.toHexString(
+ System.identityHashCode(this)) + " mUid=" + mUid
+ + " mPid=" + mPid + " mSelfReportedDisplayId=" + mSelfReportedDisplayId + "}";
+ }
+
+ ClientState(IInputMethodClientInvoker client,
+ IRemoteInputConnection fallbackInputConnection,
+ int uid, int pid, int selfReportedDisplayId,
+ IBinder.DeathRecipient clientDeathRecipient) {
+ mClient = client;
+ mFallbackInputConnection = fallbackInputConnection;
+ mUid = uid;
+ mPid = pid;
+ mSelfReportedDisplayId = selfReportedDisplayId;
+ mBinding = new InputBinding(null /*conn*/, mFallbackInputConnection.asBinder(), mUid,
+ mPid);
+ mClientDeathRecipient = clientDeathRecipient;
+ }
+}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 50340d241347..4767ebd0aab0 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -48,7 +48,6 @@ import static android.view.Display.INVALID_DISPLAY;
import static android.view.WindowManager.DISPLAY_IME_POLICY_HIDE;
import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
-import static com.android.server.inputmethod.ClientController.ClientState;
import static com.android.server.inputmethod.ImeVisibilityStateComputer.ImeTargetWindowState;
import static com.android.server.inputmethod.ImeVisibilityStateComputer.ImeVisibilityResult;
import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME;
@@ -116,7 +115,6 @@ import android.util.PrintWriterPrinter;
import android.util.Printer;
import android.util.Slog;
import android.util.SparseArray;
-import android.util.SparseBooleanArray;
import android.util.proto.ProtoOutputStream;
import android.view.InputChannel;
import android.view.InputDevice;
@@ -282,8 +280,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
@NonNull
private InputMethodSettings mSettings;
final SettingsObserver mSettingsObserver;
- private final SparseBooleanArray mLoggedDeniedGetInputMethodWindowVisibleHeightForUid =
- new SparseBooleanArray(0);
final WindowManagerInternal mWindowManagerInternal;
private final ActivityManagerInternal mActivityManagerInternal;
final PackageManagerInternal mPackageManagerInternal;
@@ -1355,13 +1351,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
clearPackageChangeState();
}
- @Override
- public void onUidRemoved(int uid) {
- synchronized (ImfLock.class) {
- mLoggedDeniedGetInputMethodWindowVisibleHeightForUid.delete(uid);
- }
- }
-
private void clearPackageChangeState() {
// No need to lock them because we access these fields only on getRegisteredHandler().
mChangedPackages.clear();
@@ -2176,7 +2165,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
/**
* Hide the IME if the removed user is the current user.
*/
- private void onClientRemoved(ClientController.ClientState client) {
+ private void onClientRemoved(ClientState client) {
synchronized (ImfLock.class) {
clearClientSessionLocked(client);
clearClientSessionForAccessibilityLocked(client);
@@ -2400,16 +2389,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
@StartInputReason int startInputReason,
int unverifiedTargetSdkVersion,
@NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
- String selectedMethodId = getSelectedMethodIdLocked();
-
- if (!mSystemReady) {
- // If the system is not yet ready, we shouldn't be running third
- // party code.
- return new InputBindResult(
- InputBindResult.ResultCode.ERROR_SYSTEM_NOT_READY,
- null, null, null, selectedMethodId, getSequenceNumberLocked(), false);
- }
-
if (!InputMethodUtils.checkIfPackageBelongsToUid(mPackageManagerInternal, cs.mUid,
editorInfo.packageName)) {
Slog.e(TAG, "Rejecting this client as it reported an invalid package name."
@@ -2430,6 +2409,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
// Potentially override the selected input method if the new display belongs to a virtual
// device with a custom IME.
+ String selectedMethodId = getSelectedMethodIdLocked();
if (oldDisplayIdToShowIme != mDisplayIdToShowIme) {
final String deviceMethodId = computeCurrentDeviceMethodIdLocked(selectedMethodId);
if (deviceMethodId == null) {
@@ -3674,7 +3654,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
+ "specified for cross-user startInputOrWindowGainedFocus()");
}
}
-
if (windowToken == null) {
Slog.e(TAG, "windowToken cannot be null.");
return InputBindResult.NULL;
@@ -3686,6 +3665,14 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
"InputMethodManagerService#startInputOrWindowGainedFocus");
final InputBindResult result;
synchronized (ImfLock.class) {
+ if (!mSystemReady) {
+ // If the system is not yet ready, we shouldn't be running third arty code.
+ return new InputBindResult(
+ InputBindResult.ResultCode.ERROR_SYSTEM_NOT_READY,
+ null /* method */, null /* accessibilitySessions */, null /* channel */,
+ getSelectedMethodIdLocked(), getSequenceNumberLocked(),
+ false /* isInputMethodSuppressingSpellChecker */);
+ }
final long ident = Binder.clearCallingIdentity();
try {
result = startInputOrWindowGainedFocusInternalLocked(startInputReason,
@@ -4277,10 +4264,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
synchronized (ImfLock.class) {
if (!canInteractWithImeLocked(callingUid, client,
"getInputMethodWindowVisibleHeight", null /* statsToken */)) {
- if (!mLoggedDeniedGetInputMethodWindowVisibleHeightForUid.get(callingUid)) {
- EventLog.writeEvent(0x534e4554, "204906124", callingUid, "");
- mLoggedDeniedGetInputMethodWindowVisibleHeightForUid.put(callingUid, true);
- }
return 0;
}
// This should probably use the caller's display id, but because this is unsupported
diff --git a/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java b/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java
index 5e38bca78a7c..2522f7b82353 100644
--- a/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java
+++ b/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java
@@ -27,6 +27,7 @@ import static java.lang.Math.max;
import android.annotation.Nullable;
import android.location.Location;
+import android.location.LocationRequest;
import android.location.LocationResult;
import android.location.provider.ProviderRequest;
import android.os.SystemClock;
@@ -179,6 +180,7 @@ public final class StationaryThrottlingLocationProvider extends DelegateLocation
private void onThrottlingChangedLocked(boolean deliverImmediate) {
long throttlingIntervalMs = INTERVAL_DISABLED;
if (mDeviceStationary && mDeviceIdle && !mIncomingRequest.isLocationSettingsIgnored()
+ && mIncomingRequest.getQuality() != LocationRequest.QUALITY_HIGH_ACCURACY
&& mLastLocation != null
&& mLastLocation.getElapsedRealtimeAgeMillis(mDeviceStationaryRealtimeMs)
<= MAX_STATIONARY_LOCATION_AGE_MS) {
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 2cd3ab1ddbbb..1d516e2931d7 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -287,10 +287,14 @@ public class MediaSessionService extends SystemService implements Monitor {
}
user.mPriorityStack.onSessionActiveStateChanged(record);
}
- setForegroundServiceAllowance(
- record,
- /* allowRunningInForeground= */ record.isActive()
- && (playbackState == null || playbackState.isActive()));
+ boolean allowRunningInForeground = record.isActive()
+ && (playbackState == null || playbackState.isActive());
+
+ Log.d(TAG, "onSessionActiveStateChanged: "
+ + "record=" + record
+ + "playbackState=" + playbackState
+ + "allowRunningInForeground=" + allowRunningInForeground);
+ setForegroundServiceAllowance(record, allowRunningInForeground);
mHandler.postSessionsChanged(record);
}
}
@@ -388,10 +392,12 @@ public class MediaSessionService extends SystemService implements Monitor {
}
user.mPriorityStack.onPlaybackStateChanged(record, shouldUpdatePriority);
if (playbackState != null) {
- setForegroundServiceAllowance(
- record,
- /* allowRunningInForeground= */ playbackState.isActive()
- && record.isActive());
+ boolean allowRunningInForeground = playbackState.isActive() && record.isActive();
+ Log.d(TAG, "onSessionPlaybackStateChanged: "
+ + "record=" + record
+ + "playbackState=" + playbackState
+ + "allowRunningInForeground=" + allowRunningInForeground);
+ setForegroundServiceAllowance(record, allowRunningInForeground);
}
}
}
@@ -556,6 +562,8 @@ public class MediaSessionService extends SystemService implements Monitor {
}
session.close();
+
+ Log.d(TAG, "destroySessionLocked: record=" + session);
setForegroundServiceAllowance(session, /* allowRunningInForeground= */ false);
mHandler.postSessionsChanged(session);
}
diff --git a/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java b/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java
index df612e63927f..bbe6d3a0c8fa 100644
--- a/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java
+++ b/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java
@@ -20,6 +20,7 @@ import android.content.Context;
import android.content.pm.PackageManager;
import android.media.MediaMetrics;
import android.media.metrics.BundleSession;
+import android.media.metrics.EditingEndedEvent;
import android.media.metrics.IMediaMetricsManager;
import android.media.metrics.NetworkEvent;
import android.media.metrics.PlaybackErrorEvent;
@@ -346,6 +347,24 @@ public final class MediaMetricsManagerService extends SystemService {
StatsLog.write(statsEvent);
}
+ @Override
+ public void reportEditingEndedEvent(String sessionId, EditingEndedEvent event, int userId) {
+ int level = loggingLevel();
+ if (level == LOGGING_LEVEL_BLOCKED) {
+ return;
+ }
+ StatsEvent statsEvent =
+ StatsEvent.newBuilder()
+ .setAtomId(798)
+ .writeString(sessionId)
+ .writeInt(event.getFinalState())
+ .writeInt(event.getErrorCode())
+ .writeLong(event.getTimeSinceCreatedMillis())
+ .usePooledBuffer()
+ .build();
+ StatsLog.write(statsEvent);
+ }
+
private int loggingLevel() {
synchronized (mLock) {
int uid = Binder.getCallingUid();
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index c067fa068b12..923be56dd545 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -1183,7 +1183,7 @@ public class ZenModeHelper {
!= newPolicy.getPriorityConversationSenders()) {
userModifiedFields |= ZenPolicy.FIELD_CONVERSATIONS;
}
- if (oldPolicy.getPriorityChannels() != newPolicy.getPriorityChannels()) {
+ if (oldPolicy.getPriorityChannelsAllowed() != newPolicy.getPriorityChannelsAllowed()) {
userModifiedFields |= ZenPolicy.FIELD_ALLOW_CHANNELS;
}
if (oldPolicy.getPriorityCategoryReminders()
diff --git a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
index 1660c3ef952a..8452c0e61a81 100644
--- a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
+++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
@@ -152,14 +152,13 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub {
@RequiresPermission(value = android.Manifest.permission.INTERACT_ACROSS_USERS,
conditional = true)
void ensureCallerPreviouslyGeneratedFile(
- Context context, Pair<Integer, String> callingInfo, int userId,
- String bugreportFile, boolean forceUpdateMapping) {
+ Context context, PackageManager packageManager, Pair<Integer, String> callingInfo,
+ int userId, String bugreportFile, boolean forceUpdateMapping) {
synchronized (mLock) {
if (onboardingBugreportV2Enabled()) {
final int uidForUser = Binder.withCleanCallingIdentity(() -> {
try {
- return context.getPackageManager()
- .getPackageUidAsUser(callingInfo.second, userId);
+ return packageManager.getPackageUidAsUser(callingInfo.second, userId);
} catch (PackageManager.NameNotFoundException exception) {
throwInvalidBugreportFileForCallerException(
bugreportFile, callingInfo.second);
@@ -441,8 +440,8 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub {
Slogf.i(TAG, "Retrieving bugreport for %s / %d", callingPackage, callingUid);
try {
mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(
- mContext, new Pair<>(callingUid, callingPackage), userId, bugreportFile,
- /* forceUpdateMapping= */ false);
+ mContext, mContext.getPackageManager(), new Pair<>(callingUid, callingPackage),
+ userId, bugreportFile, /* forceUpdateMapping= */ false);
} catch (IllegalArgumentException e) {
Slog.e(TAG, e.getMessage());
reportError(listener, IDumpstateListener.BUGREPORT_ERROR_NO_BUGREPORT_TO_RETRIEVE);
diff --git a/services/core/java/com/android/server/pm/BACKGROUND_INSTALL_OWNERS b/services/core/java/com/android/server/pm/BACKGROUND_INSTALL_OWNERS
new file mode 100644
index 000000000000..baa41a55c519
--- /dev/null
+++ b/services/core/java/com/android/server/pm/BACKGROUND_INSTALL_OWNERS
@@ -0,0 +1,2 @@
+georgechan@google.com
+wenhaowang@google.com \ No newline at end of file
diff --git a/services/core/java/com/android/server/pm/OWNERS b/services/core/java/com/android/server/pm/OWNERS
index 84324f2524fc..c8bc56ce7dcd 100644
--- a/services/core/java/com/android/server/pm/OWNERS
+++ b/services/core/java/com/android/server/pm/OWNERS
@@ -51,3 +51,5 @@ per-file ShortcutRequestPinProcessor.java = omakoto@google.com, yamasani@google.
per-file ShortcutService.java = omakoto@google.com, yamasani@google.com, sunnygoyal@google.com, mett@google.com, pinyaoting@google.com
per-file ShortcutUser.java = omakoto@google.com, yamasani@google.com, sunnygoyal@google.com, mett@google.com, pinyaoting@google.com
+# background install control service
+per-file BackgroundInstall* = file:BACKGROUND_INSTALL_OWNERS \ No newline at end of file
diff --git a/services/core/java/com/android/server/pm/PreferredComponent.java b/services/core/java/com/android/server/pm/PreferredComponent.java
index 18caafdaa56a..f3b146464864 100644
--- a/services/core/java/com/android/server/pm/PreferredComponent.java
+++ b/services/core/java/com/android/server/pm/PreferredComponent.java
@@ -28,7 +28,8 @@ import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.LocalServices;
-import com.android.server.pm.pkg.PackageUserState;
+import com.android.server.pm.pkg.PackageStateInternal;
+import com.android.server.pm.pkg.PackageUserStateInternal;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -218,11 +219,15 @@ public class PreferredComponent {
continue;
}
- // Avoid showing the disambiguation dialog if the package which is installed with
- // reason INSTALL_REASON_DEVICE_SETUP.
- final PackageUserState pkgUserState =
- pmi.getPackageStateInternal(ai.packageName).getUserStates().get(userId);
- if (pkgUserState != null && pkgUserState.getInstallReason()
+ // Avoid showing the disambiguation dialog if the package is not installed or
+ // installed with reason INSTALL_REASON_DEVICE_SETUP.
+ final PackageStateInternal ps = pmi.getPackageStateInternal(ai.packageName);
+ if (ps == null) {
+ continue;
+ }
+ final PackageUserStateInternal pkgUserState = ps.getUserStates().get(userId);
+ if (pkgUserState == null
+ || pkgUserState.getInstallReason()
== PackageManager.INSTALL_REASON_DEVICE_SETUP) {
continue;
}
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index a97652c8e167..7e3254de2385 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -3437,7 +3437,7 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile
}
str.close();
- } catch (IOException | XmlPullParserException e) {
+ } catch (IOException | XmlPullParserException | ArrayIndexOutOfBoundsException e) {
// Remove corrupted file and retry.
atomicFile.failRead(str, e);
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index a6598d602d01..c0596bb10823 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -295,8 +295,6 @@ public class UserManagerService extends IUserManager.Stub {
private static final int USER_VERSION = 11;
- private static final int MAX_USER_STRING_LENGTH = 500;
-
private static final long EPOCH_PLUS_30_YEARS = 30L * 365 * 24 * 60 * 60 * 1000L; // ms
static final int WRITE_USER_MSG = 1;
@@ -4692,16 +4690,18 @@ public class UserManagerService extends IUserManager.Stub {
if (userData.persistSeedData) {
if (userData.seedAccountName != null) {
serializer.attribute(null, ATTR_SEED_ACCOUNT_NAME,
- truncateString(userData.seedAccountName));
+ truncateString(userData.seedAccountName,
+ UserManager.MAX_ACCOUNT_STRING_LENGTH));
}
if (userData.seedAccountType != null) {
serializer.attribute(null, ATTR_SEED_ACCOUNT_TYPE,
- truncateString(userData.seedAccountType));
+ truncateString(userData.seedAccountType,
+ UserManager.MAX_ACCOUNT_STRING_LENGTH));
}
}
if (userInfo.name != null) {
serializer.startTag(null, TAG_NAME);
- serializer.text(truncateString(userInfo.name));
+ serializer.text(truncateString(userInfo.name, UserManager.MAX_USER_NAME_LENGTH));
serializer.endTag(null, TAG_NAME);
}
synchronized (mRestrictionsLock) {
@@ -4765,11 +4765,11 @@ public class UserManagerService extends IUserManager.Stub {
serializer.endDocument();
}
- private String truncateString(String original) {
- if (original == null || original.length() <= MAX_USER_STRING_LENGTH) {
+ private String truncateString(String original, int limit) {
+ if (original == null || original.length() <= limit) {
return original;
}
- return original.substring(0, MAX_USER_STRING_LENGTH);
+ return original.substring(0, limit);
}
/*
@@ -5236,7 +5236,7 @@ public class UserManagerService extends IUserManager.Stub {
@UserIdInt int parentId, boolean preCreate, @Nullable String[] disallowedPackages,
@NonNull TimingsTraceAndSlog t, @Nullable Object token)
throws UserManager.CheckedUserOperationException {
- String truncatedName = truncateString(name);
+ String truncatedName = truncateString(name, UserManager.MAX_USER_NAME_LENGTH);
final UserTypeDetails userTypeDetails = mUserTypes.get(userType);
if (userTypeDetails == null) {
throwCheckedUserOperationException(
@@ -6821,9 +6821,14 @@ public class UserManagerService extends IUserManager.Stub {
Slog.e(LOG_TAG, "No such user for settings seed data u=" + userId);
return;
}
- userData.seedAccountName = truncateString(accountName);
- userData.seedAccountType = truncateString(accountType);
- userData.seedAccountOptions = accountOptions;
+ userData.seedAccountName = truncateString(accountName,
+ UserManager.MAX_ACCOUNT_STRING_LENGTH);
+ userData.seedAccountType = truncateString(accountType,
+ UserManager.MAX_ACCOUNT_STRING_LENGTH);
+ if (accountOptions != null && accountOptions.isBundleContentsWithinLengthLimit(
+ UserManager.MAX_ACCOUNT_OPTIONS_LENGTH)) {
+ userData.seedAccountOptions = accountOptions;
+ }
userData.persistSeedData = persist;
}
if (persist) {
diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java
index 14db70e5f72e..23d0230a6080 100644
--- a/services/core/java/com/android/server/pm/UserTypeFactory.java
+++ b/services/core/java/com/android/server/pm/UserTypeFactory.java
@@ -288,6 +288,28 @@ public final class UserTypeFactory {
* configuration.
*/
private static UserTypeDetails.Builder getDefaultTypeProfilePrivate() {
+ UserProperties.Builder userPropertiesBuilder = new UserProperties.Builder()
+ .setStartWithParent(true)
+ .setCredentialShareableWithParent(true)
+ .setAuthAlwaysRequiredToDisableQuietMode(true)
+ .setAllowStoppingUserWithDelayedLocking(true)
+ .setMediaSharedWithParent(false)
+ .setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_SEPARATE)
+ .setShowInSettings(UserProperties.SHOW_IN_SETTINGS_SEPARATE)
+ .setShowInQuietMode(
+ UserProperties.SHOW_IN_QUIET_MODE_HIDDEN)
+ .setShowInSharingSurfaces(
+ UserProperties.SHOW_IN_SHARING_SURFACES_SEPARATE)
+ .setCrossProfileIntentFilterAccessControl(
+ UserProperties.CROSS_PROFILE_INTENT_FILTER_ACCESS_LEVEL_SYSTEM)
+ .setInheritDevicePolicy(UserProperties.INHERIT_DEVICE_POLICY_FROM_PARENT)
+ .setCrossProfileContentSharingStrategy(
+ UserProperties.CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT);
+ if (android.multiuser.Flags.supportHidingProfiles()) {
+ userPropertiesBuilder.setProfileApiVisibility(
+ UserProperties.PROFILE_API_VISIBILITY_HIDDEN);
+ }
+
return new UserTypeDetails.Builder()
.setName(USER_TYPE_PROFILE_PRIVATE)
.setBaseType(FLAG_PROFILE)
@@ -306,23 +328,7 @@ public final class UserTypeFactory {
.setDarkThemeBadgeColors(
R.color.white)
.setDefaultRestrictions(getDefaultProfileRestrictions())
- .setDefaultUserProperties(new UserProperties.Builder()
- .setStartWithParent(true)
- .setCredentialShareableWithParent(true)
- .setAuthAlwaysRequiredToDisableQuietMode(true)
- .setAllowStoppingUserWithDelayedLocking(true)
- .setMediaSharedWithParent(false)
- .setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_SEPARATE)
- .setShowInSettings(UserProperties.SHOW_IN_SETTINGS_SEPARATE)
- .setShowInQuietMode(
- UserProperties.SHOW_IN_QUIET_MODE_HIDDEN)
- .setShowInSharingSurfaces(
- UserProperties.SHOW_IN_SHARING_SURFACES_SEPARATE)
- .setCrossProfileIntentFilterAccessControl(
- UserProperties.CROSS_PROFILE_INTENT_FILTER_ACCESS_LEVEL_SYSTEM)
- .setInheritDevicePolicy(UserProperties.INHERIT_DEVICE_POLICY_FROM_PARENT)
- .setCrossProfileContentSharingStrategy(
- UserProperties.CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT));
+ .setDefaultUserProperties(userPropertiesBuilder);
}
/**
diff --git a/services/core/java/com/android/server/pm/VerifyingSession.java b/services/core/java/com/android/server/pm/VerifyingSession.java
index f0ff85df13d1..dd2b409c7100 100644
--- a/services/core/java/com/android/server/pm/VerifyingSession.java
+++ b/services/core/java/com/android/server/pm/VerifyingSession.java
@@ -357,7 +357,8 @@ final class VerifyingSession {
verifierUser = UserHandle.of(mPm.mUserManager.getCurrentUserId());
}
// TODO(b/300965895): Remove when inconsistencies loading classpaths from apex for
- // user > 1 are fixed.
+ // user > 1 are fixed. Tests should cover verifiers from apex classpaths run on
+ // primary user, secondary user and work profile.
if (pkgLite.isSdkLibrary) {
verifierUser = UserHandle.SYSTEM;
}
diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
index d0fe9647618a..6ed2d3126455 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -421,6 +421,11 @@ public class PackageInfoUtils {
if (ai.isArchived) {
ai.nonLocalizedLabel = state.getArchiveState().getActivityInfos().get(0).getTitle();
}
+ if (!state.isInstalled() && !state.dataExists()
+ && android.content.pm.Flags.nullableDataDir()) {
+ // The data dir has been deleted
+ ai.dataDir = null;
+ }
}
@Nullable
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 0abf304c34ee..1fdcc64a90c8 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -2185,6 +2185,10 @@ public class PhoneWindowManager implements WindowManagerPolicy {
TalkbackShortcutController getTalkbackShortcutController() {
return new TalkbackShortcutController(mContext);
}
+
+ WindowWakeUpPolicy getWindowWakeUpPolicy() {
+ return new WindowWakeUpPolicy(mContext);
+ }
}
/** {@inheritDoc} */
@@ -2433,7 +2437,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
com.android.internal.R.integer.config_keyguardDrawnTimeout);
mKeyguardDelegate = injector.getKeyguardServiceDelegate();
mTalkbackShortcutController = injector.getTalkbackShortcutController();
- mWindowWakeUpPolicy = new WindowWakeUpPolicy(mContext);
+ mWindowWakeUpPolicy = injector.getWindowWakeUpPolicy();
initKeyCombinationRules();
initSingleKeyGestureRules(injector.getLooper());
mButtonOverridePermissionChecker = injector.getButtonOverridePermissionChecker();
diff --git a/services/core/java/com/android/server/selinux/QuotaLimiter.java b/services/core/java/com/android/server/selinux/QuotaLimiter.java
new file mode 100644
index 000000000000..e89ddfd2627c
--- /dev/null
+++ b/services/core/java/com/android/server/selinux/QuotaLimiter.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.selinux;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.Clock;
+
+import java.time.Duration;
+import java.time.Instant;
+
+/**
+ * A QuotaLimiter allows to define a maximum number of Atom pushes within a specific time window.
+ *
+ * <p>The limiter divides the time line in windows of a fixed size. Every time a new permit is
+ * requested, the limiter checks whether the previous request was in the same time window as the
+ * current one. If the two windows are the same, it grants a permit only if the number of permits
+ * granted within the window does not exceed the quota. If the two windows are different, it resets
+ * the quota.
+ */
+public class QuotaLimiter {
+
+ private final Clock mClock;
+ private final Duration mWindowSize;
+ private final int mMaxPermits;
+
+ private long mCurrentWindow = 0;
+ private int mPermitsGranted = 0;
+
+ @VisibleForTesting
+ QuotaLimiter(Clock clock, Duration windowSize, int maxPermits) {
+ mClock = clock;
+ mWindowSize = windowSize;
+ mMaxPermits = maxPermits;
+ }
+
+ public QuotaLimiter(Duration windowSize, int maxPermits) {
+ this(Clock.SYSTEM_CLOCK, windowSize, maxPermits);
+ }
+
+ public QuotaLimiter(int maxPermitsPerDay) {
+ this(Clock.SYSTEM_CLOCK, Duration.ofDays(1), maxPermitsPerDay);
+ }
+
+ /**
+ * Acquires a permit if there is one available in the current time window.
+ *
+ * @return true if a permit was acquired.
+ */
+ boolean acquire() {
+ long nowWindow =
+ Duration.between(Instant.EPOCH, Instant.ofEpochMilli(mClock.currentTimeMillis()))
+ .dividedBy(mWindowSize);
+ if (nowWindow > mCurrentWindow) {
+ mCurrentWindow = nowWindow;
+ mPermitsGranted = 0;
+ }
+
+ if (mPermitsGranted < mMaxPermits) {
+ mPermitsGranted++;
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/services/core/java/com/android/server/selinux/RateLimiter.java b/services/core/java/com/android/server/selinux/RateLimiter.java
new file mode 100644
index 000000000000..599b8409cbc3
--- /dev/null
+++ b/services/core/java/com/android/server/selinux/RateLimiter.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.selinux;
+
+import android.os.SystemClock;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.Clock;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+
+/**
+ * Rate limiter to ensure Atoms are pushed only within the allowed QPS window. This class is not
+ * thread-safe.
+ *
+ * <p>The rate limiter is smoothed, meaning that a rate limiter allowing X permits per second (or X
+ * QPS) will grant permits at a ratio of one every 1/X seconds.
+ */
+public final class RateLimiter {
+
+ private Instant mNextPermit = Instant.EPOCH;
+
+ private final Clock mClock;
+ private final Duration mWindow;
+
+ @VisibleForTesting
+ RateLimiter(Clock clock, Duration window) {
+ mClock = clock;
+ // Truncating because the system clock does not support units smaller than milliseconds.
+ mWindow = window;
+ }
+
+ /**
+ * Create a rate limiter generating one permit every {@code window} of time, using the {@link
+ * Clock.SYSTEM_CLOCK}.
+ */
+ public RateLimiter(Duration window) {
+ this(Clock.SYSTEM_CLOCK, window);
+ }
+
+ /**
+ * Acquire a permit if allowed by the rate limiter. If not, wait until a permit becomes
+ * available.
+ */
+ public void acquire() {
+ Instant now = Instant.ofEpochMilli(mClock.currentTimeMillis());
+
+ if (mNextPermit.isAfter(now)) { // Sleep until we can acquire.
+ SystemClock.sleep(ChronoUnit.MILLIS.between(now, mNextPermit));
+ mNextPermit = mNextPermit.plus(mWindow);
+ } else {
+ mNextPermit = now.plus(mWindow);
+ }
+ }
+
+ /**
+ * Try to acquire a permit if allowed by the rate limiter. Non-blocking.
+ *
+ * @return true if a permit was acquired. Otherwise, return false.
+ */
+ public boolean tryAcquire() {
+ final Instant now = Instant.ofEpochMilli(mClock.currentTimeMillis());
+
+ if (mNextPermit.isAfter(now)) {
+ return false;
+ }
+ mNextPermit = now.plus(mWindow);
+ return true;
+ }
+}
diff --git a/services/core/java/com/android/server/selinux/SelinuxAuditLogBuilder.java b/services/core/java/com/android/server/selinux/SelinuxAuditLogBuilder.java
new file mode 100644
index 000000000000..8d8d5960038e
--- /dev/null
+++ b/services/core/java/com/android/server/selinux/SelinuxAuditLogBuilder.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.selinux;
+
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.Optional;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Stream;
+
+/** Builder for SelinuxAuditLogs. */
+class SelinuxAuditLogBuilder {
+
+ // Currently logs collection is hardcoded for the sdk_sandbox_audit.
+ private static final String SDK_SANDBOX_AUDIT = "sdk_sandbox_audit";
+ static final Matcher SCONTEXT_MATCHER =
+ Pattern.compile(
+ "u:r:(?<stype>"
+ + SDK_SANDBOX_AUDIT
+ + "):s0(:c)?(?<scategories>((,c)?\\d+)+)*")
+ .matcher("");
+
+ static final Matcher TCONTEXT_MATCHER =
+ Pattern.compile("u:object_r:(?<ttype>\\w+):s0(:c)?(?<tcategories>((,c)?\\d+)+)*")
+ .matcher("");
+
+ static final Matcher PATH_MATCHER =
+ Pattern.compile("\"(?<path>/\\w+(/\\w+)?)(/\\w+)*\"").matcher("");
+
+ private Iterator<String> mTokens;
+ private final SelinuxAuditLog mAuditLog = new SelinuxAuditLog();
+
+ void reset(String denialString) {
+ mTokens =
+ Arrays.asList(
+ Optional.ofNullable(denialString)
+ .map(s -> s.split("\\s+|="))
+ .orElse(new String[0]))
+ .iterator();
+ mAuditLog.reset();
+ }
+
+ SelinuxAuditLog build() {
+ while (mTokens.hasNext()) {
+ final String token = mTokens.next();
+
+ switch (token) {
+ case "granted":
+ mAuditLog.mGranted = true;
+ break;
+ case "denied":
+ mAuditLog.mGranted = false;
+ break;
+ case "{":
+ Stream.Builder<String> permissionsStream = Stream.builder();
+ boolean closed = false;
+ while (!closed && mTokens.hasNext()) {
+ String permission = mTokens.next();
+ if ("}".equals(permission)) {
+ closed = true;
+ } else {
+ permissionsStream.add(permission);
+ }
+ }
+ if (!closed) {
+ return null;
+ }
+ mAuditLog.mPermissions = permissionsStream.build().toArray(String[]::new);
+ break;
+ case "scontext":
+ if (!nextTokenMatches(SCONTEXT_MATCHER)) {
+ return null;
+ }
+ mAuditLog.mSType = SCONTEXT_MATCHER.group("stype");
+ mAuditLog.mSCategories = toCategories(SCONTEXT_MATCHER.group("scategories"));
+ break;
+ case "tcontext":
+ if (!nextTokenMatches(TCONTEXT_MATCHER)) {
+ return null;
+ }
+ mAuditLog.mTType = TCONTEXT_MATCHER.group("ttype");
+ mAuditLog.mTCategories = toCategories(TCONTEXT_MATCHER.group("tcategories"));
+ break;
+ case "tclass":
+ if (!mTokens.hasNext()) {
+ return null;
+ }
+ mAuditLog.mTClass = mTokens.next();
+ break;
+ case "path":
+ if (nextTokenMatches(PATH_MATCHER)) {
+ mAuditLog.mPath = PATH_MATCHER.group("path");
+ }
+ break;
+ case "permissive":
+ if (!mTokens.hasNext()) {
+ return null;
+ }
+ mAuditLog.mPermissive = "1".equals(mTokens.next());
+ break;
+ default:
+ break;
+ }
+ }
+ return mAuditLog;
+ }
+
+ boolean nextTokenMatches(Matcher matcher) {
+ return mTokens.hasNext() && matcher.reset(mTokens.next()).matches();
+ }
+
+ static int[] toCategories(String categories) {
+ return categories == null
+ ? null
+ : Arrays.stream(categories.split(",c")).mapToInt(Integer::parseInt).toArray();
+ }
+
+ static class SelinuxAuditLog {
+ boolean mGranted = false;
+ String[] mPermissions = null;
+ String mSType = null;
+ int[] mSCategories = null;
+ String mTType = null;
+ int[] mTCategories = null;
+ String mTClass = null;
+ String mPath = null;
+ boolean mPermissive = false;
+
+ private void reset() {
+ mGranted = false;
+ mPermissions = null;
+ mSType = null;
+ mSCategories = null;
+ mTType = null;
+ mTCategories = null;
+ mTClass = null;
+ mPath = null;
+ mPermissive = false;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/selinux/SelinuxAuditLogsCollector.java b/services/core/java/com/android/server/selinux/SelinuxAuditLogsCollector.java
new file mode 100644
index 000000000000..0219645bee38
--- /dev/null
+++ b/services/core/java/com/android/server/selinux/SelinuxAuditLogsCollector.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.selinux;
+
+import android.util.EventLog;
+import android.util.EventLog.Event;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.selinux.SelinuxAuditLogBuilder.SelinuxAuditLog;
+
+import java.io.IOException;
+import java.time.Instant;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Queue;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/** Class in charge of collecting SELinux audit logs and push the SELinux atoms. */
+class SelinuxAuditLogsCollector {
+
+ private static final String TAG = "SelinuxAuditLogs";
+
+ private static final String SELINUX_PATTERN = "^.*\\bavc:\\s+(?<denial>.*)$";
+
+ @VisibleForTesting
+ static final Matcher SELINUX_MATCHER = Pattern.compile(SELINUX_PATTERN).matcher("");
+
+ private final RateLimiter mRateLimiter;
+ private final QuotaLimiter mQuotaLimiter;
+
+ @VisibleForTesting Instant mLastWrite = Instant.MIN;
+
+ final AtomicBoolean mStopRequested = new AtomicBoolean(false);
+
+ SelinuxAuditLogsCollector(RateLimiter rateLimiter, QuotaLimiter quotaLimiter) {
+ mRateLimiter = rateLimiter;
+ mQuotaLimiter = quotaLimiter;
+ }
+
+ /**
+ * Collect and push SELinux audit logs for the provided {@code tagCode}.
+ *
+ * @return true if the job was completed. If the job was interrupted, return false.
+ */
+ boolean collect(int tagCode) {
+ Queue<Event> logLines = new ArrayDeque<>();
+ Instant latestTimestamp = collectLogLines(tagCode, logLines);
+
+ boolean quotaExceeded = writeAuditLogs(logLines);
+ if (quotaExceeded) {
+ Log.w(TAG, "Too many SELinux logs in the queue, I am giving up.");
+ mLastWrite = latestTimestamp; // next run we will ignore all these logs.
+ logLines.clear();
+ }
+
+ return logLines.isEmpty();
+ }
+
+ private Instant collectLogLines(int tagCode, Queue<Event> logLines) {
+ List<Event> events = new ArrayList<>();
+ try {
+ EventLog.readEvents(new int[] {tagCode}, events);
+ } catch (IOException e) {
+ Log.e(TAG, "Error reading event logs", e);
+ }
+
+ Instant latestTimestamp = mLastWrite;
+ for (Event event : events) {
+ Instant eventTime = Instant.ofEpochSecond(0, event.getTimeNanos());
+ if (eventTime.isAfter(latestTimestamp)) {
+ latestTimestamp = eventTime;
+ }
+ if (eventTime.isBefore(mLastWrite)) {
+ continue;
+ }
+ Object eventData = event.getData();
+ if (!(eventData instanceof String)) {
+ continue;
+ }
+ logLines.add(event);
+ }
+ return latestTimestamp;
+ }
+
+ private boolean writeAuditLogs(Queue<Event> logLines) {
+ final SelinuxAuditLogBuilder auditLogBuilder = new SelinuxAuditLogBuilder();
+
+ while (!mStopRequested.get() && !logLines.isEmpty()) {
+ Event event = logLines.poll();
+ String logLine = (String) event.getData();
+ Instant logTime = Instant.ofEpochSecond(0, event.getTimeNanos());
+ if (!SELINUX_MATCHER.reset(logLine).matches()) {
+ continue;
+ }
+
+ auditLogBuilder.reset(SELINUX_MATCHER.group("denial"));
+ final SelinuxAuditLog auditLog = auditLogBuilder.build();
+ if (auditLog == null) {
+ continue;
+ }
+
+ if (!mQuotaLimiter.acquire()) {
+ return true;
+ }
+ mRateLimiter.acquire();
+
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.SELINUX_AUDIT_LOG,
+ auditLog.mGranted,
+ auditLog.mPermissions,
+ auditLog.mSType,
+ auditLog.mSCategories,
+ auditLog.mTType,
+ auditLog.mTCategories,
+ auditLog.mTClass,
+ auditLog.mPath,
+ auditLog.mPermissive);
+
+ if (logTime.isAfter(mLastWrite)) {
+ mLastWrite = logTime;
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/services/core/java/com/android/server/selinux/SelinuxAuditLogsService.java b/services/core/java/com/android/server/selinux/SelinuxAuditLogsService.java
new file mode 100644
index 000000000000..8a661bcc13af
--- /dev/null
+++ b/services/core/java/com/android/server/selinux/SelinuxAuditLogsService.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.selinux;
+
+import static com.android.sdksandbox.flags.Flags.selinuxSdkSandboxAudit;
+
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.util.EventLog;
+import android.util.Log;
+
+import java.time.Duration;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Scheduled jobs related to logging of SELinux denials and audits. The job runs daily on idle
+ * devices.
+ */
+public class SelinuxAuditLogsService extends JobService {
+
+ private static final String TAG = "SelinuxAuditLogs";
+ private static final String SELINUX_AUDIT_NAMESPACE = "SelinuxAuditLogsNamespace";
+
+ static final int AUDITD_TAG_CODE = EventLog.getTagCode("auditd");
+
+ private static final int SELINUX_AUDIT_JOB_ID = 25327386;
+ private static final JobInfo SELINUX_AUDIT_JOB =
+ new JobInfo.Builder(
+ SELINUX_AUDIT_JOB_ID,
+ new ComponentName("android", SelinuxAuditLogsService.class.getName()))
+ .setPeriodic(TimeUnit.DAYS.toMillis(1))
+ .setRequiresDeviceIdle(true)
+ .setRequiresCharging(true)
+ .setRequiresBatteryNotLow(true)
+ .build();
+
+ private static final ExecutorService EXECUTOR_SERVICE = Executors.newSingleThreadExecutor();
+ private static final AtomicReference<Boolean> IS_RUNNING = new AtomicReference<>(false);
+
+ // Audit logging is subject to both rate and quota limiting. We can only push one atom every 10
+ // milliseconds, and no more than 50K atoms can be pushed each day.
+ private static final SelinuxAuditLogsCollector AUDIT_LOGS_COLLECTOR =
+ new SelinuxAuditLogsCollector(
+ new RateLimiter(/* window= */ Duration.ofMillis(10)),
+ new QuotaLimiter(/* maxPermitsPerDay= */ 50000));
+
+ /** Schedule jobs with the {@link JobScheduler}. */
+ public static void schedule(Context context) {
+ if (!selinuxSdkSandboxAudit()) {
+ Log.d(TAG, "SelinuxAuditLogsService not enabled");
+ return;
+ }
+
+ if (AUDITD_TAG_CODE == -1) {
+ Log.e(TAG, "auditd is not a registered tag on this system");
+ return;
+ }
+
+ if (context.getSystemService(JobScheduler.class)
+ .forNamespace(SELINUX_AUDIT_NAMESPACE)
+ .schedule(SELINUX_AUDIT_JOB)
+ == JobScheduler.RESULT_FAILURE) {
+ Log.e(TAG, "SelinuxAuditLogsService could not be started.");
+ }
+ }
+
+ @Override
+ public boolean onStartJob(JobParameters params) {
+ if (params.getJobId() != SELINUX_AUDIT_JOB_ID) {
+ Log.e(TAG, "The job id does not match the expected selinux job id.");
+ return false;
+ }
+
+ AUDIT_LOGS_COLLECTOR.mStopRequested.set(false);
+ IS_RUNNING.set(true);
+ EXECUTOR_SERVICE.execute(new LogsCollectorJob(this, params));
+
+ return true; // the job is running
+ }
+
+ @Override
+ public boolean onStopJob(JobParameters params) {
+ if (params.getJobId() != SELINUX_AUDIT_JOB_ID) {
+ return false;
+ }
+
+ AUDIT_LOGS_COLLECTOR.mStopRequested.set(true);
+ return IS_RUNNING.get();
+ }
+
+ private static class LogsCollectorJob implements Runnable {
+ private final JobService mAuditLogService;
+ private final JobParameters mParams;
+
+ LogsCollectorJob(JobService auditLogService, JobParameters params) {
+ mAuditLogService = auditLogService;
+ mParams = params;
+ }
+
+ @Override
+ public void run() {
+ IS_RUNNING.updateAndGet(
+ isRunning -> {
+ boolean done = AUDIT_LOGS_COLLECTOR.collect(AUDITD_TAG_CODE);
+ if (done) {
+ mAuditLogService.jobFinished(mParams, /* wantsReschedule= */ false);
+ }
+ return !done;
+ });
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
index d7b8495929a2..b6d0ca19d484 100644
--- a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
+++ b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
@@ -2390,6 +2390,33 @@ public class TvInteractiveAppManagerService extends SystemService {
}
@Override
+ public void sendCertificate(IBinder sessionToken, String host, int port,
+ Bundle certBundle, int userId) {
+ if (DEBUG) {
+ Slogf.d(TAG, "sendCertificate(host=%s port=%d cert=%s)", host, port,
+ certBundle);
+ }
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "sendCertificate");
+ SessionState sessionState = null;
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ sessionState = getSessionStateLocked(sessionToken, callingUid,
+ resolvedUserId);
+ getSessionLocked(sessionState).sendCertificate(host, port, certBundle);
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slogf.e(TAG, "error in sendCertificate", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
public void notifyError(IBinder sessionToken, String errMsg, Bundle params, int userId) {
if (DEBUG) {
Slogf.d(TAG, "notifyError(errMsg=%s)", errMsg);
@@ -4125,6 +4152,24 @@ public class TvInteractiveAppManagerService extends SystemService {
}
@Override
+ public void onRequestCertificate(String host, int port) {
+ synchronized (mLock) {
+ if (DEBUG) {
+ Slogf.d(TAG, "onRequestCertificate");
+ }
+ if (mSessionState.mSession == null || mSessionState.mClient == null) {
+ return;
+ }
+ try {
+ mSessionState.mClient.onRequestCertificate(host, port, mSessionState.mSeq);
+ } catch (RemoteException e) {
+ Slogf.e(TAG, "error in onRequestCertificate", e);
+ }
+ }
+ }
+
+
+ @Override
public void onAdRequest(AdRequest request) {
synchronized (mLock) {
if (DEBUG) {
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperDisplayHelper.java b/services/core/java/com/android/server/wallpaper/WallpaperDisplayHelper.java
index 19fd9a90518d..9e1b5d238d48 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperDisplayHelper.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperDisplayHelper.java
@@ -96,7 +96,8 @@ class WallpaperDisplayHelper {
}
if (populateOrientationPairs) {
int orientation = WallpaperManager.getOrientation(displaySize);
- float newSurface = displaySize.x * displaySize.y * metric.getDensity();
+ float newSurface = displaySize.x * displaySize.y
+ / (metric.getDensity() * metric.getDensity());
if (surface <= 0) {
surface = newSurface;
firstOrientation = orientation;
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 2d584c428d4c..f2d9bf810b53 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -462,17 +462,16 @@ final class AccessibilityController {
}
}
- // TODO(b/318327737): Remove parameter 't' when removing flag DRAW_IN_WM_LOCK.
- void drawMagnifiedRegionBorderIfNeeded(int displayId, SurfaceControl.Transaction t) {
+ void drawMagnifiedRegionBorderIfNeeded(int displayId) {
if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
mAccessibilityTracing.logTrace(
TAG + ".drawMagnifiedRegionBorderIfNeeded",
FLAGS_MAGNIFICATION_CALLBACK,
- "displayId=" + displayId + "; transaction={" + t + "}");
+ "displayId=" + displayId);
}
final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
if (displayMagnifier != null) {
- displayMagnifier.drawMagnifiedRegionBorderIfNeeded(t);
+ displayMagnifier.drawMagnifiedRegionBorderIfNeeded();
}
// Not relevant for the window observer.
}
@@ -870,12 +869,12 @@ final class AccessibilityController {
.sendToTarget();
}
- void drawMagnifiedRegionBorderIfNeeded(SurfaceControl.Transaction t) {
+ void drawMagnifiedRegionBorderIfNeeded() {
if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
mAccessibilityTracing.logTrace(LOG_TAG + ".drawMagnifiedRegionBorderIfNeeded",
- FLAGS_MAGNIFICATION_CALLBACK, "transition={" + t + "}");
+ FLAGS_MAGNIFICATION_CALLBACK);
}
- mMagnifedViewport.drawWindowIfNeeded(t);
+ mMagnifedViewport.drawWindowIfNeeded();
}
void dump(PrintWriter pw, String prefix) {
@@ -1121,14 +1120,6 @@ final class AccessibilityController {
}
void setMagnifiedRegionBorderShown(boolean shown, boolean animate) {
- if (ViewportWindow.DRAW_IN_WM_LOCK) {
- if (shown) {
- mFullRedrawNeeded = true;
- mOldMagnificationRegion.set(0, 0, 0, 0);
- }
- mWindow.setShown(shown, animate);
- return;
- }
if (mWindow.setShown(shown, animate)) {
mFullRedrawNeeded = true;
// Clear the old region, so recomputeBounds will refresh the current region.
@@ -1151,12 +1142,8 @@ final class AccessibilityController {
return mMagnificationSpec;
}
- void drawWindowIfNeeded(SurfaceControl.Transaction t) {
+ void drawWindowIfNeeded() {
recomputeBounds();
- if (ViewportWindow.DRAW_IN_WM_LOCK) {
- mWindow.drawOrRemoveIfNeeded(t);
- return;
- }
mWindow.postDrawIfNeeded();
}
@@ -1187,8 +1174,6 @@ final class AccessibilityController {
private final class ViewportWindow implements Runnable {
private static final String SURFACE_TITLE = "Magnification Overlay";
- // TODO(b/318327737): Remove if it is stable.
- static final boolean DRAW_IN_WM_LOCK = !Flags.drawMagnifierBorderOutsideWmlock();
private final Region mBounds = new Region();
private final Rect mDirtyRect = new Rect();
@@ -1328,14 +1313,14 @@ final class AccessibilityController {
@Override
public void run() {
- drawOrRemoveIfNeeded(mTransaction);
+ drawOrRemoveIfNeeded();
}
/**
* This method must only be called by animation handler directly to make sure
* thread safe and there is no lock held outside.
*/
- private void drawOrRemoveIfNeeded(SurfaceControl.Transaction t) {
+ private void drawOrRemoveIfNeeded() {
// Drawing variables (alpha, dirty rect, and bounds) access is synchronized
// using WindowManagerGlobalLock. Grab copies of these values before
// drawing on the canvas so that drawing can be performed outside of the lock.
@@ -1343,7 +1328,7 @@ final class AccessibilityController {
Rect drawingRect = null;
Region drawingBounds = null;
synchronized (mService.mGlobalLock) {
- if (!DRAW_IN_WM_LOCK && mBlastBufferQueue.mNativeObject == 0) {
+ if (mBlastBufferQueue.mNativeObject == 0) {
// Complete removal since releaseSurface has been called.
if (mSurface.isValid()) {
mTransaction.remove(mSurfaceControl).apply();
@@ -1388,16 +1373,8 @@ final class AccessibilityController {
mPaint.setAlpha(alpha);
canvas.drawPath(drawingBounds.getBoundaryPath(), mPaint);
mSurface.unlockCanvasAndPost(canvas);
- if (DRAW_IN_WM_LOCK) {
- t.show(mSurfaceControl);
- return;
- }
showSurface = true;
} else {
- if (DRAW_IN_WM_LOCK) {
- t.hide(mSurfaceControl);
- return;
- }
showSurface = false;
}
@@ -1413,11 +1390,6 @@ final class AccessibilityController {
@GuardedBy("mService.mGlobalLock")
void releaseSurface() {
mBlastBufferQueue.destroy();
- if (DRAW_IN_WM_LOCK) {
- mService.mTransactionFactory.get().remove(mSurfaceControl).apply();
- mSurface.release();
- return;
- }
// Post to perform cleanup on the thread which handles mSurface.
mService.mAnimationHandler.post(this);
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 3d492bbd4d37..03d55d9edfda 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -53,6 +53,7 @@ import static android.app.WindowConfiguration.activityTypeToString;
import static android.app.admin.DevicePolicyResources.Drawables.Source.PROFILE_SWITCH_ANIMATION;
import static android.app.admin.DevicePolicyResources.Drawables.Style.OUTLINE;
import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON;
+import static android.content.Context.CONTEXT_RESTRICTED;
import static android.content.Intent.ACTION_MAIN;
import static android.content.Intent.CATEGORY_HOME;
import static android.content.Intent.CATEGORY_LAUNCHER;
@@ -2231,7 +2232,17 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
mOptInOnBackInvoked = WindowOnBackInvokedDispatcher
.isOnBackInvokedCallbackEnabled(info, info.applicationInfo,
- () -> ent != null ? ent.array : null, false);
+ () -> {
+ Context appContext = null;
+ try {
+ appContext = mAtmService.mContext.createPackageContextAsUser(
+ info.packageName, CONTEXT_RESTRICTED,
+ UserHandle.of(mUserId));
+ appContext.setTheme(theme);
+ } catch (PackageManager.NameNotFoundException ignore) {
+ }
+ return appContext;
+ });
}
/**
@@ -2635,10 +2646,20 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
if (snapshot == null) {
return false;
}
- if (!snapshot.getTopActivityComponent().equals(mActivityComponent)) {
- // Obsoleted snapshot.
- return false;
- }
+ return isSnapshotComponentCompatible(snapshot) && isSnapshotOrientationCompatible(snapshot);
+ }
+
+ /**
+ * Returns {@code true} if the top activity component of task snapshot equals to this activity.
+ */
+ boolean isSnapshotComponentCompatible(@NonNull TaskSnapshot snapshot) {
+ return snapshot.getTopActivityComponent().equals(mActivityComponent);
+ }
+
+ /**
+ * Returns {@code true} if the orientation of task snapshot is compatible with this activity.
+ */
+ boolean isSnapshotOrientationCompatible(@NonNull TaskSnapshot snapshot) {
final int rotation = mDisplayContent.rotationForActivityInDifferentOrientation(this);
final int currentRotation = task.getWindowConfiguration().getRotation();
final int targetRotation = rotation != ROTATION_UNDEFINED
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index a4d15e07a3ed..83ccbdc1a4d1 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -103,10 +103,6 @@ class BackNavigationController {
static final boolean sPredictBackEnable =
SystemProperties.getBoolean("persist.wm.debug.predictive_back", true);
- static boolean isScreenshotEnabled() {
- return SystemProperties.getInt("persist.wm.debug.predictive_back_screenshot", 0) != 0;
- }
-
// Notify focus window changed
void onFocusChanged(WindowState newFocus) {
mNavigationMonitor.onFocusWindowChanged(newFocus);
@@ -310,9 +306,11 @@ class BackNavigationController {
// keyguard locked and activities are unable to show when locked.
backType = BackNavigationInfo.TYPE_CALLBACK;
}
+ } else if (currentTask.mAtmService.getLockTaskController().isTaskLocked(currentTask)) {
+ // Do not predict if current task is in task locked.
+ backType = BackNavigationInfo.TYPE_CALLBACK;
} else {
- // TODO(208789724): Create single source of truth for this, maybe in
- // RootWindowContainer
+ // Check back-to-home or cross-task
prevTask = currentTask.mRootWindowContainer.getTask(t -> {
if (t.showToCurrentUser() && !t.mChildren.isEmpty()) {
final ActivityRecord ar = t.getTopNonFinishingActivity();
@@ -958,6 +956,18 @@ class BackNavigationController {
return;
}
+ // Start fixed rotation for previous activity before create animation.
+ if (openingActivities.length == 1) {
+ final ActivityRecord next = openingActivities[0];
+ final DisplayContent dc = next.mDisplayContent;
+ dc.rotateInDifferentOrientationIfNeeded(next);
+ if (next.hasFixedRotationTransform()) {
+ // Set the record so we can recognize it to continue to update display
+ // orientation if the previous activity becomes the top later.
+ dc.setFixedRotationLaunchingApp(next,
+ next.getWindowConfiguration().getRotation());
+ }
+ }
mOpenAnimAdaptor = new BackWindowAnimationAdaptorWrapper(true, mSwitchType, open);
if (!mOpenAnimAdaptor.isValid()) {
Slog.w(TAG, "compose animations fail, skip");
@@ -1623,16 +1633,6 @@ class BackNavigationController {
}
activity.mLaunchTaskBehind = true;
- // Handle fixed rotation launching app.
- final DisplayContent dc = activity.mDisplayContent;
- dc.rotateInDifferentOrientationIfNeeded(activity);
- if (activity.hasFixedRotationTransform()) {
- // Set the record so we can recognize it to continue to update display
- // orientation if the previous activity becomes the top later.
- dc.setFixedRotationLaunchingApp(activity,
- activity.getWindowConfiguration().getRotation());
- }
-
ProtoLog.d(WM_DEBUG_BACK_PREVIEW,
"Setting Activity.mLauncherTaskBehind to true. Activity=%s", activity);
activity.mTaskSupervisor.mStoppingActivities.remove(activity);
@@ -1700,21 +1700,38 @@ class BackNavigationController {
static TaskSnapshot getSnapshot(@NonNull WindowContainer w,
ActivityRecord[] visibleOpenActivities) {
+ TaskSnapshot snapshot = null;
if (w.asTask() != null) {
final Task task = w.asTask();
- return task.mRootWindowContainer.mWindowManager.mTaskSnapshotController.getSnapshot(
+ snapshot = task.mRootWindowContainer.mWindowManager.mTaskSnapshotController.getSnapshot(
task.mTaskId, task.mUserId, false /* restoreFromDisk */,
false /* isLowResolution */);
- }
-
- if (w.asActivityRecord() != null) {
+ } else if (w.asActivityRecord() != null) {
final ActivityRecord ar = w.asActivityRecord();
- return ar.mWmService.mSnapshotController.mActivitySnapshotController
+ snapshot = ar.mWmService.mSnapshotController.mActivitySnapshotController
.getSnapshot(visibleOpenActivities);
}
- return null;
+
+ return isSnapshotCompatible(snapshot, visibleOpenActivities) ? snapshot : null;
}
+ static boolean isSnapshotCompatible(@NonNull TaskSnapshot snapshot,
+ @NonNull ActivityRecord[] visibleOpenActivities) {
+ if (snapshot == null) {
+ return false;
+ }
+ boolean oneComponentMatch = false;
+ for (int i = visibleOpenActivities.length - 1; i >= 0; --i) {
+ final ActivityRecord ar = visibleOpenActivities[i];
+ if (!ar.isSnapshotOrientationCompatible(snapshot)) {
+ return false;
+ }
+ oneComponentMatch |= ar.isSnapshotComponentCompatible(snapshot);
+ }
+ return oneComponentMatch;
+ }
+
+
void setWindowManager(WindowManagerService wm) {
mWindowManagerService = wm;
mAnimationHandler = new AnimationHandler(wm);
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index 9ac4a5c4ad5c..5423c6636786 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -717,31 +717,6 @@ public class BackgroundActivityStartController {
boolean callerCanAllow = resultForCaller.allows() && !state.callerExplicitOptOut();
boolean realCallerCanAllow = resultForRealCaller.allows()
&& !state.realCallerExplicitOptOut();
- if (callerCanAllow && realCallerCanAllow) {
- // Both caller and real caller allow with system defined behavior
- if (state.mBalAllowedByPiCreatorWithHardening.allowsBackgroundActivityStarts()) {
- // Will be allowed even with BAL hardening.
- if (DEBUG_ACTIVITY_STARTS) {
- Slog.d(TAG, "Activity start allowed by caller. "
- + state.dump());
- }
- return allowBasedOnCaller(state);
- }
- if (state.mBalAllowedByPiCreator.allowsBackgroundActivityStarts()) {
- Slog.wtf(TAG,
- "With Android 15 BAL hardening this activity start may be blocked"
- + " if the PI creator upgrades target_sdk to 35+"
- + " AND the PI sender upgrades target_sdk to 34+! "
- + state.dump());
- showBalRiskToast();
- return allowBasedOnCaller(state);
- }
- Slog.wtf(TAG,
- "Without Android 15 BAL hardening this activity start would be allowed"
- + " (missing opt in by PI creator or sender)! "
- + state.dump());
- return abortLaunch(state);
- }
if (callerCanAllow) {
// Allowed before V by creator
if (state.mBalAllowedByPiCreatorWithHardening.allowsBackgroundActivityStarts()) {
@@ -753,35 +728,29 @@ public class BackgroundActivityStartController {
return allowBasedOnCaller(state);
}
if (state.mBalAllowedByPiCreator.allowsBackgroundActivityStarts()) {
- Slog.wtf(TAG,
- "With Android 15 BAL hardening this activity start may be blocked"
+ Slog.wtf(TAG, "With Android 15 BAL hardening this activity start may be blocked"
+ " if the PI creator upgrades target_sdk to 35+! "
+ " (missing opt in by PI creator)! "
+ state.dump());
showBalRiskToast();
return allowBasedOnCaller(state);
}
- Slog.wtf(TAG,
- "Without Android 15 BAL hardening this activity start would be allowed"
- + " (missing opt in by PI creator)! "
- + state.dump());
- return abortLaunch(state);
}
if (realCallerCanAllow) {
// Allowed before U by sender
if (state.mBalAllowedByPiSender.allowsBackgroundActivityStarts()) {
- Slog.wtf(TAG,
- "With Android 14 BAL hardening this activity start will be blocked"
+ Slog.wtf(TAG, "With Android 14 BAL hardening this activity start will be blocked"
+ " if the PI sender upgrades target_sdk to 34+! "
+ " (missing opt in by PI sender)! "
+ state.dump());
showBalRiskToast();
return allowBasedOnRealCaller(state);
}
- Slog.wtf(TAG, "Without Android 14 BAL hardening this activity start would be allowed"
- + " (missing opt in by PI sender)! "
- + state.dump());
- return abortLaunch(state);
+ }
+ // caller or real caller could start the activity, but would need to explicitly opt in
+ if (callerCanAllow || realCallerCanAllow) {
+ Slog.wtf(TAG, "Without BAL hardening this activity start would be allowed "
+ + state.dump());
}
// neither the caller not the realCaller can allow or have explicitly opted out
return abortLaunch(state);
@@ -1028,7 +997,7 @@ public class BackgroundActivityStartController {
BalVerdict balAllowedForUid = proc.areBackgroundActivityStartsAllowed(
state.mAppSwitchState);
if (balAllowedForUid.allows()) {
- return balAllowedForCaller.withProcessInfo("process", proc);
+ return balAllowedForUid.withProcessInfo("process", proc);
}
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index e7ecf520425d..5dcd335baf2d 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -160,6 +160,7 @@ import static com.android.server.wm.utils.DisplayInfoOverrides.WM_OVERRIDE_FIELD
import static com.android.server.wm.utils.DisplayInfoOverrides.copyDisplayInfoFields;
import static com.android.server.wm.utils.RegionUtils.forEachRectReverse;
import static com.android.server.wm.utils.RegionUtils.rectListToRegion;
+import static com.android.window.flags.Flags.deferDisplayUpdates;
import static com.android.window.flags.Flags.explicitRefreshRateHints;
import android.annotation.IntDef;
@@ -174,7 +175,6 @@ import android.content.pm.ActivityInfo.ScreenOrientation;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
-import android.graphics.Bitmap;
import android.graphics.ColorSpace;
import android.graphics.Insets;
import android.graphics.Matrix;
@@ -246,7 +246,6 @@ import android.view.inputmethod.ImeTracker;
import android.window.DisplayWindowPolicyController;
import android.window.IDisplayAreaOrganizer;
import android.window.ScreenCapture;
-import android.window.ScreenCapture.SynchronousScreenCaptureListener;
import android.window.SystemPerformanceHinter;
import android.window.TransitionRequestInfo;
@@ -276,7 +275,6 @@ import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Predicate;
-import static com.android.window.flags.Flags.deferDisplayUpdates;
/**
* Utility class for keeping track of the WindowStates and other pertinent contents of a
@@ -5207,10 +5205,9 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
/**
- * Takes a snapshot of the display. In landscape mode this grabs the whole screen.
- * In portrait mode, it grabs the full screenshot.
+ * Creates a LayerCaptureArgs object to represent the entire DisplayContent
*/
- Bitmap screenshotDisplayLocked() {
+ ScreenCapture.LayerCaptureArgs getLayerCaptureArgs() {
if (!mWmService.mPolicy.isScreenOn()) {
if (DEBUG_SCREENSHOT) {
Slog.i(TAG_WM, "Attempted to take screenshot while display was off.");
@@ -5218,24 +5215,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
return null;
}
- SynchronousScreenCaptureListener syncScreenCapture =
- ScreenCapture.createSyncCaptureListener();
-
getBounds(mTmpRect);
mTmpRect.offsetTo(0, 0);
- ScreenCapture.LayerCaptureArgs args =
- new ScreenCapture.LayerCaptureArgs.Builder(getSurfaceControl())
- .setSourceCrop(mTmpRect).build();
-
- ScreenCapture.captureLayers(args, syncScreenCapture);
-
- final ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer =
- syncScreenCapture.getBuffer();
- final Bitmap bitmap = screenshotBuffer == null ? null : screenshotBuffer.asBitmap();
- if (bitmap == null) {
- Slog.w(TAG_WM, "Failed to take screenshot");
- }
- return bitmap;
+ return new ScreenCapture.LayerCaptureArgs.Builder(getSurfaceControl())
+ .setSourceCrop(mTmpRect).build();
}
@Override
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index f2796895d639..0e2d3d151db0 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -1424,7 +1424,7 @@ final class LetterboxUiController {
@VisibleForTesting
boolean shouldShowLetterboxUi(WindowState mainWindow) {
- if (mIsRelaunchingAfterRequestedOrientationChanged || !isSurfaceReadyToShow(mainWindow)) {
+ if (mIsRelaunchingAfterRequestedOrientationChanged) {
return mLastShouldShowLetterboxUi;
}
@@ -1442,13 +1442,6 @@ final class LetterboxUiController {
}
@VisibleForTesting
- boolean isSurfaceReadyToShow(WindowState mainWindow) {
- return mainWindow.isDrawn() // Regular case
- // Waiting for relayoutWindow to call preserveSurface
- || mainWindow.isDragResizeChanged();
- }
-
- @VisibleForTesting
boolean isSurfaceVisible(WindowState mainWindow) {
return mainWindow.isOnScreen() && (mActivityRecord.isVisible()
|| mActivityRecord.isVisibleRequested());
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index 10405ec7cd88..e027eb63f1d5 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -1135,16 +1135,17 @@ class RecentTasks {
if (!mFreezeTaskListReordering) {
// Simple case: this is not an affiliated task, so we just move it to the
// front unless overridden by the provided activity options
+ int indexToAdd = findIndexToAdd(task);
mTasks.remove(taskIndex);
- mTasks.add(0, task);
+ mTasks.add(indexToAdd, task);
if (taskIndex != 0) {
// Only notify when position changes
mTaskNotificationController.notifyTaskListUpdated();
}
if (DEBUG_RECENTS) {
- Slog.d(TAG_RECENTS, "addRecent: moving to top " + task
- + " from " + taskIndex);
+ Slog.d(TAG_RECENTS, "addRecent: moving " + task + " to index "
+ + indexToAdd + " from " + taskIndex);
}
}
notifyTaskPersisterLocked(task, false);
@@ -1231,6 +1232,37 @@ class RecentTasks {
notifyTaskPersisterLocked(task, false /* flush */);
}
+ // Looks for a new index to move the recent Task. Note that the recent Task should not be
+ // placed higher than another recent Task that has higher hierarchical z-ordering.
+ private int findIndexToAdd(Task task) {
+ int indexToAdd = 0;
+ for (int i = 0; i < mTasks.size(); i++) {
+ final Task otherTask = mTasks.get(i);
+ if (task == otherTask) {
+ break;
+ }
+
+ if (!otherTask.isAttached()) {
+ // Stop searching if not attached.
+ break;
+ }
+
+ if (otherTask.inPinnedWindowingMode()) {
+ // Skip pip task without increasing index since pip is always on screen.
+ continue;
+ }
+
+ // Stop searching if the task has higher z-ordering, or increase the index and
+ // continue the search.
+ if (task.compareTo(otherTask) > 0) {
+ break;
+ }
+
+ indexToAdd = i + 1;
+ }
+ return indexToAdd;
+ }
+
/**
* Add the task to the bottom if possible.
*/
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index f10a733040ed..083872a03edd 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -669,7 +669,7 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
}
@Override
- public Bundle sendWallpaperCommand(IBinder window, String action, int x, int y,
+ public void sendWallpaperCommand(IBinder window, String action, int x, int y,
int z, Bundle extras, boolean sync) {
synchronized (mService.mGlobalLock) {
final long ident = Binder.clearCallingIdentity();
@@ -680,10 +680,9 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
if (mCanAlwaysUpdateWallpaper
|| windowState == wallpaperController.getWallpaperTarget()
|| windowState == wallpaperController.getPrevWallpaperTarget()) {
- return wallpaperController.sendWindowWallpaperCommandUnchecked(
+ wallpaperController.sendWindowWallpaperCommandUnchecked(
windowState, action, x, y, z, extras, sync);
}
- return null;
} finally {
Binder.restoreCallingIdentity(ident);
}
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 0c6b174b2408..b2b547e7d9e5 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -45,7 +45,6 @@ import static android.view.WindowManager.TRANSIT_FLAG_OPEN_BEHIND;
import static android.view.WindowManager.TRANSIT_NONE;
import static android.view.WindowManager.TRANSIT_OPEN;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_BACK_PREVIEW;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
import static com.android.server.wm.ActivityRecord.State.PAUSED;
import static com.android.server.wm.ActivityRecord.State.PAUSING;
@@ -89,7 +88,6 @@ import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.graphics.Point;
import android.graphics.Rect;
-import android.hardware.HardwareBuffer;
import android.os.IBinder;
import android.os.UserHandle;
import android.util.DisplayMetrics;
@@ -99,7 +97,6 @@ import android.view.DisplayInfo;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.window.ITaskFragmentOrganizer;
-import android.window.ScreenCapture;
import android.window.TaskFragmentAnimationParams;
import android.window.TaskFragmentInfo;
import android.window.TaskFragmentOrganizerToken;
@@ -113,7 +110,6 @@ import com.android.window.flags.Flags;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
@@ -403,10 +399,6 @@ class TaskFragment extends WindowContainer<WindowContainer> {
/** For calculating app bounds, i.e. the area without the nav bar and display cutout. */
private final Rect mTmpNonDecorBounds = new Rect();
- //TODO(b/207481538) Remove once the infrastructure to support per-activity screenshot is
- // implemented
- HashMap<String, ScreenCapture.ScreenshotHardwareBuffer> mBackScreenshots = new HashMap<>();
-
private final EnsureActivitiesVisibleHelper mEnsureActivitiesVisibleHelper =
new EnsureActivitiesVisibleHelper(this);
@@ -2092,17 +2084,6 @@ class TaskFragment extends WindowContainer<WindowContainer> {
super.addChild(child, index);
if (isAddingActivity && task != null) {
- // TODO(b/207481538): temporary per-activity screenshoting
- if (r != null && BackNavigationController.isScreenshotEnabled()) {
- ProtoLog.v(WM_DEBUG_BACK_PREVIEW, "Screenshotting Activity %s",
- r.mActivityComponent.flattenToString());
- Rect outBounds = r.getBounds();
- ScreenCapture.ScreenshotHardwareBuffer backBuffer = ScreenCapture.captureLayers(
- r.mSurfaceControl,
- new Rect(0, 0, outBounds.width(), outBounds.height()),
- 1f);
- mBackScreenshots.put(r.mActivityComponent.flattenToString(), backBuffer);
- }
addingActivity.inHistory = true;
task.onDescendantActivityAdded(taskHadActivity, activityType, addingActivity);
}
@@ -2905,19 +2886,6 @@ class TaskFragment extends WindowContainer<WindowContainer> {
return !mCreatedByOrganizer || mIsRemovalRequested;
}
- @Nullable
- HardwareBuffer getSnapshotForActivityRecord(@Nullable ActivityRecord r) {
- if (!BackNavigationController.isScreenshotEnabled()) {
- return null;
- }
- if (r != null && r.mActivityComponent != null) {
- ScreenCapture.ScreenshotHardwareBuffer backBuffer =
- mBackScreenshots.get(r.mActivityComponent.flattenToString());
- return backBuffer != null ? backBuffer.getHardwareBuffer() : null;
- }
- return null;
- }
-
@Override
void removeChild(WindowContainer child) {
removeChild(child, true /* removeSelfIfPossible */);
@@ -2926,13 +2894,6 @@ class TaskFragment extends WindowContainer<WindowContainer> {
void removeChild(WindowContainer child, boolean removeSelfIfPossible) {
super.removeChild(child);
final ActivityRecord r = child.asActivityRecord();
- if (BackNavigationController.isScreenshotEnabled()) {
- //TODO(b/207481538) Remove once the infrastructure to support per-activity screenshot is
- // implemented
- if (r != null) {
- mBackScreenshots.remove(r.mActivityComponent.flattenToString());
- }
- }
final WindowProcessController hostProcess = getOrganizerProcessIfDifferent(r);
if (hostProcess != null) {
hostProcess.removeEmbeddedActivity(r);
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 59e3350d5c13..d7b4a399514d 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -496,6 +496,9 @@ class TransitionController {
if (mCollectingTransition != null && mCollectingTransition.isInTransientHide(task)) {
return true;
}
+ for (int i = mWaitingTransitions.size() - 1; i >= 0; --i) {
+ if (mWaitingTransitions.get(i).isInTransientHide(task)) return true;
+ }
for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) {
if (mPlayingTransitions.get(i).isInTransientHide(task)) return true;
}
@@ -506,6 +509,9 @@ class TransitionController {
if (mCollectingTransition != null && mCollectingTransition.isTransientVisible(task)) {
return true;
}
+ for (int i = mWaitingTransitions.size() - 1; i >= 0; --i) {
+ if (mWaitingTransitions.get(i).isTransientVisible(task)) return true;
+ }
for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) {
if (mPlayingTransitions.get(i).isTransientVisible(task)) return true;
}
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 0fc62a758c5e..6949a874b533 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -279,13 +279,14 @@ class WallpaperController {
return null;
}
Point largestDisplaySize = new Point();
+ float largestWidth = 0;
List<DisplayInfo> possibleDisplayInfo =
mService.getPossibleDisplayInfoLocked(DEFAULT_DISPLAY);
for (int i = 0; i < possibleDisplayInfo.size(); i++) {
DisplayInfo displayInfo = possibleDisplayInfo.get(i);
- if (displayInfo.type == Display.TYPE_INTERNAL
- && Math.max(displayInfo.logicalWidth, displayInfo.logicalHeight)
- > Math.max(largestDisplaySize.x, largestDisplaySize.y)) {
+ float width = (float) displayInfo.logicalWidth / displayInfo.physicalXDpi;
+ if (displayInfo.type == Display.TYPE_INTERNAL && width > largestWidth) {
+ largestWidth = width;
largestDisplaySize.set(displayInfo.logicalWidth,
displayInfo.logicalHeight);
}
@@ -641,11 +642,10 @@ class WallpaperController {
}
}
- Bundle sendWindowWallpaperCommandUnchecked(
+ void sendWindowWallpaperCommandUnchecked(
WindowState window, String action, int x, int y, int z,
Bundle extras, boolean sync) {
sendWindowWallpaperCommand(action, x, y, z, extras, sync);
- return null;
}
private void sendWindowWallpaperCommand(
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index 750fd509e50f..b43a4540bbde 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -146,10 +146,11 @@ public class WindowAnimator {
for (int i = 0; i < numDisplays; i++) {
final DisplayContent dc = root.getChildAt(i);
- dc.checkAppWindowsReadyToShow();
+ if (!useShellTransition) {
+ dc.checkAppWindowsReadyToShow();
+ }
if (accessibilityController.hasCallbacks()) {
- accessibilityController.drawMagnifiedRegionBorderIfNeeded(dc.mDisplayId,
- mTransaction);
+ accessibilityController.drawMagnifiedRegionBorderIfNeeded(dc.mDisplayId);
}
if (dc.isAnimating(animationFlags, ANIMATION_TYPE_ALL)) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 9650b8bc2281..426694d178af 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -4087,7 +4087,7 @@ public class WindowManagerService extends IWindowManager.Stub
throw new SecurityException("Requires READ_FRAME_BUFFER permission");
}
- final Bitmap bm;
+ ScreenCapture.LayerCaptureArgs captureArgs;
synchronized (mGlobalLock) {
final DisplayContent displayContent = mRoot.getDisplayContent(DEFAULT_DISPLAY);
if (displayContent == null) {
@@ -4095,12 +4095,30 @@ public class WindowManagerService extends IWindowManager.Stub
Slog.i(TAG_WM, "Screenshot returning null. No Display for displayId="
+ DEFAULT_DISPLAY);
}
- bm = null;
+ captureArgs = null;
} else {
- bm = displayContent.screenshotDisplayLocked();
+ captureArgs = displayContent.getLayerCaptureArgs();
}
}
+ final Bitmap bm;
+ if (captureArgs != null) {
+ ScreenCapture.SynchronousScreenCaptureListener syncScreenCapture =
+ ScreenCapture.createSyncCaptureListener();
+
+ ScreenCapture.captureLayers(captureArgs, syncScreenCapture);
+
+ final ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer =
+ syncScreenCapture.getBuffer();
+ bm = screenshotBuffer == null ? null : screenshotBuffer.asBitmap();
+ } else {
+ bm = null;
+ }
+
+ if (bm == null) {
+ Slog.w(TAG_WM, "Failed to take screenshot");
+ }
+
FgThread.getHandler().post(() -> {
try {
receiver.onHandleAssistScreenshot(bm);
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 4ba52e4c0fd7..3f889c01bafb 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -1471,7 +1471,12 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
final int index = task.mChildren.indexOf(topTaskFragment);
task.mChildren.remove(taskFragment);
task.mChildren.add(index, taskFragment);
- effects |= TRANSACT_EFFECTS_LIFECYCLE;
+ if (taskFragment.hasChild()) {
+ effects |= TRANSACT_EFFECTS_LIFECYCLE;
+ } else {
+ // Ensure that the child layers are updated if the TaskFragment is empty
+ task.assignChildLayers();
+ }
}
}
break;
@@ -1486,7 +1491,12 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
if (task != null) {
task.mChildren.remove(taskFragment);
task.mChildren.add(0, taskFragment);
- effects |= TRANSACT_EFFECTS_LIFECYCLE;
+ if (taskFragment.hasChild()) {
+ effects |= TRANSACT_EFFECTS_LIFECYCLE;
+ } else {
+ // Ensure that the child layers are updated if the TaskFragment is empty.
+ task.assignChildLayers();
+ }
}
break;
}
@@ -1495,7 +1505,12 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
if (task != null) {
task.mChildren.remove(taskFragment);
task.mChildren.add(taskFragment);
- effects |= TRANSACT_EFFECTS_LIFECYCLE;
+ if (taskFragment.hasChild()) {
+ effects |= TRANSACT_EFFECTS_LIFECYCLE;
+ } else {
+ // Ensure that the child layers are updated if the TaskFragment is empty.
+ task.assignChildLayers();
+ }
}
break;
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 7ad87ed8f094..24e50c54aa61 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -169,6 +169,7 @@ import static com.android.server.wm.WindowStateProto.PENDING_SEAMLESS_ROTATION;
import static com.android.server.wm.WindowStateProto.REMOVED;
import static com.android.server.wm.WindowStateProto.REMOVE_ON_EXIT;
import static com.android.server.wm.WindowStateProto.REQUESTED_HEIGHT;
+import static com.android.server.wm.WindowStateProto.REQUESTED_VISIBLE_TYPES;
import static com.android.server.wm.WindowStateProto.REQUESTED_WIDTH;
import static com.android.server.wm.WindowStateProto.STACK_ID;
import static com.android.server.wm.WindowStateProto.SURFACE_INSETS;
@@ -3988,6 +3989,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
proto.write(FORCE_SEAMLESS_ROTATION, mForceSeamlesslyRotate);
proto.write(HAS_COMPAT_SCALE, hasCompatScale());
proto.write(GLOBAL_SCALE, mGlobalScale);
+ proto.write(REQUESTED_VISIBLE_TYPES, mRequestedVisibleTypes);
for (Rect r : mKeepClearAreas) {
r.dumpDebug(proto, KEEP_CLEAR_AREAS);
}
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 8bc41af8af62..cbc301b87295 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -306,7 +306,7 @@ public:
void setMotionClassifierEnabled(bool enabled);
std::optional<std::string> getBluetoothAddress(int32_t deviceId);
void setStylusButtonMotionEventsEnabled(bool enabled);
- FloatPoint getMouseCursorPosition();
+ FloatPoint getMouseCursorPosition(int32_t displayId);
void setStylusPointerIconEnabled(bool enabled);
/* --- InputReaderPolicyInterface implementation --- */
@@ -1784,10 +1784,12 @@ void NativeInputManager::setStylusButtonMotionEventsEnabled(bool enabled) {
InputReaderConfiguration::Change::STYLUS_BUTTON_REPORTING);
}
-FloatPoint NativeInputManager::getMouseCursorPosition() {
+FloatPoint NativeInputManager::getMouseCursorPosition(int32_t displayId) {
if (ENABLE_POINTER_CHOREOGRAPHER) {
- return mInputManager->getChoreographer().getMouseCursorPosition(ADISPLAY_ID_NONE);
+ return mInputManager->getChoreographer().getMouseCursorPosition(displayId);
}
+ // To maintain the status-quo, the displayId parameter (used when PointerChoreographer is
+ // enabled) is ignored in the old pipeline.
std::scoped_lock _l(mLock);
const auto pc = mLocked.legacyPointerController.lock();
if (!pc) return {AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION};
@@ -2751,9 +2753,10 @@ static void nativeSetStylusButtonMotionEventsEnabled(JNIEnv* env, jobject native
im->setStylusButtonMotionEventsEnabled(enabled);
}
-static jfloatArray nativeGetMouseCursorPosition(JNIEnv* env, jobject nativeImplObj) {
+static jfloatArray nativeGetMouseCursorPosition(JNIEnv* env, jobject nativeImplObj,
+ jint displayId) {
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
- const auto p = im->getMouseCursorPosition();
+ const auto p = im->getMouseCursorPosition(displayId);
const std::array<float, 2> arr = {{p.x, p.y}};
jfloatArray outArr = env->NewFloatArray(2);
env->SetFloatArrayRegion(outArr, 0, arr.size(), arr.data());
@@ -2775,6 +2778,15 @@ static void nativeSetAccessibilityBounceKeysThreshold(JNIEnv* env, jobject nativ
}
}
+static void nativeSetAccessibilitySlowKeysThreshold(JNIEnv* env, jobject nativeImplObj,
+ jint thresholdTimeMs) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+ if (ENABLE_INPUT_FILTER_RUST) {
+ im->getInputManager()->getInputFilter().setAccessibilitySlowKeysThreshold(
+ static_cast<nsecs_t>(thresholdTimeMs) * 1000000);
+ }
+}
+
static void nativeSetAccessibilityStickyKeysEnabled(JNIEnv* env, jobject nativeImplObj,
jboolean enabled) {
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
@@ -2883,10 +2895,12 @@ static const JNINativeMethod gInputManagerMethods[] = {
{"getBluetoothAddress", "(I)Ljava/lang/String;", (void*)nativeGetBluetoothAddress},
{"setStylusButtonMotionEventsEnabled", "(Z)V",
(void*)nativeSetStylusButtonMotionEventsEnabled},
- {"getMouseCursorPosition", "()[F", (void*)nativeGetMouseCursorPosition},
+ {"getMouseCursorPosition", "(I)[F", (void*)nativeGetMouseCursorPosition},
{"setStylusPointerIconEnabled", "(Z)V", (void*)nativeSetStylusPointerIconEnabled},
{"setAccessibilityBounceKeysThreshold", "(I)V",
(void*)nativeSetAccessibilityBounceKeysThreshold},
+ {"setAccessibilitySlowKeysThreshold", "(I)V",
+ (void*)nativeSetAccessibilitySlowKeysThreshold},
{"setAccessibilityStickyKeysEnabled", "(Z)V",
(void*)nativeSetAccessibilityStickyKeysEnabled},
};
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index f288103bd954..519c9bb16eed 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -71,6 +71,7 @@ import static android.Manifest.permission.MANAGE_DEVICE_POLICY_STATUS_BAR;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_SYSTEM_UPDATES;
+import static android.Manifest.permission.MANAGE_DEVICE_POLICY_THREAD_NETWORK;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_TIME;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER;
@@ -484,6 +485,7 @@ import com.android.internal.widget.PasswordValidationError;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import com.android.net.module.util.ProxyUtils;
+import com.android.net.thread.flags.Flags;
import com.android.server.AlarmManagerInternal;
import com.android.server.LocalManagerRegistry;
import com.android.server.LocalServices;
@@ -13339,6 +13341,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
UserManager.DISALLOW_SMS, new String[]{MANAGE_DEVICE_POLICY_SMS});
USER_RESTRICTION_PERMISSIONS.put(
UserManager.DISALLOW_SYSTEM_ERROR_DIALOGS, new String[]{MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS});
+ if (Flags.threadUserRestrictionEnabled()) {
+ USER_RESTRICTION_PERMISSIONS.put(
+ UserManager.DISALLOW_THREAD_NETWORK,
+ new String[]{MANAGE_DEVICE_POLICY_THREAD_NETWORK});
+ }
USER_RESTRICTION_PERMISSIONS.put(
UserManager.DISALLOW_ULTRA_WIDEBAND_RADIO, new String[]{MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION});
USER_RESTRICTION_PERMISSIONS.put(
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 86ad49458c48..2b8bcc77281a 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -203,6 +203,7 @@ import com.android.server.security.FileIntegrityService;
import com.android.server.security.KeyAttestationApplicationIdProviderService;
import com.android.server.security.KeyChainSystemService;
import com.android.server.security.rkp.RemoteProvisioningService;
+import com.android.server.selinux.SelinuxAuditLogsService;
import com.android.server.sensorprivacy.SensorPrivacyService;
import com.android.server.sensors.SensorService;
import com.android.server.signedconfig.SignedConfigService;
@@ -433,6 +434,9 @@ public final class SystemServer implements Dumpable {
private static final String ROLE_SERVICE_CLASS = "com.android.role.RoleService";
private static final String GAME_MANAGER_SERVICE_CLASS =
"com.android.server.app.GameManagerService$Lifecycle";
+ private static final String ENHANCED_CONFIRMATION_SERVICE_CLASS =
+ "com.android.ecm.EnhancedConfirmationService";
+
private static final String UWB_APEX_SERVICE_JAR_PATH =
"/apex/com.android.uwb/javalib/service-uwb.jar";
private static final String UWB_SERVICE_CLASS = "com.android.server.uwb.UwbService";
@@ -1592,6 +1596,12 @@ public final class SystemServer implements Dumpable {
mSystemServiceManager.startService(DropBoxManagerService.class);
t.traceEnd();
+ if (android.permission.flags.Flags.enhancedConfirmationModeApisEnabled()) {
+ t.traceBegin("StartEnhancedConfirmationService");
+ mSystemServiceManager.startService(ENHANCED_CONFIRMATION_SERVICE_CLASS);
+ t.traceEnd();
+ }
+
// Grants default permissions and defines roles
t.traceBegin("StartRoleManagerService");
LocalManagerRegistry.addManager(RoleServicePlatformHelper.class,
@@ -2609,6 +2619,14 @@ public final class SystemServer implements Dumpable {
t.traceEnd();
}
+ t.traceBegin("StartSelinuxAuditLogsService");
+ try {
+ SelinuxAuditLogsService.schedule(context);
+ } catch (Throwable e) {
+ reportWtf("starting SelinuxAuditLogsService", e);
+ }
+ t.traceEnd();
+
// LauncherAppsService uses ShortcutService.
t.traceBegin("StartShortcutServiceLifecycle");
mSystemServiceManager.startService(ShortcutService.Lifecycle.class);
diff --git a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt
index 24d49523b9d1..3284cf19db43 100644
--- a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt
@@ -129,7 +129,10 @@ class DevicePermissionPolicy : SchemePolicy() {
val packageState = newState.externalState.packageStates[packageName] ?: return
val androidPackage = packageState.androidPackage ?: return
val appId = packageState.appId
- val appIdPermissionFlags = newState.userStates[userId]!!.appIdDevicePermissionFlags
+ // The user may happen removed due to DeletePackageHelper.removeUnusedPackagesLPw() calling
+ // deletePackageX() asynchronously.
+ val userState = newState.userStates[userId] ?: return
+ val devicePermissionFlags = userState.appIdDevicePermissionFlags[appId] ?: return
androidPackage.requestedPermissions.forEach { permissionName ->
val isRequestedByOtherPackages =
anyPackageInAppId(appId) {
@@ -139,7 +142,7 @@ class DevicePermissionPolicy : SchemePolicy() {
if (isRequestedByOtherPackages) {
return@forEach
}
- appIdPermissionFlags[appId]?.forEachIndexed { _, deviceId, _ ->
+ devicePermissionFlags.forEachIndexed { _, deviceId, _ ->
setPermissionFlags(appId, deviceId, userId, permissionName, 0)
}
}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ClientControllerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ClientControllerTest.java
index 30afa72e0f03..b9f1ea06aebe 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ClientControllerTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ClientControllerTest.java
@@ -16,13 +16,14 @@
package com.android.server.inputmethod;
import static com.android.server.inputmethod.ClientController.ClientControllerCallback;
-import static com.android.server.inputmethod.ClientController.ClientState;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.assertThrows;
import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyLong;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -53,6 +54,7 @@ public final class ClientControllerTest {
private static final int ANY_DISPLAY_ID = Display.DEFAULT_DISPLAY;
private static final int ANY_CALLER_UID = 1;
private static final int ANY_CALLER_PID = 1;
+ private static final String SOME_PACKAGE_NAME = "some.package";
@Rule
public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
@@ -81,7 +83,8 @@ public final class ClientControllerTest {
}
@Test
- // TODO(b/314150112): Enable host side mode for this test once b/315544364 is fixed.
+ // TODO(b/314150112): Enable host side mode for this test once Ravenwood is enabled for
+ // inputmethod server classes.
@IgnoreUnderRavenwood(blockedBy = {InputBinding.class, IInputMethodClientInvoker.class})
public void testAddClient_cannotAddTheSameClientTwice() {
var invoker = IInputMethodClientInvoker.create(mClient, mHandler);
@@ -103,7 +106,8 @@ public final class ClientControllerTest {
}
@Test
- // TODO(b/314150112): Enable host side mode for this test once b/315544364 is fixed.
+ // TODO(b/314150112): Enable host side mode for this test once Ravenwood is enabled for
+ // inputmethod server classes.
@IgnoreUnderRavenwood(blockedBy = {InputBinding.class, IInputMethodClientInvoker.class})
public void testAddClient() throws Exception {
synchronized (ImfLock.class) {
@@ -117,7 +121,8 @@ public final class ClientControllerTest {
}
@Test
- // TODO(b/314150112): Enable host side mode for this test once b/315544364 is fixed.
+ // TODO(b/314150112): Enable host side mode for this test once Ravenwood is enabled for
+ // inputmethod server classes.
@IgnoreUnderRavenwood(blockedBy = {InputBinding.class, IInputMethodClientInvoker.class})
public void testRemoveClient() {
var callback = new TestClientControllerCallback();
@@ -137,6 +142,36 @@ public final class ClientControllerTest {
assertThat(removed).isSameInstanceAs(added);
}
+ @Test
+ // TODO(b/314150112): Enable host side mode for this test once Ravenwood is enabled for
+ // inputmethod server classes and updated to newer Mockito with static mock support (mock
+ // InputMethodUtils#checkIfPackageBelongsToUid instead of PackageManagerInternal#isSameApp)
+ @IgnoreUnderRavenwood(blockedBy = {InputMethodUtils.class})
+ public void testVerifyClientAndPackageMatch() {
+ when(mMockPackageManagerInternal.isSameApp(eq(SOME_PACKAGE_NAME), /* flags= */
+ anyLong(), eq(ANY_CALLER_UID), /* userId= */ anyInt())).thenReturn(true);
+
+ synchronized (ImfLock.class) {
+ var invoker = IInputMethodClientInvoker.create(mClient, mHandler);
+ mController.addClient(invoker, mConnection, ANY_DISPLAY_ID, ANY_CALLER_UID,
+ ANY_CALLER_PID);
+ assertThat(
+ mController.verifyClientAndPackageMatch(mClient, SOME_PACKAGE_NAME)).isTrue();
+ }
+ }
+
+ @Test
+ public void testVerifyClientAndPackageMatch_unknownClient() {
+ synchronized (ImfLock.class) {
+ assertThrows(IllegalArgumentException.class,
+ () -> {
+ synchronized (ImfLock.class) {
+ mController.verifyClientAndPackageMatch(mClient, SOME_PACKAGE_NAME);
+ }
+ });
+ }
+ }
+
private static class TestClientControllerCallback implements ClientControllerCallback {
private final CountDownLatch mLatch = new CountDownLatch(1);
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
index 438bea458c47..1c71a6287c79 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
@@ -22,7 +22,6 @@ import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VI
import static com.android.internal.inputmethod.SoftInputShowHideReason.HIDE_SOFT_INPUT;
import static com.android.internal.inputmethod.SoftInputShowHideReason.HIDE_SWITCH_USER;
import static com.android.internal.inputmethod.SoftInputShowHideReason.SHOW_SOFT_INPUT;
-import static com.android.server.inputmethod.ClientController.ClientState;
import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME;
import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME_EXPLICIT;
import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME_NOT_ALWAYS;
diff --git a/services/tests/PackageManagerServiceTests/server/Android.bp b/services/tests/PackageManagerServiceTests/server/Android.bp
index 3aca1cafbf75..f8accc309931 100644
--- a/services/tests/PackageManagerServiceTests/server/Android.bp
+++ b/services/tests/PackageManagerServiceTests/server/Android.bp
@@ -103,6 +103,7 @@ android_test {
":PackageParserTestApp4",
":PackageParserTestApp5",
":PackageParserTestApp6",
+ ":PackageParserTestApp7",
],
resource_zips: [":PackageManagerServiceServerTests_apks_as_resources"],
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
index 71f5c754f22f..a0e0e1ef36ee 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
@@ -15,6 +15,17 @@
*/
package com.android.server.pm;
+import static android.content.UriRelativeFilter.PATH;
+import static android.content.UriRelativeFilter.QUERY;
+import static android.content.UriRelativeFilter.FRAGMENT;
+import static android.content.UriRelativeFilterGroup.ACTION_ALLOW;
+import static android.content.UriRelativeFilterGroup.ACTION_BLOCK;
+import static android.os.PatternMatcher.PATTERN_ADVANCED_GLOB;
+import static android.os.PatternMatcher.PATTERN_LITERAL;
+import static android.os.PatternMatcher.PATTERN_PREFIX;
+import static android.os.PatternMatcher.PATTERN_SIMPLE_GLOB;
+import static android.os.PatternMatcher.PATTERN_SUFFIX;
+
import static com.android.internal.pm.permission.CompatibilityPermissionInfo.COMPAT_PERMS;
import static com.google.common.truth.Truth.assertThat;
@@ -36,11 +47,15 @@ import static java.util.stream.Collectors.toList;
import android.annotation.NonNull;
import android.content.Context;
+import android.content.IntentFilter;
+import android.content.UriRelativeFilter;
+import android.content.UriRelativeFilterGroup;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.ConfigurationInfo;
import android.content.pm.FeatureGroupInfo;
import android.content.pm.FeatureInfo;
+import android.content.pm.Flags;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.Property;
import android.content.pm.ServiceInfo;
@@ -50,6 +65,9 @@ import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
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.annotation.Nullable;
@@ -106,6 +124,7 @@ import java.lang.reflect.Field;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
@@ -123,6 +142,9 @@ public class PackageParserTest {
@Rule
public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
private File mTmpDir;
private static final File FRAMEWORK = new File("/system/framework/framework-res.apk");
private static final String TEST_APP1_APK = "PackageParserTestApp1.apk";
@@ -131,6 +153,7 @@ public class PackageParserTest {
private static final String TEST_APP4_APK = "PackageParserTestApp4.apk";
private static final String TEST_APP5_APK = "PackageParserTestApp5.apk";
private static final String TEST_APP6_APK = "PackageParserTestApp6.apk";
+ private static final String TEST_APP7_APK = "PackageParserTestApp7.apk";
private static final String PACKAGE_NAME = "com.android.servicestests.apps.packageparserapp";
@Before
@@ -375,6 +398,87 @@ public class PackageParserTest {
assertNotEquals("$automotive", actualDisplayCategory);
}
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_RELATIVE_REFERENCE_INTENT_FILTERS)
+ public void testParseUriRelativeFilterGroups() throws Exception {
+ final File testFile = extractFile(TEST_APP7_APK);
+ try {
+ final ParsedPackage pkg = new TestPackageParser2().parsePackage(testFile, 0, false);
+ final List<ParsedActivity> activities = pkg.getActivities();
+ final List<ParsedIntentInfo> intents = activities.get(0).getIntents();
+ final IntentFilter intentFilter = intents.get(0).getIntentFilter();
+ assertEquals(7, intentFilter.countUriRelativeFilterGroups());
+
+ UriRelativeFilterGroup group = intentFilter.getUriRelativeFilterGroup(0);
+ Collection<UriRelativeFilter> filters = group.getUriRelativeFilters();
+ assertEquals(ACTION_BLOCK, group.getAction());
+ assertEquals(3, filters.size());
+ assertTrue(filters.contains(new UriRelativeFilter(PATH, PATTERN_PREFIX, "/gizmos")));
+ assertTrue(filters.contains(new UriRelativeFilter(QUERY, PATTERN_SIMPLE_GLOB,
+ ".*query=string.*")));
+ assertTrue(filters.contains(new UriRelativeFilter(FRAGMENT, PATTERN_LITERAL,
+ "fragment")));
+
+ group = intentFilter.getUriRelativeFilterGroup(1);
+ filters = group.getUriRelativeFilters();
+ assertEquals(ACTION_ALLOW, group.getAction());
+ assertEquals(2, filters.size());
+ assertTrue(filters.contains(new UriRelativeFilter(QUERY, PATTERN_LITERAL,
+ "query=string")));
+ assertTrue(filters.contains(new UriRelativeFilter(FRAGMENT, PATTERN_SUFFIX,
+ "fragment")));
+
+ group = intentFilter.getUriRelativeFilterGroup(2);
+ filters = group.getUriRelativeFilters();
+ assertEquals(ACTION_ALLOW, group.getAction());
+ assertTrue(filters.contains(new UriRelativeFilter(PATH, PATTERN_LITERAL, "/gizmos")));
+ assertTrue(filters.contains(new UriRelativeFilter(QUERY, PATTERN_LITERAL,
+ ".*query=string.*")));
+ assertTrue(filters.contains(new UriRelativeFilter(FRAGMENT, PATTERN_LITERAL,
+ "fragment")));
+
+ group = intentFilter.getUriRelativeFilterGroup(3);
+ filters = group.getUriRelativeFilters();
+ assertEquals(ACTION_ALLOW, group.getAction());
+ assertTrue(filters.contains(new UriRelativeFilter(PATH, PATTERN_PREFIX, "/gizmos")));
+ assertTrue(filters.contains(new UriRelativeFilter(QUERY, PATTERN_PREFIX,
+ ".*query=string.*")));
+ assertTrue(filters.contains(new UriRelativeFilter(FRAGMENT, PATTERN_PREFIX,
+ "fragment")));
+
+ group = intentFilter.getUriRelativeFilterGroup(4);
+ filters = group.getUriRelativeFilters();
+ assertEquals(ACTION_ALLOW, group.getAction());
+ assertTrue(filters.contains(new UriRelativeFilter(PATH, PATTERN_SIMPLE_GLOB,
+ "/gizmos")));
+ assertTrue(filters.contains(new UriRelativeFilter(QUERY, PATTERN_SIMPLE_GLOB,
+ ".*query=string.*")));
+ assertTrue(filters.contains(new UriRelativeFilter(FRAGMENT, PATTERN_SIMPLE_GLOB,
+ "fragment")));
+
+ group = intentFilter.getUriRelativeFilterGroup(5);
+ filters = group.getUriRelativeFilters();
+ assertEquals(ACTION_ALLOW, group.getAction());
+ assertTrue(filters.contains(new UriRelativeFilter(PATH, PATTERN_ADVANCED_GLOB,
+ "/gizmos")));
+ assertTrue(filters.contains(new UriRelativeFilter(QUERY, PATTERN_ADVANCED_GLOB,
+ ".*query=string.*")));
+ assertTrue(filters.contains(new UriRelativeFilter(FRAGMENT, PATTERN_ADVANCED_GLOB,
+ "fragment")));
+
+ group = intentFilter.getUriRelativeFilterGroup(6);
+ filters = group.getUriRelativeFilters();
+ assertEquals(ACTION_ALLOW, group.getAction());
+ assertTrue(filters.contains(new UriRelativeFilter(PATH, PATTERN_SUFFIX, "/gizmos")));
+ assertTrue(filters.contains(new UriRelativeFilter(QUERY, PATTERN_SUFFIX,
+ ".*query=string.*")));
+ assertTrue(filters.contains(new UriRelativeFilter(FRAGMENT, PATTERN_SUFFIX,
+ "fragment")));
+ } finally {
+ testFile.delete();
+ }
+ }
+
private static final int PROPERTY_TYPE_BOOLEAN = 1;
private static final int PROPERTY_TYPE_FLOAT = 2;
private static final int PROPERTY_TYPE_INTEGER = 3;
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ServiceBindingOomAdjPolicyTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ServiceBindingOomAdjPolicyTest.java
new file mode 100644
index 000000000000..2f12a3b858d2
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ServiceBindingOomAdjPolicyTest.java
@@ -0,0 +1,677 @@
+/*
+ * 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.am;
+
+import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_MICROPHONE;
+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;
+import static android.content.Context.BIND_WAIVE_PRIORITY;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED;
+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.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;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
+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.times;
+import static org.mockito.Mockito.verify;
+
+import android.app.IApplicationThread;
+import android.app.IServiceConnection;
+import android.app.usage.UsageStatsManagerInternal;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManagerInternal;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.DropBoxManagerInternal;
+import com.android.server.LocalServices;
+import com.android.server.am.ActivityManagerService.Injector;
+import com.android.server.am.ApplicationExitInfoTest.ServiceThreadRule;
+import com.android.server.appop.AppOpsService;
+import com.android.server.firewall.IntentFirewall;
+import com.android.server.wm.ActivityTaskManagerService;
+import com.android.server.wm.WindowProcessController;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.verification.VerificationMode;
+
+import java.io.File;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.function.Consumer;
+
+/**
+ * Test class for the service timeout.
+ *
+ * Build/Install/Run:
+ * atest ServiceBindingOomAdjPolicyTest
+ */
+@Presubmit
+public final class ServiceBindingOomAdjPolicyTest {
+ private static final String TAG = ServiceBindingOomAdjPolicyTest.class.getSimpleName();
+
+ private static final String TEST_APP1_NAME = "com.example.foo";
+ private static final String TEST_SERVICE1_NAME = "com.example.foo.Foobar";
+ private static final int TEST_APP1_UID = 10123;
+ private static final int TEST_APP1_PID = 12345;
+
+ private static final String TEST_APP2_NAME = "com.example.bar";
+ private static final String TEST_SERVICE2_NAME = "com.example.bar.Buz";
+ private static final int TEST_APP2_UID = 10124;
+ private static final int TEST_APP2_PID = 12346;
+
+ @Rule
+ public final ServiceThreadRule mServiceThreadRule = new ServiceThreadRule();
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
+
+ private Context mContext;
+ private HandlerThread mHandlerThread;
+
+ @Mock
+ private AppOpsService mAppOpsService;
+ @Mock
+ private DropBoxManagerInternal mDropBoxManagerInt;
+ @Mock
+ private PackageManagerInternal mPackageManagerInt;
+ @Mock
+ private UsageStatsManagerInternal mUsageStatsManagerInt;
+ @Mock
+ private AppErrors mAppErrors;
+ @Mock
+ private IntentFirewall mIntentFirewall;
+
+ private ActivityManagerService mAms;
+ private ProcessList mProcessList;
+ private ActiveServices mActiveServices;
+
+ private int mCurrentCallingUid;
+ private int mCurrentCallingPid;
+
+ /** Run at the test class initialization */
+ @BeforeClass
+ public static void setUpOnce() {
+ System.setProperty("dexmaker.share_classloader", "true");
+ }
+
+ @SuppressWarnings("GuardedBy")
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+
+ mHandlerThread = new HandlerThread(TAG);
+ mHandlerThread.start();
+ final ProcessList realProcessList = new ProcessList();
+ mProcessList = spy(realProcessList);
+
+ LocalServices.removeServiceForTest(DropBoxManagerInternal.class);
+ LocalServices.addService(DropBoxManagerInternal.class, mDropBoxManagerInt);
+ LocalServices.removeServiceForTest(PackageManagerInternal.class);
+ LocalServices.addService(PackageManagerInternal.class, mPackageManagerInt);
+ doReturn(new ComponentName("", "")).when(mPackageManagerInt).getSystemUiServiceComponent();
+
+ final ActivityManagerService realAms = new ActivityManagerService(
+ new TestInjector(mContext), mServiceThreadRule.getThread());
+ final ActivityTaskManagerService realAtm = new ActivityTaskManagerService(mContext);
+ realAtm.initialize(null, null, mContext.getMainLooper());
+ realAms.mActivityTaskManager = spy(realAtm);
+ realAms.mAtmInternal = spy(realAms.mActivityTaskManager.getAtmInternal());
+ realAms.mOomAdjuster = spy(realAms.mOomAdjuster);
+ realAms.mOomAdjuster.mCachedAppOptimizer = spy(realAms.mOomAdjuster.mCachedAppOptimizer);
+ realAms.mPackageManagerInt = mPackageManagerInt;
+ realAms.mUsageStatsService = mUsageStatsManagerInt;
+ realAms.mAppProfiler = spy(realAms.mAppProfiler);
+ realAms.mProcessesReady = true;
+ mAms = spy(realAms);
+ realProcessList.mService = mAms;
+
+ doReturn(false).when(mPackageManagerInt).filterAppAccess(anyString(), anyInt(), anyInt());
+ doReturn(true).when(mIntentFirewall).checkService(any(), any(), anyInt(), anyInt(), any(),
+ any());
+ doReturn(false).when(mAms.mAtmInternal).hasSystemAlertWindowPermission(anyInt(), anyInt(),
+ any());
+ doReturn(true).when(mAms.mOomAdjuster.mCachedAppOptimizer).useFreezer();
+ doNothing().when(mAms.mOomAdjuster.mCachedAppOptimizer).freezeAppAsyncInternalLSP(
+ any(), anyLong(), anyBoolean());
+ doReturn(false).when(mAms.mAppProfiler).updateLowMemStateLSP(anyInt(), anyInt(),
+ anyInt(), anyLong());
+
+ mCurrentCallingUid = TEST_APP1_UID;
+ mCurrentCallingPid = TEST_APP1_PID;
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ LocalServices.removeServiceForTest(DropBoxManagerInternal.class);
+ LocalServices.removeServiceForTest(PackageManagerInternal.class);
+ mHandlerThread.quit();
+ }
+
+ @Test
+ public void testServiceSelfBindingOomAdj() throws Exception {
+ // Enable the flags.
+ mSetFlagsRule.enableFlags(Flags.FLAG_SERVICE_BINDING_OOM_ADJ_POLICY);
+
+ // Verify that there should be 0 oom adj updates.
+ performTestServiceSelfBindingOomAdj(never(), never());
+
+ // Disable the flags.
+ mSetFlagsRule.disableFlags(Flags.FLAG_SERVICE_BINDING_OOM_ADJ_POLICY);
+
+ // Verify that there should be at least 1 oom adj update.
+ performTestServiceSelfBindingOomAdj(atLeastOnce(), atLeastOnce());
+ }
+
+ @SuppressWarnings("GuardedBy")
+ private void performTestServiceSelfBindingOomAdj(VerificationMode bindMode,
+ VerificationMode unbindMode) throws Exception {
+ final ProcessRecord app = addProcessRecord(
+ TEST_APP1_PID, // pid
+ TEST_APP1_UID, // uid
+ PROCESS_STATE_SERVICE, // procstate
+ SERVICE_ADJ, // adj
+ PROCESS_CAPABILITY_NONE, // capabilities
+ TEST_APP1_NAME // packageName
+ );
+ final Intent serviceIntent = createServiceIntent(TEST_APP1_NAME, TEST_SERVICE1_NAME,
+ TEST_APP1_UID);
+ final IServiceConnection serviceConnection = mock(IServiceConnection.class);
+
+ // Make a self binding.
+ assertNotEquals(0, mAms.bindService(
+ app.getThread(), // caller
+ null, // token
+ serviceIntent, // service
+ null, // resolveType
+ serviceConnection, // connection
+ BIND_AUTO_CREATE, // flags
+ TEST_APP1_NAME, // callingPackage
+ USER_SYSTEM // userId
+ ));
+
+ verify(mAms.mOomAdjuster, bindMode).updateOomAdjPendingTargetsLocked(anyInt());
+ clearInvocations(mAms.mOomAdjuster);
+
+ // Unbind the service.
+ mAms.unbindService(serviceConnection);
+
+ verify(mAms.mOomAdjuster, unbindMode).updateOomAdjPendingTargetsLocked(anyInt());
+ clearInvocations(mAms.mOomAdjuster);
+
+ removeProcessRecord(app);
+ }
+
+ @Test
+ public void testServiceDistinctBindingOomAdjMoreImportant() throws Exception {
+ // Enable the flags.
+ mSetFlagsRule.enableFlags(Flags.FLAG_SERVICE_BINDING_OOM_ADJ_POLICY);
+
+ // Verify that there should be at least 1 oom adj update
+ // because the client is more important.
+ performTestServiceDistinctBindingOomAdj(TEST_APP1_PID, TEST_APP1_UID,
+ PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
+ PROCESS_CAPABILITY_NONE, TEST_APP1_NAME,
+ this::setHasForegroundServices,
+ TEST_APP2_PID, TEST_APP2_UID, PROCESS_STATE_HOME,
+ HOME_APP_ADJ, PROCESS_CAPABILITY_NONE, TEST_APP2_NAME, TEST_SERVICE2_NAME,
+ this::setHomeProcess,
+ BIND_AUTO_CREATE,
+ atLeastOnce(), atLeastOnce());
+
+ // Disable the flags.
+ mSetFlagsRule.disableFlags(Flags.FLAG_SERVICE_BINDING_OOM_ADJ_POLICY);
+
+ // Verify that there should be at least 1 oom adj update
+ // because the client is more important.
+ performTestServiceDistinctBindingOomAdj(TEST_APP1_PID, TEST_APP1_UID,
+ PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
+ PROCESS_CAPABILITY_NONE, TEST_APP1_NAME,
+ this::setHasForegroundServices,
+ TEST_APP2_PID, TEST_APP2_UID, PROCESS_STATE_HOME,
+ HOME_APP_ADJ, PROCESS_CAPABILITY_NONE, TEST_APP2_NAME, TEST_SERVICE2_NAME,
+ this::setHomeProcess,
+ BIND_AUTO_CREATE,
+ atLeastOnce(), atLeastOnce());
+ }
+
+ @Test
+ public void testServiceDistinctBindingOomAdjLessImportant() throws Exception {
+ // Enable the flags.
+ mSetFlagsRule.enableFlags(Flags.FLAG_SERVICE_BINDING_OOM_ADJ_POLICY);
+
+ // Verify that there should be 0 oom adj update
+ performTestServiceDistinctBindingOomAdj(TEST_APP1_PID, TEST_APP1_UID,
+ PROCESS_STATE_HOME, HOME_APP_ADJ, PROCESS_CAPABILITY_NONE, TEST_APP1_NAME,
+ this::setHomeProcess,
+ TEST_APP2_PID, TEST_APP2_UID, PROCESS_STATE_FOREGROUND_SERVICE,
+ PERCEPTIBLE_APP_ADJ, PROCESS_CAPABILITY_NONE, TEST_APP2_NAME, TEST_SERVICE2_NAME,
+ this::setHasForegroundServices,
+ BIND_AUTO_CREATE,
+ never(), never());
+
+ // Disable the flags.
+ mSetFlagsRule.disableFlags(Flags.FLAG_SERVICE_BINDING_OOM_ADJ_POLICY);
+
+ // Verify that there should be at least 1 oom adj update
+ performTestServiceDistinctBindingOomAdj(TEST_APP1_PID, TEST_APP1_UID,
+ PROCESS_STATE_HOME, HOME_APP_ADJ, PROCESS_CAPABILITY_NONE, TEST_APP1_NAME,
+ this::setHomeProcess,
+ TEST_APP2_PID, TEST_APP2_UID, PROCESS_STATE_FOREGROUND_SERVICE,
+ PERCEPTIBLE_APP_ADJ, PROCESS_CAPABILITY_NONE, TEST_APP2_NAME, TEST_SERVICE2_NAME,
+ this::setHasForegroundServices,
+ BIND_AUTO_CREATE,
+ atLeastOnce(), atLeastOnce());
+ }
+
+ @Test
+ public void testServiceDistinctBindingOomAdjWaivePriority() throws Exception {
+ // Enable the flags.
+ mSetFlagsRule.enableFlags(Flags.FLAG_SERVICE_BINDING_OOM_ADJ_POLICY);
+
+ // Verify that there should be 0 oom adj update for binding
+ // because we're using the BIND_WAIVE_PRIORITY;
+ // but for the unbinding, because client is better than service, we can't skip it safely.
+ performTestServiceDistinctBindingOomAdj(TEST_APP1_PID, TEST_APP1_UID,
+ PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
+ PROCESS_CAPABILITY_NONE, TEST_APP1_NAME,
+ this::setHasForegroundServices,
+ TEST_APP2_PID, TEST_APP2_UID, PROCESS_STATE_HOME,
+ HOME_APP_ADJ, PROCESS_CAPABILITY_NONE, TEST_APP2_NAME, TEST_SERVICE2_NAME,
+ this::setHomeProcess,
+ BIND_AUTO_CREATE | BIND_WAIVE_PRIORITY,
+ never(), atLeastOnce());
+
+ // Verify that there should be 0 oom adj update
+ // because we're using the BIND_WAIVE_PRIORITY;
+ performTestServiceDistinctBindingOomAdj(TEST_APP1_PID, TEST_APP1_UID,
+ PROCESS_STATE_HOME, HOME_APP_ADJ, PROCESS_CAPABILITY_NONE, TEST_APP1_NAME,
+ this::setHomeProcess,
+ TEST_APP2_PID, TEST_APP2_UID, PROCESS_STATE_FOREGROUND_SERVICE,
+ PERCEPTIBLE_APP_ADJ, PROCESS_CAPABILITY_NONE, TEST_APP2_NAME, TEST_SERVICE2_NAME,
+ this::setHasForegroundServices,
+ BIND_AUTO_CREATE | BIND_WAIVE_PRIORITY,
+ never(), never());
+
+ // Disable the flags.
+ mSetFlagsRule.disableFlags(Flags.FLAG_SERVICE_BINDING_OOM_ADJ_POLICY);
+
+ // Verify that there should be at least 1 oom adj update
+ // because the client is more important.
+ performTestServiceDistinctBindingOomAdj(TEST_APP1_PID, TEST_APP1_UID,
+ PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
+ PROCESS_CAPABILITY_NONE, TEST_APP1_NAME,
+ this::setHasForegroundServices,
+ TEST_APP2_PID, TEST_APP2_UID, PROCESS_STATE_HOME,
+ HOME_APP_ADJ, PROCESS_CAPABILITY_NONE, TEST_APP2_NAME, TEST_SERVICE2_NAME,
+ this::setHomeProcess,
+ BIND_AUTO_CREATE,
+ atLeastOnce(), atLeastOnce());
+ }
+
+ @Test
+ public void testServiceDistinctBindingOomAdjNoIncludeCapabilities() throws Exception {
+ // Enable the flags.
+ mSetFlagsRule.enableFlags(Flags.FLAG_SERVICE_BINDING_OOM_ADJ_POLICY);
+
+ // Verify that there should be 0 oom adj update
+ // because we didn't specify the "BIND_INCLUDE_CAPABILITIES"
+ performTestServiceDistinctBindingOomAdj(TEST_APP1_PID, TEST_APP1_UID,
+ PROCESS_STATE_HOME, HOME_APP_ADJ,
+ PROCESS_CAPABILITY_FOREGROUND_MICROPHONE, TEST_APP1_NAME,
+ this::setHomeProcess,
+ TEST_APP2_PID, TEST_APP2_UID, PROCESS_STATE_FOREGROUND_SERVICE,
+ PERCEPTIBLE_APP_ADJ, PROCESS_CAPABILITY_NONE, TEST_APP2_NAME, TEST_SERVICE2_NAME,
+ this::setHasForegroundServices,
+ BIND_AUTO_CREATE,
+ never(), never());
+
+ // Disable the flags.
+ mSetFlagsRule.disableFlags(Flags.FLAG_SERVICE_BINDING_OOM_ADJ_POLICY);
+
+ // Verify that there should be at least 1 oom adj update
+ performTestServiceDistinctBindingOomAdj(TEST_APP1_PID, TEST_APP1_UID,
+ PROCESS_STATE_HOME, HOME_APP_ADJ,
+ PROCESS_CAPABILITY_FOREGROUND_MICROPHONE, TEST_APP1_NAME,
+ this::setHomeProcess,
+ TEST_APP2_PID, TEST_APP2_UID, PROCESS_STATE_FOREGROUND_SERVICE,
+ PERCEPTIBLE_APP_ADJ, PROCESS_CAPABILITY_NONE, TEST_APP2_NAME, TEST_SERVICE2_NAME,
+ this::setHasForegroundServices,
+ BIND_AUTO_CREATE,
+ atLeastOnce(), atLeastOnce());
+ }
+
+ @Test
+ public void testServiceDistinctBindingOomAdjWithIncludeCapabilities() throws Exception {
+ // Enable the flags.
+ mSetFlagsRule.enableFlags(Flags.FLAG_SERVICE_BINDING_OOM_ADJ_POLICY);
+
+ // Verify that there should be at least 1 oom adj update
+ // because we use the "BIND_INCLUDE_CAPABILITIES"
+ performTestServiceDistinctBindingOomAdj(TEST_APP1_PID, TEST_APP1_UID,
+ PROCESS_STATE_HOME, HOME_APP_ADJ,
+ PROCESS_CAPABILITY_FOREGROUND_MICROPHONE, TEST_APP1_NAME,
+ this::setHomeProcess,
+ TEST_APP2_PID, TEST_APP2_UID, PROCESS_STATE_FOREGROUND_SERVICE,
+ PERCEPTIBLE_APP_ADJ, PROCESS_CAPABILITY_NONE, TEST_APP2_NAME, TEST_SERVICE2_NAME,
+ this::setHasForegroundServices,
+ BIND_AUTO_CREATE | BIND_INCLUDE_CAPABILITIES,
+ atLeastOnce(), atLeastOnce());
+
+ // Disable the flags.
+ mSetFlagsRule.disableFlags(Flags.FLAG_SERVICE_BINDING_OOM_ADJ_POLICY);
+
+ // Verify that there should be at least 1 oom adj update
+ performTestServiceDistinctBindingOomAdj(TEST_APP1_PID, TEST_APP1_UID,
+ PROCESS_STATE_HOME, HOME_APP_ADJ,
+ PROCESS_CAPABILITY_FOREGROUND_MICROPHONE, TEST_APP1_NAME,
+ this::setHomeProcess,
+ TEST_APP2_PID, TEST_APP2_UID, PROCESS_STATE_FOREGROUND_SERVICE,
+ PERCEPTIBLE_APP_ADJ, PROCESS_CAPABILITY_NONE, TEST_APP2_NAME, TEST_SERVICE2_NAME,
+ this::setHasForegroundServices,
+ BIND_AUTO_CREATE | BIND_INCLUDE_CAPABILITIES,
+ atLeastOnce(), atLeastOnce());
+ }
+
+ @Test
+ public void testServiceDistinctBindingOomAdjFreezeCaller() throws Exception {
+ // Enable the flags.
+ mSetFlagsRule.enableFlags(Flags.FLAG_SERVICE_BINDING_OOM_ADJ_POLICY);
+
+ // Verify that there should be 0 oom adj update
+ performTestServiceDistinctBindingOomAdj(TEST_APP1_PID, TEST_APP1_UID,
+ PROCESS_STATE_CACHED_EMPTY, CACHED_APP_MIN_ADJ, PROCESS_CAPABILITY_NONE,
+ TEST_APP1_NAME, null,
+ TEST_APP2_PID, TEST_APP2_UID, PROCESS_STATE_FOREGROUND_SERVICE,
+ PERCEPTIBLE_APP_ADJ, PROCESS_CAPABILITY_NONE, TEST_APP2_NAME, TEST_SERVICE2_NAME,
+ this::setHasForegroundServices,
+ BIND_AUTO_CREATE,
+ never(), never());
+
+ // Disable the flags.
+ mSetFlagsRule.disableFlags(Flags.FLAG_SERVICE_BINDING_OOM_ADJ_POLICY);
+
+ // Verify that there should be at least 1 oom adj update
+ performTestServiceDistinctBindingOomAdj(TEST_APP1_PID, TEST_APP1_UID,
+ PROCESS_STATE_CACHED_EMPTY, CACHED_APP_MIN_ADJ, PROCESS_CAPABILITY_NONE,
+ TEST_APP1_NAME, null,
+ TEST_APP2_PID, TEST_APP2_UID, PROCESS_STATE_FOREGROUND_SERVICE,
+ PERCEPTIBLE_APP_ADJ, PROCESS_CAPABILITY_NONE, TEST_APP2_NAME, TEST_SERVICE2_NAME,
+ this::setHasForegroundServices,
+ BIND_AUTO_CREATE,
+ atLeastOnce(), atLeastOnce());
+ }
+
+ @SuppressWarnings("GuardedBy")
+ private void performTestServiceDistinctBindingOomAdj(int clientPid, int clientUid,
+ int clientProcState, int clientAdj, int clientCap, String clientPackageName,
+ Consumer<ProcessRecord> clientAppFixer,
+ int servicePid, int serviceUid, int serviceProcState, int serviceAdj,
+ int serviceCap, String servicePackageName, String serviceName,
+ Consumer<ProcessRecord> serviceAppFixer, int bindingFlags,
+ VerificationMode bindMode, VerificationMode unbindMode) throws Exception {
+ final ProcessRecord clientApp = addProcessRecord(
+ clientPid,
+ clientUid,
+ clientProcState,
+ clientAdj,
+ clientCap,
+ clientPackageName
+ );
+ final ProcessRecord serviceApp = addProcessRecord(
+ servicePid,
+ serviceUid,
+ serviceProcState,
+ serviceAdj,
+ serviceCap,
+ servicePackageName
+ );
+ final Intent serviceIntent = createServiceIntent(servicePackageName, serviceName,
+ serviceUid);
+ final IServiceConnection serviceConnection = mock(IServiceConnection.class);
+ if (clientAppFixer != null) clientAppFixer.accept(clientApp);
+ if (serviceAppFixer != null) serviceAppFixer.accept(serviceApp);
+
+ // Make a self binding.
+ assertNotEquals(0, mAms.bindService(
+ clientApp.getThread(), // caller
+ null, // token
+ serviceIntent, // service
+ null, // resolveType
+ serviceConnection, // connection
+ bindingFlags, // flags
+ clientPackageName, // callingPackage
+ USER_SYSTEM // userId
+ ));
+
+ verify(mAms.mOomAdjuster, bindMode).updateOomAdjPendingTargetsLocked(anyInt());
+ clearInvocations(mAms.mOomAdjuster);
+
+ if (clientApp.isFreezable()) {
+ verify(mAms.mOomAdjuster.mCachedAppOptimizer,
+ times(Flags.serviceBindingOomAdjPolicy() ? 1 : 0))
+ .freezeAppAsyncInternalLSP(eq(clientApp), eq(0L), anyBoolean());
+ clearInvocations(mAms.mOomAdjuster.mCachedAppOptimizer);
+ }
+
+ // Unbind the service.
+ mAms.unbindService(serviceConnection);
+
+ verify(mAms.mOomAdjuster, unbindMode).updateOomAdjPendingTargetsLocked(anyInt());
+ clearInvocations(mAms.mOomAdjuster);
+
+ removeProcessRecord(clientApp);
+ removeProcessRecord(serviceApp);
+ }
+
+ private void setHasForegroundServices(ProcessRecord app) {
+ app.mServices.setHasForegroundServices(true,
+ FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED, false);
+ }
+
+ private void setHomeProcess(ProcessRecord app) {
+ final WindowProcessController wpc = app.getWindowProcessController();
+ doReturn(true).when(wpc).isHomeProcess();
+ }
+
+ @SuppressWarnings("GuardedBy")
+ private ProcessRecord addProcessRecord(int pid, int uid, int procState, int adj, int cap,
+ String packageName) {
+ final IApplicationThread appThread = mock(IApplicationThread.class);
+ final IBinder threadBinder = mock(IBinder.class);
+ final ProcessRecord app = makeProcessRecord(pid, uid, uid, null, 0,
+ procState, adj, cap, 0L, 0L, packageName, packageName, mAms);
+
+ app.makeActive(appThread, mAms.mProcessStats);
+ doReturn(threadBinder).when(appThread).asBinder();
+ mProcessList.addProcessNameLocked(app);
+ mProcessList.updateLruProcessLocked(app, false, null);
+
+ setFieldValue(ProcessRecord.class, app, "mWindowProcessController",
+ mock(WindowProcessController.class));
+
+ doReturn(app.getSetCapability()).when(mAms.mOomAdjuster).getDefaultCapability(
+ eq(app), anyInt());
+
+ return app;
+ }
+
+ @SuppressWarnings("GuardedBy")
+ private Intent createServiceIntent(String packageName, String serviceName, int serviceUid) {
+ final ComponentName compName = new ComponentName(packageName, serviceName);
+ final Intent serviceIntent = new Intent().setComponent(compName);
+ final ResolveInfo rInfo = new ResolveInfo();
+ rInfo.serviceInfo = makeServiceInfo(compName.getClassName(), compName.getPackageName(),
+ serviceUid);
+ doReturn(rInfo).when(mPackageManagerInt).resolveService(any(Intent.class), any(),
+ anyLong(), anyInt(), anyInt());
+
+ return serviceIntent;
+ }
+
+ @SuppressWarnings("GuardedBy")
+ private void removeProcessRecord(ProcessRecord app) {
+ app.setKilled(true);
+ mProcessList.removeProcessNameLocked(app.processName, app.uid);
+ mProcessList.removeLruProcessLocked(app);
+ }
+
+ @SuppressWarnings("GuardedBy")
+ private ProcessRecord makeProcessRecord(int pid, int uid, int packageUid, Integer definingUid,
+ int connectionGroup, int procState, int adj, int cap, long pss, long rss,
+ String processName, String packageName, ActivityManagerService ams) {
+ final ProcessRecord app = ApplicationExitInfoTest.makeProcessRecord(pid, uid, packageUid,
+ definingUid, connectionGroup, procState, pss, rss, processName, packageName, ams);
+ app.mState.setCurProcState(procState);
+ app.mState.setSetProcState(procState);
+ app.mState.setCurAdj(adj);
+ 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;
+ }
+
+ @SuppressWarnings("GuardedBy")
+ private ServiceInfo makeServiceInfo(String serviceName, String packageName, int packageUid) {
+ final ServiceInfo sInfo = new ServiceInfo();
+ sInfo.name = serviceName;
+ sInfo.processName = packageName;
+ sInfo.packageName = packageName;
+ sInfo.applicationInfo = new ApplicationInfo();
+ sInfo.applicationInfo.uid = packageUid;
+ sInfo.applicationInfo.packageName = packageName;
+ sInfo.exported = true;
+ return sInfo;
+ }
+
+ private static <T> void setFieldValue(Class clazz, Object obj, String fieldName, T val) {
+ try {
+ Field field = clazz.getDeclaredField(fieldName);
+ field.setAccessible(true);
+ Field mfield = Field.class.getDeclaredField("accessFlags");
+ mfield.setAccessible(true);
+ mfield.setInt(field, mfield.getInt(field) & ~(Modifier.FINAL | Modifier.PRIVATE));
+ field.set(obj, val);
+ } catch (NoSuchFieldException | IllegalAccessException e) {
+ }
+ }
+
+ private class TestInjector extends Injector {
+ TestInjector(Context context) {
+ super(context);
+ }
+
+ @Override
+ public AppOpsService getAppOpsService(File recentAccessesFile, File storageFile,
+ Handler handler) {
+ return mAppOpsService;
+ }
+
+ @Override
+ public Handler getUiHandler(ActivityManagerService service) {
+ return mHandlerThread.getThreadHandler();
+ }
+
+ @Override
+ public ProcessList getProcessList(ActivityManagerService service) {
+ return mProcessList;
+ }
+
+ @Override
+ public ActiveServices getActiveServices(ActivityManagerService service) {
+ if (mActiveServices == null) {
+ mActiveServices = spy(new ActiveServices(service));
+ }
+ return mActiveServices;
+ }
+
+ @Override
+ public int getCallingUid() {
+ return mCurrentCallingUid;
+ }
+
+ @Override
+ public int getCallingPid() {
+ return mCurrentCallingPid;
+ }
+
+ @Override
+ public long clearCallingIdentity() {
+ return (((long) mCurrentCallingUid) << 32) | mCurrentCallingPid;
+ }
+
+ @Override
+ public void restoreCallingIdentity(long ident) {
+ }
+
+ @Override
+ public AppErrors getAppErrors() {
+ return mAppErrors;
+ }
+
+ @Override
+ public IntentFirewall getIntentFirewall() {
+ return mIntentFirewall;
+ }
+ }
+
+ // TODO: [b/302724778] Remove manual JNI load
+ static {
+ System.loadLibrary("mockingservicestestjni");
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
index 93a2eefba5b1..28471b37d2a0 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
@@ -180,6 +180,7 @@ public class FlexibilityControllerTest {
JobSchedulerService.sElapsedRealtimeClock =
Clock.fixed(Instant.ofEpochMilli(FROZEN_TIME), ZoneOffset.UTC);
// Initialize real objects.
+ doReturn(Long.MAX_VALUE).when(mPrefetchController).getNextEstimatedLaunchTimeLocked(any());
ArgumentCaptor<BroadcastReceiver> receiverCaptor =
ArgumentCaptor.forClass(BroadcastReceiver.class);
mFlexibilityController = new FlexibilityController(mJobSchedulerService,
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
index 293391f43828..c6608e61fc62 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
@@ -45,6 +45,7 @@ import static com.android.server.job.controllers.JobStatus.CONSTRAINT_WITHIN_QUO
import static com.android.server.job.controllers.JobStatus.NO_EARLIEST_RUNTIME;
import static com.android.server.job.controllers.JobStatus.NO_LATEST_RUNTIME;
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -78,6 +79,7 @@ import org.mockito.quality.Strictness;
import java.time.Clock;
import java.time.ZoneOffset;
+import java.util.Arrays;
@RunWith(AndroidJUnit4.class)
public class JobStatusTest {
@@ -138,6 +140,35 @@ public class JobStatusTest {
}
@Test
+ public void testApplyBasicPiiFilters_email() {
+ assertEquals("[EMAIL]", JobStatus.applyBasicPiiFilters("test@email.com"));
+ assertEquals("[EMAIL]", JobStatus.applyBasicPiiFilters("test+plus@email.com"));
+ assertEquals("[EMAIL]", JobStatus.applyBasicPiiFilters("t.e_st+plus-minus@email.com"));
+
+ assertEquals("prefix:[EMAIL]", JobStatus.applyBasicPiiFilters("prefix:test@email.com"));
+
+ assertEquals("not-an-email", JobStatus.applyBasicPiiFilters("not-an-email"));
+ }
+
+ @Test
+ public void testApplyBasicPiiFilters_mixture() {
+ assertEquals("[PHONE]:[EMAIL]",
+ JobStatus.applyBasicPiiFilters("123-456-7890:test+plus@email.com"));
+ assertEquals("prefix:[PHONE]:[EMAIL]",
+ JobStatus.applyBasicPiiFilters("prefix:123-456-7890:test+plus@email.com"));
+ }
+
+ @Test
+ public void testApplyBasicPiiFilters_phone() {
+ assertEquals("[PHONE]", JobStatus.applyBasicPiiFilters("123-456-7890"));
+ assertEquals("[PHONE]", JobStatus.applyBasicPiiFilters("+1-234-567-8900"));
+
+ assertEquals("prefix:[PHONE]", JobStatus.applyBasicPiiFilters("prefix:123-456-7890"));
+
+ assertEquals("not-a-phone-number", JobStatus.applyBasicPiiFilters("not-a-phone-number"));
+ }
+
+ @Test
public void testCanRunInBatterySaver_regular() {
final JobInfo jobInfo =
new JobInfo.Builder(101, new ComponentName("foo", "bar")).build();
@@ -245,6 +276,42 @@ public class JobStatusTest {
}
@Test
+ public void testGetFilteredDebugTags() {
+ final JobInfo jobInfo = new JobInfo.Builder(101, new ComponentName("foo", "bar"))
+ .addDebugTag("test@email.com")
+ .addDebugTag("123-456-7890")
+ .addDebugTag("random")
+ .build();
+ JobStatus job = createJobStatus(jobInfo);
+ String[] expected = new String[]{"[EMAIL]", "[PHONE]", "random"};
+ String[] result = job.getFilteredDebugTags();
+ Arrays.sort(expected);
+ Arrays.sort(result);
+ assertArrayEquals(expected, result);
+ }
+
+ @Test
+ public void testGetFilteredTraceTag() {
+ JobInfo jobInfo = new JobInfo.Builder(101, new ComponentName("foo", "bar"))
+ .setTraceTag("test@email.com")
+ .build();
+ JobStatus job = createJobStatus(jobInfo);
+ assertEquals("[EMAIL]", job.getFilteredTraceTag());
+
+ jobInfo = new JobInfo.Builder(101, new ComponentName("foo", "bar"))
+ .setTraceTag("123-456-7890")
+ .build();
+ job = createJobStatus(jobInfo);
+ assertEquals("[PHONE]", job.getFilteredTraceTag());
+
+ jobInfo = new JobInfo.Builder(101, new ComponentName("foo", "bar"))
+ .setTraceTag("random")
+ .build();
+ job = createJobStatus(jobInfo);
+ assertEquals("random", job.getFilteredTraceTag());
+ }
+
+ @Test
public void testIsUserVisibleJob() {
JobInfo jobInfo = new JobInfo.Builder(101, new ComponentName("foo", "bar"))
.setUserInitiated(false)
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/altitude/AltitudeConverterTest.java b/services/tests/mockingservicestests/src/com/android/server/location/altitude/AltitudeConverterTest.java
index 8d9a6c510576..9a143d5b3743 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/altitude/AltitudeConverterTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/altitude/AltitudeConverterTest.java
@@ -21,6 +21,8 @@ import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
import android.content.Context;
+import android.frameworks.location.altitude.GetGeoidHeightRequest;
+import android.frameworks.location.altitude.GetGeoidHeightResponse;
import android.location.Location;
import android.location.altitude.AltitudeConverter;
@@ -176,4 +178,20 @@ public class AltitudeConverterTest {
assertThrows(IllegalArgumentException.class,
() -> mAltitudeConverter.addMslAltitudeToLocation(mContext, location));
}
+
+ @Test
+ public void testGetGeoidHeight_expectedBehavior() throws IOException {
+ GetGeoidHeightRequest request = new GetGeoidHeightRequest();
+ request.latitudeDegrees = -35.334815;
+ request.longitudeDegrees = -45;
+ // Requires data to be loaded from raw assets.
+ GetGeoidHeightResponse response = mAltitudeConverter.getGeoidHeight(mContext, request);
+ assertThat(response.geoidHeightMeters).isWithin(2).of(-5.0622);
+ assertThat(response.geoidHeightErrorMeters).isGreaterThan(0f);
+ assertThat(response.geoidHeightErrorMeters).isLessThan(1f);
+ assertThat(response.expirationDistanceMeters).isWithin(1).of(-6.33);
+ assertThat(response.additionalGeoidHeightErrorMeters).isGreaterThan(0f);
+ assertThat(response.additionalGeoidHeightErrorMeters).isLessThan(1f);
+ assertThat(response.success).isTrue();
+ }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java
index 4eba21934a4e..efab19cc9663 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java
@@ -29,6 +29,7 @@ import static org.mockito.MockitoAnnotations.initMocks;
import android.content.Context;
import android.location.Location;
+import android.location.LocationRequest;
import android.location.LocationResult;
import android.location.provider.ProviderRequest;
import android.platform.test.annotations.Presubmit;
@@ -218,4 +219,22 @@ public class StationaryThrottlingLocationProviderTest {
verify(mDelegate, never()).onSetRequest(ProviderRequest.EMPTY_REQUEST);
verify(mListener, after(75).times(1)).onReportLocation(any(LocationResult.class));
}
+
+ @Test
+ public void testNoThrottle_highAccuracy() {
+ ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis(
+ 50).setQuality(LocationRequest.QUALITY_HIGH_ACCURACY).build();
+
+ mProvider.getController().setRequest(request);
+ verify(mDelegate).onSetRequest(request);
+
+ LocationResult loc = createLocationResult("test_provider", mRandom);
+ mDelegateProvider.reportLocation(loc);
+ verify(mListener, times(1)).onReportLocation(loc);
+
+ mInjector.getDeviceStationaryHelper().setStationary(true);
+ mInjector.getDeviceIdleHelper().setIdle(true);
+ verify(mDelegate, never()).onSetRequest(ProviderRequest.EMPTY_REQUEST);
+ verify(mListener, after(75).times(1)).onReportLocation(any(LocationResult.class));
+ }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
index 5bec903e6414..656bc71eebca 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -556,7 +556,7 @@ public final class UserManagerServiceTest {
@Test
public void testCreateUserWithLongName_TruncatesName() {
UserInfo user = mUms.createUserWithThrow(generateLongString(), USER_TYPE_FULL_SECONDARY, 0);
- assertThat(user.name.length()).isEqualTo(500);
+ assertThat(user.name.length()).isEqualTo(UserManager.MAX_USER_NAME_LENGTH);
UserInfo user1 = mUms.createUserWithThrow("Test", USER_TYPE_FULL_SECONDARY, 0);
assertThat(user1.name.length()).isEqualTo(4);
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/selinux/RateLimiterTest.java b/services/tests/mockingservicestests/src/com/android/server/selinux/RateLimiterTest.java
new file mode 100644
index 000000000000..01c7fbe5bfe9
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/selinux/RateLimiterTest.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.selinux;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.internal.os.Clock;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.time.Duration;
+import java.time.temporal.ChronoUnit;
+
+@RunWith(AndroidJUnit4.class)
+public class RateLimiterTest {
+
+ private final MockClock mMockClock = new MockClock();
+
+ @Test
+ public void testRateLimiter_1QPS() {
+ RateLimiter rateLimiter = new RateLimiter(mMockClock, Duration.ofSeconds(1));
+
+ // First acquire is granted.
+ assertThat(rateLimiter.tryAcquire()).isTrue();
+ // Next acquire is negated because it's too soon.
+ assertThat(rateLimiter.tryAcquire()).isFalse();
+ // Wait >=1 seconds.
+ mMockClock.currentTimeMillis += Duration.ofSeconds(1).toMillis();
+ assertThat(rateLimiter.tryAcquire()).isTrue();
+ }
+
+ @Test
+ public void testRateLimiter_3QPS() {
+ RateLimiter rateLimiter =
+ new RateLimiter(
+ mMockClock,
+ Duration.ofSeconds(1).dividedBy(3).truncatedTo(ChronoUnit.MILLIS));
+
+ assertThat(rateLimiter.tryAcquire()).isTrue();
+ mMockClock.currentTimeMillis += Duration.ofSeconds(1).dividedBy(2).toMillis();
+ assertThat(rateLimiter.tryAcquire()).isTrue();
+ mMockClock.currentTimeMillis += Duration.ofSeconds(1).dividedBy(3).toMillis();
+ assertThat(rateLimiter.tryAcquire()).isTrue();
+ mMockClock.currentTimeMillis += Duration.ofSeconds(1).dividedBy(4).toMillis();
+ assertThat(rateLimiter.tryAcquire()).isFalse();
+ }
+
+ @Test
+ public void testRateLimiter_infiniteQPS() {
+ RateLimiter rateLimiter = new RateLimiter(mMockClock, Duration.ofMillis(0));
+
+ // so many permits.
+ assertThat(rateLimiter.tryAcquire()).isTrue();
+ assertThat(rateLimiter.tryAcquire()).isTrue();
+ assertThat(rateLimiter.tryAcquire()).isTrue();
+ assertThat(rateLimiter.tryAcquire()).isTrue();
+ assertThat(rateLimiter.tryAcquire()).isTrue();
+
+ mMockClock.currentTimeMillis += Duration.ofSeconds(10).toMillis();
+ // still so many permits.
+ assertThat(rateLimiter.tryAcquire()).isTrue();
+ assertThat(rateLimiter.tryAcquire()).isTrue();
+ assertThat(rateLimiter.tryAcquire()).isTrue();
+ assertThat(rateLimiter.tryAcquire()).isTrue();
+ assertThat(rateLimiter.tryAcquire()).isTrue();
+
+ mMockClock.currentTimeMillis += Duration.ofDays(-10).toMillis();
+ // only going backwards in time you will stop the permits.
+ assertThat(rateLimiter.tryAcquire()).isFalse();
+ assertThat(rateLimiter.tryAcquire()).isFalse();
+ assertThat(rateLimiter.tryAcquire()).isFalse();
+ }
+
+ @Test
+ public void testRateLimiter_negativeQPS() {
+ RateLimiter rateLimiter = new RateLimiter(mMockClock, Duration.ofMillis(-10));
+
+ // Negative QPS is effectively turning of the rate limiter.
+ assertThat(rateLimiter.tryAcquire()).isTrue();
+ assertThat(rateLimiter.tryAcquire()).isTrue();
+ assertThat(rateLimiter.tryAcquire()).isTrue();
+ mMockClock.currentTimeMillis += Duration.ofSeconds(1000).toMillis();
+ assertThat(rateLimiter.tryAcquire()).isTrue();
+ assertThat(rateLimiter.tryAcquire()).isTrue();
+ assertThat(rateLimiter.tryAcquire()).isTrue();
+ }
+
+ private static final class MockClock extends Clock {
+
+ public long currentTimeMillis = 0;
+
+ @Override
+ public long currentTimeMillis() {
+ return currentTimeMillis;
+ }
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/selinux/SelinuxAuditLogsBuilderTest.java b/services/tests/mockingservicestests/src/com/android/server/selinux/SelinuxAuditLogsBuilderTest.java
new file mode 100644
index 000000000000..b36c9bdaf456
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/selinux/SelinuxAuditLogsBuilderTest.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.selinux;
+
+import static com.android.server.selinux.SelinuxAuditLogBuilder.PATH_MATCHER;
+import static com.android.server.selinux.SelinuxAuditLogBuilder.SCONTEXT_MATCHER;
+import static com.android.server.selinux.SelinuxAuditLogBuilder.TCONTEXT_MATCHER;
+import static com.android.server.selinux.SelinuxAuditLogBuilder.toCategories;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.server.selinux.SelinuxAuditLogBuilder.SelinuxAuditLog;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class SelinuxAuditLogsBuilderTest {
+
+ private final SelinuxAuditLogBuilder mAuditLogBuilder = new SelinuxAuditLogBuilder();
+
+ @Test
+ public void testMatcher_scontext() {
+ assertThat(SCONTEXT_MATCHER.reset("u:r:sdk_sandbox_audit:s0").matches()).isTrue();
+ assertThat(SCONTEXT_MATCHER.group("stype")).isEqualTo("sdk_sandbox_audit");
+ assertThat(SCONTEXT_MATCHER.group("scategories")).isNull();
+
+ assertThat(SCONTEXT_MATCHER.reset("u:r:sdk_sandbox_audit:s0:c123,c456").matches()).isTrue();
+ assertThat(SCONTEXT_MATCHER.group("stype")).isEqualTo("sdk_sandbox_audit");
+ assertThat(toCategories(SCONTEXT_MATCHER.group("scategories")))
+ .isEqualTo(new int[] {123, 456});
+
+ assertThat(SCONTEXT_MATCHER.reset("u:r:not_sdk_sandbox:s0").matches()).isFalse();
+ assertThat(SCONTEXT_MATCHER.reset("u:object_r:sdk_sandbox_audit:s0").matches()).isFalse();
+ assertThat(SCONTEXT_MATCHER.reset("u:r:sdk_sandbox_audit:s0:p123").matches()).isFalse();
+ }
+
+ @Test
+ public void testMatcher_tcontext() {
+ assertThat(TCONTEXT_MATCHER.reset("u:object_r:target_type:s0").matches()).isTrue();
+ assertThat(TCONTEXT_MATCHER.group("ttype")).isEqualTo("target_type");
+ assertThat(TCONTEXT_MATCHER.group("tcategories")).isNull();
+
+ assertThat(TCONTEXT_MATCHER.reset("u:object_r:target_type2:s0:c666").matches()).isTrue();
+ assertThat(TCONTEXT_MATCHER.group("ttype")).isEqualTo("target_type2");
+ assertThat(toCategories(TCONTEXT_MATCHER.group("tcategories"))).isEqualTo(new int[] {666});
+
+ assertThat(TCONTEXT_MATCHER.reset("u:r:target_type:s0").matches()).isFalse();
+ assertThat(TCONTEXT_MATCHER.reset("u:r:sdk_sandbox_audit:s0:x456").matches()).isFalse();
+ }
+
+ @Test
+ public void testMatcher_path() {
+ assertThat(PATH_MATCHER.reset("\"/data\"").matches()).isTrue();
+ assertThat(PATH_MATCHER.group("path")).isEqualTo("/data");
+ assertThat(PATH_MATCHER.reset("\"/data/local\"").matches()).isTrue();
+ assertThat(PATH_MATCHER.group("path")).isEqualTo("/data/local");
+ assertThat(PATH_MATCHER.reset("\"/data/local/tmp\"").matches()).isTrue();
+ assertThat(PATH_MATCHER.group("path")).isEqualTo("/data/local");
+
+ assertThat(PATH_MATCHER.reset("\"/data/local").matches()).isFalse();
+ assertThat(PATH_MATCHER.reset("\"_data_local\"").matches()).isFalse();
+ }
+
+ @Test
+ public void testSelinuxAuditLogsBuilder_noOptionals() {
+ mAuditLogBuilder.reset(
+ "granted { p } scontext=u:r:sdk_sandbox_audit:s0 tcontext=u:object_r:t:s0"
+ + " tclass=c");
+ assertAuditLog(
+ mAuditLogBuilder.build(), true, new String[] {"p"}, "sdk_sandbox_audit", "t", "c");
+
+ mAuditLogBuilder.reset(
+ "tclass=c2 granted { p2 } tcontext=u:object_r:t2:s0"
+ + " scontext=u:r:sdk_sandbox_audit:s0");
+ assertAuditLog(
+ mAuditLogBuilder.build(),
+ true,
+ new String[] {"p2"},
+ "sdk_sandbox_audit",
+ "t2",
+ "c2");
+ }
+
+ @Test
+ public void testSelinuxAuditLogsBuilder_withCategories() {
+ mAuditLogBuilder.reset(
+ "granted { p } scontext=u:r:sdk_sandbox_audit:s0:c123"
+ + " tcontext=u:object_r:t:s0:c456,c666 tclass=c");
+ assertAuditLog(
+ mAuditLogBuilder.build(),
+ true,
+ new String[] {"p"},
+ "sdk_sandbox_audit",
+ new int[] {123},
+ "t",
+ new int[] {456, 666},
+ "c",
+ null,
+ false);
+ }
+
+ @Test
+ public void testSelinuxAuditLogsBuilder_withPath() {
+ mAuditLogBuilder.reset(
+ "granted { p } scontext=u:r:sdk_sandbox_audit:s0 path=\"/very/long/path\""
+ + " tcontext=u:object_r:t:s0 tclass=c");
+ assertAuditLog(
+ mAuditLogBuilder.build(),
+ true,
+ new String[] {"p"},
+ "sdk_sandbox_audit",
+ null,
+ "t",
+ null,
+ "c",
+ "/very/long",
+ false);
+ }
+
+ @Test
+ public void testSelinuxAuditLogsBuilder_withPermissive() {
+ mAuditLogBuilder.reset(
+ "granted { p } scontext=u:r:sdk_sandbox_audit:s0 permissive=0"
+ + " tcontext=u:object_r:t:s0 tclass=c");
+ assertAuditLog(
+ mAuditLogBuilder.build(),
+ true,
+ new String[] {"p"},
+ "sdk_sandbox_audit",
+ null,
+ "t",
+ null,
+ "c",
+ null,
+ false);
+
+ mAuditLogBuilder.reset(
+ "granted { p } scontext=u:r:sdk_sandbox_audit:s0 tcontext=u:object_r:t:s0 tclass=c"
+ + " permissive=1");
+ assertAuditLog(
+ mAuditLogBuilder.build(),
+ true,
+ new String[] {"p"},
+ "sdk_sandbox_audit",
+ null,
+ "t",
+ null,
+ "c",
+ null,
+ true);
+ }
+
+ private void assertAuditLog(
+ SelinuxAuditLog auditLog,
+ boolean granted,
+ String[] permissions,
+ String sType,
+ String tType,
+ String tClass) {
+ assertAuditLog(
+ auditLog, granted, permissions, sType, null, tType, null, tClass, null, false);
+ }
+
+ private void assertAuditLog(
+ SelinuxAuditLog auditLog,
+ boolean granted,
+ String[] permissions,
+ String sType,
+ int[] sCategories,
+ String tType,
+ int[] tCategories,
+ String tClass,
+ String path,
+ boolean permissive) {
+ assertThat(auditLog).isNotNull();
+ assertThat(auditLog.mGranted).isEqualTo(granted);
+ assertThat(auditLog.mPermissions).isEqualTo(permissions);
+ assertThat(auditLog.mSType).isEqualTo(sType);
+ assertThat(auditLog.mSCategories).isEqualTo(sCategories);
+ assertThat(auditLog.mTType).isEqualTo(tType);
+ assertThat(auditLog.mTCategories).isEqualTo(tCategories);
+ assertThat(auditLog.mTClass).isEqualTo(tClass);
+ assertThat(auditLog.mPath).isEqualTo(path);
+ assertThat(auditLog.mPermissive).isEqualTo(permissive);
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/selinux/SelinuxAuditLogsCollectorTest.java b/services/tests/mockingservicestests/src/com/android/server/selinux/SelinuxAuditLogsCollectorTest.java
new file mode 100644
index 000000000000..9758ea596335
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/selinux/SelinuxAuditLogsCollectorTest.java
@@ -0,0 +1,644 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.selinux;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+
+import android.util.EventLog;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.internal.os.Clock;
+import com.android.internal.util.FrameworkStatsLog;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoSession;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Arrays;
+import java.util.stream.Collectors;
+
+@RunWith(AndroidJUnit4.class)
+public class SelinuxAuditLogsCollectorTest {
+
+ // Fake tag to use for testing
+ private static final int ANSWER_TAG = 42;
+
+ private final MockClock mClock = new MockClock();
+
+ private final SelinuxAuditLogsCollector mSelinuxAutidLogsCollector =
+ // Ignore rate limiting for tests
+ new SelinuxAuditLogsCollector(
+ new RateLimiter(mClock, /* window= */ Duration.ofMillis(0)),
+ new QuotaLimiter(
+ mClock, /* windowSize= */ Duration.ofHours(1), /* maxPermits= */ 5));
+
+ private MockitoSession mMockitoSession;
+
+ @Before
+ public void setUp() {
+ // move the clock forward for the limiters.
+ mClock.currentTimeMillis += Duration.ofHours(1).toMillis();
+ // Ignore what was written in the event logs by previous tests.
+ mSelinuxAutidLogsCollector.mLastWrite = Instant.now();
+
+ mMockitoSession =
+ mockitoSession().initMocks(this).mockStatic(FrameworkStatsLog.class).startMocking();
+ }
+
+ @After
+ public void tearDown() {
+ mMockitoSession.finishMocking();
+ }
+
+ @Test
+ public void testWriteSdkSandboxAuditLogs() {
+ writeTestLog("granted", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+ writeTestLog("denied", "perm1", "sdk_sandbox_audit", "ttype1", "tclass1");
+
+ boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
+
+ assertThat(done).isTrue();
+ verify(
+ () ->
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.SELINUX_AUDIT_LOG,
+ true,
+ new String[] {"perm"},
+ "sdk_sandbox_audit",
+ null,
+ "ttype",
+ null,
+ "tclass",
+ null,
+ false));
+ verify(
+ () ->
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.SELINUX_AUDIT_LOG,
+ false,
+ new String[] {"perm1"},
+ "sdk_sandbox_audit",
+ null,
+ "ttype1",
+ null,
+ "tclass1",
+ null,
+ false));
+ }
+
+ @Test
+ public void testWriteSdkSandboxAuditLogs_multiplePerms() {
+ writeTestLog("denied", "perm1 perm2", "sdk_sandbox_audit", "ttype", "tclass");
+ writeTestLog("denied", "perm3 perm4", "sdk_sandbox_audit", "ttype", "tclass");
+
+ boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
+
+ assertThat(done).isTrue();
+ verify(
+ () ->
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.SELINUX_AUDIT_LOG,
+ false,
+ new String[] {"perm1", "perm2"},
+ "sdk_sandbox_audit",
+ null,
+ "ttype",
+ null,
+ "tclass",
+ null,
+ false));
+ verify(
+ () ->
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.SELINUX_AUDIT_LOG,
+ false,
+ new String[] {"perm3", "perm4"},
+ "sdk_sandbox_audit",
+ null,
+ "ttype",
+ null,
+ "tclass",
+ null,
+ false));
+ }
+
+ @Test
+ public void testWriteSdkSandboxAuditLogs_withPaths() {
+ writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass", "/good/path");
+ writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass", "/very/long/path");
+ writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass", "/short_path");
+ writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass", "not_a_path");
+
+ boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
+
+ assertThat(done).isTrue();
+ verify(
+ () ->
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.SELINUX_AUDIT_LOG,
+ false,
+ new String[] {"perm"},
+ "sdk_sandbox_audit",
+ null,
+ "ttype",
+ null,
+ "tclass",
+ "/good/path",
+ false));
+ verify(
+ () ->
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.SELINUX_AUDIT_LOG,
+ false,
+ new String[] {"perm"},
+ "sdk_sandbox_audit",
+ null,
+ "ttype",
+ null,
+ "tclass",
+ "/very/long",
+ false));
+ verify(
+ () ->
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.SELINUX_AUDIT_LOG,
+ false,
+ new String[] {"perm"},
+ "sdk_sandbox_audit",
+ null,
+ "ttype",
+ null,
+ "tclass",
+ "/short_path",
+ false));
+ verify(
+ () ->
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.SELINUX_AUDIT_LOG,
+ false,
+ new String[] {"perm"},
+ "sdk_sandbox_audit",
+ null,
+ "ttype",
+ null,
+ "tclass",
+ null,
+ false));
+ }
+
+ @Test
+ public void testWriteSdkSandboxAuditLogs_withCategories() {
+ writeTestLog(
+ "denied", "perm", "sdk_sandbox_audit", new int[] {123}, "ttype", null, "tclass");
+ writeTestLog(
+ "denied",
+ "perm",
+ "sdk_sandbox_audit",
+ new int[] {123, 456},
+ "ttype",
+ null,
+ "tclass");
+ writeTestLog(
+ "denied", "perm", "sdk_sandbox_audit", null, "ttype", new int[] {666}, "tclass");
+ writeTestLog(
+ "denied",
+ "perm",
+ "sdk_sandbox_audit",
+ new int[] {123, 456},
+ "ttype",
+ new int[] {666, 777},
+ "tclass");
+
+ boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
+
+ assertThat(done).isTrue();
+ verify(
+ () ->
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.SELINUX_AUDIT_LOG,
+ false,
+ new String[] {"perm"},
+ "sdk_sandbox_audit",
+ new int[] {123},
+ "ttype",
+ null,
+ "tclass",
+ null,
+ false));
+ verify(
+ () ->
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.SELINUX_AUDIT_LOG,
+ false,
+ new String[] {"perm"},
+ "sdk_sandbox_audit",
+ new int[] {123, 456},
+ "ttype",
+ null,
+ "tclass",
+ null,
+ false));
+ verify(
+ () ->
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.SELINUX_AUDIT_LOG,
+ false,
+ new String[] {"perm"},
+ "sdk_sandbox_audit",
+ null,
+ "ttype",
+ new int[] {666},
+ "tclass",
+ null,
+ false));
+ verify(
+ () ->
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.SELINUX_AUDIT_LOG,
+ false,
+ new String[] {"perm"},
+ "sdk_sandbox_audit",
+ new int[] {123, 456},
+ "ttype",
+ new int[] {666, 777},
+ "tclass",
+ null,
+ false));
+ }
+
+ @Test
+ public void testWriteSdkSandboxAuditLogs_withPathAndCategories() {
+ writeTestLog(
+ "denied",
+ "perm",
+ "sdk_sandbox_audit",
+ new int[] {123},
+ "ttype",
+ new int[] {666},
+ "tclass",
+ "/a/path");
+
+ boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
+
+ assertThat(done).isTrue();
+ verify(
+ () ->
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.SELINUX_AUDIT_LOG,
+ false,
+ new String[] {"perm"},
+ "sdk_sandbox_audit",
+ new int[] {123},
+ "ttype",
+ new int[] {666},
+ "tclass",
+ "/a/path",
+ false));
+ }
+
+ @Test
+ public void testWriteSdkSandboxAuditLogs_permissive() {
+ writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+ writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass", true);
+ writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass", false);
+
+ boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
+
+ assertThat(done).isTrue();
+ verify(
+ () ->
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.SELINUX_AUDIT_LOG,
+ false,
+ new String[] {"perm"},
+ "sdk_sandbox_audit",
+ null,
+ "ttype",
+ null,
+ "tclass",
+ null,
+ false),
+ times(2));
+ verify(
+ () ->
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.SELINUX_AUDIT_LOG,
+ false,
+ new String[] {"perm"},
+ "sdk_sandbox_audit",
+ null,
+ "ttype",
+ null,
+ "tclass",
+ null,
+ true));
+ }
+
+ @Test
+ public void testNotWriteAuditLogs_notSdkSandbox() {
+ writeTestLog("denied", "perm", "stype", "ttype", "tclass");
+
+ boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
+
+ assertThat(done).isTrue();
+ verify(
+ () ->
+ FrameworkStatsLog.write(
+ anyInt(),
+ anyBoolean(),
+ any(),
+ anyString(),
+ any(),
+ anyString(),
+ any(),
+ anyString(),
+ any(),
+ anyBoolean()),
+ never());
+ }
+
+ @Test
+ public void testWriteSdkSandboxAuditLogs_upToQuota() {
+ writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+ writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+ writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+ writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+ writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+ // These are not pushed.
+ writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+ writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+
+ boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
+
+ assertThat(done).isTrue();
+ verify(
+ () ->
+ FrameworkStatsLog.write(
+ anyInt(),
+ anyBoolean(),
+ any(),
+ anyString(),
+ any(),
+ anyString(),
+ any(),
+ anyString(),
+ any(),
+ anyBoolean()),
+ times(5));
+ }
+
+ @Test
+ public void testWriteSdkSandboxAuditLogs_resetQuota() {
+ writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+ writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+ writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+ writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+ writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+ writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+ writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+
+ boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
+ assertThat(done).isTrue();
+ verify(
+ () ->
+ FrameworkStatsLog.write(
+ anyInt(),
+ anyBoolean(),
+ any(),
+ anyString(),
+ any(),
+ anyString(),
+ any(),
+ anyString(),
+ any(),
+ anyBoolean()),
+ times(5));
+
+ writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+ writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+ writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+ writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+ writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+ // move the clock forward to reset the quota limiter.
+ mClock.currentTimeMillis += Duration.ofHours(1).toMillis();
+ done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
+ assertThat(done).isTrue();
+ verify(
+ () ->
+ FrameworkStatsLog.write(
+ anyInt(),
+ anyBoolean(),
+ any(),
+ anyString(),
+ any(),
+ anyString(),
+ any(),
+ anyString(),
+ any(),
+ anyBoolean()),
+ times(10));
+ }
+
+ @Test
+ public void testNotWriteAuditLogs_stopRequested() {
+ writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+ writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+ writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+ writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+ writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+ // These are not pushed.
+ writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+ writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+
+ mSelinuxAutidLogsCollector.mStopRequested.set(true);
+ boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
+ assertThat(done).isFalse();
+ verify(
+ () ->
+ FrameworkStatsLog.write(
+ anyInt(),
+ anyBoolean(),
+ any(),
+ anyString(),
+ any(),
+ anyString(),
+ any(),
+ anyString(),
+ any(),
+ anyBoolean()),
+ never());
+
+ mSelinuxAutidLogsCollector.mStopRequested.set(false);
+ done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
+ assertThat(done).isTrue();
+ verify(
+ () ->
+ FrameworkStatsLog.write(
+ anyInt(),
+ anyBoolean(),
+ any(),
+ anyString(),
+ any(),
+ anyString(),
+ any(),
+ anyString(),
+ any(),
+ anyBoolean()),
+ times(5));
+ }
+
+ @Test
+ public void testAuditLogs_resumeJobDoesNotExceedLimit() {
+ writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+ mSelinuxAutidLogsCollector.mStopRequested.set(true);
+
+ boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
+
+ assertThat(done).isFalse();
+ verify(
+ () ->
+ FrameworkStatsLog.write(
+ anyInt(),
+ anyBoolean(),
+ any(),
+ anyString(),
+ any(),
+ anyString(),
+ any(),
+ anyString(),
+ any(),
+ anyBoolean()),
+ never());
+ }
+
+ private static void writeTestLog(
+ String granted, String permissions, String sType, String tType, String tClass) {
+ EventLog.writeEvent(
+ ANSWER_TAG,
+ String.format(
+ "avc: %s { %s } scontext=u:r:%s:s0 tcontext=u:object_r:%s:s0 tclass=%s",
+ granted, permissions, sType, tType, tClass));
+ }
+
+ private static void writeTestLog(
+ String granted,
+ String permissions,
+ String sType,
+ String tType,
+ String tClass,
+ String path) {
+ EventLog.writeEvent(
+ ANSWER_TAG,
+ String.format(
+ "avc: %s { %s } path=\"%s\" scontext=u:r:%s:s0 tcontext=u:object_r:%s:s0"
+ + " tclass=%s",
+ granted, permissions, path, sType, tType, tClass));
+ }
+
+ private static void writeTestLog(
+ String granted,
+ String permissions,
+ String sType,
+ int[] sCategories,
+ String tType,
+ int[] tCategories,
+ String tClass) {
+ EventLog.writeEvent(
+ ANSWER_TAG,
+ String.format(
+ "avc: %s { %s } scontext=u:r:%s:s0%s tcontext=u:object_r:%s:s0%s tclass=%s",
+ granted,
+ permissions,
+ sType,
+ toCategoriesString(sCategories),
+ tType,
+ toCategoriesString(tCategories),
+ tClass));
+ }
+
+ private static void writeTestLog(
+ String granted,
+ String permissions,
+ String sType,
+ int[] sCategories,
+ String tType,
+ int[] tCategories,
+ String tClass,
+ String path) {
+ EventLog.writeEvent(
+ ANSWER_TAG,
+ String.format(
+ "avc: %s { %s } path=\"%s\" scontext=u:r:%s:s0%s"
+ + " tcontext=u:object_r:%s:s0%s tclass=%s",
+ granted,
+ permissions,
+ path,
+ sType,
+ toCategoriesString(sCategories),
+ tType,
+ toCategoriesString(tCategories),
+ tClass));
+ }
+
+ private static void writeTestLog(
+ String granted,
+ String permissions,
+ String sType,
+ String tType,
+ String tClass,
+ boolean permissive) {
+ EventLog.writeEvent(
+ ANSWER_TAG,
+ String.format(
+ "avc: %s { %s } scontext=u:r:%s:s0 tcontext=u:object_r:%s:s0 tclass=%s"
+ + " permissive=%s",
+ granted, permissions, sType, tType, tClass, permissive ? "1" : "0"));
+ }
+
+ private static String toCategoriesString(int[] categories) {
+ return (categories == null || categories.length == 0)
+ ? ""
+ : ":c"
+ + Arrays.stream(categories)
+ .mapToObj(String::valueOf)
+ .collect(Collectors.joining(",c"));
+ }
+
+ private static final class MockClock extends Clock {
+
+ public long currentTimeMillis = 0;
+
+ @Override
+ public long currentTimeMillis() {
+ return currentTimeMillis;
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
index ccbbaa52ac21..5943832586b3 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
@@ -33,19 +33,21 @@ import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
-import android.view.Display;
import android.view.DisplayInfo;
import android.view.WindowManager;
import androidx.test.InstrumentationRegistry;
+import com.android.input.flags.Flags;
import com.android.server.LocalServices;
import com.android.server.input.InputManagerInternal;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -58,6 +60,9 @@ public class InputControllerTest {
private static final String LANGUAGE_TAG = "en-US";
private static final String LAYOUT_TYPE = "qwerty";
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Mock
private InputManagerInternal mInputManagerInternalMock;
@Mock
@@ -72,11 +77,12 @@ public class InputControllerTest {
@Before
public void setUp() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_POINTER_CHOREOGRAPHER);
+
MockitoAnnotations.initMocks(this);
mInputManagerMockHelper = new InputManagerMockHelper(
TestableLooper.get(this), mNativeWrapperMock, mIInputManagerMock);
- doReturn(true).when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt());
LocalServices.removeServiceForTest(InputManagerInternal.class);
LocalServices.addService(InputManagerInternal.class, mInputManagerInternalMock);
@@ -129,11 +135,7 @@ public class InputControllerTest {
mInputController.createMouse("name", /*vendorId= */ 1, /*productId= */ 1, deviceToken,
/* displayId= */ 1);
verify(mNativeWrapperMock).openUinputMouse(eq("name"), eq(1), eq(1), anyString());
- verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(1));
- doReturn(1).when(mInputManagerInternalMock).getVirtualMousePointerDisplayId();
mInputController.unregisterInputDevice(deviceToken);
- verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(
- eq(Display.INVALID_DISPLAY));
}
@Test
@@ -143,14 +145,11 @@ public class InputControllerTest {
mInputController.createMouse("mouse1", /*vendorId= */ 1, /*productId= */ 1, deviceToken,
/* displayId= */ 1);
verify(mNativeWrapperMock).openUinputMouse(eq("mouse1"), eq(1), eq(1), anyString());
- verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(1));
final IBinder deviceToken2 = new Binder();
mInputController.createMouse("mouse2", /*vendorId= */ 1, /*productId= */ 1, deviceToken2,
/* displayId= */ 2);
verify(mNativeWrapperMock).openUinputMouse(eq("mouse2"), eq(1), eq(1), anyString());
- verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(2));
mInputController.unregisterInputDevice(deviceToken);
- verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(1));
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index 9ff29d208dc0..5442af875e86 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -339,8 +339,8 @@ public class VirtualDeviceManagerServiceTest {
LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock);
mSetFlagsRule.initAllFlagsToReleaseConfigDefault();
+ mSetFlagsRule.enableFlags(com.android.input.flags.Flags.FLAG_ENABLE_POINTER_CHOREOGRAPHER);
- doReturn(true).when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt());
doNothing().when(mInputManagerInternalMock)
.setMousePointerAccelerationEnabled(anyBoolean(), anyInt());
doNothing().when(mInputManagerInternalMock).setPointerIconVisible(anyBoolean(), anyInt());
@@ -1333,7 +1333,6 @@ public class VirtualDeviceManagerServiceTest {
mInputController.addDeviceForTesting(BINDER, fd,
InputController.InputDeviceDescriptor.TYPE_MOUSE, DISPLAY_ID_1, PHYS,
DEVICE_NAME_1, INPUT_DEVICE_ID);
- doReturn(DISPLAY_ID_1).when(mInputManagerInternalMock).getVirtualMousePointerDisplayId();
assertThat(mDeviceImpl.sendButtonEvent(BINDER,
new VirtualMouseButtonEvent.Builder()
.setButtonCode(buttonCode)
@@ -1363,7 +1362,6 @@ public class VirtualDeviceManagerServiceTest {
mInputController.addDeviceForTesting(BINDER, fd,
InputController.InputDeviceDescriptor.TYPE_MOUSE, DISPLAY_ID_1, PHYS, DEVICE_NAME_1,
INPUT_DEVICE_ID);
- doReturn(DISPLAY_ID_1).when(mInputManagerInternalMock).getVirtualMousePointerDisplayId();
assertThat(mDeviceImpl.sendRelativeEvent(BINDER,
new VirtualMouseRelativeEvent.Builder()
.setRelativeX(x)
@@ -1394,7 +1392,6 @@ public class VirtualDeviceManagerServiceTest {
mInputController.addDeviceForTesting(BINDER, fd,
InputController.InputDeviceDescriptor.TYPE_MOUSE, DISPLAY_ID_1, PHYS, DEVICE_NAME_1,
INPUT_DEVICE_ID);
- doReturn(DISPLAY_ID_1).when(mInputManagerInternalMock).getVirtualMousePointerDisplayId();
assertThat(mDeviceImpl.sendScrollEvent(BINDER,
new VirtualMouseScrollEvent.Builder()
.setXAxisMovement(x)
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java
index 3e4f1df0e1d4..81981e6b16ca 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java
@@ -183,9 +183,8 @@ public class VirtualCameraControllerTest {
private VirtualCameraConfig createVirtualCameraConfig(
int width, int height, int format, int maximumFramesPerSecond,
String name, int sensorOrientation, int lensFacing) {
- return new VirtualCameraConfig.Builder()
+ return new VirtualCameraConfig.Builder(name)
.addStreamConfig(width, height, format, maximumFramesPerSecond)
- .setName(name)
.setVirtualCameraCallback(mCallbackHandler, mVirtualCameraCallbackMock)
.setSensorOrientation(sensorOrientation)
.setLensFacing(lensFacing)
diff --git a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
index dc1d2c5e54b6..1c6d36b0a0d2 100644
--- a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
@@ -17,16 +17,19 @@
package com.android.server.os;
import android.app.admin.flags.Flags;
-import static android.app.admin.flags.Flags.onboardingBugreportV2Enabled;
import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.when;
import android.app.role.RoleManager;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.BugreportManager.BugreportCallback;
import android.os.IBinder;
@@ -48,6 +51,8 @@ import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
import java.io.FileDescriptor;
import java.util.concurrent.CompletableFuture;
@@ -66,6 +71,9 @@ public class BugreportManagerServiceImplTest {
private BugreportManagerServiceImpl mService;
private BugreportManagerServiceImpl.BugreportFileManager mBugreportFileManager;
+ @Mock
+ private PackageManager mPackageManager;
+
private int mCallingUid = 1234;
private String mCallingPackage = "test.package";
private AtomicFile mMappingFile;
@@ -74,7 +82,8 @@ public class BugreportManagerServiceImplTest {
private String mBugreportFile2 = "bugreport-file2.zip";
@Before
- public void setUp() {
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
mContext = InstrumentationRegistry.getInstrumentation().getContext();
mMappingFile = new AtomicFile(mContext.getFilesDir(), "bugreport-mapping.xml");
ArraySet<String> mAllowlistedPackages = new ArraySet<>();
@@ -83,6 +92,7 @@ public class BugreportManagerServiceImplTest {
new BugreportManagerServiceImpl.Injector(mContext, mAllowlistedPackages,
mMappingFile));
mBugreportFileManager = new BugreportManagerServiceImpl.BugreportFileManager(mMappingFile);
+ when(mPackageManager.getPackageUidAsUser(anyString(), anyInt())).thenReturn(mCallingUid);
}
@After
@@ -115,12 +125,13 @@ public class BugreportManagerServiceImplTest {
assertThrows(IllegalArgumentException.class, () ->
mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(
- mContext, callingInfo, Process.myUserHandle().getIdentifier(),
- "unknown-file.zip", /* forceUpdateMapping= */ true));
+ mContext, mPackageManager, callingInfo,
+ Process.myUserHandle().getIdentifier(), "unknown-file.zip",
+ /* forceUpdateMapping= */ true));
// No exception should be thrown.
mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(
- mContext, callingInfo, mContext.getUserId(), mBugreportFile,
+ mContext, mPackageManager, callingInfo, mContext.getUserId(), mBugreportFile,
/* forceUpdateMapping= */ true);
}
@@ -132,7 +143,7 @@ public class BugreportManagerServiceImplTest {
callingInfo, mBugreportFile, /* keepOnRetrieval= */ true);
mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(
- mContext, callingInfo, mContext.getUserId(), mBugreportFile,
+ mContext, mPackageManager, callingInfo, mContext.getUserId(), mBugreportFile,
/* forceUpdateMapping= */ true);
assertThat(mBugreportFileManager.mBugreportFilesToPersist).containsExactly(mBugreportFile);
@@ -148,10 +159,10 @@ public class BugreportManagerServiceImplTest {
// No exception should be thrown.
mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(
- mContext, callingInfo, mContext.getUserId(), mBugreportFile,
+ mContext, mPackageManager, callingInfo, mContext.getUserId(), mBugreportFile,
/* forceUpdateMapping= */ true);
mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(
- mContext, callingInfo, mContext.getUserId(), mBugreportFile2,
+ mContext, mPackageManager, callingInfo, mContext.getUserId(), mBugreportFile2,
/* forceUpdateMapping= */ true);
}
@@ -160,8 +171,9 @@ public class BugreportManagerServiceImplTest {
Pair<Integer, String> callingInfo = new Pair<>(mCallingUid, mCallingPackage);
assertThrows(IllegalArgumentException.class,
() -> mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(
- mContext, callingInfo, Process.myUserHandle().getIdentifier(),
- "test-file.zip", /* forceUpdateMapping= */ true));
+ mContext, mPackageManager, callingInfo,
+ Process.myUserHandle().getIdentifier(), "test-file.zip",
+ /* forceUpdateMapping= */ true));
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
index d7ed7c2d6469..8d8dc9cc45b1 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
@@ -23,6 +23,7 @@ import static org.testng.Assert.assertThrows;
import android.content.pm.UserProperties;
import android.os.Parcel;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.util.Xml;
import androidx.test.filters.MediumTest;
@@ -31,6 +32,7 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -52,10 +54,13 @@ import java.util.function.Supplier;
@RunWith(AndroidJUnit4.class)
@MediumTest
public class UserManagerServiceUserPropertiesTest {
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
/** Test that UserProperties can properly read the xml information that it writes. */
@Test
public void testWriteReadXml() throws Exception {
+ mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_SUPPORT_HIDING_PROFILES);
final UserProperties defaultProps = new UserProperties.Builder()
.setShowInLauncher(21)
.setStartWithParent(false)
@@ -73,6 +78,7 @@ public class UserManagerServiceUserPropertiesTest {
.setDeleteAppWithParent(false)
.setAlwaysVisible(false)
.setCrossProfileContentSharingStrategy(0)
+ .setProfileApiVisibility(34)
.build();
final UserProperties actualProps = new UserProperties(defaultProps);
actualProps.setShowInLauncher(14);
@@ -90,6 +96,7 @@ public class UserManagerServiceUserPropertiesTest {
actualProps.setDeleteAppWithParent(true);
actualProps.setAlwaysVisible(true);
actualProps.setCrossProfileContentSharingStrategy(1);
+ actualProps.setProfileApiVisibility(36);
// Write the properties to xml.
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
@@ -114,6 +121,7 @@ public class UserManagerServiceUserPropertiesTest {
/** Tests parcelling an object in which all properties are present. */
@Test
public void testParcelUnparcel() throws Exception {
+ mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_SUPPORT_HIDING_PROFILES);
final UserProperties originalProps = new UserProperties.Builder()
.setShowInLauncher(2145)
.build();
@@ -124,6 +132,7 @@ public class UserManagerServiceUserPropertiesTest {
/** Tests copying a UserProperties object varying permissions. */
@Test
public void testCopyLacksPermissions() throws Exception {
+ mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_SUPPORT_HIDING_PROFILES);
final UserProperties defaultProps = new UserProperties.Builder()
.setShowInLauncher(2145)
.setStartWithParent(true)
@@ -134,6 +143,7 @@ public class UserManagerServiceUserPropertiesTest {
.setAuthAlwaysRequiredToDisableQuietMode(false)
.setAllowStoppingUserWithDelayedLocking(false)
.setAlwaysVisible(true)
+ .setProfileApiVisibility(110)
.build();
final UserProperties orig = new UserProperties(defaultProps);
orig.setShowInLauncher(2841);
@@ -209,6 +219,8 @@ public class UserManagerServiceUserPropertiesTest {
copy::isCredentialShareableWithParent, true);
assertEqualGetterOrThrows(orig::getCrossProfileContentSharingStrategy,
copy::getCrossProfileContentSharingStrategy, true);
+ assertEqualGetterOrThrows(orig::getProfileApiVisibility, copy::getProfileApiVisibility,
+ true);
}
/**
@@ -270,5 +282,6 @@ public class UserManagerServiceUserPropertiesTest {
assertThat(expected.getAlwaysVisible()).isEqualTo(actual.getAlwaysVisible());
assertThat(expected.getCrossProfileContentSharingStrategy())
.isEqualTo(actual.getCrossProfileContentSharingStrategy());
+ assertThat(expected.getProfileApiVisibility()).isEqualTo(actual.getProfileApiVisibility());
}
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
index 70837061b0bb..1ee604e78d5f 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
@@ -41,6 +41,7 @@ import android.content.res.XmlResourceParser;
import android.os.Bundle;
import android.os.UserManager;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.util.ArrayMap;
import androidx.test.InstrumentationRegistry;
@@ -50,6 +51,7 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.frameworks.servicestests.R;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -71,9 +73,11 @@ public class UserManagerServiceUserTypeTest {
public void setup() {
mResources = InstrumentationRegistry.getTargetContext().getResources();
}
-
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Test
public void testUserTypeBuilder_createUserType() {
+ mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_SUPPORT_HIDING_PROFILES);
final Bundle restrictions = makeRestrictionsBundle("r1", "r2");
final Bundle systemSettings = makeSettingsBundle("s1", "s2");
final Bundle secureSettings = makeSettingsBundle("secure_s1", "secure_s2");
@@ -97,7 +101,8 @@ public class UserManagerServiceUserTypeTest {
.setInheritDevicePolicy(340)
.setDeleteAppWithParent(true)
.setAlwaysVisible(true)
- .setCrossProfileContentSharingStrategy(1);
+ .setCrossProfileContentSharingStrategy(1)
+ .setProfileApiVisibility(34);
final UserTypeDetails type = new UserTypeDetails.Builder()
.setName("a.name")
@@ -180,6 +185,7 @@ public class UserManagerServiceUserTypeTest {
assertTrue(type.getDefaultUserPropertiesReference().getAlwaysVisible());
assertEquals(1, type.getDefaultUserPropertiesReference()
.getCrossProfileContentSharingStrategy());
+ assertEquals(34, type.getDefaultUserPropertiesReference().getProfileApiVisibility());
assertEquals(23, type.getBadgeLabel(0));
assertEquals(24, type.getBadgeLabel(1));
@@ -199,6 +205,7 @@ public class UserManagerServiceUserTypeTest {
@Test
public void testUserTypeBuilder_defaults() {
+ mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_SUPPORT_HIDING_PROFILES);
UserTypeDetails type = new UserTypeDetails.Builder()
.setName("name") // Required (no default allowed)
.setBaseType(FLAG_FULL) // Required (no default allowed)
@@ -238,6 +245,8 @@ public class UserManagerServiceUserTypeTest {
props.getShowInQuietMode());
assertEquals(UserProperties.CROSS_PROFILE_CONTENT_SHARING_NO_DELEGATION,
props.getCrossProfileContentSharingStrategy());
+ assertEquals(UserProperties.PROFILE_API_VISIBILITY_VISIBLE,
+ props.getProfileApiVisibility());
assertFalse(type.hasBadge());
}
@@ -310,6 +319,7 @@ public class UserManagerServiceUserTypeTest {
/** Tests {@link UserTypeFactory#customizeBuilders} for a reasonable xml file. */
@Test
public void testUserTypeFactoryCustomize_profile() throws Exception {
+ mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_SUPPORT_HIDING_PROFILES);
final String userTypeAosp1 = "android.test.1"; // Profile user that is not customized
final String userTypeAosp2 = "android.test.2"; // Profile user that is customized
final String userTypeOem1 = "custom.test.1"; // Custom-defined profile
@@ -332,7 +342,8 @@ public class UserManagerServiceUserTypeTest {
.setShowInQuietMode(24)
.setDeleteAppWithParent(true)
.setAlwaysVisible(false)
- .setCrossProfileContentSharingStrategy(1);
+ .setCrossProfileContentSharingStrategy(1)
+ .setProfileApiVisibility(36);
final ArrayMap<String, UserTypeDetails.Builder> builders = new ArrayMap<>();
builders.put(userTypeAosp1, new UserTypeDetails.Builder()
@@ -383,6 +394,7 @@ public class UserManagerServiceUserTypeTest {
assertFalse(aospType.getDefaultUserPropertiesReference().getAlwaysVisible());
assertEquals(1, aospType.getDefaultUserPropertiesReference()
.getCrossProfileContentSharingStrategy());
+ assertEquals(36, aospType.getDefaultUserPropertiesReference().getProfileApiVisibility());
// userTypeAosp2 should be modified.
aospType = builders.get(userTypeAosp2).createUserTypeDetails();
@@ -439,6 +451,7 @@ public class UserManagerServiceUserTypeTest {
assertTrue(aospType.getDefaultUserPropertiesReference().getAlwaysVisible());
assertEquals(0, aospType.getDefaultUserPropertiesReference()
.getCrossProfileContentSharingStrategy());
+ assertEquals(36, aospType.getDefaultUserPropertiesReference().getProfileApiVisibility());
// userTypeOem1 should be created.
UserTypeDetails.Builder customType = builders.get(userTypeOem1);
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index a743fff5d2ea..db561c436269 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -19,6 +19,7 @@ package com.android.server.pm;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
import static org.testng.Assert.assertEquals;
@@ -33,10 +34,12 @@ import android.content.pm.UserProperties;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
+import android.os.PersistableBundle;
import android.os.UserHandle;
import android.os.UserManager;
import android.platform.test.annotations.Postsubmit;
import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.test.suitebuilder.annotation.LargeTest;
import android.test.suitebuilder.annotation.MediumTest;
@@ -53,6 +56,7 @@ import com.google.common.collect.Range;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -95,6 +99,8 @@ public final class UserManagerTest {
private UserSwitchWaiter mUserSwitchWaiter;
private UserRemovalWaiter mUserRemovalWaiter;
private int mOriginalCurrentUserId;
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Before
public void setUp() throws Exception {
@@ -166,6 +172,7 @@ public final class UserManagerTest {
@Test
public void testCloneUser() throws Exception {
+ mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_SUPPORT_HIDING_PROFILES);
assumeCloneEnabled();
UserHandle mainUser = mUserManager.getMainUser();
assumeTrue("Main user is null", mainUser != null);
@@ -222,6 +229,7 @@ public final class UserManagerTest {
.isEqualTo(cloneUserProperties.getCrossProfileContentSharingStrategy());
assertThrows(SecurityException.class, cloneUserProperties::getDeleteAppWithParent);
assertThrows(SecurityException.class, cloneUserProperties::getAlwaysVisible);
+ assertThrows(SecurityException.class, cloneUserProperties::getProfileApiVisibility);
compareDrawables(mUserManager.getUserBadge(),
Resources.getSystem().getDrawable(userTypeDetails.getBadgePlain()));
@@ -303,6 +311,7 @@ public final class UserManagerTest {
@Test
public void testPrivateProfile() throws Exception {
+ mSetFlagsRule.enableFlags(android.multiuser.Flags.FLAG_SUPPORT_HIDING_PROFILES);
UserHandle mainUser = mUserManager.getMainUser();
assumeTrue("Main user is null", mainUser != null);
// Get the default properties for private profile user type.
@@ -344,7 +353,8 @@ public final class UserManagerTest {
assertThrows(SecurityException.class, privateProfileUserProperties::getDeleteAppWithParent);
assertThrows(SecurityException.class,
privateProfileUserProperties::getAllowStoppingUserWithDelayedLocking);
-
+ assertThrows(SecurityException.class,
+ privateProfileUserProperties::getProfileApiVisibility);
compareDrawables(mUserManager.getUserBadge(),
Resources.getSystem().getDrawable(userTypeDetails.getBadgePlain()));
@@ -1632,6 +1642,106 @@ public final class UserManagerTest {
assertThat(mainUserCount).isEqualTo(1);
}
+ @Test
+ public void testAddUserAccountData_validStringValuesAreSaved_validBundleIsSaved() {
+ assumeManagedUsersSupported();
+
+ String userName = "User";
+ String accountName = "accountName";
+ String accountType = "accountType";
+ String arrayKey = "StringArrayKey";
+ String stringKey = "StringKey";
+ String intKey = "IntKey";
+ String nestedBundleKey = "PersistableBundleKey";
+ String value1 = "Value 1";
+ String value2 = "Value 2";
+ String value3 = "Value 3";
+
+ UserInfo userInfo = mUserManager.createUser(userName,
+ UserManager.USER_TYPE_FULL_SECONDARY, 0);
+
+ PersistableBundle accountOptions = new PersistableBundle();
+ String[] stringArray = {value1, value2};
+ accountOptions.putInt(intKey, 1234);
+ PersistableBundle nested = new PersistableBundle();
+ nested.putString(stringKey, value3);
+ accountOptions.putPersistableBundle(nestedBundleKey, nested);
+ accountOptions.putStringArray(arrayKey, stringArray);
+
+ mUserManager.clearSeedAccountData();
+ mUserManager.setSeedAccountData(mContext.getUserId(), accountName,
+ accountType, accountOptions);
+
+ //assert userName accountName and accountType were saved correctly
+ assertTrue(mUserManager.getUserInfo(userInfo.id).name.equals(userName));
+ assertTrue(mUserManager.getSeedAccountName().equals(accountName));
+ assertTrue(mUserManager.getSeedAccountType().equals(accountType));
+
+ //assert bundle with correct values was added
+ assertThat(mUserManager.getSeedAccountOptions().containsKey(arrayKey)).isTrue();
+ assertThat(mUserManager.getSeedAccountOptions().getPersistableBundle(nestedBundleKey)
+ .getString(stringKey)).isEqualTo(value3);
+ assertThat(mUserManager.getSeedAccountOptions().getStringArray(arrayKey)[0])
+ .isEqualTo(value1);
+
+ mUserManager.removeUser(userInfo.id);
+ }
+
+ @Test
+ public void testAddUserAccountData_invalidStringValuesAreTruncated_invalidBundleIsDropped() {
+ assumeManagedUsersSupported();
+
+ String tooLongString = generateLongString();
+ String userName = "User " + tooLongString;
+ String accountType = "Account Type " + tooLongString;
+ String accountName = "accountName " + tooLongString;
+ String arrayKey = "StringArrayKey";
+ String stringKey = "StringKey";
+ String intKey = "IntKey";
+ String nestedBundleKey = "PersistableBundleKey";
+ String value1 = "Value 1";
+ String value2 = "Value 2";
+
+ UserInfo userInfo = mUserManager.createUser(userName,
+ UserManager.USER_TYPE_FULL_SECONDARY, 0);
+
+ PersistableBundle accountOptions = new PersistableBundle();
+ String[] stringArray = {value1, value2};
+ accountOptions.putInt(intKey, 1234);
+ PersistableBundle nested = new PersistableBundle();
+ nested.putString(stringKey, tooLongString);
+ accountOptions.putPersistableBundle(nestedBundleKey, nested);
+ accountOptions.putStringArray(arrayKey, stringArray);
+ mUserManager.clearSeedAccountData();
+ mUserManager.setSeedAccountData(mContext.getUserId(), accountName,
+ accountType, accountOptions);
+
+ //assert userName was truncated
+ assertTrue(mUserManager.getUserInfo(userInfo.id).name.length()
+ == UserManager.MAX_USER_NAME_LENGTH);
+
+ //assert accountName and accountType got truncated
+ assertTrue(mUserManager.getSeedAccountName().length()
+ == UserManager.MAX_ACCOUNT_STRING_LENGTH);
+ assertTrue(mUserManager.getSeedAccountType().length()
+ == UserManager.MAX_ACCOUNT_STRING_LENGTH);
+
+ //assert bundle with invalid values was dropped
+ assertThat(mUserManager.getSeedAccountOptions() == null).isTrue();
+
+ mUserManager.removeUser(userInfo.id);
+ }
+
+ private String generateLongString() {
+ String partialString = "Test Name Test Name Test Name Test Name Test Name Test Name Test "
+ + "Name Test Name Test Name Test Name "; //String of length 100
+ StringBuilder resultString = new StringBuilder();
+ for (int i = 0; i < 600; i++) {
+ resultString.append(partialString);
+ }
+ return resultString.toString();
+ }
+
private boolean isPackageInstalledForUser(String packageName, int userId) {
try {
return mPackageManager.getPackageInfoAsUser(packageName, 0, userId) != null;
diff --git a/services/tests/servicestests/test-apps/PackageParserApp/Android.bp b/services/tests/servicestests/test-apps/PackageParserApp/Android.bp
index 3e78f9a9674a..131b380d9215 100644
--- a/services/tests/servicestests/test-apps/PackageParserApp/Android.bp
+++ b/services/tests/servicestests/test-apps/PackageParserApp/Android.bp
@@ -102,3 +102,17 @@ android_test_helper_app {
resource_dirs: ["res"],
manifest: "AndroidManifestApp6.xml",
}
+
+android_test_helper_app {
+ name: "PackageParserTestApp7",
+ sdk_version: "current",
+ srcs: ["**/*.java"],
+ dex_preopt: {
+ enabled: false,
+ },
+ optimize: {
+ enabled: false,
+ },
+ resource_dirs: ["res"],
+ manifest: "AndroidManifestApp7.xml",
+}
diff --git a/services/tests/servicestests/test-apps/PackageParserApp/AndroidManifestApp7.xml b/services/tests/servicestests/test-apps/PackageParserApp/AndroidManifestApp7.xml
new file mode 100644
index 000000000000..cb87a48eb524
--- /dev/null
+++ b/services/tests/servicestests/test-apps/PackageParserApp/AndroidManifestApp7.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.servicestests.apps.packageparserapp" >
+
+ <application>
+ <activity android:name=".TestActivity"
+ android:exported="true" >
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.BROWSABLE" />
+ <data android:scheme="http"
+ android:host="www.example.com" />
+ <uri-relative-filter-group android:allow="false">
+ <data android:pathPrefix="/gizmos" />
+ <data android:queryPattern=".*query=string.*" />
+ <data android:fragment="fragment" />
+ </uri-relative-filter-group>
+ <uri-relative-filter-group>
+ <data android:query="query=string" />
+ <data android:fragmentSuffix="fragment" />
+ </uri-relative-filter-group>
+ <uri-relative-filter-group>
+ <data android:path="/gizmos" />
+ <data android:query=".*query=string.*" />
+ <data android:fragment="fragment" />
+ </uri-relative-filter-group>
+ <uri-relative-filter-group>
+ <data android:pathPrefix="/gizmos" />
+ <data android:queryPrefix=".*query=string.*" />
+ <data android:fragmentPrefix="fragment" />
+ </uri-relative-filter-group>
+ <uri-relative-filter-group>
+ <data android:pathPattern="/gizmos" />
+ <data android:queryPattern=".*query=string.*" />
+ <data android:fragmentPattern="fragment" />
+ </uri-relative-filter-group>
+ <uri-relative-filter-group>
+ <data android:pathAdvancedPattern="/gizmos" />
+ <data android:queryAdvancedPattern=".*query=string.*" />
+ <data android:fragmentAdvancedPattern="fragment" />
+ </uri-relative-filter-group>
+ <uri-relative-filter-group>
+ <data android:pathSuffix="/gizmos" />
+ <data android:querySuffix=".*query=string.*" />
+ <data android:fragmentSuffix="fragment" />
+ </uri-relative-filter-group>
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest> \ No newline at end of file
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenAdaptersTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenAdaptersTest.java
index 0e20daf2c0f1..99d5a6d9118a 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenAdaptersTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenAdaptersTest.java
@@ -143,12 +143,13 @@ public class ZenAdaptersTest extends UiServiceTestCase {
Policy.policyState(false, true), 0);
ZenPolicy zenPolicy = notificationPolicyToZenPolicy(policy);
- assertThat(zenPolicy.getPriorityChannels()).isEqualTo(ZenPolicy.STATE_ALLOW);
+ assertThat(zenPolicy.getPriorityChannelsAllowed()).isEqualTo(ZenPolicy.STATE_ALLOW);
Policy notAllowed = new Policy(0, 0, 0, 0,
Policy.policyState(false, false), 0);
ZenPolicy zenPolicyNotAllowed = notificationPolicyToZenPolicy(notAllowed);
- assertThat(zenPolicyNotAllowed.getPriorityChannels()).isEqualTo(ZenPolicy.STATE_DISALLOW);
+ assertThat(zenPolicyNotAllowed.getPriorityChannelsAllowed()).isEqualTo(
+ ZenPolicy.STATE_DISALLOW);
}
@Test
@@ -158,11 +159,12 @@ public class ZenAdaptersTest extends UiServiceTestCase {
Policy.policyState(false, true), 0);
ZenPolicy zenPolicy = notificationPolicyToZenPolicy(policy);
- assertThat(zenPolicy.getPriorityChannels()).isEqualTo(ZenPolicy.STATE_UNSET);
+ assertThat(zenPolicy.getPriorityChannelsAllowed()).isEqualTo(ZenPolicy.STATE_UNSET);
Policy notAllowed = new Policy(0, 0, 0, 0,
Policy.policyState(false, false), 0);
ZenPolicy zenPolicyNotAllowed = notificationPolicyToZenPolicy(notAllowed);
- assertThat(zenPolicyNotAllowed.getPriorityChannels()).isEqualTo(ZenPolicy.STATE_UNSET);
+ assertThat(zenPolicyNotAllowed.getPriorityChannelsAllowed()).isEqualTo(
+ ZenPolicy.STATE_UNSET);
}
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
index e523e79f6370..539bb37419f1 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
@@ -284,7 +284,7 @@ public class ZenModeConfigTest extends UiServiceTestCase {
actual.getPriorityConversationSenders());
assertEquals(expected.getPriorityCallSenders(), actual.getPriorityCallSenders());
assertEquals(expected.getPriorityMessageSenders(), actual.getPriorityMessageSenders());
- assertEquals(expected.getPriorityChannels(), actual.getPriorityChannels());
+ assertEquals(expected.getPriorityChannelsAllowed(), actual.getPriorityChannelsAllowed());
}
@Test
@@ -716,7 +716,7 @@ public class ZenModeConfigTest extends UiServiceTestCase {
assertEquals(policy.getPriorityCategorySystem(), fromXml.getPriorityCategorySystem());
assertEquals(policy.getPriorityCategoryReminders(), fromXml.getPriorityCategoryReminders());
assertEquals(policy.getPriorityCategoryEvents(), fromXml.getPriorityCategoryEvents());
- assertEquals(policy.getPriorityChannels(), fromXml.getPriorityChannels());
+ assertEquals(policy.getPriorityChannelsAllowed(), fromXml.getPriorityChannelsAllowed());
assertEquals(policy.getVisualEffectFullScreenIntent(),
fromXml.getVisualEffectFullScreenIntent());
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 25ad7dbac30c..227265aa0a5b 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -4075,7 +4075,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
// UPDATE_ORIGIN_USER should change the bitmask and change the values.
assertThat(rule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_PRIORITY);
- assertThat(rule.getZenPolicy().getPriorityChannels()).isEqualTo(ZenPolicy.STATE_DISALLOW);
+ assertThat(rule.getZenPolicy().getPriorityChannelsAllowed()).isEqualTo(
+ ZenPolicy.STATE_DISALLOW);
assertThat(rule.getDeviceEffects().shouldDisplayGrayscale()).isTrue();
@@ -4339,7 +4340,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
// New ZenPolicy differs from the default config
assertThat(rule.getZenPolicy()).isNotNull();
- assertThat(rule.getZenPolicy().getPriorityChannels()).isEqualTo(ZenPolicy.STATE_DISALLOW);
+ assertThat(rule.getZenPolicy().getPriorityChannelsAllowed()).isEqualTo(
+ ZenPolicy.STATE_DISALLOW);
ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
assertThat(storedRule.canBeUpdatedByApp()).isFalse();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java
index 57e11328d5e1..3a88294a0fa1 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java
@@ -218,7 +218,7 @@ public class ZenPolicyTest extends UiServiceTestCase {
// unset applied, channels setting keeps its state
channelsPriority.apply(unset);
- assertThat(channelsPriority.getPriorityChannels()).isEqualTo(ZenPolicy.STATE_ALLOW);
+ assertThat(channelsPriority.getPriorityChannelsAllowed()).isEqualTo(ZenPolicy.STATE_ALLOW);
}
@Test
@@ -234,7 +234,7 @@ public class ZenPolicyTest extends UiServiceTestCase {
// priority channels (less strict state) cannot override a setting that sets it to none
none.apply(priority);
- assertThat(none.getPriorityChannels()).isEqualTo(ZenPolicy.STATE_DISALLOW);
+ assertThat(none.getPriorityChannelsAllowed()).isEqualTo(ZenPolicy.STATE_DISALLOW);
}
@Test
@@ -250,7 +250,7 @@ public class ZenPolicyTest extends UiServiceTestCase {
// applying a policy with channelType=none overrides priority setting
priority.apply(none);
- assertThat(priority.getPriorityChannels()).isEqualTo(ZenPolicy.STATE_DISALLOW);
+ assertThat(priority.getPriorityChannelsAllowed()).isEqualTo(ZenPolicy.STATE_DISALLOW);
}
@Test
@@ -265,7 +265,7 @@ public class ZenPolicyTest extends UiServiceTestCase {
// applying a policy with a set channel type actually goes through
unset.apply(priority);
- assertThat(unset.getPriorityChannels()).isEqualTo(ZenPolicy.STATE_ALLOW);
+ assertThat(unset.getPriorityChannelsAllowed()).isEqualTo(ZenPolicy.STATE_ALLOW);
}
@Test
@@ -379,7 +379,7 @@ public class ZenPolicyTest extends UiServiceTestCase {
ZenPolicy.Builder builder = new ZenPolicy.Builder();
ZenPolicy policy = builder.build();
- assertThat(policy.getPriorityChannels()).isEqualTo(ZenPolicy.STATE_UNSET);
+ assertThat(policy.getPriorityChannelsAllowed()).isEqualTo(ZenPolicy.STATE_UNSET);
}
@Test
@@ -696,7 +696,7 @@ public class ZenPolicyTest extends UiServiceTestCase {
builder.allowPriorityChannels(true);
ZenPolicy policy = builder.build();
- assertThat(policy.getPriorityChannels()).isEqualTo(ZenPolicy.STATE_UNSET);
+ assertThat(policy.getPriorityChannelsAllowed()).isEqualTo(ZenPolicy.STATE_UNSET);
assertThat(policy.toString().contains("allowChannels")).isFalse();
}
@@ -708,12 +708,12 @@ public class ZenPolicyTest extends UiServiceTestCase {
ZenPolicy.Builder builder = new ZenPolicy.Builder();
builder.allowPriorityChannels(true);
ZenPolicy policy = builder.build();
- assertThat(policy.getPriorityChannels()).isEqualTo(ZenPolicy.STATE_ALLOW);
+ assertThat(policy.getPriorityChannelsAllowed()).isEqualTo(ZenPolicy.STATE_ALLOW);
// disallow priority channels
builder.allowPriorityChannels(false);
policy = builder.build();
- assertThat(policy.getPriorityChannels()).isEqualTo(ZenPolicy.STATE_DISALLOW);
+ assertThat(policy.getPriorityChannelsAllowed()).isEqualTo(ZenPolicy.STATE_DISALLOW);
}
@Test
@@ -734,7 +734,7 @@ public class ZenPolicyTest extends UiServiceTestCase {
assertThat(newPolicy.getVisualEffectBadge()).isEqualTo(ZenPolicy.STATE_DISALLOW);
assertThat(newPolicy.getVisualEffectPeek()).isEqualTo(ZenPolicy.STATE_UNSET);
- assertThat(newPolicy.getPriorityChannels()).isEqualTo(ZenPolicy.STATE_ALLOW);
+ assertThat(newPolicy.getPriorityChannelsAllowed()).isEqualTo(ZenPolicy.STATE_ALLOW);
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index 7c2f7eedff9d..c8abd8d7297e 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -166,6 +166,7 @@ class TestPhoneWindowManager {
@Mock
private PhoneWindowManager.ButtonOverridePermissionChecker mButtonOverridePermissionChecker;
+ @Mock private WindowWakeUpPolicy mWindowWakeUpPolicy;
@Mock private IBinder mInputToken;
@Mock private IBinder mImeTargetWindowToken;
@@ -230,6 +231,10 @@ class TestPhoneWindowManager {
TalkbackShortcutController getTalkbackShortcutController() {
return new TestTalkbackShortcutController(mContext);
}
+
+ WindowWakeUpPolicy getWindowWakeUpPolicy() {
+ return mWindowWakeUpPolicy;
+ }
}
TestPhoneWindowManager(Context context, boolean supportSettingsUpdate) {
@@ -620,7 +625,8 @@ class TestPhoneWindowManager {
void assertPowerWakeUp() {
mTestLooper.dispatchAll();
- verify(mPowerManager).wakeUp(anyLong(), anyInt(), anyString());
+ verify(mWindowWakeUpPolicy)
+ .wakeUpFromKey(anyLong(), eq(KeyEvent.KEYCODE_POWER), anyBoolean());
}
void assertNoPowerSleep() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java b/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java
index 6e5baee3dc67..37e0818eb083 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java
@@ -46,6 +46,7 @@ import android.content.Intent;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
+import android.os.IRemoteCallback;
import android.platform.test.annotations.Presubmit;
import android.util.Log;
import android.util.Rational;
@@ -63,6 +64,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
/**
* Build/Install/Run:
@@ -119,6 +121,29 @@ public class ActivityOptionsTest {
}
@Test
+ public void testAbortListenerCalled() {
+ AtomicBoolean callbackCalled = new AtomicBoolean(false);
+
+ ActivityOptions options = ActivityOptions.makeBasic();
+ options.setOnAnimationAbortListener(new IRemoteCallback.Stub() {
+ @Override
+ public void sendResult(Bundle data) {
+ callbackCalled.set(true);
+ }
+ });
+
+ // Verify that the callback is called on abort
+ options.abort();
+ assertTrue(callbackCalled.get());
+
+ // Verify that the callback survives saving to bundle
+ ActivityOptions optionsCopy = ActivityOptions.fromBundle(options.toBundle());
+ callbackCalled.set(false);
+ optionsCopy.abort();
+ assertTrue(callbackCalled.get());
+ }
+
+ @Test
public void testTransferLaunchCookie() {
final Binder cookie = new Binder();
final ActivityOptions options = ActivityOptions.makeBasic();
@@ -279,7 +304,9 @@ public class ActivityOptionsTest {
case "android.activity.pendingIntentCreatorBackgroundActivityStartMode":
// KEY_PENDING_INTENT_CREATOR_BACKGROUND_ACTIVITY_START_MODE
case "android.activity.launchCookie": // KEY_LAUNCH_COOKIE
+ case "android:activity.animAbortListener": // KEY_ANIM_ABORT_LISTENER
// Existing keys
+
break;
default:
unknownKeys.add(key);
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 752dc5e8e7f6..0c1a9c3ab5b9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -886,8 +886,6 @@ public class SizeCompatTests extends WindowTestsBase {
spyOn(mActivity.mLetterboxUiController);
doReturn(true).when(mActivity.mLetterboxUiController)
- .isSurfaceReadyToShow(any());
- doReturn(true).when(mActivity.mLetterboxUiController)
.isSurfaceVisible(any());
assertTrue(mActivity.mLetterboxUiController.shouldShowLetterboxUi(
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 51df1d4cb14d..7d8eb90ea79c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -1602,6 +1602,33 @@ public class TransitionTests extends WindowTestsBase {
}
@Test
+ public void testTransientWithParallelLaunch() {
+ final Task recentTask = mDisplayContent.getDefaultTaskDisplayArea().getRootHomeTask();
+ final ActivityRecord recent = new ActivityBuilder(mAtm).setTask(recentTask)
+ .setVisible(false).build();
+ final ActivityRecord app = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ final Task appTask = app.getTask();
+ registerTestTransitionPlayer();
+ final TransitionController controller = mRootWindowContainer.mTransitionController;
+ final Transition transition = createTestTransition(TRANSIT_OPEN, controller);
+ transition.mParallelCollectType = Transition.PARALLEL_TYPE_RECENTS;
+ controller.moveToCollecting(transition);
+ transition.collect(recentTask);
+ transition.collect(appTask);
+ transition.setTransientLaunch(recent, appTask);
+ recentTask.moveToFront("move-recent-to-front");
+ transition.setAllReady();
+ transition.start();
+ // Assume that the app starts another activity in its task.
+ final Transition newTransition = controller.createAndStartCollecting(TRANSIT_OPEN);
+
+ assertEquals(newTransition, controller.getCollectingTransition());
+ assertTrue(controller.mWaitingTransitions.contains(transition));
+ assertTrue(controller.isTransientHide(appTask));
+ assertTrue(controller.isTransientVisible(appTask));
+ }
+
+ @Test
public void testNotReadyPushPop() {
final TransitionController controller = new TestTransitionController(mAtm);
controller.setSyncEngine(mWm.mSyncEngine);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTransactionTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTransactionTests.java
index d2552718f218..e9ece5dbdcc4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTransactionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTransactionTests.java
@@ -16,6 +16,9 @@
package com.android.server.wm;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static org.junit.Assert.assertEquals;
@@ -23,6 +26,7 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.times;
import android.content.Intent;
import android.platform.test.annotations.Presubmit;
@@ -35,6 +39,9 @@ import androidx.test.filters.SmallTest;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* Test class for {@link WindowContainerTransaction}.
*
@@ -45,7 +52,6 @@ import org.junit.runner.RunWith;
@Presubmit
@RunWith(WindowTestRunner.class)
public class WindowContainerTransactionTests extends WindowTestsBase {
-
@Test
public void testRemoveTask() {
final Task rootTask = createTask(mDisplayContent);
@@ -72,6 +78,123 @@ public class WindowContainerTransactionTests extends WindowTestsBase {
verify(mAtm.getLockTaskController(), atLeast(1)).clearLockedTask(rootTask);
}
+ @Test
+ public void testDesktopMode_tasksAreBroughtToFront() {
+ final TestDesktopOrganizer desktopOrganizer = new TestDesktopOrganizer(mAtm);
+ TaskDisplayArea tda = desktopOrganizer.mDefaultTDA;
+ List<ActivityRecord> activityRecords = new ArrayList<>();
+ int numberOfTasks = 4;
+ desktopOrganizer.createFreeformTasksWithActivities(desktopOrganizer,
+ activityRecords, numberOfTasks);
+
+ final Task task = createTask(mDisplayContent);
+ final ActivityRecord activity = createActivityRecord(mDisplayContent, task);
+ task.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+
+ // Bring home to front of the tasks
+ desktopOrganizer.bringHomeToFront();
+
+ // Bring tasks in front of the home
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ desktopOrganizer.bringDesktopTasksToFront(wct);
+ applyTransaction(wct);
+
+ // Verify tasks are resumed and in correct z-order
+ verify(mRootWindowContainer, times(2)).ensureActivitiesVisible();
+ for (int i = 0; i < numberOfTasks - 1; i++) {
+ assertTrue(tda.mChildren
+ .indexOf(desktopOrganizer.mTasks.get(i).getRootTask())
+ < tda.mChildren.indexOf(desktopOrganizer.mTasks.get(i + 1).getRootTask()));
+ }
+ }
+
+ @Test
+ public void testDesktopMode_moveTaskToDesktop() {
+ final TestDesktopOrganizer desktopOrganizer = new TestDesktopOrganizer(mAtm);
+ TaskDisplayArea tda = desktopOrganizer.mDefaultTDA;
+ List<ActivityRecord> activityRecords = new ArrayList<>();
+ int numberOfTasks = 4;
+ desktopOrganizer.createFreeformTasksWithActivities(desktopOrganizer,
+ activityRecords, numberOfTasks);
+
+ final Task task = createTask(mDisplayContent);
+ final ActivityRecord activity = createActivityRecord(mDisplayContent, task);
+ task.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+
+ // Bring home to front of the tasks
+ desktopOrganizer.bringHomeToFront();
+
+ // Bring tasks in front of the home and newly moved task to on top of them
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ desktopOrganizer.bringDesktopTasksToFront(wct);
+ desktopOrganizer.addMoveToDesktopChanges(wct, task, true);
+ wct.setBounds(task.getTaskInfo().token, desktopOrganizer.getDefaultDesktopTaskBounds());
+ applyTransaction(wct);
+
+ // Verify tasks are resumed
+ verify(mRootWindowContainer, times(2)).ensureActivitiesVisible();
+
+ // Tasks are in correct z-order
+ for (int i = 0; i < numberOfTasks - 1; i++) {
+ assertTrue(tda.mChildren
+ .indexOf(desktopOrganizer.mTasks.get(i).getRootTask())
+ < tda.mChildren.indexOf(desktopOrganizer.mTasks.get(i + 1).getRootTask()));
+ }
+ // New task is on top of other tasks
+ assertTrue(tda.mChildren
+ .indexOf(desktopOrganizer.mTasks.get(3).getRootTask())
+ < tda.mChildren.indexOf(task));
+
+ // New task is in freeform and has specified bounds
+ assertEquals(WINDOWING_MODE_FREEFORM, task.getWindowingMode());
+ assertEquals(desktopOrganizer.getDefaultDesktopTaskBounds(), task.getBounds());
+ }
+
+
+ @Test
+ public void testDesktopMode_moveTaskToFullscreen() {
+ final TestDesktopOrganizer desktopOrganizer = new TestDesktopOrganizer(mAtm);
+ List<ActivityRecord> activityRecords = new ArrayList<>();
+ int numberOfTasks = 4;
+ desktopOrganizer.createFreeformTasksWithActivities(desktopOrganizer,
+ activityRecords, numberOfTasks);
+
+ Task taskToMove = desktopOrganizer.mTasks.get(numberOfTasks - 1);
+
+ // Bring tasks in front of the home and newly moved task to on top of them
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ desktopOrganizer.addMoveToFullscreen(wct, taskToMove, false);
+ applyTransaction(wct);
+
+ // New task is in freeform
+ assertEquals(WINDOWING_MODE_FULLSCREEN, taskToMove.getWindowingMode());
+ }
+
+ @Test
+ public void testDesktopMode_moveTaskToFront() {
+ final TestDesktopOrganizer desktopOrganizer = new TestDesktopOrganizer(mAtm);
+ TaskDisplayArea tda = desktopOrganizer.mDefaultTDA;
+ List<ActivityRecord> activityRecords = new ArrayList<>();
+ int numberOfTasks = 5;
+ desktopOrganizer.createFreeformTasksWithActivities(desktopOrganizer,
+ activityRecords, numberOfTasks);
+
+ // Bring task 2 on top of other tasks
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.reorder(desktopOrganizer.mTasks.get(2).getTaskInfo().token, true /* onTop */);
+ applyTransaction(wct);
+
+ // Tasks are in correct z-order
+ assertTrue(tda.mChildren.indexOf(desktopOrganizer.mTasks.get(0).getRootTask())
+ < tda.mChildren.indexOf(desktopOrganizer.mTasks.get(1).getRootTask()));
+ assertTrue(tda.mChildren.indexOf(desktopOrganizer.mTasks.get(1).getRootTask())
+ < tda.mChildren.indexOf(desktopOrganizer.mTasks.get(3).getRootTask()));
+ assertTrue(tda.mChildren.indexOf(desktopOrganizer.mTasks.get(3).getRootTask())
+ < tda.mChildren.indexOf(desktopOrganizer.mTasks.get(4).getRootTask()));
+ assertTrue(tda.mChildren.indexOf(desktopOrganizer.mTasks.get(4).getRootTask())
+ < tda.mChildren.indexOf(desktopOrganizer.mTasks.get(2).getRootTask()));
+ }
+
private Task createTask(int taskId) {
return new Task.Builder(mAtm)
.setTaskId(taskId)
@@ -87,3 +210,4 @@ public class WindowContainerTransactionTests extends WindowTestsBase {
}
}
}
+
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index fe9d83776ad9..06afa381c9c0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -1068,7 +1068,7 @@ public class WindowManagerServiceTests extends WindowTestsBase {
invocationOnMock.callRealMethod();
return null;
}).when(surface).lockCanvas(any());
- mWm.mAccessibilityController.drawMagnifiedRegionBorderIfNeeded(displayId, mTransaction);
+ mWm.mAccessibilityController.drawMagnifiedRegionBorderIfNeeded(displayId);
waitUntilHandlersIdle();
try {
verify(surface).lockCanvas(any());
@@ -1076,14 +1076,9 @@ public class WindowManagerServiceTests extends WindowTestsBase {
clearInvocations(surface);
// Invalidate and redraw.
mWm.mAccessibilityController.onDisplaySizeChanged(mDisplayContent);
- mWm.mAccessibilityController.drawMagnifiedRegionBorderIfNeeded(displayId, mTransaction);
+ mWm.mAccessibilityController.drawMagnifiedRegionBorderIfNeeded(displayId);
// Turn off magnification to release surface.
mWm.mAccessibilityController.setMagnificationCallbacks(displayId, null);
- if (!com.android.window.flags.Flags.drawMagnifierBorderOutsideWmlock()) {
- verify(surface).release();
- assertTrue(lockCanvasInWmLock[0]);
- return;
- }
waitUntilHandlersIdle();
// lockCanvas must not be called after releasing.
verify(surface, never()).lockCanvas(any());
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index a0bafb64090f..be837441ef94 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -116,6 +116,7 @@ import android.window.StartingWindowRemovalInfo;
import android.window.TaskFragmentOrganizer;
import android.window.TransitionInfo;
import android.window.TransitionRequestInfo;
+import android.window.WindowContainerTransaction;
import com.android.internal.policy.AttributeCache;
import com.android.internal.util.ArrayUtils;
@@ -1899,12 +1900,14 @@ class WindowTestsBase extends SystemServiceTestsBase {
final int mDesktopModeDefaultWidthDp = 840;
final int mDesktopModeDefaultHeightDp = 630;
final int mDesktopDensity = 284;
+ final int mOverrideDensity = 285;
final ActivityTaskManagerService mService;
final TaskDisplayArea mDefaultTDA;
List<Task> mTasks;
final DisplayContent mDisplay;
Rect mStableBounds;
+ Task mHomeTask;
TestDesktopOrganizer(ActivityTaskManagerService service, DisplayContent display) {
mService = service;
@@ -1913,8 +1916,8 @@ class WindowTestsBase extends SystemServiceTestsBase {
mService.mTaskOrganizerController.registerTaskOrganizer(this);
mTasks = new ArrayList<>();
mStableBounds = display.getBounds();
+ mHomeTask = mDefaultTDA.getRootHomeTask();
}
-
TestDesktopOrganizer(ActivityTaskManagerService service) {
this(service, service.mTaskSupervisor.mRootWindowContainer.getDefaultDisplay());
}
@@ -1929,8 +1932,10 @@ class WindowTestsBase extends SystemServiceTestsBase {
}
public Rect getDefaultDesktopTaskBounds() {
- int width = (int) (mDesktopModeDefaultWidthDp * mDesktopDensity + 0.5f);
- int height = (int) (mDesktopModeDefaultHeightDp * mDesktopDensity + 0.5f);
+ int width = (int) (mDesktopModeDefaultWidthDp
+ * (mOverrideDensity / mDesktopDensity) + 0.5f);
+ int height = (int) (mDesktopModeDefaultHeightDp
+ * (mOverrideDensity / mDesktopDensity) + 0.5f);
Rect outBounds = new Rect();
outBounds.set(0, 0, width, height);
@@ -1942,8 +1947,69 @@ class WindowTestsBase extends SystemServiceTestsBase {
return outBounds;
}
+ public void createFreeformTasksWithActivities(TestDesktopOrganizer desktopOrganizer,
+ List<ActivityRecord> activityRecords, int numberOfTasks) {
+ for (int i = 0; i < numberOfTasks; i++) {
+ Rect bounds = new Rect(desktopOrganizer.getDefaultDesktopTaskBounds());
+ bounds.offset(20 * i, 20 * i);
+ desktopOrganizer.createTask(bounds);
+ }
+
+ for (int i = 0; i < numberOfTasks; i++) {
+ activityRecords.add(new TaskBuilder(mService.mTaskSupervisor)
+ .setParentTask(desktopOrganizer.mTasks.get(i))
+ .setCreateActivity(true)
+ .build()
+ .getTopMostActivity());
+ }
+
+ for (int i = 0; i < numberOfTasks; i++) {
+ activityRecords.get(i).setVisibleRequested(true);
+ }
+
+ for (int i = 0; i < numberOfTasks; i++) {
+ assertEquals(desktopOrganizer.mTasks.get(i), activityRecords.get(i).getRootTask());
+ }
+ }
+
+ public void bringHomeToFront() {
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.reorder(mHomeTask.getTaskInfo().token, true /* onTop */);
+ applyTransaction(wct);
+ }
+
+ public void bringDesktopTasksToFront(WindowContainerTransaction wct) {
+ for (Task task: mTasks) {
+ wct.reorder(task.getTaskInfo().token, true /* onTop */);
+ }
+ }
+
+ public void addMoveToDesktopChanges(WindowContainerTransaction wct, Task task,
+ boolean overrideDensity) {
+ wct.setWindowingMode(task.getTaskInfo().token, WINDOWING_MODE_FREEFORM);
+ wct.reorder(task.getTaskInfo().token, true /* onTop */);
+ if (overrideDensity) {
+ wct.setDensityDpi(task.getTaskInfo().token, mOverrideDensity);
+ }
+ }
+
+ public void addMoveToFullscreen(WindowContainerTransaction wct, Task task,
+ boolean overrideDensity) {
+ wct.setWindowingMode(task.getTaskInfo().token, WINDOWING_MODE_FULLSCREEN);
+ wct.setBounds(task.getTaskInfo().token, new Rect());
+ if (overrideDensity) {
+ wct.setDensityDpi(task.getTaskInfo().token, mOverrideDensity);
+ }
+ }
+
+ private void applyTransaction(@androidx.annotation.NonNull WindowContainerTransaction wct) {
+ if (!wct.isEmpty()) {
+ mService.mWindowOrganizerController.applyTransaction(wct);
+ }
+ }
}
+
static TestWindowToken createTestWindowToken(int type, DisplayContent dc) {
return createTestWindowToken(type, dc, false /* persistOnEmpty */);
}
diff --git a/telecomm/java/android/telecom/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java
index 63db29713825..b7706a926a3d 100644
--- a/telecomm/java/android/telecom/PhoneAccount.java
+++ b/telecomm/java/android/telecom/PhoneAccount.java
@@ -1283,6 +1283,17 @@ public final class PhoneAccount implements Parcelable {
sb.append(mExtras);
sb.append(" GroupId: ");
sb.append(Log.pii(mGroupId));
+ sb.append(" SC Restrictions: ");
+ if (hasSimultaneousCallingRestriction()) {
+ sb.append("[ ");
+ for (PhoneAccountHandle handle : mSimultaneousCallingRestriction) {
+ sb.append(handle);
+ sb.append(" ");
+ }
+ sb.append("]");
+ } else {
+ sb.append("[NONE]");
+ }
sb.append("]");
return sb.toString();
}
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index e81f48280e46..2c6e1e48b57f 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -1360,7 +1360,7 @@ public class TelecomManager {
/**
* Returns a list of {@link PhoneAccountHandle}s which can be used to make and receive phone
* calls. The returned list includes those accounts which have been explicitly enabled by
- * the user or other users visible to the user.
+ * the user or enabled by other users but visible to the user.
*
* @see #EXTRA_PHONE_ACCOUNT_HANDLE
* @return A list of {@code PhoneAccountHandle} objects.
@@ -1486,7 +1486,7 @@ public class TelecomManager {
return service.getCallCapablePhoneAccounts(includeDisabledAccounts,
mContext.getOpPackageName(), mContext.getAttributionTag(), true).getList();
} catch (RemoteException e) {
- throw e.rethrowAsRuntimeException();
+ throw e.rethrowFromSystemServer();
}
}
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index a73c46b12c53..e5a94c302c89 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -2179,6 +2179,14 @@ public class CarrierConfigManager {
*/
public static final String KEY_MMS_NETWORK_RELEASE_TIMEOUT_MILLIS_INT =
"mms_network_release_timeout_millis_int";
+ /**
+ * Maximum size in bytes of the PDU to send or download when connected to a non-terrestrial
+ * network. MmsService will return a result code of MMS_ERROR_TOO_LARGE_FOR_TRANSPORT if
+ * the PDU exceeds this limit when connected to a non-terrestrial network.
+ * @hide
+ */
+ public static final String KEY_MMS_MAX_NTN_PAYLOAD_SIZE_BYTES_INT =
+ "mms_max_ntn_payload_size_bytes_int";
/**
* The flatten {@link android.content.ComponentName componentName} of the activity that can
@@ -10376,6 +10384,7 @@ public class CarrierConfigManager {
sDefaults.putInt(KEY_MMS_SMS_TO_MMS_TEXT_THRESHOLD_INT, -1);
sDefaults.putInt(KEY_MMS_SUBJECT_MAX_LENGTH_INT, 40);
sDefaults.putInt(KEY_MMS_NETWORK_RELEASE_TIMEOUT_MILLIS_INT, 5 * 1000);
+ sDefaults.putInt(KEY_MMS_MAX_NTN_PAYLOAD_SIZE_BYTES_INT, 3 * 1000);
sDefaults.putString(KEY_MMS_EMAIL_GATEWAY_NUMBER_STRING, "");
sDefaults.putString(KEY_MMS_HTTP_PARAMS_STRING, "");
sDefaults.putString(KEY_MMS_NAI_SUFFIX_STRING, "");
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index c958aba1d758..b7baabf7c319 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -3123,6 +3123,12 @@ public final class SmsManager {
@FlaggedApi(Flags.FLAG_MMS_DISABLED_ERROR)
public static final int MMS_ERROR_MMS_DISABLED_BY_CARRIER = 12;
+ /**
+ * The MMS pdu was too large to send or too large to download over the current connection.
+ * @hide
+ */
+ public static final int MMS_ERROR_TOO_LARGE_FOR_TRANSPORT = 13;
+
/** Intent extra name for MMS sending result data in byte array type */
public static final String EXTRA_MMS_DATA = "android.telephony.extra.MMS_DATA";
/** Intent extra name for HTTP status code for MMS HTTP failure in integer type */
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index cbd552454642..c1ceaef64d5d 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -25,6 +25,7 @@ import static com.android.internal.util.Preconditions.checkNotNull;
import android.Manifest;
import android.annotation.BytesLong;
import android.annotation.CallbackExecutor;
+import android.annotation.CurrentTimeMillisLong;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.LongDef;
@@ -18714,51 +18715,93 @@ public class TelephonyManager {
* call diagnostic data
* @hide
*/
- public static class EmergencyCallDiagnosticParams {
+ @SystemApi
+ @FlaggedApi(com.android.server.telecom.flags.Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+ public static final class EmergencyCallDiagnosticParams {
+ public static final class Builder {
+ private boolean mCollectTelecomDumpSys;
+ private boolean mCollectTelephonyDumpsys;
+
+ // If this is set to a value other than -1L, then the logcat collection is enabled.
+ // Logcat lines with this time or greater are collected how much is collected is
+ // dependent on internal implementation. Time represented as milliseconds since boot.
+ private long mLogcatStartTimeMillis = sUnsetLogcatStartTime;
+
+ /**
+ * Allows enabling of telecom dumpsys collection.
+ * @param collectTelecomDumpsys Determines whether telecom dumpsys should be collected.
+ * @return Builder instance corresponding to the configured call diagnostic params.
+ */
+ public @NonNull Builder setTelecomDumpSysCollectionEnabled(
+ boolean collectTelecomDumpsys) {
+ mCollectTelecomDumpSys = collectTelecomDumpsys;
+ return this;
+ }
+
+ /**
+ * Allows enabling of telephony dumpsys collection.
+ * @param collectTelephonyDumpsys Determines if telephony dumpsys should be collected.
+ * @return Builder instance corresponding to the configured call diagnostic params.
+ */
+ public @NonNull Builder setTelephonyDumpSysCollectionEnabled(
+ boolean collectTelephonyDumpsys) {
+ mCollectTelephonyDumpsys = collectTelephonyDumpsys;
+ return this;
+ }
+
+ /**
+ * Allows enabling of logcat (system,radio) collection.
+ * @param startTimeMillis Enables logcat collection as of the indicated timestamp.
+ * @return Builder instance corresponding to the configured call diagnostic params.
+ */
+ public @NonNull Builder setLogcatCollectionStartTimeMillis(
+ @CurrentTimeMillisLong long startTimeMillis) {
+ mLogcatStartTimeMillis = startTimeMillis;
+ return this;
+ }
- private boolean mCollectTelecomDumpSys;
- private boolean mCollectTelephonyDumpsys;
- private boolean mCollectLogcat;
+ /**
+ * Build the EmergencyCallDiagnosticParams from the provided Builder config.
+ * @return {@link EmergencyCallDiagnosticParams} instance from provided builder.
+ */
+ public @NonNull EmergencyCallDiagnosticParams build() {
+ return new EmergencyCallDiagnosticParams(mCollectTelecomDumpSys,
+ mCollectTelephonyDumpsys, mLogcatStartTimeMillis);
+ }
+ }
- //logcat lines with this time or greater are collected
- //how much is collected is dependent on internal implementation.
- //Time represented as milliseconds since January 1, 1970 UTC
+ private boolean mCollectTelecomDumpSys;
+ private boolean mCollectTelephonyDumpsys;
+ private boolean mCollectLogcat;
private long mLogcatStartTimeMillis;
+ private static long sUnsetLogcatStartTime = -1L;
- public boolean isTelecomDumpSysCollectionEnabled() {
- return mCollectTelecomDumpSys;
+ private EmergencyCallDiagnosticParams(boolean collectTelecomDumpSys,
+ boolean collectTelephonyDumpsys, long logcatStartTimeMillis) {
+ mCollectTelecomDumpSys = collectTelecomDumpSys;
+ mCollectTelephonyDumpsys = collectTelephonyDumpsys;
+ mLogcatStartTimeMillis = logcatStartTimeMillis;
+ mCollectLogcat = logcatStartTimeMillis != sUnsetLogcatStartTime;
}
- public void setTelecomDumpSysCollection(boolean collectTelecomDumpSys) {
- mCollectTelecomDumpSys = collectTelecomDumpSys;
+ public boolean isTelecomDumpSysCollectionEnabled() {
+ return mCollectTelecomDumpSys;
}
public boolean isTelephonyDumpSysCollectionEnabled() {
return mCollectTelephonyDumpsys;
}
- public void setTelephonyDumpSysCollection(boolean collectTelephonyDumpsys) {
- mCollectTelephonyDumpsys = collectTelephonyDumpsys;
- }
-
public boolean isLogcatCollectionEnabled() {
return mCollectLogcat;
}
- public long getLogcatStartTime()
+ public long getLogcatCollectionStartTimeMillis()
{
return mLogcatStartTimeMillis;
}
- public void setLogcatCollection(boolean collectLogcat, long startTimeMillis) {
- mCollectLogcat = collectLogcat;
- if(mCollectLogcat)
- {
- mLogcatStartTimeMillis = startTimeMillis;
- }
- }
-
@Override
public String toString() {
return "EmergencyCallDiagnosticParams{" +
@@ -18774,11 +18817,12 @@ public class TelephonyManager {
* Request telephony to persist state for debugging emergency call failures.
*
* @param dropboxTag Tag to use when persisting data to dropbox service.
- *
- * @see params Parameters controlling what is collected
+ * @param params Parameters controlling what is collected.
*
* @hide
*/
+ @SystemApi
+ @FlaggedApi(com.android.server.telecom.flags.Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
@RequiresPermission(android.Manifest.permission.DUMP)
public void persistEmergencyCallDiagnosticData(@NonNull String dropboxTag,
@NonNull EmergencyCallDiagnosticParams params) {
@@ -18791,7 +18835,7 @@ public class TelephonyManager {
if (telephony != null) {
telephony.persistEmergencyCallDiagnosticData(dropboxTag,
params.isLogcatCollectionEnabled(),
- params.getLogcatStartTime(),
+ params.getLogcatCollectionStartTimeMillis(),
params.isTelecomDumpSysCollectionEnabled(),
params.isTelephonyDumpSysCollectionEnabled());
}
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 70047a6feb9c..a1ac477d3519 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -34,7 +34,6 @@ import android.os.ICancellationSignal;
import android.os.OutcomeReceiver;
import android.os.RemoteException;
import android.os.ResultReceiver;
-import android.os.ServiceSpecificException;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyCallback;
import android.telephony.TelephonyFrameworkInitializer;
@@ -336,6 +335,12 @@ public final class SatelliteManager {
@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
public static final int SATELLITE_RESULT_MODEM_BUSY = 22;
+ /**
+ * Telephony process is not currently available or satellite is not supported.
+ */
+ @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ public static final int SATELLITE_RESULT_ILLEGAL_STATE = 23;
+
/** @hide */
@IntDef(prefix = {"SATELLITE_RESULT_"}, value = {
SATELLITE_RESULT_SUCCESS,
@@ -360,7 +365,8 @@ public final class SatelliteManager {
SATELLITE_RESULT_NOT_AUTHORIZED,
SATELLITE_RESULT_NOT_SUPPORTED,
SATELLITE_RESULT_REQUEST_IN_PROGRESS,
- SATELLITE_RESULT_MODEM_BUSY
+ SATELLITE_RESULT_MODEM_BUSY,
+ SATELLITE_RESULT_ILLEGAL_STATE
})
@Retention(RetentionPolicy.SOURCE)
public @interface SatelliteResult {}
@@ -510,7 +516,7 @@ public final class SatelliteManager {
}
} catch (RemoteException ex) {
Rlog.e(TAG, "requestSatelliteEnabled() RemoteException: ", ex);
- ex.rethrowFromSystemServer();
+ ex.rethrowAsRuntimeException();
}
}
@@ -526,7 +532,6 @@ public final class SatelliteManager {
* will return a {@link SatelliteException} with the {@link SatelliteResult}.
*
* @throws SecurityException if the caller doesn't have required permission.
- * @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
@@ -561,11 +566,12 @@ public final class SatelliteManager {
};
telephony.requestIsSatelliteEnabled(mSubId, receiver);
} else {
- throw new IllegalStateException("telephony service is null.");
+ executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+ new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
}
} catch (RemoteException ex) {
loge("requestIsSatelliteEnabled() RemoteException: " + ex);
- ex.rethrowFromSystemServer();
+ ex.rethrowAsRuntimeException();
}
}
@@ -581,7 +587,6 @@ public final class SatelliteManager {
* will return a {@link SatelliteException} with the {@link SatelliteResult}.
*
* @throws SecurityException if the caller doesn't have required permission.
- * @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
@@ -616,11 +621,12 @@ public final class SatelliteManager {
};
telephony.requestIsDemoModeEnabled(mSubId, receiver);
} else {
- throw new IllegalStateException("telephony service is null.");
+ executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+ new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
}
} catch (RemoteException ex) {
loge("requestIsDemoModeEnabled() RemoteException: " + ex);
- ex.rethrowFromSystemServer();
+ ex.rethrowAsRuntimeException();
}
}
@@ -639,8 +645,6 @@ public final class SatelliteManager {
* service is supported on the device and {@code false} otherwise.
* If the request is not successful, {@link OutcomeReceiver#onError(Throwable)}
* will return a {@link SatelliteException} with the {@link SatelliteResult}.
- *
- * @throws IllegalStateException if the Telephony process is not currently available.
*/
@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
public void requestIsSatelliteSupported(@NonNull @CallbackExecutor Executor executor,
@@ -674,11 +678,12 @@ public final class SatelliteManager {
};
telephony.requestIsSatelliteSupported(mSubId, receiver);
} else {
- throw new IllegalStateException("telephony service is null.");
+ executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+ new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
}
} catch (RemoteException ex) {
loge("requestIsSatelliteSupported() RemoteException: " + ex);
- ex.rethrowFromSystemServer();
+ ex.rethrowAsRuntimeException();
}
}
@@ -693,7 +698,6 @@ public final class SatelliteManager {
* will return a {@link SatelliteException} with the {@link SatelliteResult}.
*
* @throws SecurityException if the caller doesn't have required permission.
- * @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
@@ -729,11 +733,12 @@ public final class SatelliteManager {
};
telephony.requestSatelliteCapabilities(mSubId, receiver);
} else {
- throw new IllegalStateException("telephony service is null.");
+ executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+ new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
}
} catch (RemoteException ex) {
loge("requestSatelliteCapabilities() RemoteException: " + ex);
- ex.rethrowFromSystemServer();
+ ex.rethrowAsRuntimeException();
}
}
@@ -959,7 +964,6 @@ public final class SatelliteManager {
* @param callback The callback to notify of satellite transmission updates.
*
* @throws SecurityException if the caller doesn't have required permission.
- * @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
@@ -1009,11 +1013,12 @@ public final class SatelliteManager {
telephony.startSatelliteTransmissionUpdates(mSubId, errorCallback,
internalCallback);
} else {
- throw new IllegalStateException("telephony service is null.");
+ executor.execute(() -> Binder.withCleanCallingIdentity(
+ () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE)));
}
} catch (RemoteException ex) {
loge("startSatelliteTransmissionUpdates() RemoteException: " + ex);
- ex.rethrowFromSystemServer();
+ ex.rethrowAsRuntimeException();
}
}
@@ -1029,7 +1034,6 @@ public final class SatelliteManager {
* @param resultListener Listener for the {@link SatelliteResult} result of the operation.
*
* @throws SecurityException if the caller doesn't have required permission.
- * @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
@@ -1063,11 +1067,12 @@ public final class SatelliteManager {
() -> resultListener.accept(SATELLITE_RESULT_INVALID_ARGUMENTS)));
}
} else {
- throw new IllegalStateException("telephony service is null.");
+ executor.execute(() -> Binder.withCleanCallingIdentity(
+ () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE)));
}
} catch (RemoteException ex) {
loge("stopSatelliteTransmissionUpdates() RemoteException: " + ex);
- ex.rethrowFromSystemServer();
+ ex.rethrowAsRuntimeException();
}
}
@@ -1112,11 +1117,12 @@ public final class SatelliteManager {
cancelRemote = telephony.provisionSatelliteService(mSubId, token, provisionData,
errorCallback);
} else {
- throw new IllegalStateException("telephony service is null.");
+ executor.execute(() -> Binder.withCleanCallingIdentity(
+ () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE)));
}
} catch (RemoteException ex) {
loge("provisionSatelliteService() RemoteException=" + ex);
- ex.rethrowFromSystemServer();
+ ex.rethrowAsRuntimeException();
}
if (cancellationSignal != null) {
cancellationSignal.setRemote(cancelRemote);
@@ -1138,7 +1144,6 @@ public final class SatelliteManager {
* @param resultListener Listener for the {@link SatelliteResult} result of the operation.
*
* @throws SecurityException if the caller doesn't have required permission.
- * @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
@@ -1161,11 +1166,12 @@ public final class SatelliteManager {
};
telephony.deprovisionSatelliteService(mSubId, token, errorCallback);
} else {
- throw new IllegalStateException("telephony service is null.");
+ executor.execute(() -> Binder.withCleanCallingIdentity(
+ () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE)));
}
} catch (RemoteException ex) {
loge("deprovisionSatelliteService() RemoteException=" + ex);
- ex.rethrowFromSystemServer();
+ ex.rethrowAsRuntimeException();
}
}
@@ -1208,7 +1214,7 @@ public final class SatelliteManager {
}
} catch (RemoteException ex) {
loge("registerForSatelliteProvisionStateChanged() RemoteException: " + ex);
- ex.rethrowFromSystemServer();
+ ex.rethrowAsRuntimeException();
}
return SATELLITE_RESULT_REQUEST_FAILED;
}
@@ -1244,7 +1250,7 @@ public final class SatelliteManager {
}
} catch (RemoteException ex) {
loge("unregisterForSatelliteProvisionStateChanged() RemoteException: " + ex);
- ex.rethrowFromSystemServer();
+ ex.rethrowAsRuntimeException();
}
}
@@ -1260,7 +1266,6 @@ public final class SatelliteManager {
* will return a {@link SatelliteException} with the {@link SatelliteResult}.
*
* @throws SecurityException if the caller doesn't have required permission.
- * @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
@@ -1295,11 +1300,12 @@ public final class SatelliteManager {
};
telephony.requestIsSatelliteProvisioned(mSubId, receiver);
} else {
- throw new IllegalStateException("telephony service is null.");
+ executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+ new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
}
} catch (RemoteException ex) {
loge("requestIsSatelliteProvisioned() RemoteException: " + ex);
- ex.rethrowFromSystemServer();
+ ex.rethrowAsRuntimeException();
}
}
@@ -1340,7 +1346,7 @@ public final class SatelliteManager {
}
} catch (RemoteException ex) {
loge("registerForSatelliteModemStateChanged() RemoteException:" + ex);
- ex.rethrowFromSystemServer();
+ ex.rethrowAsRuntimeException();
}
return SATELLITE_RESULT_REQUEST_FAILED;
}
@@ -1376,7 +1382,7 @@ public final class SatelliteManager {
}
} catch (RemoteException ex) {
loge("unregisterForSatelliteModemStateChanged() RemoteException:" + ex);
- ex.rethrowFromSystemServer();
+ ex.rethrowAsRuntimeException();
}
}
@@ -1436,7 +1442,7 @@ public final class SatelliteManager {
}
} catch (RemoteException ex) {
loge("registerForSatelliteDatagram() RemoteException:" + ex);
- ex.rethrowFromSystemServer();
+ ex.rethrowAsRuntimeException();
}
return SATELLITE_RESULT_REQUEST_FAILED;
}
@@ -1471,7 +1477,7 @@ public final class SatelliteManager {
}
} catch (RemoteException ex) {
loge("unregisterForSatelliteDatagram() RemoteException:" + ex);
- ex.rethrowFromSystemServer();
+ ex.rethrowAsRuntimeException();
}
}
@@ -1488,7 +1494,6 @@ public final class SatelliteManager {
* @param resultListener Listener for the {@link SatelliteResult} result of the operation.
*
* @throws SecurityException if the caller doesn't have required permission.
- * @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
@@ -1509,11 +1514,12 @@ public final class SatelliteManager {
};
telephony.pollPendingSatelliteDatagrams(mSubId, internalCallback);
} else {
- throw new IllegalStateException("telephony service is null.");
+ executor.execute(() -> Binder.withCleanCallingIdentity(
+ () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE)));
}
} catch (RemoteException ex) {
loge("pollPendingSatelliteDatagrams() RemoteException:" + ex);
- ex.rethrowFromSystemServer();
+ ex.rethrowAsRuntimeException();
}
}
@@ -1541,7 +1547,6 @@ public final class SatelliteManager {
* @param resultListener Listener for the {@link SatelliteResult} result of the operation.
*
* @throws SecurityException if the caller doesn't have required permission.
- * @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
@@ -1566,11 +1571,12 @@ public final class SatelliteManager {
telephony.sendSatelliteDatagram(mSubId, datagramType, datagram,
needFullScreenPointingUI, internalCallback);
} else {
- throw new IllegalStateException("telephony service is null.");
+ executor.execute(() -> Binder.withCleanCallingIdentity(
+ () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE)));
}
} catch (RemoteException ex) {
loge("sendSatelliteDatagram() RemoteException:" + ex);
- ex.rethrowFromSystemServer();
+ ex.rethrowAsRuntimeException();
}
}
@@ -1587,7 +1593,6 @@ public final class SatelliteManager {
* will return a {@link SatelliteException} with the {@link SatelliteResult}.
*
* @throws SecurityException if the caller doesn't have required permission.
- * @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
@@ -1624,12 +1629,13 @@ public final class SatelliteManager {
telephony.requestIsSatelliteCommunicationAllowedForCurrentLocation(mSubId,
receiver);
} else {
- throw new IllegalStateException("telephony service is null.");
+ executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+ new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
}
} catch (RemoteException ex) {
loge("requestIsSatelliteCommunicationAllowedForCurrentLocation() RemoteException: "
+ ex);
- ex.rethrowFromSystemServer();
+ ex.rethrowAsRuntimeException();
}
}
@@ -1645,7 +1651,6 @@ public final class SatelliteManager {
* will return a {@link SatelliteException} with the {@link SatelliteResult}.
*
* @throws SecurityException if the caller doesn't have required permission.
- * @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
@@ -1681,11 +1686,12 @@ public final class SatelliteManager {
};
telephony.requestTimeForNextSatelliteVisibility(mSubId, receiver);
} else {
- throw new IllegalStateException("telephony service is null.");
+ executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+ new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
}
} catch (RemoteException ex) {
loge("requestTimeForNextSatelliteVisibility() RemoteException: " + ex);
- ex.rethrowFromSystemServer();
+ ex.rethrowAsRuntimeException();
}
}
@@ -1713,7 +1719,7 @@ public final class SatelliteManager {
}
} catch (RemoteException ex) {
loge("informDeviceAlignedToSatellite() RemoteException:" + ex);
- ex.rethrowFromSystemServer();
+ ex.rethrowAsRuntimeException();
}
}
@@ -1727,7 +1733,7 @@ public final class SatelliteManager {
* <ul>
* <li>Users want to enable it.</li>
* <li>There is no satellite communication restriction, which is added by
- * {@link #addSatelliteAttachRestrictionForCarrier(int, Executor, Consumer)}</li>
+ * {@link #addSatelliteAttachRestrictionForCarrier(int, int, Executor, Consumer)}</li>
* <li>The carrier config {@link
* android.telephony.CarrierConfigManager#KEY_SATELLITE_ATTACH_SUPPORTED_BOOL} is set to
* {@code true}.</li>
@@ -1739,7 +1745,6 @@ public final class SatelliteManager {
* @param resultListener Listener for the {@link SatelliteResult} result of the operation.
*
* @throws SecurityException if the caller doesn't have required permission.
- * @throws IllegalStateException if the Telephony process is not currently available.
* @throws IllegalArgumentException if the subscription is invalid.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
@@ -1799,7 +1804,6 @@ public final class SatelliteManager {
* @param resultListener Listener for the {@link SatelliteResult} result of the operation.
*
* @throws SecurityException if the caller doesn't have required permission.
- * @throws IllegalStateException if the Telephony process is not currently available.
* @throws IllegalArgumentException if the subscription is invalid.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
@@ -1824,11 +1828,12 @@ public final class SatelliteManager {
};
telephony.addSatelliteAttachRestrictionForCarrier(subId, reason, errorCallback);
} else {
- throw new IllegalStateException("telephony service is null.");
+ executor.execute(() -> Binder.withCleanCallingIdentity(
+ () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE)));
}
} catch (RemoteException ex) {
loge("addSatelliteAttachRestrictionForCarrier() RemoteException:" + ex);
- ex.rethrowFromSystemServer();
+ ex.rethrowAsRuntimeException();
}
}
@@ -1842,7 +1847,6 @@ public final class SatelliteManager {
* @param resultListener Listener for the {@link SatelliteResult} result of the operation.
*
* @throws SecurityException if the caller doesn't have required permission.
- * @throws IllegalStateException if the Telephony process is not currently available.
* @throws IllegalArgumentException if the subscription is invalid.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
@@ -1867,17 +1871,18 @@ public final class SatelliteManager {
};
telephony.removeSatelliteAttachRestrictionForCarrier(subId, reason, errorCallback);
} else {
- throw new IllegalStateException("telephony service is null.");
+ executor.execute(() -> Binder.withCleanCallingIdentity(
+ () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE)));
}
} catch (RemoteException ex) {
loge("removeSatelliteAttachRestrictionForCarrier() RemoteException:" + ex);
- ex.rethrowFromSystemServer();
+ ex.rethrowAsRuntimeException();
}
}
/**
* Get reasons for disallowing satellite attach, as requested by
- * {@link #addSatelliteAttachRestrictionForCarrier(int, Executor, Consumer)}
+ * {@link #addSatelliteAttachRestrictionForCarrier(int, int, Executor, Consumer)}
*
* @param subId The subscription ID of the carrier.
* @return Set of reasons for disallowing satellite communication.
@@ -1910,7 +1915,7 @@ public final class SatelliteManager {
}
} catch (RemoteException ex) {
loge("getSatelliteAttachRestrictionReasonsForCarrier() RemoteException: " + ex);
- ex.rethrowFromSystemServer();
+ ex.rethrowAsRuntimeException();
}
return new HashSet<>();
}
@@ -1932,11 +1937,12 @@ public final class SatelliteManager {
* The {@link NtnSignalStrength#NTN_SIGNAL_STRENGTH_NONE} will be returned if there is no
* signal strength data available.
* If the request is not successful, {@link OutcomeReceiver#onError(Throwable)} will return a
- * {@link SatelliteException} with the {@link SatelliteResult}.
+ * {@link SatelliteException} with the {@link SatelliteResult}, or return a
+ * {@link IllegalStateException} if the Telephony process is not currently available or
+ * satellite is not supported, or return a {@link RuntimeException} when remote procedure call
+ * has failed.
*
* @throws SecurityException if the caller doesn't have required permission.
- * @throws IllegalStateException if the Telephony process is not currently available or
- * satellite is not supported.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
@@ -1972,11 +1978,12 @@ public final class SatelliteManager {
};
telephony.requestNtnSignalStrength(mSubId, receiver);
} else {
- throw new IllegalStateException("Telephony service is null.");
+ executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+ new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
}
} catch (RemoteException ex) {
loge("requestNtnSignalStrength() RemoteException: " + ex);
- ex.rethrowFromSystemServer();
+ ex.rethrowAsRuntimeException();
}
}
@@ -1997,12 +2004,11 @@ public final class SatelliteManager {
*
* @throws SecurityException if the caller doesn't have required permission.
* @throws IllegalStateException if the Telephony process is not currently available.
- * @throws SatelliteException if the callback registration operation fails.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
public void registerForNtnSignalStrengthChanged(@NonNull @CallbackExecutor Executor executor,
- @NonNull NtnSignalStrengthCallback callback) throws SatelliteException {
+ @NonNull NtnSignalStrengthCallback callback) {
Objects.requireNonNull(executor);
Objects.requireNonNull(callback);
@@ -2024,12 +2030,9 @@ public final class SatelliteManager {
} else {
throw new IllegalStateException("Telephony service is null.");
}
- } catch (ServiceSpecificException ex) {
- logd("registerForNtnSignalStrengthChanged() registration fails: " + ex.errorCode);
- throw new SatelliteException(ex.errorCode);
} catch (RemoteException ex) {
loge("registerForNtnSignalStrengthChanged() RemoteException: " + ex);
- ex.rethrowFromSystemServer();
+ ex.rethrowAsRuntimeException();
}
}
@@ -2072,7 +2075,7 @@ public final class SatelliteManager {
}
} catch (RemoteException ex) {
loge("unregisterForNtnSignalStrengthChanged() RemoteException: " + ex);
- ex.rethrowFromSystemServer();
+ ex.rethrowAsRuntimeException();
}
}
@@ -2113,7 +2116,7 @@ public final class SatelliteManager {
}
} catch (RemoteException ex) {
loge("registerForSatelliteCapabilitiesChanged() RemoteException: " + ex);
- ex.rethrowFromSystemServer();
+ ex.rethrowAsRuntimeException();
}
return SATELLITE_RESULT_REQUEST_FAILED;
}
@@ -2149,7 +2152,7 @@ public final class SatelliteManager {
}
} catch (RemoteException ex) {
loge("unregisterForSatelliteCapabilitiesChanged() RemoteException: " + ex);
- ex.rethrowFromSystemServer();
+ ex.rethrowAsRuntimeException();
}
}
@@ -2177,7 +2180,7 @@ public final class SatelliteManager {
}
} catch (RemoteException ex) {
loge("getAllSatellitePlmnsForCarrier() RemoteException: " + ex);
- ex.rethrowFromSystemServer();
+ ex.rethrowAsRuntimeException();
}
return new ArrayList<>();
}
diff --git a/telephony/java/android/telephony/satellite/stub/INtnSignalStrengthConsumer.aidl b/telephony/java/android/telephony/satellite/stub/INtnSignalStrengthConsumer.aidl
index b7712bd83cf6..655da740012b 100644
--- a/telephony/java/android/telephony/satellite/stub/INtnSignalStrengthConsumer.aidl
+++ b/telephony/java/android/telephony/satellite/stub/INtnSignalStrengthConsumer.aidl
@@ -16,7 +16,7 @@
package android.telephony.satellite.stub;
-import android.telephony.satellite.NtnSignalStrength;
+import android.telephony.satellite.stub.NtnSignalStrength;
/**
* Consumer pattern for a request that receives the signal strength of non-terrestrial network from
diff --git a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
index 256a4696b763..566e51a9062a 100644
--- a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
+++ b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
@@ -24,6 +24,7 @@ import android.hardware.input.InputManager
import android.hardware.input.InputManagerGlobal
import android.os.test.TestLooper
import android.platform.test.annotations.Presubmit
+import android.platform.test.flag.junit.SetFlagsRule
import android.provider.Settings
import android.test.mock.MockContentResolver
import android.view.Display
@@ -72,6 +73,9 @@ class InputManagerServiceTests {
@get:Rule
val fakeSettingsProviderRule = FakeSettingsProvider.rule()!!
+ @get:Rule
+ val setFlagsRule = SetFlagsRule()
+
@Mock
private lateinit var native: NativeInputManagerService
@@ -170,6 +174,8 @@ class InputManagerServiceTests {
@Test
fun testSetVirtualMousePointerDisplayId() {
+ setFlagsRule.disableFlags(com.android.input.flags.Flags.FLAG_ENABLE_POINTER_CHOREOGRAPHER)
+
// Set the virtual mouse pointer displayId, and ensure that the calling thread is blocked
// until the native callback happens.
var countDownLatch = CountDownLatch(1)
@@ -221,6 +227,8 @@ class InputManagerServiceTests {
@Test
fun testSetVirtualMousePointerDisplayId_unsuccessfulUpdate() {
+ setFlagsRule.disableFlags(com.android.input.flags.Flags.FLAG_ENABLE_POINTER_CHOREOGRAPHER)
+
// Set the virtual mouse pointer displayId, and ensure that the calling thread is blocked
// until the native callback happens.
val countDownLatch = CountDownLatch(1)
@@ -246,6 +254,8 @@ class InputManagerServiceTests {
@Test
fun testSetVirtualMousePointerDisplayId_competingRequests() {
+ setFlagsRule.disableFlags(com.android.input.flags.Flags.FLAG_ENABLE_POINTER_CHOREOGRAPHER)
+
val firstRequestSyncLatch = CountDownLatch(1)
doAnswer {
firstRequestSyncLatch.countDown()
@@ -289,6 +299,8 @@ class InputManagerServiceTests {
@Test
fun onDisplayRemoved_resetAllAdditionalInputProperties() {
+ setFlagsRule.disableFlags(com.android.input.flags.Flags.FLAG_ENABLE_POINTER_CHOREOGRAPHER)
+
setVirtualMousePointerDisplayIdAndVerify(10)
localService.setPointerIconVisible(false, 10)
@@ -313,6 +325,8 @@ class InputManagerServiceTests {
@Test
fun updateAdditionalInputPropertiesForOverrideDisplay() {
+ setFlagsRule.disableFlags(com.android.input.flags.Flags.FLAG_ENABLE_POINTER_CHOREOGRAPHER)
+
setVirtualMousePointerDisplayIdAndVerify(10)
localService.setPointerIconVisible(false, 10)
@@ -341,6 +355,8 @@ class InputManagerServiceTests {
@Test
fun setAdditionalInputPropertiesBeforeOverride() {
+ setFlagsRule.disableFlags(com.android.input.flags.Flags.FLAG_ENABLE_POINTER_CHOREOGRAPHER)
+
localService.setPointerIconVisible(false, 10)
localService.setMousePointerAccelerationEnabled(false, 10)
diff --git a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/EmbeddedWindowService.java b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/EmbeddedWindowService.java
index 5aaf30a5b3a7..14230fe4c323 100644
--- a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/EmbeddedWindowService.java
+++ b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/EmbeddedWindowService.java
@@ -134,7 +134,7 @@ public class EmbeddedWindowService extends Service {
c.drawText("Remote", 250, 250, paint);
surface.unlockCanvasAndPost(c);
WindowManager wm = getSystemService(WindowManager.class);
- mInputToken = wm.registerBatchedSurfaceControlInputReceiver(displayId, hostToken,
+ wm.registerBatchedSurfaceControlInputReceiver(displayId, hostToken,
mSurfaceControl,
Choreographer.getInstance(), event -> {
Log.d(TAG, "onInputEvent-remote " + event);
@@ -147,11 +147,9 @@ public class EmbeddedWindowService extends Service {
@Override
public void tearDownEmbeddedSurfaceControl() {
if (mSurfaceControl != null) {
- new SurfaceControl.Transaction().remove(mSurfaceControl);
- }
- if (mInputToken != null) {
WindowManager wm = getSystemService(WindowManager.class);
- wm.unregisterSurfaceControlInputReceiver(mInputToken);
+ wm.unregisterSurfaceControlInputReceiver(mSurfaceControl);
+ new SurfaceControl.Transaction().remove(mSurfaceControl).apply();
}
}
}
diff --git a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceInputTestActivity.java b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceInputTestActivity.java
index e5f8f47aeecd..7330ec14011b 100644
--- a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceInputTestActivity.java
+++ b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceInputTestActivity.java
@@ -50,10 +50,11 @@ public class SurfaceInputTestActivity extends Activity {
private static final String TAG = "SurfaceInputTestActivity";
private SurfaceView mLocalSurfaceView;
private SurfaceView mRemoteSurfaceView;
- private IBinder mInputToken;
private IAttachEmbeddedWindow mIAttachEmbeddedWindow;
private SurfaceControl mParentSurfaceControl;
+ private SurfaceControl mLocalSurfaceControl;
+
private final ServiceConnection mConnection = new ServiceConnection() {
// Called when the connection with the service is established
public void onServiceConnected(ComponentName className, IBinder service) {
@@ -112,30 +113,33 @@ public class SurfaceInputTestActivity extends Activity {
@Override
protected void onDestroy() {
super.onDestroy();
- getWindowManager().unregisterSurfaceControlInputReceiver(mInputToken);
+ if (mLocalSurfaceControl != null) {
+ getWindowManager().unregisterSurfaceControlInputReceiver(mLocalSurfaceControl);
+ new SurfaceControl.Transaction().remove(mLocalSurfaceControl).apply();
+ }
}
private void addLocalChildSurfaceControl(AttachedSurfaceControl attachedSurfaceControl) {
- SurfaceControl surfaceControl = new SurfaceControl.Builder().setName("LocalSC")
+ mLocalSurfaceControl = new SurfaceControl.Builder().setName("LocalSC")
.setBufferSize(100, 100).build();
- attachedSurfaceControl.buildReparentTransaction(surfaceControl)
- .setVisibility(surfaceControl, true)
- .setCrop(surfaceControl, new Rect(0, 0, 100, 100))
- .setPosition(surfaceControl, 250, 1000)
- .setLayer(surfaceControl, 1).apply();
+ attachedSurfaceControl.buildReparentTransaction(mLocalSurfaceControl)
+ .setVisibility(mLocalSurfaceControl, true)
+ .setCrop(mLocalSurfaceControl, new Rect(0, 0, 100, 100))
+ .setPosition(mLocalSurfaceControl, 250, 1000)
+ .setLayer(mLocalSurfaceControl, 1).apply();
Paint paint = new Paint();
paint.setColor(Color.WHITE);
paint.setTextSize(20);
- Surface surface = new Surface(surfaceControl);
+ Surface surface = new Surface(mLocalSurfaceControl);
Canvas c = surface.lockCanvas(null);
c.drawColor(Color.GREEN);
c.drawText("Local SC", 0, 0, paint);
surface.unlockCanvasAndPost(c);
WindowManager wm = getSystemService(WindowManager.class);
- mInputToken = wm.registerBatchedSurfaceControlInputReceiver(getDisplayId(),
- attachedSurfaceControl.getHostToken(), surfaceControl,
+ wm.registerBatchedSurfaceControlInputReceiver(getDisplayId(),
+ attachedSurfaceControl.getHostToken(), mLocalSurfaceControl,
Choreographer.getInstance(), event -> {
Log.d(TAG, "onInputEvent-sc " + event);
return false;
@@ -143,8 +147,6 @@ public class SurfaceInputTestActivity extends Activity {
}
private final SurfaceHolder.Callback mLocalSurfaceViewCallback = new SurfaceHolder.Callback() {
- private IBinder mInputToken;
-
@Override
public void surfaceCreated(@NonNull SurfaceHolder holder) {
Paint paint = new Paint();
@@ -157,7 +159,7 @@ public class SurfaceInputTestActivity extends Activity {
holder.unlockCanvasAndPost(c);
WindowManager wm = getSystemService(WindowManager.class);
- mInputToken = wm.registerBatchedSurfaceControlInputReceiver(getDisplayId(),
+ wm.registerBatchedSurfaceControlInputReceiver(getDisplayId(),
mLocalSurfaceView.getHostToken(), mLocalSurfaceView.getSurfaceControl(),
Choreographer.getInstance(), event -> {
Log.d(TAG, "onInputEvent-local " + event);
@@ -173,9 +175,8 @@ public class SurfaceInputTestActivity extends Activity {
@Override
public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
- if (mInputToken != null) {
- getWindowManager().unregisterSurfaceControlInputReceiver(mInputToken);
- }
+ getWindowManager().unregisterSurfaceControlInputReceiver(
+ mLocalSurfaceView.getSurfaceControl());
}
};
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
index 0b16e2c7efe4..d03f97e28156 100644
--- a/tools/aapt2/link/ManifestFixer.cpp
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -415,6 +415,8 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor, android::IDiagn
intent_filter_action["action"].Action(RequiredNameIsNotEmpty);
intent_filter_action["category"].Action(RequiredNameIsNotEmpty);
intent_filter_action["data"];
+ intent_filter_action["uri-relative-filter-group"];
+ intent_filter_action["uri-relative-filter-group"]["data"];
// Common <meta-data> actions.
xml::XmlNodeAction meta_data_action;