summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.bp1
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java76
-rw-r--r--apex/jobscheduler/service/java/com/android/server/tare/Analyst.java2
-rw-r--r--apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java28
-rw-r--r--apex/jobscheduler/service/java/com/android/server/tare/Scribe.java4
-rw-r--r--core/api/current.txt57
-rw-r--r--core/api/system-current.txt9
-rw-r--r--core/api/test-current.txt5
-rw-r--r--core/java/android/animation/Animator.java33
-rw-r--r--core/java/android/animation/AnimatorSet.java375
-rw-r--r--core/java/android/animation/ValueAnimator.java118
-rw-r--r--core/java/android/app/AppOpsManager.java30
-rw-r--r--core/java/android/app/BroadcastOptions.java45
-rw-r--r--core/java/android/app/DisabledWallpaperManager.java10
-rw-r--r--core/java/android/app/IWallpaperManager.aidl9
-rw-r--r--core/java/android/app/Notification.java2
-rw-r--r--core/java/android/app/WallpaperManager.java50
-rw-r--r--core/java/android/companion/virtual/IVirtualDeviceManager.aidl14
-rw-r--r--core/java/android/companion/virtual/IVirtualDeviceSoundEffectListener.aidl34
-rw-r--r--core/java/android/companion/virtual/VirtualDeviceManager.java101
-rw-r--r--core/java/android/content/pm/ApkChecksum.java20
-rw-r--r--core/java/android/content/pm/UserInfo.java20
-rw-r--r--core/java/android/credentials/CreateCredentialException.java4
-rw-r--r--core/java/android/credentials/CreateCredentialRequest.java21
-rw-r--r--core/java/android/credentials/CredentialDescription.aidl22
-rw-r--r--core/java/android/credentials/CredentialDescription.java139
-rw-r--r--core/java/android/credentials/CredentialManager.java174
-rw-r--r--core/java/android/credentials/GetCredentialException.java4
-rw-r--r--core/java/android/credentials/GetCredentialOption.java20
-rw-r--r--core/java/android/credentials/ICredentialManager.aidl9
-rw-r--r--core/java/android/credentials/IRegisterCredentialDescriptionCallback.aidl27
-rw-r--r--core/java/android/credentials/IUnregisterCredentialDescriptionCallback.aidl27
-rw-r--r--core/java/android/credentials/RegisterCredentialDescriptionException.java80
-rw-r--r--core/java/android/credentials/RegisterCredentialDescriptionRequest.aidl19
-rw-r--r--core/java/android/credentials/RegisterCredentialDescriptionRequest.java93
-rw-r--r--core/java/android/credentials/UnregisterCredentialDescriptionException.java80
-rw-r--r--core/java/android/credentials/UnregisterCredentialDescriptionRequest.aidl19
-rw-r--r--core/java/android/credentials/UnregisterCredentialDescriptionRequest.java81
-rw-r--r--core/java/android/credentials/ui/RequestInfo.java4
-rw-r--r--core/java/android/hardware/input/HostUsiVersion.aidl19
-rw-r--r--core/java/android/hardware/input/HostUsiVersion.java204
-rw-r--r--core/java/android/hardware/input/InputManager.java63
-rw-r--r--core/java/android/inputmethodservice/IInputMethodWrapper.java16
-rw-r--r--core/java/android/inputmethodservice/InputMethodService.java12
-rw-r--r--core/java/android/nfc/OWNERS3
-rw-r--r--core/java/android/nfc/cardemulation/OWNERS3
-rw-r--r--core/java/android/nfc/dta/OWNERS3
-rw-r--r--core/java/android/nfc/tech/OWNERS3
-rw-r--r--core/java/android/os/IncidentManager.java16
-rw-r--r--core/java/android/os/UserManager.java12
-rw-r--r--core/java/android/provider/Settings.java73
-rw-r--r--core/java/android/service/quickaccesswallet/WalletCard.java128
-rw-r--r--core/java/android/service/voice/VoiceInteractionService.java18
-rw-r--r--core/java/android/service/voice/VoiceInteractionSession.java16
-rw-r--r--core/java/android/text/method/InsertModeTransformationMethod.java440
-rw-r--r--core/java/android/view/ImeInsetsSourceConsumer.java7
-rw-r--r--core/java/android/view/InputDevice.java50
-rw-r--r--core/java/android/view/InsetsController.java125
-rw-r--r--core/java/android/view/ViewRootImpl.java15
-rw-r--r--core/java/android/view/ViewRootInsetsControllerHost.java6
-rw-r--r--core/java/android/view/WindowManager.java14
-rw-r--r--core/java/android/view/autofill/AutofillManager.java52
-rw-r--r--core/java/android/view/autofill/VirtualViewFillInfo.java141
-rw-r--r--core/java/android/view/inputmethod/EditorInfo.java6
-rw-r--r--core/java/android/view/inputmethod/HandwritingGesture.java9
-rw-r--r--core/java/android/view/inputmethod/ImeTracker.java193
-rw-r--r--core/java/android/view/inputmethod/InputMethodManager.java64
-rw-r--r--core/java/android/view/inputmethod/InsertModeGesture.aidl19
-rw-r--r--core/java/android/view/inputmethod/InsertModeGesture.java187
-rw-r--r--core/java/android/view/inputmethod/ParcelableHandwritingGesture.java2
-rw-r--r--core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java11
-rw-r--r--core/java/com/android/internal/app/AbstractResolverComparator.java49
-rw-r--r--core/java/com/android/internal/app/AppPredictionServiceResolverComparator.java41
-rw-r--r--core/java/com/android/internal/app/ChooserActivity.java78
-rw-r--r--core/java/com/android/internal/app/ChooserListAdapter.java8
-rw-r--r--core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java21
-rw-r--r--core/java/com/android/internal/app/LocalePickerWithRegion.java15
-rw-r--r--core/java/com/android/internal/app/LocaleStore.java83
-rw-r--r--core/java/com/android/internal/app/NoAppsAvailableEmptyStateProvider.java13
-rw-r--r--core/java/com/android/internal/app/NoCrossProfileEmptyStateProvider.java11
-rw-r--r--core/java/com/android/internal/app/ResolverActivity.java199
-rw-r--r--core/java/com/android/internal/app/ResolverComparatorModel.java8
-rw-r--r--core/java/com/android/internal/app/ResolverListAdapter.java20
-rw-r--r--core/java/com/android/internal/app/ResolverListController.java40
-rw-r--r--core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java21
-rw-r--r--core/java/com/android/internal/app/ResolverRankerServiceResolverComparator.java264
-rw-r--r--core/java/com/android/internal/app/SystemLocaleCollector.java12
-rw-r--r--core/java/com/android/internal/content/InstallLocationUtils.java2
-rw-r--r--core/java/com/android/internal/jank/InteractionJankMonitor.java57
-rw-r--r--core/java/com/android/internal/os/RoSystemProperties.java3
-rw-r--r--core/java/com/android/internal/protolog/ProtoLogGroup.java4
-rw-r--r--core/java/com/android/internal/util/LatencyTracker.java53
-rw-r--r--core/java/com/android/internal/util/ScreenshotHelper.java297
-rw-r--r--core/java/com/android/internal/util/ScreenshotRequest.aidl19
-rw-r--r--core/java/com/android/internal/util/ScreenshotRequest.java332
-rw-r--r--core/java/com/android/internal/widget/ConversationLayout.java6
-rw-r--r--core/java/com/android/internal/widget/LockPatternUtils.java3
-rw-r--r--core/java/com/android/internal/widget/MessagingGroup.java4
-rw-r--r--core/java/com/android/internal/widget/MessagingLayout.java3
-rw-r--r--core/java/com/android/internal/widget/MessagingMessage.java4
-rw-r--r--core/jni/OWNERS1
-rw-r--r--core/jni/android_view_InputDevice.cpp7
-rw-r--r--core/proto/android/providers/settings/global.proto2
-rw-r--r--core/proto/android/providers/settings/secure.proto3
-rw-r--r--core/res/AndroidManifest.xml13
-rw-r--r--core/res/res/values/config.xml18
-rw-r--r--core/res/res/values/symbols.xml1
-rw-r--r--core/tests/PackageInstallerSessions/src/android/content/pm/PackageSessionTests.kt4
-rw-r--r--core/tests/coretests/src/android/app/NotificationTest.java7
-rw-r--r--core/tests/coretests/src/android/app/activity/IntentSenderTest.java18
-rw-r--r--core/tests/coretests/src/android/service/settings/suggestions/SuggestionTest.java3
-rw-r--r--core/tests/coretests/src/android/text/method/InsertModeTransformationMethodTest.java796
-rw-r--r--core/tests/coretests/src/android/view/inputmethod/ParcelableHandwritingGestureTest.java9
-rw-r--r--core/tests/coretests/src/android/widget/RemoteViewsTest.java24
-rw-r--r--core/tests/coretests/src/com/android/internal/app/AbstractResolverComparatorTest.java10
-rw-r--r--core/tests/coretests/src/com/android/internal/app/ChooserActivityOverrideData.java13
-rw-r--r--core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java124
-rw-r--r--core/tests/coretests/src/com/android/internal/app/ChooserActivityWorkProfileTest.java18
-rw-r--r--core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java28
-rw-r--r--core/tests/coretests/src/com/android/internal/app/FakeResolverComparatorModel.java9
-rw-r--r--core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java361
-rw-r--r--core/tests/coretests/src/com/android/internal/app/ResolverActivityWorkProfileTest.java16
-rw-r--r--core/tests/coretests/src/com/android/internal/app/ResolverDataProvider.java21
-rw-r--r--core/tests/coretests/src/com/android/internal/app/ResolverListControllerTest.java55
-rw-r--r--core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java58
-rw-r--r--core/tests/notificationtests/src/android/app/NotificationStressTest.java5
-rw-r--r--core/tests/screenshothelpertests/Android.bp3
-rw-r--r--core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java41
-rw-r--r--core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotRequestTest.java139
-rw-r--r--data/etc/services.core.protolog.json21
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java277
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java17
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java12
-rw-r--r--libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt12
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt68
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt4
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt3
-rw-r--r--libs/hwui/Properties.cpp4
-rw-r--r--libs/hwui/Properties.h27
-rw-r--r--libs/hwui/RecordingCanvas.cpp1
-rw-r--r--libs/hwui/SkiaInterpolator.cpp1
-rw-r--r--libs/hwui/jni/BitmapFactory.cpp1
-rw-r--r--libs/hwui/jni/Graphics.cpp1
-rw-r--r--libs/usb/tests/AccessoryChat/src/com/android/accessorychat/AccessoryChat.java4
-rw-r--r--location/java/android/location/GnssAntennaInfo.java4
-rw-r--r--media/java/android/media/AudioManager.java3
-rw-r--r--media/java/android/media/MediaRouter2.java17
-rw-r--r--media/java/android/media/RingtoneManager.java22
-rw-r--r--omapi/OWNERS3
-rw-r--r--omapi/java/android/se/OWNERS3
-rw-r--r--omapi/java/android/se/omapi/OWNERS3
-rw-r--r--packages/CarrierDefaultApp/AndroidManifest.xml3
-rw-r--r--packages/CredentialManager/res/values/strings.xml25
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt8
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt2
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt109
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt12
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt1
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt64
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt32
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialRequest.kt2
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java7
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAlertFragment.java3
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/AnnotatedStringResource.kt120
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Footer.kt17
-rw-r--r--packages/SettingsLib/Spa/tests/res/values/strings.xml2
-rw-r--r--packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/AnnotatedStringResourceTest.kt61
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfoPage.kt7
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt1
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt6
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java6
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java6
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java7
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java2
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java9
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java29
-rw-r--r--packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java1
-rw-r--r--packages/SystemUI/AndroidManifest.xml14
-rw-r--r--packages/SystemUI/README.md90
-rw-r--r--packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java70
-rw-r--r--packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuAdapter.java2
-rw-r--r--packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuViewPager.java2
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt10
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt2
-rw-r--r--packages/SystemUI/checks/src/com/android/internal/systemui/lint/CleanArchitectureDependencyViolationDetector.kt150
-rw-r--r--packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt16
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/CleanArchitectureDependencyViolationDetectorTest.kt296
-rw-r--r--packages/SystemUI/docs/dagger.md186
-rw-r--r--packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt118
-rw-r--r--packages/SystemUI/res/drawable/ic_camera.xml10
-rw-r--r--packages/SystemUI/res/drawable/ic_videocam.xml10
-rw-r--r--packages/SystemUI/res/layout/ongoing_privacy_chip.xml1
-rw-r--r--packages/SystemUI/res/layout/status_bar_notification_footer.xml11
-rw-r--r--packages/SystemUI/res/values-sw600dp-land/dimens.xml1
-rw-r--r--packages/SystemUI/res/values/strings.xml25
-rw-r--r--packages/SystemUI/res/xml/large_screen_shade_header.xml95
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/hardware/InputDevice.kt39
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/hardware/InputManager.kt69
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl14
-rw-r--r--packages/SystemUI/src/com/android/keyguard/logging/TrustRepositoryLogger.kt89
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/camera/CameraIntents.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/camera/CameraIntentsWrapper.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java65
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfig.kt105
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/TrustRepository.kt99
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TrustModel.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaColorSchemes.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt37
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java27
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java319
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt214
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutsModule.java40
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java25
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java73
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java50
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java43
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapper.java32
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapper.java40
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java21
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/telephony/ui/activity/SwitchToManagedProfileForCallActivity.kt81
-rw-r--r--packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java57
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsController.kt115
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt26
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt32
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfigTest.kt115
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt163
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java37
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt81
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt174
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt21
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java131
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FooterViewTest.java13
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt84
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapperTest.java43
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapperTest.kt143
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapperTest.kt103
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java36
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java33
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsControllerTest.kt243
-rw-r--r--packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java295
-rw-r--r--packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java338
-rw-r--r--services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java2
-rw-r--r--services/api/current.txt2
-rw-r--r--services/backup/java/com/android/server/backup/UserBackupManagerService.java3
-rw-r--r--services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java3
-rw-r--r--services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java14
-rw-r--r--services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java18
-rw-r--r--services/core/java/com/android/server/BinaryTransparencyService.java151
-rw-r--r--services/core/java/com/android/server/TelephonyRegistry.java60
-rw-r--r--services/core/java/com/android/server/VpnManagerService.java30
-rw-r--r--services/core/java/com/android/server/accounts/AccountManagerService.java8
-rw-r--r--services/core/java/com/android/server/am/ActiveServices.java135
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java93
-rw-r--r--services/core/java/com/android/server/am/BatteryStatsService.java2
-rw-r--r--services/core/java/com/android/server/am/BugReportHandlerUtil.java60
-rw-r--r--services/core/java/com/android/server/am/ContentProviderHelper.java35
-rw-r--r--services/core/java/com/android/server/am/PendingIntentRecord.java39
-rw-r--r--services/core/java/com/android/server/am/UserController.java6
-rw-r--r--services/core/java/com/android/server/am/UserSwitchingDialog.java4
-rw-r--r--services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java23
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java334
-rw-r--r--services/core/java/com/android/server/devicestate/DeviceState.java19
-rw-r--r--services/core/java/com/android/server/devicestate/DeviceStateManagerService.java70
-rw-r--r--services/core/java/com/android/server/display/LogicalDisplayMapper.java24
-rw-r--r--services/core/java/com/android/server/dreams/DreamController.java8
-rw-r--r--services/core/java/com/android/server/incident/PendingReports.java37
-rw-r--r--services/core/java/com/android/server/input/BatteryController.java3
-rw-r--r--services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java6
-rw-r--r--services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java10
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodManagerService.java39
-rw-r--r--services/core/java/com/android/server/pm/AppDataHelper.java43
-rw-r--r--services/core/java/com/android/server/pm/ComputerEngine.java51
-rw-r--r--services/core/java/com/android/server/pm/DexOptHelper.java271
-rw-r--r--services/core/java/com/android/server/pm/InstallPackageHelper.java71
-rw-r--r--services/core/java/com/android/server/pm/OtaDexoptService.java8
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerInternalBase.java8
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java83
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java1
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java1
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerServiceUtils.java13
-rw-r--r--services/core/java/com/android/server/pm/PackageSetting.java2
-rw-r--r--services/core/java/com/android/server/pm/RemovePackageHelper.java21
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java18
-rw-r--r--services/core/java/com/android/server/pm/dex/ArtManagerService.java122
-rw-r--r--services/core/java/com/android/server/pm/dex/DexoptOptions.java137
-rw-r--r--services/core/java/com/android/server/pm/pkg/PackageState.java5
-rw-r--r--services/core/java/com/android/server/pm/pkg/PackageStateImpl.java4
-rw-r--r--services/core/java/com/android/server/policy/DeviceStateProviderImpl.java83
-rw-r--r--services/core/java/com/android/server/policy/LegacyGlobalActions.java7
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java21
-rw-r--r--services/core/java/com/android/server/power/stats/CpuWakeupStats.java60
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperManagerService.java38
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java17
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java7
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerService.java12
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java8
-rw-r--r--services/core/java/com/android/server/wm/DisplayPolicy.java6
-rw-r--r--services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java12
-rw-r--r--services/core/java/com/android/server/wm/InsetsPolicy.java3
-rw-r--r--services/core/java/com/android/server/wm/Transition.java35
-rw-r--r--services/core/java/com/android/server/wm/TransitionController.java74
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java4
-rw-r--r--services/core/java/com/android/server/wm/WindowOrganizerController.java1
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java21
-rw-r--r--services/credentials/java/com/android/server/credentials/CreateRequestSession.java2
-rw-r--r--services/credentials/java/com/android/server/credentials/CredentialDescriptionRegistry.java125
-rw-r--r--services/credentials/java/com/android/server/credentials/CredentialManagerService.java147
-rw-r--r--services/java/com/android/server/SystemServer.java8
-rw-r--r--services/people/java/com/android/server/people/data/DataManager.java22
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java25
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java3
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java4
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java69
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java5
-rw-r--r--services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java14
-rw-r--r--services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java153
-rw-r--r--services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java1
-rw-r--r--services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt3
-rw-r--r--services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java55
-rw-r--r--services/tests/servicestests/src/com/android/server/job/WorkTypeConfigTest.java37
-rw-r--r--services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java47
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java167
-rw-r--r--services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java65
-rw-r--r--services/tests/servicestests/src/com/android/server/power/stats/CpuWakeupStatsTest.java15
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java12
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TransitionTests.java17
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java2
-rw-r--r--telecomm/java/android/telecom/CallControl.java15
-rw-r--r--telecomm/java/android/telecom/Phone.java50
-rw-r--r--telephony/java/android/telephony/CarrierConfigManager.java46
-rw-r--r--telephony/java/android/telephony/TelephonyManager.java70
-rw-r--r--telephony/java/android/telephony/satellite/PointingInfo.java144
-rw-r--r--telephony/java/android/telephony/satellite/SatelliteCapabilities.java201
-rw-r--r--telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java110
-rw-r--r--telephony/java/com/android/internal/telephony/RILConstants.java23
-rw-r--r--tests/FlickerTests/AndroidManifest.xml2
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt66
-rw-r--r--tests/Input/src/com/android/test/input/InputDeviceTest.java5
-rw-r--r--tests/Internal/src/com/android/internal/app/LocaleStoreTest.java111
-rw-r--r--tests/RollbackTest/SampleRollbackApp/src/com/android/sample/rollbackapp/MainActivity.java1
368 files changed, 14004 insertions, 3885 deletions
diff --git a/Android.bp b/Android.bp
index cfab18eeb089..ea7ced99e4fd 100644
--- a/Android.bp
+++ b/Android.bp
@@ -224,6 +224,7 @@ java_library {
"android.hardware.radio.messaging-V2-java",
"android.hardware.radio.modem-V2-java",
"android.hardware.radio.network-V2-java",
+ "android.hardware.radio.satellite-V1-java",
"android.hardware.radio.sim-V2-java",
"android.hardware.radio.voice-V2-java",
"android.hardware.thermal-V1.0-java-constants",
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index bf8984feb0a7..62d973580501 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -158,33 +158,37 @@ class JobConcurrencyManager {
* state (excluding {@link ActivityManager#PROCESS_STATE_TOP} for a currently active user.
*/
static final int WORK_TYPE_FGS = 1 << 1;
+ /** The job is allowed to run as a user-initiated job for a currently active user. */
+ static final int WORK_TYPE_UI = 1 << 2;
/** The job is allowed to run as an expedited job for a currently active user. */
- static final int WORK_TYPE_EJ = 1 << 2;
+ static final int WORK_TYPE_EJ = 1 << 3;
/**
* The job does not satisfy any of the conditions for {@link #WORK_TYPE_TOP},
* {@link #WORK_TYPE_FGS}, or {@link #WORK_TYPE_EJ}, but is for a currently active user, so
* can run as a background job.
*/
- static final int WORK_TYPE_BG = 1 << 3;
+ static final int WORK_TYPE_BG = 1 << 4;
/**
* The job is for an app in a {@link ActivityManager#PROCESS_STATE_FOREGROUND_SERVICE} or higher
- * state, or is allowed to run as an expedited job, but is for a completely background user.
+ * state, or is allowed to run as an expedited or user-initiated job,
+ * but is for a completely background user.
*/
- static final int WORK_TYPE_BGUSER_IMPORTANT = 1 << 4;
+ static final int WORK_TYPE_BGUSER_IMPORTANT = 1 << 5;
/**
* The job does not satisfy any of the conditions for {@link #WORK_TYPE_TOP},
* {@link #WORK_TYPE_FGS}, or {@link #WORK_TYPE_EJ}, but is for a completely background user,
* so can run as a background user job.
*/
- static final int WORK_TYPE_BGUSER = 1 << 5;
+ static final int WORK_TYPE_BGUSER = 1 << 6;
@VisibleForTesting
- static final int NUM_WORK_TYPES = 6;
+ static final int NUM_WORK_TYPES = 7;
private static final int ALL_WORK_TYPES = (1 << NUM_WORK_TYPES) - 1;
@IntDef(prefix = {"WORK_TYPE_"}, flag = true, value = {
WORK_TYPE_NONE,
WORK_TYPE_TOP,
WORK_TYPE_FGS,
+ WORK_TYPE_UI,
WORK_TYPE_EJ,
WORK_TYPE_BG,
WORK_TYPE_BGUSER_IMPORTANT,
@@ -203,6 +207,8 @@ class JobConcurrencyManager {
return "TOP";
case WORK_TYPE_FGS:
return "FGS";
+ case WORK_TYPE_UI:
+ return "UI";
case WORK_TYPE_EJ:
return "EJ";
case WORK_TYPE_BG:
@@ -238,8 +244,9 @@ class JobConcurrencyManager {
// defaultMin
List.of(Pair.create(WORK_TYPE_TOP, .4f),
Pair.create(WORK_TYPE_FGS, .2f),
- Pair.create(WORK_TYPE_EJ, .2f), Pair.create(WORK_TYPE_BG, .1f),
- Pair.create(WORK_TYPE_BGUSER_IMPORTANT, .1f)),
+ Pair.create(WORK_TYPE_UI, .1f),
+ Pair.create(WORK_TYPE_EJ, .1f), Pair.create(WORK_TYPE_BG, .05f),
+ Pair.create(WORK_TYPE_BGUSER_IMPORTANT, .05f)),
// defaultMax
List.of(Pair.create(WORK_TYPE_BG, .5f),
Pair.create(WORK_TYPE_BGUSER_IMPORTANT, .25f),
@@ -250,6 +257,7 @@ class JobConcurrencyManager {
// defaultMin
List.of(Pair.create(WORK_TYPE_TOP, .4f),
Pair.create(WORK_TYPE_FGS, .1f),
+ Pair.create(WORK_TYPE_UI, .1f),
Pair.create(WORK_TYPE_EJ, .1f), Pair.create(WORK_TYPE_BG, .1f),
Pair.create(WORK_TYPE_BGUSER_IMPORTANT, .1f)),
// defaultMax
@@ -260,8 +268,9 @@ class JobConcurrencyManager {
new WorkTypeConfig("screen_on_low", DEFAULT_CONCURRENCY_LIMIT,
/* defaultMaxTotal */ DEFAULT_CONCURRENCY_LIMIT * 4 / 10,
// defaultMin
- List.of(Pair.create(WORK_TYPE_TOP, 2.0f / 3),
+ List.of(Pair.create(WORK_TYPE_TOP, .6f),
Pair.create(WORK_TYPE_FGS, .1f),
+ Pair.create(WORK_TYPE_UI, .1f),
Pair.create(WORK_TYPE_EJ, .1f)),
// defaultMax
List.of(Pair.create(WORK_TYPE_BG, 1.0f / 3),
@@ -271,9 +280,10 @@ class JobConcurrencyManager {
new WorkTypeConfig("screen_on_critical", DEFAULT_CONCURRENCY_LIMIT,
/* defaultMaxTotal */ DEFAULT_CONCURRENCY_LIMIT * 4 / 10,
// defaultMin
- List.of(Pair.create(WORK_TYPE_TOP, 2.0f / 3),
+ List.of(Pair.create(WORK_TYPE_TOP, .7f),
Pair.create(WORK_TYPE_FGS, .1f),
- Pair.create(WORK_TYPE_EJ, .1f)),
+ Pair.create(WORK_TYPE_UI, .1f),
+ Pair.create(WORK_TYPE_EJ, .05f)),
// defaultMax
List.of(Pair.create(WORK_TYPE_BG, 1.0f / 6),
Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 1.0f / 6),
@@ -287,8 +297,9 @@ class JobConcurrencyManager {
// defaultMin
List.of(Pair.create(WORK_TYPE_TOP, .3f),
Pair.create(WORK_TYPE_FGS, .2f),
- Pair.create(WORK_TYPE_EJ, .3f), Pair.create(WORK_TYPE_BG, .2f),
- Pair.create(WORK_TYPE_BGUSER_IMPORTANT, .1f)),
+ Pair.create(WORK_TYPE_UI, .2f),
+ Pair.create(WORK_TYPE_EJ, .15f), Pair.create(WORK_TYPE_BG, .1f),
+ Pair.create(WORK_TYPE_BGUSER_IMPORTANT, .05f)),
// defaultMax
List.of(Pair.create(WORK_TYPE_BG, .6f),
Pair.create(WORK_TYPE_BGUSER_IMPORTANT, .2f),
@@ -299,8 +310,9 @@ class JobConcurrencyManager {
// defaultMin
List.of(Pair.create(WORK_TYPE_TOP, .3f),
Pair.create(WORK_TYPE_FGS, .2f),
- Pair.create(WORK_TYPE_EJ, .3f), Pair.create(WORK_TYPE_BG, .2f),
- Pair.create(WORK_TYPE_BGUSER_IMPORTANT, .1f)),
+ Pair.create(WORK_TYPE_UI, .2f),
+ Pair.create(WORK_TYPE_EJ, .15f), Pair.create(WORK_TYPE_BG, .1f),
+ Pair.create(WORK_TYPE_BGUSER_IMPORTANT, .05f)),
// defaultMax
List.of(Pair.create(WORK_TYPE_BG, .5f),
Pair.create(WORK_TYPE_BGUSER_IMPORTANT, .1f),
@@ -309,9 +321,11 @@ class JobConcurrencyManager {
new WorkTypeConfig("screen_off_low", DEFAULT_CONCURRENCY_LIMIT,
/* defaultMaxTotal */ DEFAULT_CONCURRENCY_LIMIT * 6 / 10,
// defaultMin
- List.of(Pair.create(WORK_TYPE_TOP, .4f),
- Pair.create(WORK_TYPE_FGS, .1f),
- Pair.create(WORK_TYPE_EJ, .2f), Pair.create(WORK_TYPE_BG, .1f)),
+ List.of(Pair.create(WORK_TYPE_TOP, .3f),
+ Pair.create(WORK_TYPE_FGS, .15f),
+ Pair.create(WORK_TYPE_UI, .15f),
+ Pair.create(WORK_TYPE_EJ, .1f), Pair.create(WORK_TYPE_BG, .05f),
+ Pair.create(WORK_TYPE_BGUSER_IMPORTANT, .05f)),
// defaultMax
List.of(Pair.create(WORK_TYPE_BG, .25f),
Pair.create(WORK_TYPE_BGUSER_IMPORTANT, .1f),
@@ -320,9 +334,10 @@ class JobConcurrencyManager {
new WorkTypeConfig("screen_off_critical", DEFAULT_CONCURRENCY_LIMIT,
/* defaultMaxTotal */ DEFAULT_CONCURRENCY_LIMIT * 4 / 10,
// defaultMin
- List.of(Pair.create(WORK_TYPE_TOP, .5f),
+ List.of(Pair.create(WORK_TYPE_TOP, .3f),
Pair.create(WORK_TYPE_FGS, .1f),
- Pair.create(WORK_TYPE_EJ, .1f)),
+ Pair.create(WORK_TYPE_UI, .1f),
+ Pair.create(WORK_TYPE_EJ, .05f)),
// defaultMax
List.of(Pair.create(WORK_TYPE_BG, .1f),
Pair.create(WORK_TYPE_BGUSER_IMPORTANT, .1f),
@@ -2097,10 +2112,12 @@ class JobConcurrencyManager {
if (js.shouldTreatAsExpeditedJob()) {
classification |= WORK_TYPE_EJ;
+ } else if (js.shouldTreatAsUserInitiatedJob()) {
+ classification |= WORK_TYPE_UI;
}
} else {
if (js.lastEvaluatedBias >= JobInfo.BIAS_FOREGROUND_SERVICE
- || js.shouldTreatAsExpeditedJob()) {
+ || js.shouldTreatAsExpeditedJob() || js.shouldTreatAsUserInitiatedJob()) {
classification |= WORK_TYPE_BGUSER_IMPORTANT;
}
// BGUSER_IMPORTANT jobs can also run as BGUSER jobs, so not an 'else' here.
@@ -2120,6 +2137,7 @@ class JobConcurrencyManager {
static final String KEY_PREFIX_MAX_RATIO = KEY_PREFIX_MAX + "ratio_";
private static final String KEY_PREFIX_MAX_RATIO_TOP = KEY_PREFIX_MAX_RATIO + "top_";
private static final String KEY_PREFIX_MAX_RATIO_FGS = KEY_PREFIX_MAX_RATIO + "fgs_";
+ private static final String KEY_PREFIX_MAX_RATIO_UI = KEY_PREFIX_MAX_RATIO + "ui_";
private static final String KEY_PREFIX_MAX_RATIO_EJ = KEY_PREFIX_MAX_RATIO + "ej_";
private static final String KEY_PREFIX_MAX_RATIO_BG = KEY_PREFIX_MAX_RATIO + "bg_";
private static final String KEY_PREFIX_MAX_RATIO_BGUSER = KEY_PREFIX_MAX_RATIO + "bguser_";
@@ -2129,6 +2147,7 @@ class JobConcurrencyManager {
static final String KEY_PREFIX_MIN_RATIO = KEY_PREFIX_MIN + "ratio_";
private static final String KEY_PREFIX_MIN_RATIO_TOP = KEY_PREFIX_MIN_RATIO + "top_";
private static final String KEY_PREFIX_MIN_RATIO_FGS = KEY_PREFIX_MIN_RATIO + "fgs_";
+ private static final String KEY_PREFIX_MIN_RATIO_UI = KEY_PREFIX_MIN_RATIO + "ui_";
private static final String KEY_PREFIX_MIN_RATIO_EJ = KEY_PREFIX_MIN_RATIO + "ej_";
private static final String KEY_PREFIX_MIN_RATIO_BG = KEY_PREFIX_MIN_RATIO + "bg_";
private static final String KEY_PREFIX_MIN_RATIO_BGUSER = KEY_PREFIX_MIN_RATIO + "bguser_";
@@ -2209,6 +2228,9 @@ class JobConcurrencyManager {
final int maxFgs = getMaxValue(properties,
KEY_PREFIX_MAX_RATIO_FGS + mConfigIdentifier, WORK_TYPE_FGS, oneIntBits);
mMaxAllowedSlots.put(WORK_TYPE_FGS, maxFgs);
+ final int maxUi = getMaxValue(properties,
+ KEY_PREFIX_MAX_RATIO_UI + mConfigIdentifier, WORK_TYPE_UI, oneIntBits);
+ mMaxAllowedSlots.put(WORK_TYPE_UI, maxUi);
final int maxEj = getMaxValue(properties,
KEY_PREFIX_MAX_RATIO_EJ + mConfigIdentifier, WORK_TYPE_EJ, oneIntBits);
mMaxAllowedSlots.put(WORK_TYPE_EJ, maxEj);
@@ -2237,6 +2259,12 @@ class JobConcurrencyManager {
0, Math.min(maxFgs, remaining));
mMinReservedSlots.put(WORK_TYPE_FGS, minFgs);
remaining -= minFgs;
+ // Ensure ui is in the range [0, min(maxUi, remaining)]
+ final int minUi = getMinValue(properties,
+ KEY_PREFIX_MIN_RATIO_UI + mConfigIdentifier, WORK_TYPE_UI,
+ 0, Math.min(maxUi, remaining));
+ mMinReservedSlots.put(WORK_TYPE_UI, minUi);
+ remaining -= minUi;
// Ensure ej is in the range [0, min(maxEj, remaining)]
final int minEj = getMinValue(properties,
KEY_PREFIX_MIN_RATIO_EJ + mConfigIdentifier, WORK_TYPE_EJ,
@@ -2313,6 +2341,12 @@ class JobConcurrencyManager {
pw.print(KEY_PREFIX_MAX_RATIO_FGS + mConfigIdentifier,
mMaxAllowedSlots.get(WORK_TYPE_FGS))
.println();
+ pw.print(KEY_PREFIX_MIN_RATIO_UI + mConfigIdentifier,
+ mMinReservedSlots.get(WORK_TYPE_UI))
+ .println();
+ pw.print(KEY_PREFIX_MAX_RATIO_UI + mConfigIdentifier,
+ mMaxAllowedSlots.get(WORK_TYPE_UI))
+ .println();
pw.print(KEY_PREFIX_MIN_RATIO_EJ + mConfigIdentifier,
mMinReservedSlots.get(WORK_TYPE_EJ))
.println();
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/Analyst.java b/apex/jobscheduler/service/java/com/android/server/tare/Analyst.java
index f27da4a1a4b2..06333f16dbf2 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Analyst.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Analyst.java
@@ -408,7 +408,7 @@ public class Analyst {
if (report.screenOffDurationMs > 0) {
pw.print(padStringWithSpaces(String.format("%d mAh (%.2f%%/hr)",
report.screenOffDischargeMah,
- 1.0 * report.screenOffDischargeMah * HOUR_IN_MILLIS
+ 100.0 * report.screenOffDischargeMah * HOUR_IN_MILLIS
/ (batteryCapacityMah * report.screenOffDurationMs)),
statColsLength));
} else {
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
index 4001d9b06ee0..08c1a0c37383 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
@@ -209,8 +209,10 @@ public class InternalResourceService extends SystemService {
@GuardedBy("mLock")
private int mCurrentBatteryLevel;
- // TODO(250007395): make configurable per device
- private final int mTargetBackgroundBatteryLifeHours;
+ // TODO(250007395): make configurable per device (via config.xml)
+ private final int mDefaultTargetBackgroundBatteryLifeHours;
+ @GuardedBy("mLock")
+ private int mTargetBackgroundBatteryLifeHours;
private final IAppOpsCallback mApbListener = new IAppOpsCallback.Stub() {
@Override
@@ -353,10 +355,11 @@ public class InternalResourceService extends SystemService {
mConfigObserver = new ConfigObserver(mHandler, context);
- mTargetBackgroundBatteryLifeHours =
+ mDefaultTargetBackgroundBatteryLifeHours =
mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)
- ? 200 // ~ 0.5%/hr
- : 100; // ~ 1%/hr
+ ? 100 // ~ 1.0%/hr
+ : 40; // ~ 2.5%/hr
+ mTargetBackgroundBatteryLifeHours = mDefaultTargetBackgroundBatteryLifeHours;
publishLocalService(EconomyManagerInternal.class, new LocalService());
}
@@ -1483,6 +1486,8 @@ public class InternalResourceService extends SystemService {
private class ConfigObserver extends ContentObserver
implements DeviceConfig.OnPropertiesChangedListener {
private static final String KEY_ENABLE_TIP3 = "enable_tip3";
+ private static final String KEY_TARGET_BACKGROUND_BATTERY_LIFE_HOURS =
+ "target_bg_battery_life_hrs";
private static final boolean DEFAULT_ENABLE_TIP3 = true;
@@ -1541,6 +1546,13 @@ public class InternalResourceService extends SystemService {
case KEY_ENABLE_TIP3:
ENABLE_TIP3 = properties.getBoolean(name, DEFAULT_ENABLE_TIP3);
break;
+ case KEY_TARGET_BACKGROUND_BATTERY_LIFE_HOURS:
+ synchronized (mLock) {
+ mTargetBackgroundBatteryLifeHours = properties.getInt(name,
+ mDefaultTargetBackgroundBatteryLifeHours);
+ maybeAdjustDesiredStockLevelLocked();
+ }
+ break;
default:
if (!economicPolicyUpdated
&& (name.startsWith("am") || name.startsWith("js")
@@ -1670,6 +1682,12 @@ public class InternalResourceService extends SystemService {
pw.print("/");
pw.println(cakeToString(mScribe.getSatiatedConsumptionLimitLocked()));
+ pw.print("Target bg battery life (hours): ");
+ pw.print(mTargetBackgroundBatteryLifeHours);
+ pw.print(" (");
+ pw.print(String.format("%.2f", 100f / mTargetBackgroundBatteryLifeHours));
+ pw.println("%/hr)");
+
final long remainingConsumable = mScribe.getRemainingConsumableCakesLocked();
pw.print("Goods remaining: ");
pw.print(cakeToString(remainingConsumable));
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java b/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java
index b41c0d1371c0..66327fd63f13 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java
@@ -717,8 +717,8 @@ public class Scribe {
out.startTag(null, XML_TAG_USER);
out.attributeInt(null, XML_ATTR_USER_ID, userId);
out.attributeLong(null, XML_ATTR_TIME_SINCE_FIRST_SETUP_MS,
- mRealtimeSinceUsersAddedOffsets.get(userId,
- mLoadedTimeSinceFirstSetup + SystemClock.elapsedRealtime()));
+ mRealtimeSinceUsersAddedOffsets.get(userId, mLoadedTimeSinceFirstSetup)
+ + SystemClock.elapsedRealtime());
for (int pIdx = mLedgers.numElementsForKey(userId) - 1; pIdx >= 0; --pIdx) {
final String pkgName = mLedgers.keyAt(uIdx, pIdx);
final Ledger ledger = mLedgers.get(userId, pkgName);
diff --git a/core/api/current.txt b/core/api/current.txt
index 9e4c95873770..d0a2e91570a2 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -13318,7 +13318,7 @@ package android.credentials {
method @NonNull public android.os.Bundle getCandidateQueryData();
method @NonNull public android.os.Bundle getCredentialData();
method @NonNull public String getType();
- method public boolean requireSystemProvider();
+ method public boolean isSystemProviderRequired();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.credentials.CreateCredentialRequest> CREATOR;
}
@@ -13343,8 +13343,8 @@ package android.credentials {
public final class CredentialManager {
method public void clearCredentialState(@NonNull android.credentials.ClearCredentialStateRequest, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.credentials.ClearCredentialStateException>);
- method public void executeCreateCredential(@NonNull android.credentials.CreateCredentialRequest, @NonNull android.app.Activity, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.credentials.CreateCredentialResponse,android.credentials.CreateCredentialException>);
- method public void executeGetCredential(@NonNull android.credentials.GetCredentialRequest, @NonNull android.app.Activity, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.GetCredentialException>);
+ method public void createCredential(@NonNull android.credentials.CreateCredentialRequest, @NonNull android.app.Activity, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.credentials.CreateCredentialResponse,android.credentials.CreateCredentialException>);
+ method public void getCredential(@NonNull android.credentials.GetCredentialRequest, @NonNull android.app.Activity, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.GetCredentialException>);
}
public class GetCredentialException extends java.lang.Exception {
@@ -13365,7 +13365,7 @@ package android.credentials {
method @NonNull public android.os.Bundle getCandidateQueryData();
method @NonNull public android.os.Bundle getCredentialRetrievalData();
method @NonNull public String getType();
- method public boolean requireSystemProvider();
+ method public boolean isSystemProviderRequired();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.credentials.GetCredentialOption> CREATOR;
}
@@ -19278,7 +19278,16 @@ package android.hardware.fingerprint {
package android.hardware.input {
+ public final class HostUsiVersion implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getMajorVersion();
+ method public int getMinorVersion();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.hardware.input.HostUsiVersion> CREATOR;
+ }
+
public final class InputManager {
+ method @Nullable public android.hardware.input.HostUsiVersion getHostUsiVersion(@NonNull android.view.Display);
method @Nullable public android.view.InputDevice getInputDevice(int);
method public int[] getInputDeviceIds();
method @FloatRange(from=0, to=1) public float getMaximumObscuringOpacityForTouch();
@@ -24051,6 +24060,7 @@ package android.media {
method @Nullable public android.os.Bundle getControlHints();
method @NonNull public java.util.List<android.media.MediaRoute2Info> getDeselectableRoutes();
method @NonNull public String getId();
+ method @NonNull public android.media.RoutingSessionInfo getRoutingSessionInfo();
method @NonNull public java.util.List<android.media.MediaRoute2Info> getSelectableRoutes();
method @NonNull public java.util.List<android.media.MediaRoute2Info> getSelectedRoutes();
method public int getVolume();
@@ -40432,17 +40442,24 @@ package android.service.quickaccesswallet {
method @NonNull public String getCardId();
method @NonNull public android.graphics.drawable.Icon getCardImage();
method @Nullable public CharSequence getCardLabel();
+ method @NonNull public int getCardType();
method @NonNull public CharSequence getContentDescription();
method @NonNull public android.app.PendingIntent getPendingIntent();
+ method @Nullable public android.graphics.drawable.Icon getValuableCardSecondaryImage();
method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field public static final int CARD_TYPE_PAYMENT = 1; // 0x1
+ field public static final int CARD_TYPE_UNKNOWN = 0; // 0x0
+ field public static final int CARD_TYPE_VALUABLE = 2; // 0x2
field @NonNull public static final android.os.Parcelable.Creator<android.service.quickaccesswallet.WalletCard> CREATOR;
}
public static final class WalletCard.Builder {
+ ctor public WalletCard.Builder(@NonNull String, @NonNull int, @NonNull android.graphics.drawable.Icon, @NonNull CharSequence, @NonNull android.app.PendingIntent);
ctor public WalletCard.Builder(@NonNull String, @NonNull android.graphics.drawable.Icon, @NonNull CharSequence, @NonNull android.app.PendingIntent);
method @NonNull public android.service.quickaccesswallet.WalletCard build();
method @NonNull public android.service.quickaccesswallet.WalletCard.Builder setCardIcon(@Nullable android.graphics.drawable.Icon);
method @NonNull public android.service.quickaccesswallet.WalletCard.Builder setCardLabel(@Nullable CharSequence);
+ method @NonNull public android.service.quickaccesswallet.WalletCard.Builder setValuableCardSecondaryImage(@Nullable android.graphics.drawable.Icon);
}
public final class WalletServiceEvent implements android.os.Parcelable {
@@ -40563,7 +40580,6 @@ package android.service.voice {
method public void setDisabledShowContext(int);
method public final void setUiHints(@NonNull android.os.Bundle);
method public void showSession(android.os.Bundle, int);
- field public static final String KEY_SHOW_SESSION_ID = "android.service.voice.SHOW_SESSION_ID";
field public static final String SERVICE_INTERFACE = "android.service.voice.VoiceInteractionService";
field public static final String SERVICE_META_DATA = "android.voice_interaction";
}
@@ -40624,6 +40640,7 @@ package android.service.voice {
method public void startAssistantActivity(android.content.Intent);
method public void startVoiceActivity(android.content.Intent);
method public final void unregisterVisibleActivityCallback(@NonNull android.service.voice.VoiceInteractionSession.VisibleActivityCallback);
+ field public static final String KEY_SHOW_SESSION_ID = "android.service.voice.SHOW_SESSION_ID";
field public static final int SHOW_SOURCE_ACTIVITY = 16; // 0x10
field public static final int SHOW_SOURCE_APPLICATION = 8; // 0x8
field public static final int SHOW_SOURCE_ASSIST_GESTURE = 4; // 0x4
@@ -41362,8 +41379,7 @@ package android.telecom {
field public static final int ROUTE_WIRED_OR_EARPIECE = 5; // 0x5
}
- public final class CallControl implements java.lang.AutoCloseable {
- method public void close();
+ public final class CallControl {
method public void disconnect(@NonNull android.telecom.DisconnectCause, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
method @NonNull public android.os.ParcelUuid getCallId();
method public void rejectCall(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
@@ -54248,6 +54264,7 @@ package android.view.autofill {
method public void notifyViewExited(@NonNull android.view.View, int);
method public void notifyViewVisibilityChanged(@NonNull android.view.View, boolean);
method public void notifyViewVisibilityChanged(@NonNull android.view.View, int, boolean);
+ method public void notifyVirtualViewsReady(@NonNull android.view.View, @NonNull android.util.SparseArray<android.view.autofill.VirtualViewFillInfo>);
method public void registerCallback(@Nullable android.view.autofill.AutofillManager.AutofillCallback);
method public void requestAutofill(@NonNull android.view.View);
method public void requestAutofill(@NonNull android.view.View, int, @NonNull android.graphics.Rect);
@@ -54294,6 +54311,16 @@ package android.view.autofill {
field @NonNull public static final android.os.Parcelable.Creator<android.view.autofill.AutofillValue> CREATOR;
}
+ public final class VirtualViewFillInfo {
+ method @Nullable public String[] getAutofillHints();
+ }
+
+ public static final class VirtualViewFillInfo.Builder {
+ ctor public VirtualViewFillInfo.Builder();
+ method @NonNull public android.view.autofill.VirtualViewFillInfo build();
+ method @NonNull public android.view.autofill.VirtualViewFillInfo.Builder setAutofillHints(@NonNull java.lang.String...);
+ }
+
}
package android.view.contentcapture {
@@ -55020,6 +55047,22 @@ package android.view.inputmethod {
method @NonNull public android.view.inputmethod.InsertGesture.Builder setTextToInsert(@NonNull String);
}
+ public final class InsertModeGesture extends android.view.inputmethod.HandwritingGesture implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public android.os.CancellationSignal getCancellationSignal();
+ method @NonNull public android.graphics.PointF getInsertionPoint();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.view.inputmethod.InsertModeGesture> CREATOR;
+ }
+
+ public static final class InsertModeGesture.Builder {
+ ctor public InsertModeGesture.Builder();
+ method @NonNull public android.view.inputmethod.InsertModeGesture build();
+ method @NonNull public android.view.inputmethod.InsertModeGesture.Builder setCancellationSignal(@NonNull android.os.CancellationSignal);
+ method @NonNull public android.view.inputmethod.InsertModeGesture.Builder setFallbackText(@Nullable String);
+ method @NonNull public android.view.inputmethod.InsertModeGesture.Builder setInsertionPoint(@NonNull android.graphics.PointF);
+ }
+
public final class JoinOrSplitGesture extends android.view.inputmethod.HandwritingGesture implements android.os.Parcelable {
method public int describeContents();
method @NonNull public android.graphics.PointF getJoinOrSplitPoint();
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index b70049b82dce..5183a828b8c7 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -91,6 +91,7 @@ package android {
field public static final String CAMERA_DISABLE_TRANSMIT_LED = "android.permission.CAMERA_DISABLE_TRANSMIT_LED";
field public static final String CAMERA_OPEN_CLOSE_LISTENER = "android.permission.CAMERA_OPEN_CLOSE_LISTENER";
field public static final String CAPTURE_AUDIO_HOTWORD = "android.permission.CAPTURE_AUDIO_HOTWORD";
+ field public static final String CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD = "android.permission.CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD";
field public static final String CAPTURE_MEDIA_OUTPUT = "android.permission.CAPTURE_MEDIA_OUTPUT";
field public static final String CAPTURE_TUNER_AUDIO_INPUT = "android.permission.CAPTURE_TUNER_AUDIO_INPUT";
field public static final String CAPTURE_TV_INPUT = "android.permission.CAPTURE_TV_INPUT";
@@ -582,6 +583,7 @@ package android.app {
field public static final String OPSTR_AUTO_REVOKE_MANAGED_BY_INSTALLER = "android:auto_revoke_managed_by_installer";
field public static final String OPSTR_AUTO_REVOKE_PERMISSIONS_IF_UNUSED = "android:auto_revoke_permissions_if_unused";
field public static final String OPSTR_BIND_ACCESSIBILITY_SERVICE = "android:bind_accessibility_service";
+ field public static final String OPSTR_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD = "android:capture_consentless_bugreport_on_userdebug_build";
field public static final String OPSTR_CHANGE_WIFI_STATE = "android:change_wifi_state";
field public static final String OPSTR_ESTABLISH_VPN_MANAGER = "android:establish_vpn_manager";
field public static final String OPSTR_ESTABLISH_VPN_SERVICE = "android:establish_vpn_service";
@@ -3008,8 +3010,13 @@ package android.companion.virtual {
method public void onIntentIntercepted(@NonNull android.content.Intent);
}
+ public static interface VirtualDeviceManager.SoundEffectListener {
+ method public void onPlaySoundEffect(int);
+ }
+
public static class VirtualDeviceManager.VirtualDevice implements java.lang.AutoCloseable {
method public void addActivityListener(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.VirtualDeviceManager.ActivityListener);
+ method public void addSoundEffectListener(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.VirtualDeviceManager.SoundEffectListener);
method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void close();
method @NonNull public android.content.Context createContext();
method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.companion.virtual.audio.VirtualAudioDevice createVirtualAudioDevice(@NonNull android.hardware.display.VirtualDisplay, @Nullable java.util.concurrent.Executor, @Nullable android.companion.virtual.audio.VirtualAudioDevice.AudioConfigurationChangeCallback);
@@ -3028,6 +3035,7 @@ package android.companion.virtual {
method public void launchPendingIntent(int, @NonNull android.app.PendingIntent, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer);
method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void registerIntentInterceptor(@NonNull android.content.IntentFilter, @NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.VirtualDeviceManager.IntentInterceptorCallback);
method public void removeActivityListener(@NonNull android.companion.virtual.VirtualDeviceManager.ActivityListener);
+ method public void removeSoundEffectListener(@NonNull android.companion.virtual.VirtualDeviceManager.SoundEffectListener);
method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void setShowPointerIcon(boolean);
method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void unregisterIntentInterceptor(@NonNull android.companion.virtual.VirtualDeviceManager.IntentInterceptorCallback);
}
@@ -10045,6 +10053,7 @@ package android.os {
method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public void reportIncident(android.os.IncidentReportArgs);
method @RequiresPermission("android.permission.REQUEST_INCIDENT_REPORT_APPROVAL") public void requestAuthorization(int, String, int, android.os.IncidentManager.AuthListener);
method public void unregisterSection(int);
+ field public static final int FLAG_ALLOW_CONSENTLESS_BUGREPORT = 2; // 0x2
field public static final int FLAG_CONFIRMATION_DIALOG = 1; // 0x1
field public static final int PRIVACY_POLICY_AUTO = 200; // 0xc8
field public static final int PRIVACY_POLICY_EXPLICIT = 100; // 0x64
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 8c64e4046013..94075e58b01d 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -908,8 +908,6 @@ package android.content.pm {
method public boolean isProfile();
method public boolean isQuietModeEnabled();
method public boolean isRestricted();
- method public boolean isSystemOnly();
- method public static boolean isSystemOnly(int);
method public boolean supportsSwitchTo();
method public boolean supportsSwitchToByUser();
method public void writeToParcel(android.os.Parcel, int);
@@ -2032,7 +2030,6 @@ package android.os {
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public String getUserType();
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public java.util.List<android.content.pm.UserInfo> getUsers(boolean, boolean, boolean);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean hasBaseUserRestriction(@NonNull String, @NonNull android.os.UserHandle);
- method public static boolean isSplitSystemUser();
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean isUserTypeEnabled(@NonNull String);
method public boolean isVisibleBackgroundUsersSupported();
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo preCreateUser(@NonNull String) throws android.os.UserManager.UserOperationException;
@@ -2796,6 +2793,7 @@ package android.telephony {
field public static final int HAL_SERVICE_MESSAGING = 2; // 0x2
field public static final int HAL_SERVICE_MODEM = 3; // 0x3
field public static final int HAL_SERVICE_NETWORK = 4; // 0x4
+ field public static final int HAL_SERVICE_SATELLITE = 8; // 0x8
field public static final int HAL_SERVICE_SIM = 5; // 0x5
field public static final int HAL_SERVICE_VOICE = 6; // 0x6
field public static final android.util.Pair HAL_VERSION_UNKNOWN;
@@ -3338,6 +3336,7 @@ package android.view.inputmethod {
field public static final int GESTURE_TYPE_DELETE = 4; // 0x4
field public static final int GESTURE_TYPE_DELETE_RANGE = 64; // 0x40
field public static final int GESTURE_TYPE_INSERT = 2; // 0x2
+ field public static final int GESTURE_TYPE_INSERT_MODE = 128; // 0x80
field public static final int GESTURE_TYPE_JOIN_OR_SPLIT = 16; // 0x10
field public static final int GESTURE_TYPE_NONE = 0; // 0x0
field public static final int GESTURE_TYPE_REMOVE_SPACE = 8; // 0x8
diff --git a/core/java/android/animation/Animator.java b/core/java/android/animation/Animator.java
index a9d14df8bcf4..a81ef18c8022 100644
--- a/core/java/android/animation/Animator.java
+++ b/core/java/android/animation/Animator.java
@@ -23,6 +23,7 @@ import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.ActivityInfo.Config;
import android.content.res.ConstantState;
import android.os.Build;
+import android.util.LongArray;
import java.util.ArrayList;
@@ -546,7 +547,6 @@ public abstract class Animator implements Cloneable {
*/
void skipToEndValue(boolean inReverse) {}
-
/**
* Internal use only.
*
@@ -559,9 +559,36 @@ public abstract class Animator implements Cloneable {
}
/**
- * Internal use only.
+ * Internal use only. Changes the value of the animator as if currentPlayTime has passed since
+ * the start of the animation. Therefore, currentPlayTime includes the start delay, and any
+ * repetition. lastPlayTime is similar and is used to calculate how many repeats have been
+ * done between the two times.
+ */
+ void animateValuesInRange(long currentPlayTime, long lastPlayTime, boolean notify) {}
+
+ /**
+ * Internal use only. This animates any animation that has ended since lastPlayTime.
+ * If an animation hasn't been finished, no change will be made.
+ */
+ void animateSkipToEnds(long currentPlayTime, long lastPlayTime, boolean notify) {}
+
+ /**
+ * Internal use only. Adds all start times (after delay) to and end times to times.
+ * The value must include offset.
*/
- void animateBasedOnPlayTime(long currentPlayTime, long lastPlayTime, boolean inReverse) {}
+ void getStartAndEndTimes(LongArray times, long offset) {
+ long startTime = offset + getStartDelay();
+ if (times.indexOf(startTime) < 0) {
+ times.add(startTime);
+ }
+ long duration = getTotalDuration();
+ if (duration != DURATION_INFINITE) {
+ long endTime = duration + offset;
+ if (times.indexOf(endTime) < 0) {
+ times.add(endTime);
+ }
+ }
+ }
/**
* <p>An animation listener receives notifications from an animation.
diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java
index bc8db029e06d..257adfe390d6 100644
--- a/core/java/android/animation/AnimatorSet.java
+++ b/core/java/android/animation/AnimatorSet.java
@@ -23,9 +23,11 @@ import android.os.Looper;
import android.util.AndroidRuntimeException;
import android.util.ArrayMap;
import android.util.Log;
+import android.util.LongArray;
import android.view.animation.Animation;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
@@ -181,6 +183,16 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
*/
private long mPauseTime = -1;
+ /**
+ * The start and stop times of all descendant animators.
+ */
+ private long[] mChildStartAndStopTimes;
+
+ /**
+ * Tracks whether we've notified listeners of the onAnimationStart() event.
+ */
+ private boolean mStartListenersCalled;
+
// This is to work around a bug in b/34736819. This needs to be removed once app team
// fixes their side.
private AnimatorListenerAdapter mAnimationEndListener = new AnimatorListenerAdapter() {
@@ -729,19 +741,38 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
startAnimation();
}
- if (mListeners != null) {
+ notifyStartListeners(inReverse);
+ if (isEmptySet) {
+ // In the case of empty AnimatorSet, or 0 duration scale, we will trigger the
+ // onAnimationEnd() right away.
+ end();
+ }
+ }
+
+ private void notifyStartListeners(boolean inReverse) {
+ if (mListeners != null && !mStartListenersCalled) {
ArrayList<AnimatorListener> tmpListeners =
(ArrayList<AnimatorListener>) mListeners.clone();
int numListeners = tmpListeners.size();
for (int i = 0; i < numListeners; ++i) {
- tmpListeners.get(i).onAnimationStart(this, inReverse);
+ AnimatorListener listener = tmpListeners.get(i);
+ listener.onAnimationStart(this, inReverse);
}
}
- if (isEmptySet) {
- // In the case of empty AnimatorSet, or 0 duration scale, we will trigger the
- // onAnimationEnd() right away.
- end();
+ mStartListenersCalled = true;
+ }
+
+ private void notifyEndListeners(boolean inReverse) {
+ if (mListeners != null && mStartListenersCalled) {
+ ArrayList<AnimatorListener> tmpListeners =
+ (ArrayList<AnimatorListener>) mListeners.clone();
+ int numListeners = tmpListeners.size();
+ for (int i = 0; i < numListeners; ++i) {
+ AnimatorListener listener = tmpListeners.get(i);
+ listener.onAnimationEnd(this, inReverse);
+ }
}
+ mStartListenersCalled = false;
}
// Returns true if set is empty or contains nothing but animator sets with no start delay.
@@ -779,26 +810,25 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
@Override
void skipToEndValue(boolean inReverse) {
- if (!isInitialized()) {
- throw new UnsupportedOperationException("Children must be initialized.");
- }
-
// This makes sure the animation events are sorted an up to date.
initAnimation();
+ initChildren();
// Calling skip to the end in the sequence that they would be called in a forward/reverse
// run, such that the sequential animations modifying the same property would have
// the right value in the end.
if (inReverse) {
for (int i = mEvents.size() - 1; i >= 0; i--) {
- if (mEvents.get(i).mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) {
- mEvents.get(i).mNode.mAnimation.skipToEndValue(true);
+ AnimationEvent event = mEvents.get(i);
+ if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) {
+ event.mNode.mAnimation.skipToEndValue(true);
}
}
} else {
for (int i = 0; i < mEvents.size(); i++) {
- if (mEvents.get(i).mEvent == AnimationEvent.ANIMATION_END) {
- mEvents.get(i).mNode.mAnimation.skipToEndValue(false);
+ AnimationEvent event = mEvents.get(i);
+ if (event.mEvent == AnimationEvent.ANIMATION_END) {
+ event.mNode.mAnimation.skipToEndValue(false);
}
}
}
@@ -814,72 +844,216 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
* {@link android.view.animation.Animation.AnimationListener#onAnimationRepeat(Animation)},
* as needed, based on the last play time and current play time.
*/
- @Override
- void animateBasedOnPlayTime(long currentPlayTime, long lastPlayTime, boolean inReverse) {
- if (currentPlayTime < 0 || lastPlayTime < 0) {
+ private void animateBasedOnPlayTime(
+ long currentPlayTime,
+ long lastPlayTime,
+ boolean inReverse,
+ boolean notify
+ ) {
+ if (currentPlayTime < 0 || lastPlayTime < -1) {
throw new UnsupportedOperationException("Error: Play time should never be negative.");
}
// TODO: take into account repeat counts and repeat callback when repeat is implemented.
- // Clamp currentPlayTime and lastPlayTime
- // TODO: Make this more efficient
-
- // Convert the play times to the forward direction.
if (inReverse) {
- if (getTotalDuration() == DURATION_INFINITE) {
- throw new UnsupportedOperationException("Cannot reverse AnimatorSet with infinite"
- + " duration");
+ long duration = getTotalDuration();
+ if (duration == DURATION_INFINITE) {
+ throw new UnsupportedOperationException(
+ "Cannot reverse AnimatorSet with infinite duration"
+ );
}
- long duration = getTotalDuration() - mStartDelay;
+ // Convert the play times to the forward direction.
currentPlayTime = Math.min(currentPlayTime, duration);
currentPlayTime = duration - currentPlayTime;
lastPlayTime = duration - lastPlayTime;
- inReverse = false;
}
- ArrayList<Node> unfinishedNodes = new ArrayList<>();
- // Assumes forward playing from here on.
- for (int i = 0; i < mEvents.size(); i++) {
- AnimationEvent event = mEvents.get(i);
- if (event.getTime() > currentPlayTime || event.getTime() == DURATION_INFINITE) {
- break;
+ long[] startEndTimes = ensureChildStartAndEndTimes();
+ int index = findNextIndex(lastPlayTime, startEndTimes);
+ int endIndex = findNextIndex(currentPlayTime, startEndTimes);
+
+ // Change values at the start/end times so that values are set in the right order.
+ // We don't want an animator that would finish before another to override the value
+ // set by another animator that finishes earlier.
+ if (currentPlayTime >= lastPlayTime) {
+ while (index < endIndex) {
+ long playTime = startEndTimes[index];
+ if (lastPlayTime != playTime) {
+ animateSkipToEnds(playTime, lastPlayTime, notify);
+ animateValuesInRange(playTime, lastPlayTime, notify);
+ lastPlayTime = playTime;
+ }
+ index++;
+ }
+ } else {
+ while (index > endIndex) {
+ index--;
+ long playTime = startEndTimes[index];
+ if (lastPlayTime != playTime) {
+ animateSkipToEnds(playTime, lastPlayTime, notify);
+ animateValuesInRange(playTime, lastPlayTime, notify);
+ lastPlayTime = playTime;
+ }
}
+ }
+ if (currentPlayTime != lastPlayTime) {
+ animateSkipToEnds(currentPlayTime, lastPlayTime, notify);
+ animateValuesInRange(currentPlayTime, lastPlayTime, notify);
+ }
+ }
+
+ /**
+ * Looks through startEndTimes for playTime. If it is in startEndTimes, the index after
+ * is returned. Otherwise, it returns the index at which it would be placed if it were
+ * to be inserted.
+ */
+ private int findNextIndex(long playTime, long[] startEndTimes) {
+ int index = Arrays.binarySearch(startEndTimes, playTime);
+ if (index < 0) {
+ index = -index - 1;
+ } else {
+ index++;
+ }
+ return index;
+ }
- // This animation started prior to the current play time, and won't finish before the
- // play time, add to the unfinished list.
- if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) {
- if (event.mNode.mEndTime == DURATION_INFINITE
- || event.mNode.mEndTime > currentPlayTime) {
- unfinishedNodes.add(event.mNode);
+ @Override
+ void animateSkipToEnds(long currentPlayTime, long lastPlayTime, boolean notify) {
+ initAnimation();
+
+ if (lastPlayTime > currentPlayTime) {
+ if (notify) {
+ notifyStartListeners(true);
+ }
+ for (int i = mEvents.size() - 1; i >= 0; i--) {
+ AnimationEvent event = mEvents.get(i);
+ Node node = event.mNode;
+ if (event.mEvent == AnimationEvent.ANIMATION_END
+ && node.mStartTime != DURATION_INFINITE
+ ) {
+ Animator animator = node.mAnimation;
+ long start = node.mStartTime;
+ long end = node.mTotalDuration == DURATION_INFINITE
+ ? Long.MAX_VALUE : node.mEndTime;
+ if (currentPlayTime <= start && start < lastPlayTime) {
+ animator.animateSkipToEnds(
+ 0,
+ lastPlayTime - node.mStartTime,
+ notify
+ );
+ } else if (start <= currentPlayTime && currentPlayTime <= end) {
+ animator.animateSkipToEnds(
+ currentPlayTime - node.mStartTime,
+ lastPlayTime - node.mStartTime,
+ notify
+ );
+ }
}
}
- // For animations that do finish before the play time, end them in the sequence that
- // they would in a normal run.
- if (event.mEvent == AnimationEvent.ANIMATION_END) {
- // Skip to the end of the animation.
- event.mNode.mAnimation.skipToEndValue(false);
+ if (currentPlayTime <= 0 && notify) {
+ notifyEndListeners(true);
+ }
+ } else {
+ if (notify) {
+ notifyStartListeners(false);
+ }
+ int eventsSize = mEvents.size();
+ for (int i = 0; i < eventsSize; i++) {
+ AnimationEvent event = mEvents.get(i);
+ Node node = event.mNode;
+ if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED
+ && node.mStartTime != DURATION_INFINITE
+ ) {
+ Animator animator = node.mAnimation;
+ long start = node.mStartTime;
+ long end = node.mTotalDuration == DURATION_INFINITE
+ ? Long.MAX_VALUE : node.mEndTime;
+ if (lastPlayTime < end && end <= currentPlayTime) {
+ animator.animateSkipToEnds(
+ end - node.mStartTime,
+ lastPlayTime - node.mStartTime,
+ notify
+ );
+ } else if (start <= currentPlayTime && currentPlayTime <= end) {
+ animator.animateSkipToEnds(
+ currentPlayTime - node.mStartTime,
+ lastPlayTime - node.mStartTime,
+ notify
+ );
+ }
+ }
+ }
+ if (currentPlayTime >= getTotalDuration() && notify) {
+ notifyEndListeners(false);
}
}
+ }
+
+ @Override
+ void animateValuesInRange(long currentPlayTime, long lastPlayTime, boolean notify) {
+ initAnimation();
- // Seek unfinished animation to the right time.
- for (int i = 0; i < unfinishedNodes.size(); i++) {
- Node node = unfinishedNodes.get(i);
- long playTime = getPlayTimeForNode(currentPlayTime, node, inReverse);
- if (!inReverse) {
- playTime -= node.mAnimation.getStartDelay();
+ if (notify) {
+ if (lastPlayTime < 0 || (lastPlayTime == 0 && currentPlayTime > 0)) {
+ notifyStartListeners(false);
+ } else {
+ long duration = getTotalDuration();
+ if (duration >= 0
+ && (lastPlayTime > duration || (lastPlayTime == duration
+ && currentPlayTime < duration))
+ ) {
+ notifyStartListeners(true);
+ }
}
- node.mAnimation.animateBasedOnPlayTime(playTime, lastPlayTime, inReverse);
}
- // Seek not yet started animations.
- for (int i = 0; i < mEvents.size(); i++) {
+ int eventsSize = mEvents.size();
+ for (int i = 0; i < eventsSize; i++) {
AnimationEvent event = mEvents.get(i);
- if (event.getTime() > currentPlayTime
- && event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) {
- event.mNode.mAnimation.skipToEndValue(true);
+ Node node = event.mNode;
+ if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED
+ && node.mStartTime != DURATION_INFINITE
+ ) {
+ Animator animator = node.mAnimation;
+ long start = node.mStartTime;
+ long end = node.mTotalDuration == DURATION_INFINITE
+ ? Long.MAX_VALUE : node.mEndTime;
+ if ((start < currentPlayTime && currentPlayTime < end)
+ || (start == currentPlayTime && lastPlayTime < start)
+ || (end == currentPlayTime && lastPlayTime > end)
+ ) {
+ animator.animateValuesInRange(
+ currentPlayTime - node.mStartTime,
+ Math.max(-1, lastPlayTime - node.mStartTime),
+ notify
+ );
+ }
}
}
+ }
+ private long[] ensureChildStartAndEndTimes() {
+ if (mChildStartAndStopTimes == null) {
+ LongArray startAndEndTimes = new LongArray();
+ getStartAndEndTimes(startAndEndTimes, 0);
+ long[] times = startAndEndTimes.toArray();
+ Arrays.sort(times);
+ mChildStartAndStopTimes = times;
+ }
+ return mChildStartAndStopTimes;
+ }
+
+ @Override
+ void getStartAndEndTimes(LongArray times, long offset) {
+ int eventsSize = mEvents.size();
+ for (int i = 0; i < eventsSize; i++) {
+ AnimationEvent event = mEvents.get(i);
+ if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED
+ && event.mNode.mStartTime != DURATION_INFINITE
+ ) {
+ event.mNode.mAnimation.getStartAndEndTimes(times, offset + event.mNode.mStartTime);
+ }
+ }
}
@Override
@@ -899,10 +1073,6 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
return mChildrenInitialized;
}
- private void skipToStartValue(boolean inReverse) {
- skipToEndValue(!inReverse);
- }
-
/**
* Sets the position of the animation to the specified point in time. This time should
* be between 0 and the total duration of the animation, including any repetition. If
@@ -910,6 +1080,11 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
* set to this time; it will simply set the time to this value and perform any appropriate
* actions based on that time. If the animation is already running, then setCurrentPlayTime()
* will set the current playing time to this value and continue playing from that point.
+ * On {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and above, an AnimatorSet
+ * that hasn't been {@link #start()}ed, will issue
+ * {@link android.animation.Animator.AnimatorListener#onAnimationStart(Animator, boolean)}
+ * and {@link android.animation.Animator.AnimatorListener#onAnimationEnd(Animator, boolean)}
+ * events.
*
* @param playTime The time, in milliseconds, to which the animation is advanced or rewound.
* Unless the animation is reversing, the playtime is considered the time since
@@ -926,29 +1101,27 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
if ((getTotalDuration() != DURATION_INFINITE && playTime > getTotalDuration() - mStartDelay)
|| playTime < 0) {
throw new UnsupportedOperationException("Error: Play time should always be in between"
- + "0 and duration.");
+ + " 0 and duration.");
}
initAnimation();
+ long lastPlayTime = mSeekState.getPlayTime();
if (!isStarted() || isPaused()) {
- if (mReversing) {
+ if (mReversing && !isStarted()) {
throw new UnsupportedOperationException("Error: Something went wrong. mReversing"
+ " should not be set when AnimatorSet is not started.");
}
if (!mSeekState.isActive()) {
findLatestEventIdForTime(0);
- // Set all the values to start values.
initChildren();
+ // Set all the values to start values.
+ skipToEndValue(!mReversing);
mSeekState.setPlayTime(0, mReversing);
}
- animateBasedOnPlayTime(playTime, 0, mReversing);
- mSeekState.setPlayTime(playTime, mReversing);
- } else {
- // If the animation is running, just set the seek time and wait until the next frame
- // (i.e. doAnimationFrame(...)) to advance the animation.
- mSeekState.setPlayTime(playTime, mReversing);
}
+ animateBasedOnPlayTime(playTime, lastPlayTime, mReversing, true);
+ mSeekState.setPlayTime(playTime, mReversing);
}
/**
@@ -981,10 +1154,16 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
private void initChildren() {
if (!isInitialized()) {
mChildrenInitialized = true;
- // Forcefully initialize all children based on their end time, so that if the start
- // value of a child is dependent on a previous animation, the animation will be
- // initialized after the the previous animations have been advanced to the end.
- skipToEndValue(false);
+
+ // We have to initialize all the start values so that they are based on the previous
+ // values.
+ long[] times = ensureChildStartAndEndTimes();
+
+ long previousTime = -1;
+ for (long time : times) {
+ animateBasedOnPlayTime(time, previousTime, false, false);
+ previousTime = time;
+ }
}
}
@@ -1058,7 +1237,7 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
for (int i = 0; i < mPlayingSet.size(); i++) {
Node node = mPlayingSet.get(i);
if (!node.mEnded) {
- pulseFrame(node, getPlayTimeForNode(unscaledPlayTime, node));
+ pulseFrame(node, getPlayTimeForNodeIncludingDelay(unscaledPlayTime, node));
}
}
@@ -1129,7 +1308,7 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
pulseFrame(node, 0);
} else if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED && !node.mEnded) {
// end event:
- pulseFrame(node, getPlayTimeForNode(playTime, node));
+ pulseFrame(node, getPlayTimeForNodeIncludingDelay(playTime, node));
}
}
} else {
@@ -1150,7 +1329,7 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
pulseFrame(node, 0);
} else if (event.mEvent == AnimationEvent.ANIMATION_END && !node.mEnded) {
// start event:
- pulseFrame(node, getPlayTimeForNode(playTime, node));
+ pulseFrame(node, getPlayTimeForNodeIncludingDelay(playTime, node));
}
}
}
@@ -1172,11 +1351,15 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
}
}
- private long getPlayTimeForNode(long overallPlayTime, Node node) {
- return getPlayTimeForNode(overallPlayTime, node, mReversing);
+ private long getPlayTimeForNodeIncludingDelay(long overallPlayTime, Node node) {
+ return getPlayTimeForNodeIncludingDelay(overallPlayTime, node, mReversing);
}
- private long getPlayTimeForNode(long overallPlayTime, Node node, boolean inReverse) {
+ private long getPlayTimeForNodeIncludingDelay(
+ long overallPlayTime,
+ Node node,
+ boolean inReverse
+ ) {
if (inReverse) {
overallPlayTime = getTotalDuration() - overallPlayTime;
return node.mEndTime - overallPlayTime;
@@ -1198,26 +1381,8 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
}
// Set the child animators to the right end:
if (mShouldResetValuesAtStart) {
- if (isInitialized()) {
- skipToEndValue(!mReversing);
- } else if (mReversing) {
- // Reversing but haven't initialized all the children yet.
- initChildren();
- skipToEndValue(!mReversing);
- } else {
- // If not all children are initialized and play direction is forward
- for (int i = mEvents.size() - 1; i >= 0; i--) {
- if (mEvents.get(i).mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) {
- Animator anim = mEvents.get(i).mNode.mAnimation;
- // Only reset the animations that have been initialized to start value,
- // so that if they are defined without a start value, they will get the
- // values set at the right time (i.e. the next animation run)
- if (anim.isInitialized()) {
- anim.skipToEndValue(true);
- }
- }
- }
- }
+ initChildren();
+ skipToEndValue(!mReversing);
}
if (mReversing || mStartDelay == 0 || mSeekState.isActive()) {
@@ -1292,15 +1457,7 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
// No longer receive callbacks
removeAnimationCallback();
- // Call end listener
- if (mListeners != null) {
- ArrayList<AnimatorListener> tmpListeners =
- (ArrayList<AnimatorListener>) mListeners.clone();
- int numListeners = tmpListeners.size();
- for (int i = 0; i < numListeners; ++i) {
- tmpListeners.get(i).onAnimationEnd(this, mReversing);
- }
- }
+ notifyEndListeners(mReversing);
removeAnimationEndListener();
mSelfPulse = true;
mReversing = false;
@@ -1922,11 +2079,11 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
}
void setPlayTime(long playTime, boolean inReverse) {
- // TODO: This can be simplified.
-
// Clamp the play time
if (getTotalDuration() != DURATION_INFINITE) {
mPlayTime = Math.min(playTime, getTotalDuration() - mStartDelay);
+ } else {
+ mPlayTime = playTime;
}
mPlayTime = Math.max(0, mPlayTime);
mSeekingInReverse = inReverse;
diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java
index 6ab7ae6d0cee..7009725aa32b 100644
--- a/core/java/android/animation/ValueAnimator.java
+++ b/core/java/android/animation/ValueAnimator.java
@@ -324,8 +324,9 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio
listenerCopy = new ArrayList<>(sDurationScaleChangeListeners);
}
- for (WeakReference<DurationScaleChangeListener> listenerRef : listenerCopy) {
- final DurationScaleChangeListener listener = listenerRef.get();
+ int listenersSize = listenerCopy.size();
+ for (int i = 0; i < listenersSize; i++) {
+ final DurationScaleChangeListener listener = listenerCopy.get(i).get();
if (listener != null) {
listener.onChanged(durationScale);
}
@@ -624,7 +625,7 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio
public void setValues(PropertyValuesHolder... values) {
int numValues = values.length;
mValues = values;
- mValuesMap = new HashMap<String, PropertyValuesHolder>(numValues);
+ mValuesMap = new HashMap<>(numValues);
for (int i = 0; i < numValues; ++i) {
PropertyValuesHolder valuesHolder = values[i];
mValuesMap.put(valuesHolder.getPropertyName(), valuesHolder);
@@ -658,9 +659,11 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio
@CallSuper
void initAnimation() {
if (!mInitialized) {
- int numValues = mValues.length;
- for (int i = 0; i < numValues; ++i) {
- mValues[i].init();
+ if (mValues != null) {
+ int numValues = mValues.length;
+ for (int i = 0; i < numValues; ++i) {
+ mValues[i].init();
+ }
}
mInitialized = true;
}
@@ -1105,18 +1108,30 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio
}
}
- private void notifyStartListeners() {
+ private void notifyStartListeners(boolean isReversing) {
if (mListeners != null && !mStartListenersCalled) {
ArrayList<AnimatorListener> tmpListeners =
(ArrayList<AnimatorListener>) mListeners.clone();
int numListeners = tmpListeners.size();
for (int i = 0; i < numListeners; ++i) {
- tmpListeners.get(i).onAnimationStart(this, mReversing);
+ tmpListeners.get(i).onAnimationStart(this, isReversing);
}
}
mStartListenersCalled = true;
}
+ private void notifyEndListeners(boolean isReversing) {
+ if (mListeners != null && mStartListenersCalled) {
+ ArrayList<AnimatorListener> tmpListeners =
+ (ArrayList<AnimatorListener>) mListeners.clone();
+ int numListeners = tmpListeners.size();
+ for (int i = 0; i < numListeners; ++i) {
+ tmpListeners.get(i).onAnimationEnd(this, isReversing);
+ }
+ }
+ mStartListenersCalled = false;
+ }
+
/**
* Start the animation playing. This version of start() takes a boolean flag that indicates
* whether the animation should play in reverse. The flag is usually false, but may be set
@@ -1207,12 +1222,16 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio
if ((mStarted || mRunning) && mListeners != null) {
if (!mRunning) {
// If it's not yet running, then start listeners weren't called. Call them now.
- notifyStartListeners();
+ notifyStartListeners(mReversing);
}
- ArrayList<AnimatorListener> tmpListeners =
- (ArrayList<AnimatorListener>) mListeners.clone();
- for (AnimatorListener listener : tmpListeners) {
- listener.onAnimationCancel(this);
+ int listenersSize = mListeners.size();
+ if (listenersSize > 0) {
+ ArrayList<AnimatorListener> tmpListeners =
+ (ArrayList<AnimatorListener>) mListeners.clone();
+ for (int i = 0; i < listenersSize; i++) {
+ AnimatorListener listener = tmpListeners.get(i);
+ listener.onAnimationCancel(this);
+ }
}
}
endAnimation();
@@ -1317,22 +1336,14 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio
boolean notify = (mStarted || mRunning) && mListeners != null;
if (notify && !mRunning) {
// If it's not yet running, then start listeners weren't called. Call them now.
- notifyStartListeners();
+ notifyStartListeners(mReversing);
}
mRunning = false;
mStarted = false;
- mStartListenersCalled = false;
mLastFrameTime = -1;
mFirstFrameTime = -1;
mStartTime = -1;
- if (notify && mListeners != null) {
- ArrayList<AnimatorListener> tmpListeners =
- (ArrayList<AnimatorListener>) mListeners.clone();
- int numListeners = tmpListeners.size();
- for (int i = 0; i < numListeners; ++i) {
- tmpListeners.get(i).onAnimationEnd(this, mReversing);
- }
- }
+ notifyEndListeners(mReversing);
// mReversing needs to be reset *after* notifying the listeners for the end callbacks.
mReversing = false;
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
@@ -1359,9 +1370,8 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio
} else {
mOverallFraction = 0f;
}
- if (mListeners != null) {
- notifyStartListeners();
- }
+
+ notifyStartListeners(mReversing);
}
/**
@@ -1452,16 +1462,32 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio
* will be called.
*/
@Override
- void animateBasedOnPlayTime(long currentPlayTime, long lastPlayTime, boolean inReverse) {
- if (currentPlayTime < 0 || lastPlayTime < 0) {
+ void animateValuesInRange(long currentPlayTime, long lastPlayTime, boolean notify) {
+ if (currentPlayTime < 0 || lastPlayTime < -1) {
throw new UnsupportedOperationException("Error: Play time should never be negative.");
}
initAnimation();
+ long duration = getTotalDuration();
+ if (notify) {
+ if (lastPlayTime < 0 || (lastPlayTime == 0 && currentPlayTime > 0)) {
+ notifyStartListeners(false);
+ } else if (lastPlayTime > duration
+ || (lastPlayTime == duration && currentPlayTime < duration)
+ ) {
+ notifyStartListeners(true);
+ }
+ }
+ if (duration >= 0) {
+ lastPlayTime = Math.min(duration, lastPlayTime);
+ }
+ lastPlayTime -= mStartDelay;
+ currentPlayTime -= mStartDelay;
+
// Check whether repeat callback is needed only when repeat count is non-zero
if (mRepeatCount > 0) {
- int iteration = (int) (currentPlayTime / mDuration);
- int lastIteration = (int) (lastPlayTime / mDuration);
+ int iteration = Math.max(0, (int) (currentPlayTime / mDuration));
+ int lastIteration = Math.max(0, (int) (lastPlayTime / mDuration));
// Clamp iteration to [0, mRepeatCount]
iteration = Math.min(iteration, mRepeatCount);
@@ -1477,16 +1503,37 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio
}
}
- if (mRepeatCount != INFINITE && currentPlayTime >= (mRepeatCount + 1) * mDuration) {
- skipToEndValue(inReverse);
+ if (mRepeatCount != INFINITE && currentPlayTime > (mRepeatCount + 1) * mDuration) {
+ throw new IllegalStateException("Can't animate a value outside of the duration");
} else {
// Find the current fraction:
- float fraction = currentPlayTime / (float) mDuration;
- fraction = getCurrentIterationFraction(fraction, inReverse);
+ float fraction = Math.max(0, currentPlayTime) / (float) mDuration;
+ fraction = getCurrentIterationFraction(fraction, false);
animateValue(fraction);
}
}
+ @Override
+ void animateSkipToEnds(long currentPlayTime, long lastPlayTime, boolean notify) {
+ boolean inReverse = currentPlayTime < lastPlayTime;
+ boolean doSkip;
+ if (currentPlayTime <= 0 && lastPlayTime > 0) {
+ doSkip = true;
+ } else {
+ long duration = getTotalDuration();
+ doSkip = duration >= 0 && currentPlayTime >= duration && lastPlayTime < duration;
+ }
+ if (doSkip) {
+ if (notify) {
+ notifyStartListeners(inReverse);
+ }
+ skipToEndValue(inReverse);
+ if (notify) {
+ notifyEndListeners(inReverse);
+ }
+ }
+ }
+
/**
* Internal use only.
* Skips the animation value to end/start, depending on whether the play direction is forward
@@ -1641,6 +1688,9 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio
Trace.traceCounter(Trace.TRACE_TAG_VIEW, getNameForTrace() + hashCode(),
(int) (fraction * 1000));
}
+ if (mValues == null) {
+ return;
+ }
fraction = mInterpolator.getInterpolation(fraction);
mCurrentFraction = fraction;
int numValues = mValues.length;
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index a14f3d35fa48..84320caf4abc 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1439,9 +1439,18 @@ public class AppOpsManager {
public static final int OP_SYSTEM_EXEMPT_FROM_FGS_STOP_BUTTON =
AppProtoEnums.APP_OP_SYSTEM_EXEMPT_FROM_FGS_STOP_BUTTON;
+ /**
+ * Allows an application to capture bugreport directly without consent dialog when using the
+ * bugreporting API on userdebug/eng build.
+ *
+ * @hide
+ */
+ public static final int OP_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD =
+ AppProtoEnums.APP_OP_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD;
+
/** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public static final int _NUM_OP = 131;
+ public static final int _NUM_OP = 132;
/** Access to coarse location information. */
public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -2011,6 +2020,16 @@ public class AppOpsManager {
public static final String OPSTR_SYSTEM_EXEMPT_FROM_FGS_STOP_BUTTON =
"android:system_exempt_from_fgs_stop_button";
+ /**
+ * Allows an application to capture bugreport directly without consent dialog when using the
+ * bugreporting API on userdebug/eng build.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String OPSTR_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD =
+ "android:capture_consentless_bugreport_on_userdebug_build";
+
/** {@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} */
@@ -2108,6 +2127,7 @@ public class AppOpsManager {
OP_RUN_LONG_JOBS,
OP_READ_MEDIA_VISUAL_USER_SELECTED,
OP_FOREGROUND_SERVICE_SPECIAL_USE,
+ OP_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD,
};
static final AppOpInfo[] sAppOpInfos = new AppOpInfo[]{
@@ -2515,7 +2535,13 @@ public class AppOpsManager {
.build(),
new AppOpInfo.Builder(OP_SYSTEM_EXEMPT_FROM_FGS_STOP_BUTTON,
OPSTR_SYSTEM_EXEMPT_FROM_FGS_STOP_BUTTON,
- "SYSTEM_EXEMPT_FROM_FGS_STOP_BUTTON").build()
+ "SYSTEM_EXEMPT_FROM_FGS_STOP_BUTTON").build(),
+ new AppOpInfo.Builder(
+ OP_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD,
+ OPSTR_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD,
+ "CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD")
+ .setPermission(Manifest.permission.CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD)
+ .build()
};
// The number of longs needed to form a full bitmask of app ops
diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java
index 9ecf8ffef84a..88765c3b35e2 100644
--- a/core/java/android/app/BroadcastOptions.java
+++ b/core/java/android/app/BroadcastOptions.java
@@ -909,7 +909,6 @@ public class BroadcastOptions extends ComponentOptions {
/**
* Clear the {@link BundleMerger} object that was previously set using
* {@link #setDeliveryGroupExtrasMerger(BundleMerger)}.
- *
* @hide
*/
public void clearDeliveryGroupExtrasMerger() {
@@ -917,6 +916,36 @@ public class BroadcastOptions extends ComponentOptions {
}
/**
+ * Set PendingIntent activity is allowed to be started in the background if the caller
+ * can start background activities.
+ *
+ * @deprecated use #setPendingIntentBackgroundActivityStartMode(int) to set the full range
+ * of states
+ * @hide
+ */
+ @SystemApi
+ @Override
+ @Deprecated public void setPendingIntentBackgroundActivityLaunchAllowed(boolean allowed) {
+ super.setPendingIntentBackgroundActivityLaunchAllowed(allowed);
+ }
+
+ /**
+ * Get PendingIntent activity is allowed to be started in the background if the caller can start
+ * background activities.
+ *
+ * @deprecated use {@link #getPendingIntentBackgroundActivityStartMode()} since for apps
+ * targeting {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or higher this value might
+ * not match the actual behavior if the value was not explicitly set.
+ * @hide
+ */
+ @SystemApi
+ @Override
+ @Deprecated public boolean isPendingIntentBackgroundActivityLaunchAllowed() {
+ return super.isPendingIntentBackgroundActivityLaunchAllowed();
+ }
+
+
+ /**
* Sets the mode for allowing or denying the senders privileges to start background activities
* to the PendingIntent.
*
@@ -924,7 +953,6 @@ public class BroadcastOptions extends ComponentOptions {
* methods. A privileged sender of a PendingIntent should only grant
* MODE_BACKGROUND_ACTIVITY_START_ALLOWED if the PendingIntent is from a trusted source and/or
* executed on behalf the user.
- *
* @hide
*/
@SystemApi
@@ -936,6 +964,19 @@ public class BroadcastOptions extends ComponentOptions {
}
/**
+ * Gets the mode for allowing or denying the senders privileges to start background activities
+ * to the PendingIntent.
+ *
+ * @see #setPendingIntentBackgroundActivityStartMode(int)
+ * @hide
+ */
+ @SystemApi
+ @Override // to narrow down the return type
+ public @BackgroundActivityStartMode int getPendingIntentBackgroundActivityStartMode() {
+ return super.getPendingIntentBackgroundActivityStartMode();
+ }
+
+ /**
* Returns the created options as a Bundle, which can be passed to
* {@link android.content.Context#sendBroadcast(android.content.Intent)
* Context.sendBroadcast(Intent)} and related methods.
diff --git a/core/java/android/app/DisabledWallpaperManager.java b/core/java/android/app/DisabledWallpaperManager.java
index fae68872bc02..4a5836cef76d 100644
--- a/core/java/android/app/DisabledWallpaperManager.java
+++ b/core/java/android/app/DisabledWallpaperManager.java
@@ -178,6 +178,11 @@ final class DisabledWallpaperManager extends WallpaperManager {
}
@Override
+ public ParcelFileDescriptor getWallpaperFile(int which, boolean getCropped) {
+ return unsupported();
+ }
+
+ @Override
public void forgetLoadedWallpaper() {
unsupported();
}
@@ -188,6 +193,11 @@ final class DisabledWallpaperManager extends WallpaperManager {
}
@Override
+ public ParcelFileDescriptor getWallpaperInfoFile() {
+ return unsupported();
+ }
+
+ @Override
public WallpaperInfo getWallpaperInfoForUser(int userId) {
return unsupported();
}
diff --git a/core/java/android/app/IWallpaperManager.aidl b/core/java/android/app/IWallpaperManager.aidl
index b1ed152e3344..f4373a67a967 100644
--- a/core/java/android/app/IWallpaperManager.aidl
+++ b/core/java/android/app/IWallpaperManager.aidl
@@ -74,7 +74,8 @@ interface IWallpaperManager {
* Get the wallpaper for a given user.
*/
ParcelFileDescriptor getWallpaperWithFeature(String callingPkg, String callingFeatureId,
- IWallpaperManagerCallback cb, int which, out Bundle outParams, int userId);
+ IWallpaperManagerCallback cb, int which, out Bundle outParams, int userId,
+ boolean getCropped);
/**
* Retrieve the given user's current wallpaper ID of the given kind.
@@ -96,6 +97,12 @@ interface IWallpaperManager {
WallpaperInfo getWallpaperInfoWithFlags(int which, int userId);
/**
+ * Return a file descriptor for the file that contains metadata about the given user's
+ * wallpaper.
+ */
+ ParcelFileDescriptor getWallpaperInfoFile(int userId);
+
+ /**
* Clear the system wallpaper.
*/
void clearWallpaper(in String callingPackage, int which, int userId);
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index c081d826e4b4..9b348fce01a5 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -7947,8 +7947,6 @@ public class Notification implements Parcelable
* @hide
*/
public MessagingStyle setShortcutIcon(@Nullable Icon conversationIcon) {
- // TODO(b/228941516): This icon should be downscaled to avoid using too much memory,
- // see reduceImageSizes.
mShortcutIcon = conversationIcon;
return this;
}
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 118745968aca..f3a83d8ef70b 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -640,7 +640,7 @@ public class WallpaperManager {
Bundle params = new Bundle();
try (ParcelFileDescriptor pfd = mService.getWallpaperWithFeature(
context.getOpPackageName(), context.getAttributionTag(), this, which,
- params, userId)) {
+ params, userId, /* getCropped = */ true)) {
// Let's peek user wallpaper first.
if (pfd != null) {
BitmapFactory.Options options = new BitmapFactory.Options();
@@ -690,7 +690,7 @@ public class WallpaperManager {
Bundle params = new Bundle();
ParcelFileDescriptor pfd = mService.getWallpaperWithFeature(
context.getOpPackageName(), context.getAttributionTag(), this, which,
- params, userId);
+ params, userId, /* getCropped = */ true);
if (pfd != null) {
try (BufferedInputStream bis = new BufferedInputStream(
@@ -1437,6 +1437,27 @@ public class WallpaperManager {
*/
@UnsupportedAppUsage
public ParcelFileDescriptor getWallpaperFile(@SetWallpaperFlags int which, int userId) {
+ return getWallpaperFile(which, userId, /* getCropped = */ true);
+ }
+
+ /**
+ * Version of {@link #getWallpaperFile(int)} that allows specifying whether to get the
+ * cropped version of the wallpaper file or the original.
+ *
+ * @param which The wallpaper whose image file is to be retrieved. Must be a single
+ * defined kind of wallpaper, either {@link #FLAG_SYSTEM} or {@link #FLAG_LOCK}.
+ * @param getCropped If true the cropped file will be retrieved, if false the original will
+ * be retrieved.
+ *
+ * @hide
+ */
+ @Nullable
+ public ParcelFileDescriptor getWallpaperFile(@SetWallpaperFlags int which, boolean getCropped) {
+ return getWallpaperFile(which, mContext.getUserId(), getCropped);
+ }
+
+ private ParcelFileDescriptor getWallpaperFile(@SetWallpaperFlags int which, int userId,
+ boolean getCropped) {
checkExactlyOneWallpaperFlagSet(which);
if (sGlobals.mService == null) {
@@ -1446,7 +1467,8 @@ public class WallpaperManager {
try {
Bundle outParams = new Bundle();
return sGlobals.mService.getWallpaperWithFeature(mContext.getOpPackageName(),
- mContext.getAttributionTag(), null, which, outParams, userId);
+ mContext.getAttributionTag(), null, which, outParams,
+ userId, getCropped);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (SecurityException e) {
@@ -1530,6 +1552,28 @@ public class WallpaperManager {
}
/**
+ * Get an open, readable file descriptor for the file that contains metadata about the
+ * context user's wallpaper.
+ *
+ * The caller is responsible for closing the file descriptor when done ingesting the file.
+ *
+ * @hide
+ */
+ @Nullable
+ public ParcelFileDescriptor getWallpaperInfoFile() {
+ if (sGlobals.mService == null) {
+ Log.w(TAG, "WallpaperService not running");
+ throw new RuntimeException(new DeadSystemException());
+ } else {
+ try {
+ return sGlobals.mService.getWallpaperInfoFile(mContext.getUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
* Get the ID of the current wallpaper of the given kind. If there is no
* such wallpaper configured, returns a negative number.
*
diff --git a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
index e96a2c18037b..4f49b8dbd0dc 100644
--- a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
+++ b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
@@ -18,6 +18,7 @@ package android.companion.virtual;
import android.companion.virtual.IVirtualDevice;
import android.companion.virtual.IVirtualDeviceActivityListener;
+import android.companion.virtual.IVirtualDeviceSoundEffectListener;
import android.companion.virtual.VirtualDevice;
import android.companion.virtual.VirtualDeviceParams;
import android.hardware.display.IVirtualDisplayCallback;
@@ -41,10 +42,12 @@ interface IVirtualDeviceManager {
* @param params The parameters for creating this virtual device. See {@link
* VirtualDeviceManager.VirtualDeviceParams}.
* @param activityListener The listener to listen for activity changes in a virtual device.
+ * @param soundEffectListener The listener to listen for sound effect playback requests.
*/
IVirtualDevice createVirtualDevice(
in IBinder token, String packageName, int associationId,
- in VirtualDeviceParams params, in IVirtualDeviceActivityListener activityListener);
+ in VirtualDeviceParams params, in IVirtualDeviceActivityListener activityListener,
+ in IVirtualDeviceSoundEffectListener soundEffectListener);
/**
* Returns the details of all available virtual devices.
@@ -92,4 +95,13 @@ interface IVirtualDeviceManager {
* if there's none.
*/
int getAudioRecordingSessionId(int deviceId);
+
+ /**
+ * Triggers sound effect playback on virtual device.
+ *
+ * @param deviceId id of the virtual device.
+ * @param sound effect type corresponding to
+ * {@code android.media.AudioManager.SystemSoundEffect}
+ */
+ void playSoundEffect(int deviceId, int effectType);
}
diff --git a/core/java/android/companion/virtual/IVirtualDeviceSoundEffectListener.aidl b/core/java/android/companion/virtual/IVirtualDeviceSoundEffectListener.aidl
new file mode 100644
index 000000000000..91c209fa098e
--- /dev/null
+++ b/core/java/android/companion/virtual/IVirtualDeviceSoundEffectListener.aidl
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion.virtual;
+
+/**
+ * Interface to listen for sound effect playback on Virtual Device.
+ *
+ * @hide
+ */
+oneway interface IVirtualDeviceSoundEffectListener {
+
+
+ /**
+ * Called when there's sound effect to be played on Virtual Device.
+ *
+ * @param sound effect type corresponding to
+ * {@code android.media.AudioManager.SystemSoundEffect}
+ */
+ void onPlaySoundEffect(int effectType);
+}
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index 3bc1628d3576..1bc3091a3605 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -69,6 +69,8 @@ import android.util.ArrayMap;
import android.util.Log;
import android.view.Surface;
+import com.android.internal.annotations.GuardedBy;
+
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -333,10 +335,15 @@ public final class VirtualDeviceManager {
* @hide
*/
public void playSoundEffect(int deviceId, @AudioManager.SystemSoundEffect int effectType) {
- //TODO - handle requests to play sound effects by custom callbacks or SoundPool asociated
- // with device session id.
- // For now, this is intentionally left empty and effectively disables sound effects for
- // virtual devices with custom device audio policy.
+ if (mService == null) {
+ Log.w(TAG, "Failed to dispatch sound effect; no virtual device manager service.");
+ return;
+ }
+ try {
+ mService.playSoundEffect(deviceId, effectType);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/**
@@ -359,6 +366,10 @@ public final class VirtualDeviceManager {
private final ArrayMap<IntentInterceptorCallback,
VirtualIntentInterceptorDelegate> mIntentInterceptorListeners =
new ArrayMap<>();
+ private final Object mSoundEffectListenersLock = new Object();
+ @GuardedBy("mSoundEffectListenersLock")
+ private final ArrayMap<SoundEffectListener, SoundEffectListenerDelegate>
+ mSoundEffectListeners = new ArrayMap<>();
private final IVirtualDeviceActivityListener mActivityListenerBinder =
new IVirtualDeviceActivityListener.Stub() {
@@ -387,6 +398,22 @@ public final class VirtualDeviceManager {
}
}
};
+ private final IVirtualDeviceSoundEffectListener mSoundEffectListener =
+ new IVirtualDeviceSoundEffectListener.Stub() {
+ @Override
+ public void onPlaySoundEffect(int soundEffect) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mSoundEffectListenersLock) {
+ for (int i = 0; i < mSoundEffectListeners.size(); i++) {
+ mSoundEffectListeners.valueAt(i).onPlaySoundEffect(soundEffect);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ };
@Nullable
private VirtualCameraDevice mVirtualCameraDevice;
@NonNull
@@ -407,7 +434,8 @@ public final class VirtualDeviceManager {
mContext.getPackageName(),
associationId,
params,
- mActivityListenerBinder);
+ mActivityListenerBinder,
+ mSoundEffectListener);
final List<VirtualSensorConfig> virtualSensorConfigs = params.getVirtualSensorConfigs();
for (int i = 0; i < virtualSensorConfigs.size(); ++i) {
mVirtualSensors.add(createVirtualSensor(virtualSensorConfigs.get(i)));
@@ -947,6 +975,35 @@ public final class VirtualDeviceManager {
}
/**
+ * Adds a sound effect listener.
+ *
+ * @param executor The executor where the listener is executed on.
+ * @param soundEffectListener The listener to add.
+ * @see #removeActivityListener(ActivityListener)
+ */
+ public void addSoundEffectListener(@CallbackExecutor @NonNull Executor executor,
+ @NonNull SoundEffectListener soundEffectListener) {
+ final SoundEffectListenerDelegate delegate =
+ new SoundEffectListenerDelegate(Objects.requireNonNull(executor),
+ Objects.requireNonNull(soundEffectListener));
+ synchronized (mSoundEffectListenersLock) {
+ mSoundEffectListeners.put(soundEffectListener, delegate);
+ }
+ }
+
+ /**
+ * Removes a sound effect listener previously added with {@link #addActivityListener}.
+ *
+ * @param soundEffectListener The listener to remove.
+ * @see #addActivityListener(Executor, ActivityListener)
+ */
+ public void removeSoundEffectListener(@NonNull SoundEffectListener soundEffectListener) {
+ synchronized (mSoundEffectListenersLock) {
+ mSoundEffectListeners.remove(soundEffectListener);
+ }
+ }
+
+ /**
* Registers an intent interceptor that will intercept an intent attempting to launch
* when matching the provided IntentFilter and calls the callback with the intercepted
* intent.
@@ -1090,4 +1147,38 @@ public final class VirtualDeviceManager {
}
}
}
+
+ /**
+ * Listener for system sound effect playback on virtual device.
+ * @hide
+ */
+ @SystemApi
+ public interface SoundEffectListener {
+
+ /**
+ * Called when there's a system sound effect to be played on virtual device.
+ *
+ * @param effectType - system sound effect type, see
+ * {@code android.media.AudioManager.SystemSoundEffect}
+ */
+ void onPlaySoundEffect(@AudioManager.SystemSoundEffect int effectType);
+ }
+
+ /**
+ * A wrapper for {@link SoundEffectListener} that executes callbacks on the given executor.
+ */
+ private static class SoundEffectListenerDelegate {
+ @NonNull private final SoundEffectListener mSoundEffectListener;
+ @NonNull private final Executor mExecutor;
+
+ private SoundEffectListenerDelegate(Executor executor,
+ SoundEffectListener soundEffectCallback) {
+ mSoundEffectListener = soundEffectCallback;
+ mExecutor = executor;
+ }
+
+ public void onPlaySoundEffect(@AudioManager.SystemSoundEffect int effectType) {
+ mExecutor.execute(() -> mSoundEffectListener.onPlaySoundEffect(effectType));
+ }
+ }
}
diff --git a/core/java/android/content/pm/ApkChecksum.java b/core/java/android/content/pm/ApkChecksum.java
index d550f411f6f7..88a19606a489 100644
--- a/core/java/android/content/pm/ApkChecksum.java
+++ b/core/java/android/content/pm/ApkChecksum.java
@@ -36,7 +36,7 @@ import java.security.cert.X509Certificate;
*
* @see PackageManager#requestChecksums
*/
-@DataClass(genHiddenConstructor = true)
+@DataClass(genHiddenConstructor = true, genToString = true)
@DataClass.Suppress({"getChecksum"})
public final class ApkChecksum implements Parcelable {
/**
@@ -178,6 +178,20 @@ public final class ApkChecksum implements Parcelable {
@Override
@DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "ApkChecksum { " +
+ "splitName = " + mSplitName + ", " +
+ "checksum = " + mChecksum + ", " +
+ "installerPackageName = " + mInstallerPackageName + ", " +
+ "installerCertificate = " + java.util.Arrays.toString(mInstallerCertificate) +
+ " }";
+ }
+
+ @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) { ... }
@@ -235,10 +249,10 @@ public final class ApkChecksum implements Parcelable {
};
@DataClass.Generated(
- time = 1619810171079L,
+ time = 1674080488372L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/content/pm/ApkChecksum.java",
- inputSignatures = "private final @android.annotation.Nullable java.lang.String mSplitName\nprivate final @android.annotation.NonNull android.content.pm.Checksum mChecksum\nprivate final @android.annotation.Nullable java.lang.String mInstallerPackageName\nprivate final @android.annotation.Nullable byte[] mInstallerCertificate\npublic @android.content.pm.Checksum.Type int getType()\npublic @android.annotation.NonNull byte[] getValue()\npublic @android.annotation.Nullable byte[] getInstallerCertificateBytes()\npublic @android.annotation.Nullable java.security.cert.Certificate getInstallerCertificate()\nclass ApkChecksum extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genHiddenConstructor=true)")
+ inputSignatures = "private final @android.annotation.Nullable java.lang.String mSplitName\nprivate final @android.annotation.NonNull android.content.pm.Checksum mChecksum\nprivate final @android.annotation.Nullable java.lang.String mInstallerPackageName\nprivate final @android.annotation.Nullable byte[] mInstallerCertificate\npublic @android.content.pm.Checksum.Type int getType()\npublic @android.annotation.NonNull byte[] getValue()\npublic @android.annotation.Nullable byte[] getInstallerCertificateBytes()\npublic @android.annotation.Nullable java.security.cert.Certificate getInstallerCertificate()\nclass ApkChecksum extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genHiddenConstructor=true, genToString=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java
index 44747fabad97..e38cb65f991f 100644
--- a/core/java/android/content/pm/UserInfo.java
+++ b/core/java/android/content/pm/UserInfo.java
@@ -406,24 +406,6 @@ public class UserInfo implements Parcelable {
}
/**
- * Returns true if the user is a split system user.
- * <p>If {@link UserManager#isSplitSystemUser split system user mode} is not enabled,
- * the method always returns false.
- */
- public boolean isSystemOnly() {
- return isSystemOnly(id);
- }
-
- /**
- * Returns true if the given user is a split system user.
- * <p>If {@link UserManager#isSplitSystemUser split system user mode} is not enabled,
- * the method always returns false.
- */
- public static boolean isSystemOnly(int userId) {
- return userId == UserHandle.USER_SYSTEM && UserManager.isSplitSystemUser();
- }
-
- /**
* @return true if this user can be switched to.
**/
public boolean supportsSwitchTo() {
@@ -454,7 +436,7 @@ public class UserInfo implements Parcelable {
if (isProfile() || isGuest() || isRestricted()) {
return false;
}
- if (UserManager.isSplitSystemUser() || UserManager.isHeadlessSystemUserMode()) {
+ if (UserManager.isHeadlessSystemUserMode()) {
return id != UserHandle.USER_SYSTEM;
} else {
return id == UserHandle.USER_SYSTEM;
diff --git a/core/java/android/credentials/CreateCredentialException.java b/core/java/android/credentials/CreateCredentialException.java
index fefa60ae23ee..84cc9a84b23c 100644
--- a/core/java/android/credentials/CreateCredentialException.java
+++ b/core/java/android/credentials/CreateCredentialException.java
@@ -28,7 +28,7 @@ import java.util.concurrent.Executor;
/**
* Represents an error encountered during the
- * {@link CredentialManager#executeCreateCredential(CreateCredentialRequest,
+ * {@link CredentialManager#createCredential(CreateCredentialRequest,
* Activity, CancellationSignal, Executor, OutcomeReceiver)} operation.
*/
public class CreateCredentialException extends Exception {
@@ -41,7 +41,7 @@ public class CreateCredentialException extends Exception {
/**
* The error type value for when no credential is available for the given {@link
- * CredentialManager#executeCreateCredential(CreateCredentialRequest, Activity,
+ * CredentialManager#createCredential(CreateCredentialRequest, Activity,
* CancellationSignal, Executor, OutcomeReceiver)} request.
*/
@NonNull
diff --git a/core/java/android/credentials/CreateCredentialRequest.java b/core/java/android/credentials/CreateCredentialRequest.java
index be887a513e3f..26f8831003cd 100644
--- a/core/java/android/credentials/CreateCredentialRequest.java
+++ b/core/java/android/credentials/CreateCredentialRequest.java
@@ -54,7 +54,7 @@ public final class CreateCredentialRequest implements Parcelable {
/**
* Determines whether the request must only be fulfilled by a system provider.
*/
- private final boolean mRequireSystemProvider;
+ private final boolean mIsSystemProviderRequired;
/**
* Returns the requested credential type.
@@ -99,8 +99,8 @@ public final class CreateCredentialRequest implements Parcelable {
* Returns true if the request must only be fulfilled by a system provider, and false
* otherwise.
*/
- public boolean requireSystemProvider() {
- return mRequireSystemProvider;
+ public boolean isSystemProviderRequired() {
+ return mIsSystemProviderRequired;
}
@Override
@@ -108,7 +108,7 @@ public final class CreateCredentialRequest implements Parcelable {
dest.writeString8(mType);
dest.writeBundle(mCredentialData);
dest.writeBundle(mCandidateQueryData);
- dest.writeBoolean(mRequireSystemProvider);
+ dest.writeBoolean(mIsSystemProviderRequired);
}
@Override
@@ -122,7 +122,7 @@ public final class CreateCredentialRequest implements Parcelable {
+ "type=" + mType
+ ", credentialData=" + mCredentialData
+ ", candidateQueryData=" + mCandidateQueryData
- + ", requireSystemProvider=" + mRequireSystemProvider
+ + ", isSystemProviderRequired=" + mIsSystemProviderRequired
+ "}";
}
@@ -133,7 +133,8 @@ public final class CreateCredentialRequest implements Parcelable {
* @param credentialData the full credential creation request data
* @param candidateQueryData the partial request data that will be sent to the provider
* during the initial creation candidate query stage
- * @param requireSystemProvider whether the request must only be fulfilled by a system provider
+ * @param isSystemProviderRequired whether the request must only be fulfilled by a system
+ * provider
*
* @throws IllegalArgumentException If type is empty.
*/
@@ -141,19 +142,19 @@ public final class CreateCredentialRequest implements Parcelable {
@NonNull String type,
@NonNull Bundle credentialData,
@NonNull Bundle candidateQueryData,
- boolean requireSystemProvider) {
+ boolean isSystemProviderRequired) {
mType = Preconditions.checkStringNotEmpty(type, "type must not be empty");
mCredentialData = requireNonNull(credentialData, "credentialData must not be null");
mCandidateQueryData = requireNonNull(candidateQueryData,
"candidateQueryData must not be null");
- mRequireSystemProvider = requireSystemProvider;
+ mIsSystemProviderRequired = isSystemProviderRequired;
}
private CreateCredentialRequest(@NonNull Parcel in) {
String type = in.readString8();
Bundle credentialData = in.readBundle();
Bundle candidateQueryData = in.readBundle();
- boolean requireSystemProvider = in.readBoolean();
+ boolean isSystemProviderRequired = in.readBoolean();
mType = type;
AnnotationValidations.validate(NonNull.class, null, mType);
@@ -161,7 +162,7 @@ public final class CreateCredentialRequest implements Parcelable {
AnnotationValidations.validate(NonNull.class, null, mCredentialData);
mCandidateQueryData = candidateQueryData;
AnnotationValidations.validate(NonNull.class, null, mCandidateQueryData);
- mRequireSystemProvider = requireSystemProvider;
+ mIsSystemProviderRequired = isSystemProviderRequired;
}
public static final @NonNull Parcelable.Creator<CreateCredentialRequest> CREATOR =
diff --git a/core/java/android/credentials/CredentialDescription.aidl b/core/java/android/credentials/CredentialDescription.aidl
new file mode 100644
index 000000000000..1b5739e7ff16
--- /dev/null
+++ b/core/java/android/credentials/CredentialDescription.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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;
+
+/**
+ * @hide
+ */
+parcelable CredentialDescription; \ No newline at end of file
diff --git a/core/java/android/credentials/CredentialDescription.java b/core/java/android/credentials/CredentialDescription.java
new file mode 100644
index 000000000000..b4310f2218d3
--- /dev/null
+++ b/core/java/android/credentials/CredentialDescription.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.service.credentials.CredentialEntry;
+
+import com.android.internal.util.AnnotationValidations;
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Represents the type and contained data fields of a {@link Credential}.
+ * @hide
+ */
+public final class CredentialDescription implements Parcelable {
+
+ /**
+ * The credential type.
+ */
+ @NonNull
+ private final String mType;
+
+ /**
+ * The flattened JSON string that will be matched with requests.
+ */
+ @NonNull
+ private final String mFlattenedRequestString;
+
+ /**
+ * The entry to be used in the UI.
+ */
+ @NonNull
+ private final List<CredentialEntry> mCredentialEntries;
+
+ /**
+ * Constructs a {@link CredentialDescription}.
+ *
+ * @param type the type of the credential returned.
+ * @param flattenedRequestString flattened JSON string that will be matched with requests.
+ * @param credentialEntries a list of {@link CredentialEntry}s that have been returned
+ * to the developer upon credential creation.
+ *
+ * @throws IllegalArgumentException If type is empty.
+ */
+ public CredentialDescription(@NonNull String type,
+ @NonNull String flattenedRequestString,
+ @NonNull List<CredentialEntry> credentialEntries) {
+ mType = Preconditions.checkStringNotEmpty(type, "type must not be empty");
+ mFlattenedRequestString = Preconditions.checkStringNotEmpty(flattenedRequestString);
+ mCredentialEntries = Objects.requireNonNull(credentialEntries);
+ }
+
+ private CredentialDescription(@NonNull Parcel in) {
+ String type = in.readString8();
+ String flattenedRequestString = in.readString();
+ List<CredentialEntry> entries = new ArrayList<>();
+ in.readTypedList(entries, CredentialEntry.CREATOR);
+
+ mType = type;
+ AnnotationValidations.validate(android.annotation.NonNull.class, null, mType);
+ mFlattenedRequestString = flattenedRequestString;
+ AnnotationValidations.validate(android.annotation.NonNull.class, null,
+ mFlattenedRequestString);
+ mCredentialEntries = entries;
+ AnnotationValidations.validate(android.annotation.NonNull.class, null,
+ mCredentialEntries);
+ }
+
+ public static final @NonNull Parcelable.Creator<CredentialDescription> CREATOR =
+ new Parcelable.Creator<CredentialDescription>() {
+ @Override
+ public CredentialDescription createFromParcel(Parcel in) {
+ return new CredentialDescription(in);
+ }
+
+ @Override
+ public CredentialDescription[] newArray(int size) {
+ return new CredentialDescription[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString(mType);
+ dest.writeString(mFlattenedRequestString);
+ dest.writeTypedList(mCredentialEntries, flags);
+ }
+
+ @NonNull
+ public String getType() {
+ return mType;
+ }
+
+ @NonNull
+ public String getFlattenedRequestString() {
+ return mFlattenedRequestString;
+ }
+
+ @NonNull
+ public List<CredentialEntry> getCredentialEntries() {
+ return mCredentialEntries;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mType, mFlattenedRequestString);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return Objects.equals(mType, ((CredentialDescription) obj).getType())
+ && Objects.equals(mFlattenedRequestString, ((CredentialDescription) obj).getType());
+ }
+}
diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java
index d4daf364f76e..e15cec84cbd0 100644
--- a/core/java/android/credentials/CredentialManager.java
+++ b/core/java/android/credentials/CredentialManager.java
@@ -63,6 +63,14 @@ public final class CredentialManager {
"enable_credential_manager";
/**
+ * Flag to enable and disable Credential Description api.
+ *
+ * @hide
+ */
+ private static final String DEVICE_CONFIG_ENABLE_CREDENTIAL_DESC_API =
+ "enable_credential_description_api";
+
+ /**
* @hide instantiated by ContextImpl.
*/
public CredentialManager(Context context, ICredentialManager service) {
@@ -82,7 +90,7 @@ public final class CredentialManager {
* @param executor the callback will take place on this {@link Executor}
* @param callback the callback invoked when the request succeeds or fails
*/
- public void executeGetCredential(
+ public void getCredential(
@NonNull GetCredentialRequest request,
@NonNull Activity activity,
@Nullable CancellationSignal cancellationSignal,
@@ -94,7 +102,7 @@ public final class CredentialManager {
requireNonNull(callback, "callback must not be null");
if (cancellationSignal != null && cancellationSignal.isCanceled()) {
- Log.w(TAG, "executeGetCredential already canceled");
+ Log.w(TAG, "getCredential already canceled");
return;
}
@@ -126,7 +134,7 @@ public final class CredentialManager {
* @param executor the callback will take place on this {@link Executor}
* @param callback the callback invoked when the request succeeds or fails
*/
- public void executeCreateCredential(
+ public void createCredential(
@NonNull CreateCredentialRequest request,
@NonNull Activity activity,
@Nullable CancellationSignal cancellationSignal,
@@ -139,7 +147,7 @@ public final class CredentialManager {
requireNonNull(callback, "callback must not be null");
if (cancellationSignal != null && cancellationSignal.isCanceled()) {
- Log.w(TAG, "executeCreateCredential already canceled");
+ Log.w(TAG, "createCredential already canceled");
return;
}
@@ -185,7 +193,7 @@ public final class CredentialManager {
requireNonNull(callback, "callback must not be null");
if (cancellationSignal != null && cancellationSignal.isCanceled()) {
- Log.w(TAG, "executeCreateCredential already canceled");
+ Log.w(TAG, "clearCredentialState already canceled");
return;
}
@@ -294,6 +302,112 @@ public final class CredentialManager {
true);
}
+ /**
+ * Returns whether the credential description api is enabled.
+ *
+ * @hide
+ */
+ public static boolean isCredentialDescriptionApiEnabled() {
+ return DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_CREDENTIAL, DEVICE_CONFIG_ENABLE_CREDENTIAL_DESC_API, false);
+ }
+
+ /**
+ * Registers a {@link CredentialDescription} for an actively provisioned {@link Credential}
+ * a CredentialProvider has. This registry will then be used by
+ * {@link #executeGetCredential(GetCredentialRequest, Activity,
+ * CancellationSignal, Executor, OutcomeReceiver)} to determine where to
+ * fetch the requested {@link Credential} from.
+ *
+ *
+ * @param request the request data
+ * @param cancellationSignal an optional signal that allows for cancelling this call
+ * @param executor the callback will take place on this {@link Executor}
+ * @param callback the callback invoked when the request succeeds or fails
+ *
+ * @throws {@link UnsupportedOperationException} if the feature has not been enabled.
+ *
+ * @hide
+ */
+ public void registerCredentialDescription(
+ @NonNull RegisterCredentialDescriptionRequest request,
+ @Nullable CancellationSignal cancellationSignal,
+ @CallbackExecutor @NonNull Executor executor,
+ @NonNull OutcomeReceiver<Void, RegisterCredentialDescriptionException> callback) {
+
+ if (!isCredentialDescriptionApiEnabled()) {
+ throw new UnsupportedOperationException("This API is not currently supported.");
+ }
+
+ requireNonNull(executor, "executor must not be null");
+ requireNonNull(callback, "callback must not be null");
+
+ if (cancellationSignal != null && cancellationSignal.isCanceled()) {
+ Log.w(TAG, "executeCreateCredential already canceled");
+ return;
+ }
+
+ ICancellationSignal cancelRemote = null;
+ try {
+ cancelRemote = mService.registerCredentialDescription(request,
+ new RegisterCredentialDescriptionTransport(executor, callback),
+ mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+
+ if (cancellationSignal != null && cancelRemote != null) {
+ cancellationSignal.setRemote(cancelRemote);
+ }
+ }
+
+
+ /**
+ * Unregisters a {@link CredentialDescription} for an actively provisioned {@link Credential}
+ * that has been registered previously.
+ *
+ *
+ * @param request the request data
+ * @param cancellationSignal an optional signal that allows for cancelling this call
+ * @param executor the callback will take place on this {@link Executor}
+ * @param callback the callback invoked when the request succeeds or fails
+ *
+ * @throws {@link UnsupportedOperationException} if the feature has not been enabled.
+ *
+ * @hide
+ */
+ public void unRegisterCredentialDescription(
+ @NonNull UnregisterCredentialDescriptionRequest request,
+ @Nullable CancellationSignal cancellationSignal,
+ @CallbackExecutor @NonNull Executor executor,
+ @NonNull OutcomeReceiver<Void, UnregisterCredentialDescriptionException> callback) {
+
+ if (!isCredentialDescriptionApiEnabled()) {
+ throw new UnsupportedOperationException("This API is not currently supported.");
+ }
+
+ requireNonNull(executor, "executor must not be null");
+ requireNonNull(callback, "callback must not be null");
+
+ if (cancellationSignal != null && cancellationSignal.isCanceled()) {
+ Log.w(TAG, "executeCreateCredential already canceled");
+ return;
+ }
+
+ ICancellationSignal cancelRemote = null;
+ try {
+ cancelRemote = mService.unRegisterCredentialDescription(request,
+ new UnregisterCredentialDescriptionTransport(executor, callback),
+ mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+
+ if (cancellationSignal != null && cancelRemote != null) {
+ cancellationSignal.setRemote(cancelRemote);
+ }
+ }
+
private static class GetCredentialTransport extends IGetCredentialCallback.Stub {
// TODO: listen for cancellation to release callback.
@@ -455,4 +569,54 @@ public final class CredentialManager {
() -> mCallback.onError(new SetEnabledProvidersException(errorType, message)));
}
}
+
+ private static class RegisterCredentialDescriptionTransport
+ extends IRegisterCredentialDescriptionCallback.Stub {
+
+ private final Executor mExecutor;
+ private final OutcomeReceiver<Void, RegisterCredentialDescriptionException> mCallback;
+
+ private RegisterCredentialDescriptionTransport(Executor executor,
+ OutcomeReceiver<Void, RegisterCredentialDescriptionException> callback) {
+ mExecutor = executor;
+ mCallback = callback;
+ }
+
+ @Override
+ public void onResponse() {
+ mCallback.onResult(null);
+ }
+
+ @Override
+ public void onError(String errorCode, String message) {
+ mExecutor.execute(
+ () -> mCallback.onError(new RegisterCredentialDescriptionException(errorCode,
+ message)));
+ }
+ }
+
+ private static class UnregisterCredentialDescriptionTransport
+ extends IUnregisterCredentialDescriptionCallback.Stub {
+
+ private final Executor mExecutor;
+ private final OutcomeReceiver<Void, UnregisterCredentialDescriptionException> mCallback;
+
+ private UnregisterCredentialDescriptionTransport(Executor executor,
+ OutcomeReceiver<Void, UnregisterCredentialDescriptionException> callback) {
+ mExecutor = executor;
+ mCallback = callback;
+ }
+
+ @Override
+ public void onResponse() {
+ mCallback.onResult(null);
+ }
+
+ @Override
+ public void onError(String errorCode, String message) {
+ mExecutor.execute(
+ () -> mCallback.onError(new UnregisterCredentialDescriptionException(errorCode,
+ message)));
+ }
+ }
}
diff --git a/core/java/android/credentials/GetCredentialException.java b/core/java/android/credentials/GetCredentialException.java
index 478afff1fae1..720c53ba51cd 100644
--- a/core/java/android/credentials/GetCredentialException.java
+++ b/core/java/android/credentials/GetCredentialException.java
@@ -28,7 +28,7 @@ import java.util.concurrent.Executor;
/**
* Represents an error encountered during the
- * {@link CredentialManager#executeGetCredential(GetCredentialRequest,
+ * {@link CredentialManager#getCredential(GetCredentialRequest,
* Activity, CancellationSignal, Executor, OutcomeReceiver)} operation.
*/
public class GetCredentialException extends Exception {
@@ -41,7 +41,7 @@ public class GetCredentialException extends Exception {
/**
* The error type value for when no credential is found available for the given {@link
- * CredentialManager#executeGetCredential(GetCredentialRequest, Activity, CancellationSignal,
+ * CredentialManager#getCredential(GetCredentialRequest, Activity, CancellationSignal,
* Executor, OutcomeReceiver)} request.
*/
@NonNull
diff --git a/core/java/android/credentials/GetCredentialOption.java b/core/java/android/credentials/GetCredentialOption.java
index 47731dd7b517..55daf86adfe3 100644
--- a/core/java/android/credentials/GetCredentialOption.java
+++ b/core/java/android/credentials/GetCredentialOption.java
@@ -53,7 +53,7 @@ public final class GetCredentialOption implements Parcelable {
/**
* Determines whether the request must only be fulfilled by a system provider.
*/
- private final boolean mRequireSystemProvider;
+ private final boolean mIsSystemProviderRequired;
/**
* Returns the requested credential type.
@@ -91,8 +91,8 @@ public final class GetCredentialOption implements Parcelable {
* Returns true if the request must only be fulfilled by a system provider, and false
* otherwise.
*/
- public boolean requireSystemProvider() {
- return mRequireSystemProvider;
+ public boolean isSystemProviderRequired() {
+ return mIsSystemProviderRequired;
}
@Override
@@ -100,7 +100,7 @@ public final class GetCredentialOption implements Parcelable {
dest.writeString8(mType);
dest.writeBundle(mCredentialRetrievalData);
dest.writeBundle(mCandidateQueryData);
- dest.writeBoolean(mRequireSystemProvider);
+ dest.writeBoolean(mIsSystemProviderRequired);
}
@Override
@@ -114,7 +114,7 @@ public final class GetCredentialOption implements Parcelable {
+ "type=" + mType
+ ", requestData=" + mCredentialRetrievalData
+ ", candidateQueryData=" + mCandidateQueryData
- + ", requireSystemProvider=" + mRequireSystemProvider
+ + ", isSystemProviderRequired=" + mIsSystemProviderRequired
+ "}";
}
@@ -125,7 +125,7 @@ public final class GetCredentialOption implements Parcelable {
* @param credentialRetrievalData the request data
* @param candidateQueryData the partial request data that will be sent to the provider
* during the initial credential candidate query stage
- * @param requireSystemProvider whether the request must only be fulfilled by a system
+ * @param isSystemProviderRequired whether the request must only be fulfilled by a system
* provider
* @throws IllegalArgumentException If type is empty.
*/
@@ -133,20 +133,20 @@ public final class GetCredentialOption implements Parcelable {
@NonNull String type,
@NonNull Bundle credentialRetrievalData,
@NonNull Bundle candidateQueryData,
- boolean requireSystemProvider) {
+ boolean isSystemProviderRequired) {
mType = Preconditions.checkStringNotEmpty(type, "type must not be empty");
mCredentialRetrievalData = requireNonNull(credentialRetrievalData,
"requestData must not be null");
mCandidateQueryData = requireNonNull(candidateQueryData,
"candidateQueryData must not be null");
- mRequireSystemProvider = requireSystemProvider;
+ mIsSystemProviderRequired = isSystemProviderRequired;
}
private GetCredentialOption(@NonNull Parcel in) {
String type = in.readString8();
Bundle data = in.readBundle();
Bundle candidateQueryData = in.readBundle();
- boolean requireSystemProvider = in.readBoolean();
+ boolean isSystemProviderRequired = in.readBoolean();
mType = type;
AnnotationValidations.validate(NonNull.class, null, mType);
@@ -154,7 +154,7 @@ public final class GetCredentialOption implements Parcelable {
AnnotationValidations.validate(NonNull.class, null, mCredentialRetrievalData);
mCandidateQueryData = candidateQueryData;
AnnotationValidations.validate(NonNull.class, null, mCandidateQueryData);
- mRequireSystemProvider = requireSystemProvider;
+ mIsSystemProviderRequired = isSystemProviderRequired;
}
public static final @NonNull Parcelable.Creator<GetCredentialOption> CREATOR =
diff --git a/core/java/android/credentials/ICredentialManager.aidl b/core/java/android/credentials/ICredentialManager.aidl
index c3ca03dcdfd2..75b3d0c78d23 100644
--- a/core/java/android/credentials/ICredentialManager.aidl
+++ b/core/java/android/credentials/ICredentialManager.aidl
@@ -21,10 +21,14 @@ import java.util.List;
import android.credentials.ClearCredentialStateRequest;
import android.credentials.CreateCredentialRequest;
import android.credentials.GetCredentialRequest;
+import android.credentials.RegisterCredentialDescriptionRequest;
+import android.credentials.UnregisterCredentialDescriptionRequest;
import android.credentials.IClearCredentialStateCallback;
import android.credentials.ICreateCredentialCallback;
import android.credentials.IGetCredentialCallback;
import android.credentials.IListEnabledProvidersCallback;
+import android.credentials.IRegisterCredentialDescriptionCallback;
+import android.credentials.IUnregisterCredentialDescriptionCallback;
import android.credentials.ISetEnabledProvidersCallback;
import android.os.ICancellationSignal;
@@ -44,4 +48,9 @@ interface ICredentialManager {
@nullable ICancellationSignal listEnabledProviders(in IListEnabledProvidersCallback callback);
void setEnabledProviders(in List<String> providers, in int userId, in ISetEnabledProvidersCallback callback);
+
+ @nullable ICancellationSignal registerCredentialDescription(in RegisterCredentialDescriptionRequest request, in IRegisterCredentialDescriptionCallback callback, String callingPackage);
+
+ @nullable ICancellationSignal unRegisterCredentialDescription(in UnregisterCredentialDescriptionRequest request, in IUnregisterCredentialDescriptionCallback callback, String callingPackage);
}
+
diff --git a/core/java/android/credentials/IRegisterCredentialDescriptionCallback.aidl b/core/java/android/credentials/IRegisterCredentialDescriptionCallback.aidl
new file mode 100644
index 000000000000..124a319e11d8
--- /dev/null
+++ b/core/java/android/credentials/IRegisterCredentialDescriptionCallback.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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;
+
+/**
+ * Listener for an registerCredentialDescription request.
+ *
+ * @hide
+ */
+interface IRegisterCredentialDescriptionCallback {
+ oneway void onResponse();
+ oneway void onError(String errorCode, String message);
+} \ No newline at end of file
diff --git a/core/java/android/credentials/IUnregisterCredentialDescriptionCallback.aidl b/core/java/android/credentials/IUnregisterCredentialDescriptionCallback.aidl
new file mode 100644
index 000000000000..b30a12a5cd37
--- /dev/null
+++ b/core/java/android/credentials/IUnregisterCredentialDescriptionCallback.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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;
+
+/**
+ * Listener for an registerCredentialDescription request.
+ *
+ * @hide
+ */
+interface IUnregisterCredentialDescriptionCallback {
+ oneway void onResponse();
+ oneway void onError(String errorCode, String message);
+} \ No newline at end of file
diff --git a/core/java/android/credentials/RegisterCredentialDescriptionException.java b/core/java/android/credentials/RegisterCredentialDescriptionException.java
new file mode 100644
index 000000000000..3cf5a752495f
--- /dev/null
+++ b/core/java/android/credentials/RegisterCredentialDescriptionException.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.CancellationSignal;
+import android.os.OutcomeReceiver;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Represents an error encountered during the {@link
+ * CredentialManager#registerCredentialDescription(RegisterCredentialDescriptionRequest,
+ * CancellationSignal, Executor, OutcomeReceiver)} operation.
+ *
+ * @hide
+ */
+public class RegisterCredentialDescriptionException extends Exception {
+
+ @NonNull public final String errorType;
+
+ /**
+ * Constructs a {@link RegisterCredentialDescriptionException}.
+ *
+ * @throws IllegalArgumentException If errorType is empty.
+ */
+ public RegisterCredentialDescriptionException(@NonNull String errorType,
+ @Nullable String message) {
+ this(errorType, message, null);
+ }
+
+ /**
+ * Constructs a {@link RegisterCredentialDescriptionException}.
+ *
+ * @throws IllegalArgumentException If errorType is empty.
+ */
+ public RegisterCredentialDescriptionException(
+ @NonNull String errorType, @Nullable String message, @Nullable Throwable cause) {
+ super(message, cause);
+ this.errorType =
+ Preconditions
+ .checkStringNotEmpty(errorType, "errorType must not be empty");
+ }
+
+ /**
+ * Constructs a {@link RegisterCredentialDescriptionException}.
+ *
+ * @throws IllegalArgumentException If errorType is empty.
+ */
+ public RegisterCredentialDescriptionException(@NonNull String errorType,
+ @Nullable Throwable cause) {
+ this(errorType, null, cause);
+ }
+
+ /**
+ * Constructs a {@link RegisterCredentialDescriptionException}.
+ *
+ * @throws IllegalArgumentException If errorType is empty.
+ */
+ public RegisterCredentialDescriptionException(@NonNull String errorType) {
+ this(errorType, null, null);
+ }
+}
diff --git a/core/java/android/credentials/RegisterCredentialDescriptionRequest.aidl b/core/java/android/credentials/RegisterCredentialDescriptionRequest.aidl
new file mode 100644
index 000000000000..1d567282f447
--- /dev/null
+++ b/core/java/android/credentials/RegisterCredentialDescriptionRequest.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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;
+
+parcelable RegisterCredentialDescriptionRequest; \ No newline at end of file
diff --git a/core/java/android/credentials/RegisterCredentialDescriptionRequest.java b/core/java/android/credentials/RegisterCredentialDescriptionRequest.java
new file mode 100644
index 000000000000..de312797c5ac
--- /dev/null
+++ b/core/java/android/credentials/RegisterCredentialDescriptionRequest.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.AnnotationValidations;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+
+/**
+ * A request to register a {@link ComponentName} that contains an actively provisioned
+ * {@link Credential} represented by a {@link CredentialDescription}.
+ *
+ * @hide
+ */
+public final class RegisterCredentialDescriptionRequest implements Parcelable {
+
+ public static final String FLATTENED_REQUEST_STRING_KEY = "flattened_request_string";
+
+ @NonNull
+ private final List<CredentialDescription> mCredentialDescriptions;
+
+ public RegisterCredentialDescriptionRequest(
+ @NonNull CredentialDescription credentialDescription) {
+ mCredentialDescriptions = Arrays.asList(requireNonNull(credentialDescription));
+ }
+
+ public RegisterCredentialDescriptionRequest(
+ @NonNull List<CredentialDescription> credentialDescriptions) {
+ mCredentialDescriptions = new ArrayList<>(requireNonNull(credentialDescriptions));
+ }
+
+ private RegisterCredentialDescriptionRequest(@NonNull Parcel in) {
+ List<CredentialDescription> credentialDescriptions = new ArrayList<>();
+ in.readTypedList(credentialDescriptions, CredentialDescription.CREATOR);
+
+ mCredentialDescriptions = new ArrayList<>();
+ AnnotationValidations.validate(android.annotation.NonNull.class, null,
+ credentialDescriptions);
+ mCredentialDescriptions.addAll(credentialDescriptions);
+ }
+
+ public static final @NonNull Parcelable.Creator<RegisterCredentialDescriptionRequest> CREATOR =
+ new Parcelable.Creator<RegisterCredentialDescriptionRequest>() {
+ @Override
+ public RegisterCredentialDescriptionRequest createFromParcel(Parcel in) {
+ return new RegisterCredentialDescriptionRequest(in);
+ }
+
+ @Override
+ public RegisterCredentialDescriptionRequest[] newArray(int size) {
+ return new RegisterCredentialDescriptionRequest[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeTypedList(mCredentialDescriptions, flags);
+ }
+
+ @NonNull
+ public List<CredentialDescription> getCredentialDescriptions() {
+ return mCredentialDescriptions;
+ }
+}
diff --git a/core/java/android/credentials/UnregisterCredentialDescriptionException.java b/core/java/android/credentials/UnregisterCredentialDescriptionException.java
new file mode 100644
index 000000000000..0c786bda54a4
--- /dev/null
+++ b/core/java/android/credentials/UnregisterCredentialDescriptionException.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.CancellationSignal;
+import android.os.OutcomeReceiver;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Represents an error encountered during the {@link
+ * CredentialManager#registerCredentialDescription(RegisterCredentialDescriptionRequest,
+ * CancellationSignal, Executor, OutcomeReceiver)} operation.
+ *
+ * @hide
+ */
+public class UnregisterCredentialDescriptionException extends Exception {
+
+ @NonNull public final String errorType;
+
+ /**
+ * Constructs a {@link RegisterCredentialDescriptionException}.
+ *
+ * @throws IllegalArgumentException If errorType is empty.
+ */
+ public UnregisterCredentialDescriptionException(@NonNull String errorType,
+ @Nullable String message) {
+ this(errorType, message, null);
+ }
+
+ /**
+ * Constructs a {@link RegisterCredentialDescriptionException}.
+ *
+ * @throws IllegalArgumentException If errorType is empty.
+ */
+ public UnregisterCredentialDescriptionException(
+ @NonNull String errorType, @Nullable String message, @Nullable Throwable cause) {
+ super(message, cause);
+ this.errorType =
+ Preconditions
+ .checkStringNotEmpty(errorType, "errorType must not be empty");
+ }
+
+ /**
+ * Constructs a {@link RegisterCredentialDescriptionException}.
+ *
+ * @throws IllegalArgumentException If errorType is empty.
+ */
+ public UnregisterCredentialDescriptionException(@NonNull String errorType,
+ @Nullable Throwable cause) {
+ this(errorType, null, cause);
+ }
+
+ /**
+ * Constructs a {@link RegisterCredentialDescriptionException}.
+ *
+ * @throws IllegalArgumentException If errorType is empty.
+ */
+ public UnregisterCredentialDescriptionException(@NonNull String errorType) {
+ this(errorType, null, null);
+ }
+}
diff --git a/core/java/android/credentials/UnregisterCredentialDescriptionRequest.aidl b/core/java/android/credentials/UnregisterCredentialDescriptionRequest.aidl
new file mode 100644
index 000000000000..e25f13c0c623
--- /dev/null
+++ b/core/java/android/credentials/UnregisterCredentialDescriptionRequest.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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;
+
+parcelable UnregisterCredentialDescriptionRequest; \ No newline at end of file
diff --git a/core/java/android/credentials/UnregisterCredentialDescriptionRequest.java b/core/java/android/credentials/UnregisterCredentialDescriptionRequest.java
new file mode 100644
index 000000000000..f3454c101d9c
--- /dev/null
+++ b/core/java/android/credentials/UnregisterCredentialDescriptionRequest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.AnnotationValidations;
+
+
+/**
+ * A request to unregister a {@link ComponentName} that contains an actively provisioned
+ * {@link Credential} represented by a {@link CredentialDescription}. *
+ *
+ * @hide
+ */
+public final class UnregisterCredentialDescriptionRequest implements Parcelable {
+
+ @NonNull
+ private final CredentialDescription mCredentialDescription;
+
+ public UnregisterCredentialDescriptionRequest(@NonNull CredentialDescription
+ credentialDescription) {
+ mCredentialDescription = requireNonNull(credentialDescription);
+ }
+
+ private UnregisterCredentialDescriptionRequest(@NonNull Parcel in) {
+ CredentialDescription credentialDescription =
+ CredentialDescription.CREATOR.createFromParcel(in);
+
+ mCredentialDescription = credentialDescription;
+ AnnotationValidations.validate(android.annotation.NonNull.class, null,
+ credentialDescription);
+ }
+
+ public static final @NonNull Parcelable.Creator<UnregisterCredentialDescriptionRequest>
+ CREATOR = new Parcelable.Creator<UnregisterCredentialDescriptionRequest>() {
+ @Override
+ public UnregisterCredentialDescriptionRequest createFromParcel(Parcel in) {
+ return new UnregisterCredentialDescriptionRequest(in);
+ }
+
+ @Override
+ public UnregisterCredentialDescriptionRequest[] newArray(int size) {
+ return new UnregisterCredentialDescriptionRequest[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ mCredentialDescription.writeToParcel(dest, flags);
+ }
+
+ @NonNull
+ public CredentialDescription getCredentialDescription() {
+ return mCredentialDescription;
+ }
+}
diff --git a/core/java/android/credentials/ui/RequestInfo.java b/core/java/android/credentials/ui/RequestInfo.java
index e7b8dab9bc0b..49ae9e97b840 100644
--- a/core/java/android/credentials/ui/RequestInfo.java
+++ b/core/java/android/credentials/ui/RequestInfo.java
@@ -48,9 +48,9 @@ public final class RequestInfo implements Parcelable {
/** 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 an executeGetCredential request. */
+ /** Type value for a getCredential request. */
@NonNull public static final String TYPE_GET = "android.credentials.ui.TYPE_GET";
- /** Type value for an executeCreateCredential 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/hardware/input/HostUsiVersion.aidl b/core/java/android/hardware/input/HostUsiVersion.aidl
new file mode 100644
index 000000000000..74f0ba869944
--- /dev/null
+++ b/core/java/android/hardware/input/HostUsiVersion.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.hardware.input;
+
+parcelable HostUsiVersion;
diff --git a/core/java/android/hardware/input/HostUsiVersion.java b/core/java/android/hardware/input/HostUsiVersion.java
new file mode 100644
index 000000000000..8f13d75c30f0
--- /dev/null
+++ b/core/java/android/hardware/input/HostUsiVersion.java
@@ -0,0 +1,204 @@
+/*
+ * 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.hardware.input;
+
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+/**
+ * Provides information about the supported Universal Stylus Initiative (USI) version of the
+ * host device.
+ *
+ * This holds version information about the host device (e.g. the touchscreen/display), not
+ * the USI version of a stylus.
+ *
+ * @see InputManager#getHostUsiVersion(android.view.Display)
+ * @see <a href="https://universalstylus.org">Universal Stylus Initiative</a>
+ */
+@DataClass(genParcelable = true, genHiddenConstructor = true, genToString = true,
+ genEqualsHashCode = true)
+public final class HostUsiVersion implements Parcelable {
+ /**
+ * The major USI version supported by the input device.
+ * For example, if the device supports USI 2.0, this will return 2.
+ */
+ private final int mMajorVersion;
+
+ /**
+ * The minor USI version supported by the input device.
+ * For example, if the device supports USI 2.0, this will return 0.
+ */
+ private final int mMinorVersion;
+
+ /** @hide */
+ public boolean isValid() {
+ return mMajorVersion >= 0 && mMinorVersion >= 0;
+ }
+
+
+
+ // Code below generated by codegen v1.0.23.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/hardware/input/HostUsiVersion.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /**
+ * Creates a new HostUsiVersion.
+ *
+ * @param majorVersion
+ * The major USI version supported by the input device.
+ * For example, if the device supports USI 2.0, this will return 2.
+ * @param minorVersion
+ * The minor USI version supported by the input device.
+ * For example, if the device supports USI 2.0, this will return 0.
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public HostUsiVersion(
+ int majorVersion,
+ int minorVersion) {
+ this.mMajorVersion = majorVersion;
+ this.mMinorVersion = minorVersion;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * The major USI version supported by the input device.
+ * For example, if the device supports USI 2.0, this will return 2.
+ */
+ @DataClass.Generated.Member
+ public int getMajorVersion() {
+ return mMajorVersion;
+ }
+
+ /**
+ * The minor USI version supported by the input device.
+ * For example, if the device supports USI 2.0, this will return 0.
+ */
+ @DataClass.Generated.Member
+ public int getMinorVersion() {
+ return mMinorVersion;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "HostUsiVersion { " +
+ "majorVersion = " + mMajorVersion + ", " +
+ "minorVersion = " + mMinorVersion +
+ " }";
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public boolean equals(@android.annotation.Nullable Object o) {
+ // You can override field equality logic by defining either of the methods like:
+ // boolean fieldNameEquals(HostUsiVersion other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ HostUsiVersion that = (HostUsiVersion) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && mMajorVersion == that.mMajorVersion
+ && mMinorVersion == that.mMinorVersion;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int hashCode() {
+ // You can override field hashCode logic by defining methods like:
+ // int fieldNameHashCode() { ... }
+
+ int _hash = 1;
+ _hash = 31 * _hash + mMajorVersion;
+ _hash = 31 * _hash + mMinorVersion;
+ return _hash;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@android.annotation.NonNull android.os.Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ dest.writeInt(mMajorVersion);
+ dest.writeInt(mMinorVersion);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ HostUsiVersion(@android.annotation.NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ int majorVersion = in.readInt();
+ int minorVersion = in.readInt();
+
+ this.mMajorVersion = majorVersion;
+ this.mMinorVersion = minorVersion;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @android.annotation.NonNull Parcelable.Creator<HostUsiVersion> CREATOR
+ = new Parcelable.Creator<HostUsiVersion>() {
+ @Override
+ public HostUsiVersion[] newArray(int size) {
+ return new HostUsiVersion[size];
+ }
+
+ @Override
+ public HostUsiVersion createFromParcel(@android.annotation.NonNull android.os.Parcel in) {
+ return new HostUsiVersion(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1673884256908L,
+ codegenVersion = "1.0.23",
+ sourceFile = "frameworks/base/core/java/android/hardware/input/HostUsiVersion.java",
+ inputSignatures = "private final int mMajorVersion\nprivate final int mMinorVersion\npublic boolean isValid()\nclass HostUsiVersion extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genHiddenConstructor=true, genToString=true, genEqualsHashCode=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index fb201cfbbe38..6acd8ce63270 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -56,8 +56,10 @@ import android.os.Vibrator;
import android.os.VibratorManager;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
+import android.util.DisplayUtils;
import android.util.Log;
import android.util.SparseArray;
+import android.view.Display;
import android.view.InputDevice;
import android.view.InputEvent;
import android.view.InputMonitor;
@@ -69,6 +71,7 @@ import android.view.WindowManager.LayoutParams;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodSubtype;
+import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.SomeArgs;
@@ -1584,6 +1587,66 @@ public final class InputManager {
}
}
+ /**
+ * Reports the version of the Universal Stylus Initiative (USI) protocol supported by the given
+ * display, if any.
+ *
+ * @return the USI version supported by the display, or null if the device does not support USI
+ * @see <a href="https://universalstylus.org">Universal Stylus Initiative</a>
+ */
+ @Nullable
+ public HostUsiVersion getHostUsiVersion(@NonNull Display display) {
+ Objects.requireNonNull(display, "display should not be null");
+
+ // Return the first valid USI version reported by any input device associated with
+ // the display.
+ synchronized (mInputDevicesLock) {
+ populateInputDevicesLocked();
+
+ for (int i = 0; i < mInputDevices.size(); i++) {
+ final InputDevice device = getInputDevice(mInputDevices.keyAt(i));
+ if (device != null && device.getAssociatedDisplayId() == display.getDisplayId()) {
+ if (device.getHostUsiVersion() != null) {
+ return device.getHostUsiVersion();
+ }
+ }
+ }
+ }
+
+ // If there are no input devices that report a valid USI version, see if there is a config
+ // that specifies the USI version for the display. This is to handle cases where the USI
+ // input device is not registered by the kernel/driver all the time.
+ return findConfigUsiVersionForDisplay(display);
+ }
+
+ private HostUsiVersion findConfigUsiVersionForDisplay(@NonNull Display display) {
+ final Context context = Objects.requireNonNull(ActivityThread.currentApplication());
+ final String[] displayUniqueIds = context.getResources().getStringArray(
+ R.array.config_displayUniqueIdArray);
+ final int index;
+ if (displayUniqueIds.length == 0 && display.getDisplayId() == context.getDisplayId()) {
+ index = 0;
+ } else {
+ index = DisplayUtils.getDisplayUniqueIdConfigIndex(context.getResources(),
+ display.getUniqueId());
+ }
+
+ final String[] versions = context.getResources().getStringArray(
+ R.array.config_displayUsiVersionArray);
+ if (index < 0 || index >= versions.length) {
+ return null;
+ }
+ final String version = versions[index];
+ if (version == null || version.isEmpty()) {
+ return null;
+ }
+ final String[] majorMinor = version.split("\\.");
+ if (majorMinor.length != 2) {
+ throw new IllegalStateException("Failed to parse USI version: " + version);
+ }
+ return new HostUsiVersion(Integer.parseInt(majorMinor[0]), Integer.parseInt(majorMinor[1]));
+ }
+
private void populateInputDevicesLocked() {
if (mInputDevicesChangedListener == null) {
final InputDevicesChangedListener listener = new InputDevicesChangedListener();
diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java
index ed6a88f3693d..d55367f19a70 100644
--- a/core/java/android/inputmethodservice/IInputMethodWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java
@@ -223,13 +223,11 @@ class IInputMethodWrapper extends IInputMethod.Stub
final SomeArgs args = (SomeArgs) msg.obj;
final ImeTracker.Token statsToken = (ImeTracker.Token) args.arg3;
if (isValid(inputMethod, target, "DO_SHOW_SOFT_INPUT")) {
- ImeTracker.forLogging().onProgress(
- statsToken, ImeTracker.PHASE_IME_WRAPPER_DISPATCH);
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_IME_WRAPPER_DISPATCH);
inputMethod.showSoftInputWithToken(
msg.arg1, (ResultReceiver) args.arg2, (IBinder) args.arg1, statsToken);
} else {
- ImeTracker.forLogging().onFailed(
- statsToken, ImeTracker.PHASE_IME_WRAPPER_DISPATCH);
+ ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_IME_WRAPPER_DISPATCH);
}
args.recycle();
return;
@@ -238,13 +236,11 @@ class IInputMethodWrapper extends IInputMethod.Stub
final SomeArgs args = (SomeArgs) msg.obj;
final ImeTracker.Token statsToken = (ImeTracker.Token) args.arg3;
if (isValid(inputMethod, target, "DO_HIDE_SOFT_INPUT")) {
- ImeTracker.forLogging().onProgress(
- statsToken, ImeTracker.PHASE_IME_WRAPPER_DISPATCH);
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_IME_WRAPPER_DISPATCH);
inputMethod.hideSoftInputWithToken(msg.arg1, (ResultReceiver) args.arg2,
(IBinder) args.arg1, statsToken);
} else {
- ImeTracker.forLogging().onFailed(
- statsToken, ImeTracker.PHASE_IME_WRAPPER_DISPATCH);
+ ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_IME_WRAPPER_DISPATCH);
}
args.recycle();
return;
@@ -432,7 +428,7 @@ class IInputMethodWrapper extends IInputMethod.Stub
@Override
public void showSoftInput(IBinder showInputToken, @Nullable ImeTracker.Token statsToken,
int flags, ResultReceiver resultReceiver) {
- ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_IME_WRAPPER);
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_IME_WRAPPER);
mCaller.executeOrSendMessage(mCaller.obtainMessageIOOO(DO_SHOW_SOFT_INPUT,
flags, showInputToken, resultReceiver, statsToken));
}
@@ -441,7 +437,7 @@ class IInputMethodWrapper extends IInputMethod.Stub
@Override
public void hideSoftInput(IBinder hideInputToken, @Nullable ImeTracker.Token statsToken,
int flags, ResultReceiver resultReceiver) {
- ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_IME_WRAPPER);
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_IME_WRAPPER);
mCaller.executeOrSendMessage(mCaller.obtainMessageIOOO(DO_HIDE_SOFT_INPUT,
flags, hideInputToken, resultReceiver, statsToken));
}
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index ee9d8a44167c..872414a7c147 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -896,8 +896,7 @@ public class InputMethodService extends AbstractInputMethodService {
@MainThread
@Override
public void hideSoftInput(int flags, ResultReceiver resultReceiver) {
- ImeTracker.forLogging().onProgress(
- mCurStatsToken, ImeTracker.PHASE_IME_HIDE_SOFT_INPUT);
+ ImeTracker.get().onProgress(mCurStatsToken, ImeTracker.PHASE_IME_HIDE_SOFT_INPUT);
if (DEBUG) Log.v(TAG, "hideSoftInput()");
if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R
&& !mSystemCallingHideSoftInput) {
@@ -951,8 +950,7 @@ public class InputMethodService extends AbstractInputMethodService {
@MainThread
@Override
public void showSoftInput(int flags, ResultReceiver resultReceiver) {
- ImeTracker.forLogging().onProgress(
- mCurStatsToken, ImeTracker.PHASE_IME_SHOW_SOFT_INPUT);
+ ImeTracker.get().onProgress(mCurStatsToken, ImeTracker.PHASE_IME_SHOW_SOFT_INPUT);
if (DEBUG) Log.v(TAG, "showSoftInput()");
// TODO(b/148086656): Disallow IME developers from calling InputMethodImpl methods.
if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R
@@ -968,11 +966,11 @@ public class InputMethodService extends AbstractInputMethodService {
null /* icProto */);
final boolean wasVisible = isInputViewShown();
if (dispatchOnShowInputRequested(flags, false)) {
- ImeTracker.forLogging().onProgress(mCurStatsToken,
+ ImeTracker.get().onProgress(mCurStatsToken,
ImeTracker.PHASE_IME_ON_SHOW_SOFT_INPUT_TRUE);
showWindow(true);
} else {
- ImeTracker.forLogging().onFailed(mCurStatsToken,
+ ImeTracker.get().onFailed(mCurStatsToken,
ImeTracker.PHASE_IME_ON_SHOW_SOFT_INPUT_TRUE);
}
setImeWindowStatus(mapToImeWindowStatus(), mBackDisposition);
@@ -2981,7 +2979,7 @@ public class InputMethodService extends AbstractInputMethodService {
ImeTracing.getInstance().triggerServiceDump(
"InputMethodService#applyVisibilityInInsetsConsumerIfNecessary", mDumper,
null /* icProto */);
- ImeTracker.forLogging().onProgress(mCurStatsToken,
+ ImeTracker.get().onProgress(mCurStatsToken,
ImeTracker.PHASE_IME_APPLY_VISIBILITY_INSETS_CONSUMER);
mPrivOps.applyImeVisibilityAsync(setVisible
? mCurShowInputToken : mCurHideInputToken, setVisible, mCurStatsToken);
diff --git a/core/java/android/nfc/OWNERS b/core/java/android/nfc/OWNERS
index 6aaf039244c9..9a2e44665ec8 100644
--- a/core/java/android/nfc/OWNERS
+++ b/core/java/android/nfc/OWNERS
@@ -1,4 +1,5 @@
# Bug component: 48448
-zachoverflow@google.com
+sattiraju@google.com
+henrichataing@google.com
alisher@google.com
diff --git a/core/java/android/nfc/cardemulation/OWNERS b/core/java/android/nfc/cardemulation/OWNERS
index 6aaf039244c9..9a2e44665ec8 100644
--- a/core/java/android/nfc/cardemulation/OWNERS
+++ b/core/java/android/nfc/cardemulation/OWNERS
@@ -1,4 +1,5 @@
# Bug component: 48448
-zachoverflow@google.com
+sattiraju@google.com
+henrichataing@google.com
alisher@google.com
diff --git a/core/java/android/nfc/dta/OWNERS b/core/java/android/nfc/dta/OWNERS
index 6aaf039244c9..9a2e44665ec8 100644
--- a/core/java/android/nfc/dta/OWNERS
+++ b/core/java/android/nfc/dta/OWNERS
@@ -1,4 +1,5 @@
# Bug component: 48448
-zachoverflow@google.com
+sattiraju@google.com
+henrichataing@google.com
alisher@google.com
diff --git a/core/java/android/nfc/tech/OWNERS b/core/java/android/nfc/tech/OWNERS
index 6aaf039244c9..9a2e44665ec8 100644
--- a/core/java/android/nfc/tech/OWNERS
+++ b/core/java/android/nfc/tech/OWNERS
@@ -1,4 +1,5 @@
# Bug component: 48448
-zachoverflow@google.com
+sattiraju@google.com
+henrichataing@google.com
alisher@google.com
diff --git a/core/java/android/os/IncidentManager.java b/core/java/android/os/IncidentManager.java
index a543a2d6a983..b97993af6b8e 100644
--- a/core/java/android/os/IncidentManager.java
+++ b/core/java/android/os/IncidentManager.java
@@ -119,6 +119,19 @@ public class IncidentManager {
public static final int FLAG_CONFIRMATION_DIALOG = 0x1;
/**
+ * Flag marking whether corresponding pending report allows consentless bugreport.
+ */
+ public static final int FLAG_ALLOW_CONSENTLESS_BUGREPORT = 0x2;
+
+ /** @hide */
+ @IntDef(flag = true, prefix = { "FLAG_" }, value = {
+ FLAG_CONFIRMATION_DIALOG,
+ FLAG_ALLOW_CONSENTLESS_BUGREPORT,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PendingReportFlags {}
+
+ /**
* Flag marking fields and incident reports than can be taken
* off the device only via adb.
*/
@@ -220,8 +233,9 @@ public class IncidentManager {
/**
* Get the flags requested for this pending report.
*
- * @see #FLAG_CONFIRMATION_DIALOG
+ * @see PendingReportFlags
*/
+ @PendingReportFlags
public int getFlags() {
return mFlags;
}
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 62d8fb29e697..9a25c703003c 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -65,7 +65,6 @@ import android.util.Log;
import android.view.WindowManager.LayoutParams;
import com.android.internal.R;
-import com.android.internal.os.RoSystemProperties;
import com.android.internal.util.FrameworkStatsLog;
import java.io.IOException;
@@ -2111,17 +2110,6 @@ public class UserManager {
}
/**
- * @hide
- * @return Whether the device is running with split system user. It means the system user and
- * primary user are two separate users. Previously system user and primary user are combined as
- * a single owner user. see @link {android.os.UserHandle#USER_OWNER}
- */
- @TestApi
- public static boolean isSplitSystemUser() {
- return RoSystemProperties.FW_SYSTEM_USER_SPLIT;
- }
-
- /**
* @return Whether guest user is always ephemeral
* @hide
*/
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index d113ca00e1c3..62b99ba2ef53 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1060,8 +1060,13 @@ public final class Settings {
* In some cases, a matching Activity may not exist, so ensure you
* safeguard against this.
* <p>
- * Input: Nothing.
- * <p>
+ * Input: The optional {@code #EXTRA_EXPLICIT_LOCALES} with language tags that contains locales
+ * to limit available locales. This is only supported when device is under demo mode.
+ * If intent does not contain this extra, it will show system supported locale list.
+ * <br/>
+ * If {@code #EXTRA_EXPLICIT_LOCALES} contain a unsupported locale, it will still show this
+ * locale on list, but may not be supported by the devcie.
+ *
* Output: Nothing.
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
@@ -1069,6 +1074,18 @@ public final class Settings {
"android.settings.LOCALE_SETTINGS";
/**
+ * Activity Extra: Show explicit locales in launched locale picker activity.
+ *
+ * This can be passed as an extra field in an Activity Intent with one or more language tags
+ * as a {@link LocaleList}. This must be passed as an extra field to the
+ * {@link #ACTION_LOCALE_SETTINGS}.
+ *
+ * @hide
+ */
+ public static final String EXTRA_EXPLICIT_LOCALES =
+ "android.provider.extra.EXPLICIT_LOCALES";
+
+ /**
* Activity Action: Show settings to allow configuration of per application locale.
* <p>
* Input: The Intent's data URI can specify the application package name to directly invoke the
@@ -6142,7 +6159,6 @@ public final class Settings {
MOVED_TO_GLOBAL.add(Settings.Global.ADB_ENABLED);
MOVED_TO_GLOBAL.add(Settings.Global.ASSISTED_GPS_ENABLED);
MOVED_TO_GLOBAL.add(Settings.Global.BLUETOOTH_ON);
- MOVED_TO_GLOBAL.add(Settings.Global.BUGREPORT_IN_POWER_MENU);
MOVED_TO_GLOBAL.add(Settings.Global.CDMA_CELL_BROADCAST_SMS);
MOVED_TO_GLOBAL.add(Settings.Global.CDMA_ROAMING_MODE);
MOVED_TO_GLOBAL.add(Settings.Global.CDMA_SUBSCRIPTION_MODE);
@@ -6763,14 +6779,26 @@ public final class Settings {
/**
* When the user has enable the option to have a "bug report" command
* in the power menu.
- * @deprecated Use {@link android.provider.Settings.Global#BUGREPORT_IN_POWER_MENU} instead
* @hide
*/
- @Deprecated
@Readable
public static final String BUGREPORT_IN_POWER_MENU = "bugreport_in_power_menu";
/**
+ * The package name for the custom bugreport handler app. This app must be bugreport
+ * allow-listed. This is currently used only by Power Menu short press.
+ * @hide
+ */
+ public static final String CUSTOM_BUGREPORT_HANDLER_APP = "custom_bugreport_handler_app";
+
+ /**
+ * The user id for the custom bugreport handler app. This is currently used only by Power
+ * Menu short press.
+ * @hide
+ */
+ public static final String CUSTOM_BUGREPORT_HANDLER_USER = "custom_bugreport_handler_user";
+
+ /**
* @deprecated Use {@link android.provider.Settings.Global#ADB_ENABLED} instead
*/
@Deprecated
@@ -11721,26 +11749,32 @@ public final class Settings {
/**
* When the user has enable the option to have a "bug report" command
* in the power menu.
+ * @deprecated Use {@link android.provider.Settings.Secure#BUGREPORT_IN_POWER_MENU} instead
* @hide
*/
+ @Deprecated
@Readable
public static final String BUGREPORT_IN_POWER_MENU = "bugreport_in_power_menu";
/**
* The package name for the custom bugreport handler app. This app must be whitelisted.
* This is currently used only by Power Menu short press.
- *
+ * @deprecated Use {@link android.provider.Settings.Secure#CUSTOM_BUGREPORT_HANDLER_APP}
+ * instead
* @hide
*/
+ @Deprecated
@Readable
public static final String CUSTOM_BUGREPORT_HANDLER_APP = "custom_bugreport_handler_app";
/**
* The user id for the custom bugreport handler app. This is currently used only by Power
* Menu short press.
- *
+ * @deprecated Use {@link android.provider.Settings.Secure#CUSTOM_BUGREPORT_HANDLER_USER}
+ * instead
* @hide
*/
+ @Deprecated
@Readable
public static final String CUSTOM_BUGREPORT_HANDLER_USER = "custom_bugreport_handler_user";
@@ -16364,6 +16398,9 @@ public final class Settings {
MOVED_TO_SECURE.add(Global.CHARGING_SOUNDS_ENABLED);
MOVED_TO_SECURE.add(Global.CHARGING_VIBRATION_ENABLED);
MOVED_TO_SECURE.add(Global.NOTIFICATION_BUBBLES);
+ MOVED_TO_SECURE.add(Global.BUGREPORT_IN_POWER_MENU);
+ MOVED_TO_SECURE.add(Global.CUSTOM_BUGREPORT_HANDLER_APP);
+ MOVED_TO_SECURE.add(Global.CUSTOM_BUGREPORT_HANDLER_USER);
}
// Certain settings have been moved from global to the per-user system namespace
@@ -17964,6 +18001,28 @@ public final class Settings {
public static final String OEM_SETUP_VERSION = "oem_setup_version";
/**
+ * The key to indicate to Setup Wizard if OEM setup is completed in Wear Services.
+ * @hide
+ */
+ public static final String OEM_SETUP_COMPLETED_STATUS = "oem_setup_completed_status";
+
+ /**
+ * Constant provided to Setup Wizard to inform about failure of OEM setup in Wear
+ * Services. The value should be provided with setting name {@link
+ * #OEM_SETUP_COMPLETED_STATUS}.
+ * @hide
+ */
+ public static final int OEM_SETUP_COMPLETED_FAILURE = 0;
+
+ /**
+ * Constant provided to Setup Wizard to inform about successful completion of OEM setup
+ * in Wear Services. The value should be provided with setting name {@link
+ * #OEM_SETUP_COMPLETED_STATUS}.
+ * @hide
+ */
+ public static final int OEM_SETUP_COMPLETED_SUCCESS = 1;
+
+ /**
* Controls the gestures feature.
* @hide
*/
diff --git a/core/java/android/service/quickaccesswallet/WalletCard.java b/core/java/android/service/quickaccesswallet/WalletCard.java
index b09d2e9e7f13..950f9b5a2e6d 100644
--- a/core/java/android/service/quickaccesswallet/WalletCard.java
+++ b/core/java/android/service/quickaccesswallet/WalletCard.java
@@ -16,6 +16,7 @@
package android.service.quickaccesswallet;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.PendingIntent;
@@ -24,28 +25,70 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+
/**
* A {@link WalletCard} can represent anything that a user might carry in their wallet -- a credit
* card, library card, transit pass, etc. Cards are identified by a String identifier and contain a
- * card image, card image content description, and a {@link PendingIntent} to be used if the user
- * clicks on the card. Cards may be displayed with an icon and label, though these are optional.
+ * card type, card image, card image content description, and a {@link PendingIntent} to be used if
+ * the user clicks on the card. Cards may be displayed with an icon and label, though these are
+ * optional. Valuable cards will also have a second image that will be displayed when the card is
+ * tapped.
*/
+
public final class WalletCard implements Parcelable {
+ /**
+ * Unknown cards refer to cards whose types are unspecified.
+ */
+ public static final int CARD_TYPE_UNKNOWN = 0;
+
+ /**
+ * Payment cards refer to credit cards, debit cards or any other cards in the wallet used to
+ * make cash-equivalent payments.
+ */
+ public static final int CARD_TYPE_PAYMENT = 1;
+
+ /**
+ * Valuable cards refer to any cards that are not used for cash-equivalent payment.
+ * This includes event tickets, flights, offers, loyalty cards, gift cards and transit tickets.
+ */
+ public static final int CARD_TYPE_VALUABLE = 2;
+
private final String mCardId;
+ private final int mCardType;
private final Icon mCardImage;
private final CharSequence mContentDescription;
private final PendingIntent mPendingIntent;
private final Icon mCardIcon;
private final CharSequence mCardLabel;
+ private final Icon mValuableCardSecondaryImage;
private WalletCard(Builder builder) {
this.mCardId = builder.mCardId;
+ this.mCardType = builder.mCardType;
this.mCardImage = builder.mCardImage;
this.mContentDescription = builder.mContentDescription;
this.mPendingIntent = builder.mPendingIntent;
this.mCardIcon = builder.mCardIcon;
this.mCardLabel = builder.mCardLabel;
+ this.mValuableCardSecondaryImage = builder.mValuableCardSecondaryImage;
+ }
+
+ /**
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"CARD_TYPE_"}, value = {
+ CARD_TYPE_UNKNOWN,
+ CARD_TYPE_PAYMENT,
+ CARD_TYPE_VALUABLE
+ })
+ public @interface CardType {
}
@Override
@@ -56,29 +99,44 @@ public final class WalletCard implements Parcelable {
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString(mCardId);
+ dest.writeInt(mCardType);
mCardImage.writeToParcel(dest, flags);
TextUtils.writeToParcel(mContentDescription, dest, flags);
PendingIntent.writePendingIntentOrNullToParcel(mPendingIntent, dest);
- if (mCardIcon == null) {
+ writeIconIfNonNull(mCardIcon, dest, flags);
+ TextUtils.writeToParcel(mCardLabel, dest, flags);
+ writeIconIfNonNull(mValuableCardSecondaryImage, dest, flags);
+
+ }
+
+ /** Utility function called by writeToParcel
+ */
+ private void writeIconIfNonNull(Icon icon, Parcel dest, int flags) {
+ if (icon == null) {
dest.writeByte((byte) 0);
} else {
dest.writeByte((byte) 1);
- mCardIcon.writeToParcel(dest, flags);
+ icon.writeToParcel(dest, flags);
}
- TextUtils.writeToParcel(mCardLabel, dest, flags);
}
private static WalletCard readFromParcel(Parcel source) {
String cardId = source.readString();
+ int cardType = source.readInt();
Icon cardImage = Icon.CREATOR.createFromParcel(source);
CharSequence contentDesc = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
PendingIntent pendingIntent = PendingIntent.readPendingIntentOrNullFromParcel(source);
Icon cardIcon = source.readByte() == 0 ? null : Icon.CREATOR.createFromParcel(source);
CharSequence cardLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
- return new Builder(cardId, cardImage, contentDesc, pendingIntent)
+ Icon valuableCardSecondaryImage = source.readByte() == 0 ? null :
+ Icon.CREATOR.createFromParcel(source);
+ Builder builder = new Builder(cardId, cardType, cardImage, contentDesc, pendingIntent)
.setCardIcon(cardIcon)
- .setCardLabel(cardLabel)
- .build();
+ .setCardLabel(cardLabel);
+
+ return cardType == CARD_TYPE_VALUABLE
+ ? builder.setValuableCardSecondaryImage(valuableCardSecondaryImage).build() :
+ builder.build();
}
@NonNull
@@ -104,6 +162,15 @@ public final class WalletCard implements Parcelable {
}
/**
+ * Returns the card type.
+ */
+ @NonNull
+ @CardType
+ public int getCardType() {
+ return mCardType;
+ }
+
+ /**
* The visual representation of the card. If the card image Icon is a bitmap, it should have a
* width of {@link GetWalletCardsRequest#getCardWidthPx()} and a height of {@link
* GetWalletCardsRequest#getCardHeightPx()}.
@@ -158,23 +225,36 @@ public final class WalletCard implements Parcelable {
}
/**
- * Builder for {@link WalletCard} objects. You must to provide cardId, cardImage,
+ * Visual representation of the card when it is tapped. Includes a barcode to scan the card in
+ * addition to the information in the primary image.
+ */
+ @Nullable
+ public Icon getValuableCardSecondaryImage() {
+ return mValuableCardSecondaryImage;
+ }
+
+ /**
+ * Builder for {@link WalletCard} objects. You must provide cardId, cardImage,
* contentDescription, and pendingIntent. If the card is opaque and should be shown with
* elevation, set hasShadow to true. cardIcon and cardLabel are optional.
*/
public static final class Builder {
private String mCardId;
+ private int mCardType;
private Icon mCardImage;
private CharSequence mContentDescription;
private PendingIntent mPendingIntent;
private Icon mCardIcon;
private CharSequence mCardLabel;
+ private Icon mValuableCardSecondaryImage;
/**
* @param cardId The card id must be non-null and unique within the list of
* cards returned. <b>Note:
* </b> this card ID should <b>not</b> contain PII (Personally
* Identifiable Information, such as username or email address).
+ * @param cardType Integer representing the card type. The card type must be
+ * non-null. If not provided, it defaults to unknown.
* @param cardImage The visual representation of the card. If the card image Icon
* is a bitmap, it should have a width of {@link
* GetWalletCardsRequest#getCardWidthPx()} and a height of {@link
@@ -193,18 +273,34 @@ public final class WalletCard implements Parcelable {
* request device unlock before sending the pending intent. It is
* recommended that the pending intent be immutable (use {@link
* PendingIntent#FLAG_IMMUTABLE}).
+ *
*/
public Builder(@NonNull String cardId,
+ @NonNull @CardType int cardType,
@NonNull Icon cardImage,
@NonNull CharSequence contentDescription,
- @NonNull PendingIntent pendingIntent) {
+ @NonNull PendingIntent pendingIntent
+ ) {
mCardId = cardId;
+ mCardType = cardType;
mCardImage = cardImage;
mContentDescription = contentDescription;
mPendingIntent = pendingIntent;
}
/**
+ * Called when a card type is not provided. Calls {@link
+ * Builder#Builder(String, int, Icon, CharSequence, PendingIntent)} with default card type
+ */
+ public Builder(@NonNull String cardId,
+ @NonNull Icon cardImage,
+ @NonNull CharSequence contentDescription,
+ @NonNull PendingIntent pendingIntent) {
+ this(cardId, WalletCard.CARD_TYPE_UNKNOWN, cardImage, contentDescription,
+ pendingIntent);
+ }
+
+ /**
* An icon may be shown alongside the card image to convey information about how the card
* can be used, or if some other action must be taken before using the card. For example, an
* NFC logo could indicate that the card is NFC-enabled and will be provided to an NFC
@@ -236,6 +332,18 @@ public final class WalletCard implements Parcelable {
}
/**
+ * Visual representation of the card when it is tapped. Includes a barcode to scan the card
+ * in addition to the information in the primary image.
+ */
+ @NonNull
+ public Builder setValuableCardSecondaryImage(@Nullable Icon valuableCardSecondaryImage) {
+ Preconditions.checkState(mCardType == CARD_TYPE_VALUABLE,
+ "This field can only be set on valuable cards");
+ mValuableCardSecondaryImage = valuableCardSecondaryImage;
+ return this;
+ }
+
+ /**
* Builds a new {@link WalletCard} instance.
*
* @return A built response.
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index df739e357144..ca4716bacc2f 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -95,20 +95,6 @@ public class VoiceInteractionService extends Service {
public static final String SERVICE_META_DATA = "android.voice_interaction";
/**
- * Bundle key used to specify the id when the system prepares to show session. It increases for
- * each request.
- * <p>
- * Type: int
- * </p>
- * @see #showSession(Bundle, int)
- * @see #onPrepareToShowSession(Bundle, int)
- * @see #onShowSessionFailed(Bundle)
- * @see VoiceInteractionSession#onShow(Bundle, int)
- * @see VoiceInteractionSession#show(Bundle, int)
- */
- public static final String KEY_SHOW_SESSION_ID = "android.service.voice.SHOW_SESSION_ID";
-
- /**
* For apps targeting Build.VERSION_CODES.TRAMISU and above, implementors of this
* service can create multiple AlwaysOnHotwordDetector instances in parallel. They will
* also e ale to create a single SoftwareHotwordDetector in parallel with any other
@@ -219,7 +205,7 @@ public class VoiceInteractionService extends Service {
* bind the session service.
*
* @param args The arguments that were supplied to {@link #showSession(Bundle, int)}.
- * It always includes {@link #KEY_SHOW_SESSION_ID}.
+ * It always includes {@link VoiceInteractionSession#KEY_SHOW_SESSION_ID}.
* @param flags The show flags originally provided to {@link #showSession(Bundle, int)}.
* @see #showSession(Bundle, int)
* @see #onShowSessionFailed(Bundle)
@@ -233,7 +219,7 @@ public class VoiceInteractionService extends Service {
* Called when the show session failed. E.g. When the system bound the session service failed.
*
* @param args Additional info about the show session attempt that failed. For now, includes
- * {@link #KEY_SHOW_SESSION_ID}.
+ * {@link VoiceInteractionSession#KEY_SHOW_SESSION_ID}.
* @see #showSession(Bundle, int)
* @see #onPrepareToShowSession(Bundle, int)
* @see VoiceInteractionSession#onShow(Bundle, int)
diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java
index d55fedefb5cb..0d513951a4a7 100644
--- a/core/java/android/service/voice/VoiceInteractionSession.java
+++ b/core/java/android/service/voice/VoiceInteractionSession.java
@@ -169,6 +169,20 @@ public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCall
@Retention(RetentionPolicy.SOURCE)
public @interface VoiceInteractionActivityEventType{}
+ /**
+ * Bundle key used to specify the id when the system prepares to show session. It increases for
+ * each request.
+ * <p>
+ * Type: int
+ * </p>
+ * @see VoiceInteractionService#showSession(Bundle, int)
+ * @see VoiceInteractionService#onPrepareToShowSession(Bundle, int)
+ * @see VoiceInteractionService#onShowSessionFailed(Bundle)
+ * @see #onShow(Bundle, int)
+ * @see #show(Bundle, int)
+ */
+ public static final String KEY_SHOW_SESSION_ID = "android.service.voice.SHOW_SESSION_ID";
+
final Context mContext;
final HandlerCaller mHandlerCaller;
@@ -1763,7 +1777,7 @@ public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCall
* @param args The arguments that were supplied to
* {@link VoiceInteractionService#showSession VoiceInteractionService.showSession}.
* Some example keys include : "invocation_type", "invocation_phone_state",
- * {@link VoiceInteractionService#KEY_SHOW_SESSION_ID}, "invocation_time_ms",
+ * {@link #KEY_SHOW_SESSION_ID}, "invocation_time_ms",
* Intent.EXTRA_TIME ("android.intent.extra.TIME") indicating timing
* in milliseconds of the KeyEvent that triggered Assistant and
* Intent.EXTRA_ASSIST_INPUT_DEVICE_ID (android.intent.extra.ASSIST_INPUT_DEVICE_ID)
diff --git a/core/java/android/text/method/InsertModeTransformationMethod.java b/core/java/android/text/method/InsertModeTransformationMethod.java
new file mode 100644
index 000000000000..fbdaa3d735f4
--- /dev/null
+++ b/core/java/android/text/method/InsertModeTransformationMethod.java
@@ -0,0 +1,440 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.text.method;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.text.Editable;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.Spanned;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.text.style.ReplacementSpan;
+import android.util.DisplayMetrics;
+import android.util.MathUtils;
+import android.util.TypedValue;
+import android.view.View;
+
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.Preconditions;
+
+/**
+ * The transformation method used by handwriting insert mode.
+ * This transformation will insert a placeholder string to the original text at the given
+ * offset. And it also provides a highlight range for the newly inserted text and the placeholder
+ * text.
+ *
+ * For example,
+ * original text: "Hello world"
+ * insert mode is started at index: 5,
+ * placeholder text: "\n\n"
+ * The transformed text will be: "Hello\n\n world", and the highlight range will be [5, 7)
+ * including the inserted placeholder text.
+ *
+ * If " abc" is inserted to the original text at index 5,
+ * the new original text: "Hello abc world"
+ * the new transformed text: "hello abc\n\n world", and the highlight range will be [5, 11).
+ * @hide
+ */
+public class InsertModeTransformationMethod implements TransformationMethod, TextWatcher {
+ /** The start offset of the highlight range in the original text, inclusive. */
+ private int mStart;
+ /**
+ * The end offset of the highlight range in the original text, exclusive. The placeholder text
+ * is also inserted at this index.
+ */
+ private int mEnd;
+ /** The transformation method that's already set on the {@link android.widget.TextView}. */
+ private final TransformationMethod mOldTransformationMethod;
+ /** Whether the {@link android.widget.TextView} is single-lined. */
+ private final boolean mSingleLine;
+
+ /**
+ * @param offset the original offset to start the insert mode. It must be in the range from 0
+ * to the length of the transformed text.
+ * @param singleLine whether the text is single line.
+ * @param oldTransformationMethod the old transformation method at the
+ * {@link android.widget.TextView}. If it's not null, this {@link TransformationMethod} will
+ * first call {@link TransformationMethod#getTransformation(CharSequence, View)} on the old one,
+ * and then do the transformation for the insert mode.
+ *
+ */
+ public InsertModeTransformationMethod(@IntRange(from = 0) int offset, boolean singleLine,
+ @NonNull TransformationMethod oldTransformationMethod) {
+ mStart = offset;
+ mEnd = offset;
+ mSingleLine = singleLine;
+ mOldTransformationMethod = oldTransformationMethod;
+ }
+
+ public TransformationMethod getOldTransformationMethod() {
+ return mOldTransformationMethod;
+ }
+
+ private CharSequence getPlaceholderText(View view) {
+ if (!mSingleLine) {
+ return "\n\n";
+ }
+ final SpannableString singleLinePlaceholder = new SpannableString("\uFFFD");
+ final DisplayMetrics displayMetrics = view.getResources().getDisplayMetrics();
+ final int widthPx = (int) Math.ceil(
+ TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 108, displayMetrics));
+
+ singleLinePlaceholder.setSpan(new SingleLinePlaceholderSpan(widthPx), 0, 1,
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ return singleLinePlaceholder;
+ }
+
+ @Override
+ public CharSequence getTransformation(CharSequence source, View view) {
+ final CharSequence charSequence;
+ if (mOldTransformationMethod != null) {
+ charSequence = mOldTransformationMethod.getTransformation(source, view);
+ if (source instanceof Spannable) {
+ final Spannable spannable = (Spannable) source;
+ spannable.setSpan(mOldTransformationMethod, 0, spannable.length(),
+ Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+ }
+ } else {
+ charSequence = source;
+ }
+
+ final CharSequence placeholderText = getPlaceholderText(view);
+ return new TransformedText(charSequence, placeholderText);
+ }
+
+ @Override
+ public void onFocusChanged(View view, CharSequence sourceText, boolean focused, int direction,
+ Rect previouslyFocusedRect) {
+ if (mOldTransformationMethod != null) {
+ mOldTransformationMethod.onFocusChanged(view, sourceText, focused, direction,
+ previouslyFocusedRect);
+ }
+ }
+
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ // The text change is after the offset where placeholder is inserted, return.
+ if (start > mEnd) return;
+ final int diff = count - before;
+
+ // Note: If start == mStart and before == 0, the change is also considered after the
+ // highlight start. It won't modify the mStart in this case.
+ if (start < mStart) {
+ if (start + before <= mStart) {
+ // The text change is before the highlight start, move the highlight start.
+ mStart += diff;
+ } else {
+ // The text change covers the highlight start. Extend the highlight start to the
+ // change start. This should be a rare case.
+ mStart = start;
+ }
+ }
+
+ if (start + before <= mEnd) {
+ // The text change is before the highlight end, move the highlight end.
+ mEnd += diff;
+ } else if (start < mEnd) {
+ // The text change covers the highlight end. Extend the highlight end to the
+ // change end. This should be a rare case.
+ mEnd = start + count;
+ }
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) { }
+
+ /**
+ * The transformed text returned by the {@link InsertModeTransformationMethod}.
+ */
+ public class TransformedText implements OffsetMapping, Spanned {
+ private final CharSequence mOriginal;
+ private final CharSequence mPlaceholder;
+ private final Spanned mSpannedOriginal;
+ private final Spanned mSpannedPlaceholder;
+
+ TransformedText(CharSequence original, CharSequence placeholder) {
+ mOriginal = original;
+ if (original instanceof Spanned) {
+ mSpannedOriginal = (Spanned) original;
+ } else {
+ mSpannedOriginal = null;
+ }
+ mPlaceholder = placeholder;
+ if (placeholder instanceof Spanned) {
+ mSpannedPlaceholder = (Spanned) placeholder;
+ } else {
+ mSpannedPlaceholder = null;
+ }
+ }
+
+ @Override
+ public int originalToTransformed(int offset, int strategy) {
+ if (offset < 0) return offset;
+ Preconditions.checkArgumentInRange(offset, 0, mOriginal.length(), "offset");
+ if (offset == mEnd && strategy == OffsetMapping.MAP_STRATEGY_CURSOR) {
+ // The offset equals to mEnd. For a cursor position it's considered before the
+ // inserted placeholder text.
+ return offset;
+ }
+ if (offset < mEnd) {
+ return offset;
+ }
+ return offset + mPlaceholder.length();
+ }
+
+ @Override
+ public int transformedToOriginal(int offset, int strategy) {
+ if (offset < 0) return offset;
+ Preconditions.checkArgumentInRange(offset, 0, length(), "offset");
+
+ // The placeholder text is inserted at mEnd. Because the offset is smaller than
+ // mEnd, we can directly return it.
+ if (offset < mEnd) return offset;
+ if (offset < mEnd + mPlaceholder.length()) {
+ return mEnd;
+ }
+ return offset - mPlaceholder.length();
+ }
+
+ @Override
+ public void originalToTransformed(TextUpdate textUpdate) {
+ if (textUpdate.where > mEnd) {
+ textUpdate.where += mPlaceholder.length();
+ } else if (textUpdate.where + textUpdate.before > mEnd) {
+ // The update also covers the placeholder string.
+ textUpdate.before += mPlaceholder.length();
+ textUpdate.after += mPlaceholder.length();
+ }
+ }
+
+ @Override
+ public int length() {
+ return mOriginal.length() + mPlaceholder.length();
+ }
+
+ @Override
+ public char charAt(int index) {
+ Preconditions.checkArgumentInRange(index, 0, length() - 1, "index");
+ if (index < mEnd) {
+ return mOriginal.charAt(index);
+ }
+ if (index < mEnd + mPlaceholder.length()) {
+ return mPlaceholder.charAt(index - mEnd);
+ }
+ return mOriginal.charAt(index - mPlaceholder.length());
+ }
+
+ @Override
+ public CharSequence subSequence(int start, int end) {
+ if (end < start || start < 0 || end > length()) {
+ throw new IndexOutOfBoundsException();
+ }
+ if (start == end) {
+ return "";
+ }
+
+ final int placeholderLength = mPlaceholder.length();
+
+ final int seg1Start = Math.min(start, mEnd);
+ final int seg1End = Math.min(end, mEnd);
+
+ final int seg2Start = MathUtils.constrain(start - mEnd, 0, placeholderLength);
+ final int seg2End = MathUtils.constrain(end - mEnd, 0, placeholderLength);
+
+ final int seg3Start = Math.max(start - placeholderLength, mEnd);
+ final int seg3End = Math.max(end - placeholderLength, mEnd);
+
+ return TextUtils.concat(
+ mOriginal.subSequence(seg1Start, seg1End),
+ mPlaceholder.subSequence(seg2Start, seg2End),
+ mOriginal.subSequence(seg3Start, seg3End));
+ }
+
+ @Override
+ public String toString() {
+ return String.valueOf(mOriginal.subSequence(0, mEnd))
+ + mPlaceholder
+ + mOriginal.subSequence(mEnd, mOriginal.length());
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public <T> T[] getSpans(int start, int end, Class<T> type) {
+ if (end < start) {
+ return ArrayUtils.emptyArray(type);
+ }
+
+ final T[] spansOriginal;
+ if (mSpannedOriginal != null) {
+ final int originalStart =
+ transformedToOriginal(start, OffsetMapping.MAP_STRATEGY_CURSOR);
+ final int originalEnd =
+ transformedToOriginal(end, OffsetMapping.MAP_STRATEGY_CURSOR);
+ spansOriginal = mSpannedOriginal.getSpans(originalStart, originalEnd, type);
+ } else {
+ spansOriginal = null;
+ }
+
+ final T[] spansPlaceholder;
+ if (mSpannedPlaceholder != null
+ && intersect(start, end, mEnd, mEnd + mPlaceholder.length())) {
+ final int placeholderStart = Math.max(start - mEnd, 0);
+ final int placeholderEnd = Math.min(end - mEnd, mPlaceholder.length());
+ spansPlaceholder =
+ mSpannedPlaceholder.getSpans(placeholderStart, placeholderEnd, type);
+ } else {
+ spansPlaceholder = null;
+ }
+
+ // TODO: sort the spans based on their priority.
+ return ArrayUtils.concat(type, spansOriginal, spansPlaceholder);
+ }
+
+ @Override
+ public int getSpanStart(Object tag) {
+ if (mSpannedOriginal != null) {
+ final int index = mSpannedOriginal.getSpanStart(tag);
+ if (index >= 0) {
+ if (index < mEnd) {
+ return index;
+ }
+ return index + mPlaceholder.length();
+ }
+ }
+
+ // The span is not on original text, try find it on the placeholder.
+ if (mSpannedPlaceholder != null) {
+ final int index = mSpannedPlaceholder.getSpanStart(tag);
+ if (index >= 0) {
+ // Find the span on placeholder, transform it and return.
+ return index + mEnd;
+ }
+ }
+ return -1;
+ }
+
+ @Override
+ public int getSpanEnd(Object tag) {
+ if (mSpannedOriginal != null) {
+ final int index = mSpannedOriginal.getSpanEnd(tag);
+ if (index >= 0) {
+ if (index <= mEnd) {
+ return index;
+ }
+ return index + mPlaceholder.length();
+ }
+ }
+
+ // The span is not on original text, try find it on the placeholder.
+ if (mSpannedPlaceholder != null) {
+ final int index = mSpannedPlaceholder.getSpanEnd(tag);
+ if (index >= 0) {
+ // Find the span on placeholder, transform it and return.
+ return index + mEnd;
+ }
+ }
+ return -1;
+ }
+
+ @Override
+ public int getSpanFlags(Object tag) {
+ if (mSpannedOriginal != null) {
+ final int flags = mSpannedOriginal.getSpanFlags(tag);
+ if (flags != 0) {
+ return flags;
+ }
+ }
+ if (mSpannedPlaceholder != null) {
+ return mSpannedPlaceholder.getSpanFlags(tag);
+ }
+ return 0;
+ }
+
+ @Override
+ public int nextSpanTransition(int start, int limit, Class type) {
+ if (limit <= start) return limit;
+ final Object[] spans = getSpans(start, limit, type);
+ for (int i = 0; i < spans.length; ++i) {
+ int spanStart = getSpanStart(spans[i]);
+ int spanEnd = getSpanEnd(spans[i]);
+ if (start < spanStart && spanStart < limit) {
+ limit = spanStart;
+ }
+ if (start < spanEnd && spanEnd < limit) {
+ limit = spanEnd;
+ }
+ }
+ return limit;
+ }
+
+ /**
+ * Return the start index of the highlight range for the insert mode, inclusive.
+ */
+ public int getHighlightStart() {
+ return mStart;
+ }
+
+ /**
+ * Return the end index of the highlight range for the insert mode, exclusive.
+ */
+ public int getHighlightEnd() {
+ return mEnd + mPlaceholder.length();
+ }
+ }
+
+ /**
+ * The placeholder span used for single line
+ */
+ public static class SingleLinePlaceholderSpan extends ReplacementSpan {
+ private final int mWidth;
+ SingleLinePlaceholderSpan(int width) {
+ mWidth = width;
+ }
+ @Override
+ public int getSize(@NonNull Paint paint, CharSequence text, int start, int end,
+ @Nullable Paint.FontMetricsInt fm) {
+ return mWidth;
+ }
+
+ @Override
+ public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, float x,
+ int top, int y, int bottom, @NonNull Paint paint) { }
+ }
+
+ /**
+ * Return true if the given two ranges intersects. This logic is the same one used in
+ * {@link Spanned} to determine whether a span range intersect with the query range.
+ */
+ private static boolean intersect(int s1, int e1, int s2, int e2) {
+ if (s1 > e2) return false;
+ if (e1 < s2) return false;
+ if (s1 != e1 && s2 != e2) {
+ if (s1 == e2) return false;
+ if (e1 == s2) return false;
+ }
+ return true;
+ }
+}
diff --git a/core/java/android/view/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java
index 3e8415391438..e89be476a9f0 100644
--- a/core/java/android/view/ImeInsetsSourceConsumer.java
+++ b/core/java/android/view/ImeInsetsSourceConsumer.java
@@ -123,7 +123,7 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer {
// TODO: ResultReceiver for IME.
// TODO: Set mShowOnNextImeRender to automatically show IME and guard it with a flag.
- ImeTracker.forLogging().onProgress(statsToken,
+ ImeTracker.get().onProgress(statsToken,
ImeTracker.PHASE_CLIENT_INSETS_CONSUMER_REQUEST_SHOW);
if (getControl() == null) {
@@ -164,13 +164,12 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer {
// - we do already have one, but we have control and use the passed in token
// for the insets animation already.
if (statsToken == null || getControl() != null) {
- statsToken = ImeTracker.forLogging().onRequestHide(null /* component */,
- Process.myUid(),
+ statsToken = ImeTracker.get().onRequestHide(null /* component */, Process.myUid(),
ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API);
}
- ImeTracker.forLogging().onProgress(statsToken,
+ ImeTracker.get().onProgress(statsToken,
ImeTracker.PHASE_CLIENT_INSETS_CONSUMER_NOTIFY_HIDDEN);
getImm().notifyImeHidden(mController.getHost().getWindowToken(), statsToken);
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index 8683cc2a8009..47da3f604178 100644
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -26,6 +26,7 @@ import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.hardware.BatteryState;
import android.hardware.SensorManager;
+import android.hardware.input.HostUsiVersion;
import android.hardware.input.InputDeviceIdentifier;
import android.hardware.input.InputManager;
import android.hardware.lights.LightsManager;
@@ -83,7 +84,8 @@ public final class InputDevice implements Parcelable {
private final boolean mHasButtonUnderPad;
private final boolean mHasSensor;
private final boolean mHasBattery;
- private final boolean mSupportsUsi;
+ private final HostUsiVersion mHostUsiVersion;
+ private final int mAssociatedDisplayId;
private final ArrayList<MotionRange> mMotionRanges = new ArrayList<MotionRange>();
@GuardedBy("mMotionRanges")
@@ -467,7 +469,8 @@ public final class InputDevice implements Parcelable {
int productId, String descriptor, boolean isExternal, int sources, int keyboardType,
KeyCharacterMap keyCharacterMap, @Nullable String keyboardLanguageTag,
@Nullable String keyboardLayoutType, boolean hasVibrator, boolean hasMicrophone,
- boolean hasButtonUnderPad, boolean hasSensor, boolean hasBattery, boolean supportsUsi) {
+ boolean hasButtonUnderPad, boolean hasSensor, boolean hasBattery, int usiVersionMajor,
+ int usiVersionMinor, int associatedDisplayId) {
mId = id;
mGeneration = generation;
mControllerNumber = controllerNumber;
@@ -493,7 +496,8 @@ public final class InputDevice implements Parcelable {
mHasSensor = hasSensor;
mHasBattery = hasBattery;
mIdentifier = new InputDeviceIdentifier(descriptor, vendorId, productId);
- mSupportsUsi = supportsUsi;
+ mHostUsiVersion = new HostUsiVersion(usiVersionMajor, usiVersionMinor);
+ mAssociatedDisplayId = associatedDisplayId;
}
private InputDevice(Parcel in) {
@@ -515,7 +519,8 @@ public final class InputDevice implements Parcelable {
mHasButtonUnderPad = in.readInt() != 0;
mHasSensor = in.readInt() != 0;
mHasBattery = in.readInt() != 0;
- mSupportsUsi = in.readInt() != 0;
+ mHostUsiVersion = HostUsiVersion.CREATOR.createFromParcel(in);
+ mAssociatedDisplayId = in.readInt();
mIdentifier = new InputDeviceIdentifier(mDescriptor, mVendorId, mProductId);
int numRanges = in.readInt();
@@ -554,7 +559,8 @@ public final class InputDevice implements Parcelable {
private boolean mHasBattery = false;
private String mKeyboardLanguageTag = null;
private String mKeyboardLayoutType = null;
- private boolean mSupportsUsi = false;
+ private int mUsiVersionMajor = -1;
+ private int mUsiVersionMinor = -1;
private List<MotionRange> mMotionRanges = new ArrayList<>();
/** @see InputDevice#getId() */
@@ -665,9 +671,10 @@ public final class InputDevice implements Parcelable {
return this;
}
- /** @see InputDevice#supportsUsi() () */
- public Builder setSupportsUsi(boolean supportsUsi) {
- mSupportsUsi = supportsUsi;
+ /** @see InputDevice#getHostUsiVersion() */
+ public Builder setUsiVersion(@Nullable HostUsiVersion usiVersion) {
+ mUsiVersionMajor = usiVersion != null ? usiVersion.getMajorVersion() : -1;
+ mUsiVersionMinor = usiVersion != null ? usiVersion.getMinorVersion() : -1;
return this;
}
@@ -699,7 +706,9 @@ public final class InputDevice implements Parcelable {
mHasButtonUnderPad,
mHasSensor,
mHasBattery,
- mSupportsUsi);
+ mUsiVersionMajor,
+ mUsiVersionMinor,
+ Display.INVALID_DISPLAY);
final int numRanges = mMotionRanges.size();
for (int i = 0; i < numRanges; i++) {
@@ -1276,12 +1285,22 @@ public final class InputDevice implements Parcelable {
}
/**
- * Reports whether the device supports the Universal Stylus Initiative (USI) protocol for
- * styluses.
+ * Reports the version of the Universal Stylus Initiative (USI) protocol supported by this
+ * input device.
+ *
+ * @return the supported USI version, or null if the device does not support USI
+ * @see <a href="https://universalstylus.org">Universal Stylus Initiative</a>
+ * @see InputManager#getHostUsiVersion(int)
* @hide
*/
- public boolean supportsUsi() {
- return mSupportsUsi;
+ @Nullable
+ public HostUsiVersion getHostUsiVersion() {
+ return mHostUsiVersion.isValid() ? mHostUsiVersion : null;
+ }
+
+ /** @hide */
+ public int getAssociatedDisplayId() {
+ return mAssociatedDisplayId;
}
/**
@@ -1415,7 +1434,8 @@ public final class InputDevice implements Parcelable {
out.writeInt(mHasButtonUnderPad ? 1 : 0);
out.writeInt(mHasSensor ? 1 : 0);
out.writeInt(mHasBattery ? 1 : 0);
- out.writeInt(mSupportsUsi ? 1 : 0);
+ mHostUsiVersion.writeToParcel(out, flags);
+ out.writeInt(mAssociatedDisplayId);
int numRanges = mMotionRanges.size();
numRanges = numRanges > MAX_RANGES ? MAX_RANGES : numRanges;
@@ -1468,7 +1488,7 @@ public final class InputDevice implements Parcelable {
description.append(" Has mic: ").append(mHasMicrophone).append("\n");
- description.append(" Supports USI: ").append(mSupportsUsi).append("\n");
+ description.append(" USI Version: ").append(getHostUsiVersion()).append("\n");
if (mKeyboardLanguageTag != null) {
description.append(" Keyboard language tag: ").append(mKeyboardLanguageTag).append(
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index c9924509e591..8e8e28a65eb4 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -26,7 +26,6 @@ import static android.view.WindowInsets.Type.FIRST;
import static android.view.WindowInsets.Type.LAST;
import static android.view.WindowInsets.Type.all;
import static android.view.WindowInsets.Type.ime;
-import static android.view.inputmethod.ImeTracker.PHASE_CLIENT_ANIMATION_CANCEL;
import android.animation.AnimationHandler;
import android.animation.Animator;
@@ -36,8 +35,6 @@ import android.animation.ValueAnimator;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.ActivityThread;
-import android.content.Context;
import android.content.res.CompatibilityInfo;
import android.graphics.Insets;
import android.graphics.Rect;
@@ -63,7 +60,6 @@ import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import android.view.animation.PathInterpolator;
import android.view.inputmethod.ImeTracker;
-import android.view.inputmethod.ImeTracker.InputMethodJankContext;
import android.view.inputmethod.InputMethodManager;
import com.android.internal.annotations.VisibleForTesting;
@@ -190,14 +186,6 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
@Nullable
String getRootViewTitle();
- /**
- * @return the context related to the rootView.
- */
- @Nullable
- default Context getRootViewContext() {
- return null;
- }
-
/** @see ViewRootImpl#dipToPx */
int dipToPx(int dips);
@@ -318,7 +306,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
@Retention(RetentionPolicy.SOURCE)
@IntDef(value = {ANIMATION_TYPE_NONE, ANIMATION_TYPE_SHOW, ANIMATION_TYPE_HIDE,
ANIMATION_TYPE_USER, ANIMATION_TYPE_RESIZE})
- public @interface AnimationType {
+ @interface AnimationType {
}
/**
@@ -333,27 +321,6 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
/** Logging listener. */
private WindowInsetsAnimationControlListener mLoggingListener;
- /** Context for {@link android.view.inputmethod.ImeTracker.ImeJankTracker} to monitor jank. */
- private final InputMethodJankContext mJankContext = new InputMethodJankContext() {
- @Override
- public Context getDisplayContext() {
- return mHost != null ? mHost.getRootViewContext() : null;
- }
-
- @Override
- public SurfaceControl getTargetSurfaceControl() {
- final InsetsSourceConsumer imeSourceConsumer = mSourceConsumers.get(ITYPE_IME);
- final InsetsSourceControl imeSourceControl =
- imeSourceConsumer != null ? imeSourceConsumer.getControl() : null;
- return imeSourceControl != null ? imeSourceControl.getLeash() : null;
- }
-
- @Override
- public String getHostPackageName() {
- return mHost != null ? mHost.getRootViewContext().getPackageName() : null;
- }
- };
-
/**
* The default implementation of listener, to be used by InsetsController and InsetsPolicy to
* animate insets.
@@ -371,7 +338,6 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
private final boolean mDisable;
private final int mFloatingImeBottomInset;
private final WindowInsetsAnimationControlListener mLoggingListener;
- private final InputMethodJankContext mInputMethodJankContext;
private final ThreadLocal<AnimationHandler> mSfAnimationHandlerThreadLocal =
new ThreadLocal<AnimationHandler>() {
@@ -385,8 +351,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
public InternalAnimationControlListener(boolean show, boolean hasAnimationCallbacks,
@InsetsType int requestedTypes, @Behavior int behavior, boolean disable,
- int floatingImeBottomInset, WindowInsetsAnimationControlListener loggingListener,
- @Nullable InputMethodJankContext jankContext) {
+ int floatingImeBottomInset, WindowInsetsAnimationControlListener loggingListener) {
mShow = show;
mHasAnimationCallbacks = hasAnimationCallbacks;
mRequestedTypes = requestedTypes;
@@ -395,7 +360,6 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
mDisable = disable;
mFloatingImeBottomInset = floatingImeBottomInset;
mLoggingListener = loggingListener;
- mInputMethodJankContext = jankContext;
}
@Override
@@ -442,26 +406,10 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
+ insetsFraction);
});
mAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationStart(Animator animation) {
- if (mInputMethodJankContext == null) return;
- ImeTracker.forJank().onRequestAnimation(
- mInputMethodJankContext,
- mShow ? ANIMATION_TYPE_SHOW : ANIMATION_TYPE_HIDE,
- !mHasAnimationCallbacks);
- }
-
- @Override
- public void onAnimationCancel(Animator animation) {
- if (mInputMethodJankContext == null) return;
- ImeTracker.forJank().onCancelAnimation();
- }
@Override
public void onAnimationEnd(Animator animation) {
onAnimationFinish();
- if (mInputMethodJankContext == null) return;
- ImeTracker.forJank().onFinishAnimation();
}
});
if (!mHasAnimationCallbacks) {
@@ -925,7 +873,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
/**
* @see InsetsState#calculateInsets(Rect, InsetsState, boolean, boolean, int, int, int, int,
- * int, android.util.SparseIntArray)
+ * int, SparseIntArray)
*/
@VisibleForTesting
public WindowInsets calculateInsets(boolean isScreenRound, boolean alwaysConsumeSystemBars,
@@ -1033,7 +981,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
public void show(@InsetsType int types) {
ImeTracker.Token statsToken = null;
if ((types & ime()) != 0) {
- statsToken = ImeTracker.forLogging().onRequestShow(null /* component */,
+ statsToken = ImeTracker.get().onRequestShow(null /* component */,
Process.myUid(), ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT,
SoftInputShowHideReason.SHOW_SOFT_INPUT_BY_INSETS_API);
}
@@ -1058,9 +1006,6 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
}
// Handle pending request ready in case there was one set.
if (fromIme && mPendingImeControlRequest != null) {
- if ((types & Type.ime()) != 0) {
- ImeTracker.forLatency().onShown(statsToken, ActivityThread::currentApplication);
- }
handlePendingControlRequest(statsToken);
return;
}
@@ -1083,7 +1028,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
"show ignored for type: %d animType: %d requestedVisible: %s",
type, animationType, requestedVisible));
if (isImeAnimation) {
- ImeTracker.forLogging().onCancelled(statsToken,
+ ImeTracker.get().onCancelled(statsToken,
ImeTracker.PHASE_CLIENT_APPLY_ANIMATION);
}
continue;
@@ -1091,21 +1036,16 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
if (fromIme && animationType == ANIMATION_TYPE_USER) {
// App is already controlling the IME, don't cancel it.
if (isImeAnimation) {
- ImeTracker.forLogging().onFailed(
- statsToken, ImeTracker.PHASE_CLIENT_APPLY_ANIMATION);
+ ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_APPLY_ANIMATION);
}
continue;
}
if (isImeAnimation) {
- ImeTracker.forLogging().onProgress(
- statsToken, ImeTracker.PHASE_CLIENT_APPLY_ANIMATION);
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_APPLY_ANIMATION);
}
typesReady |= type;
}
if (DEBUG) Log.d(TAG, "show typesReady: " + typesReady);
- if (fromIme && (typesReady & Type.ime()) != 0) {
- ImeTracker.forLatency().onShown(statsToken, ActivityThread::currentApplication);
- }
applyAnimation(typesReady, true /* show */, fromIme, statsToken);
}
@@ -1134,7 +1074,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
public void hide(@InsetsType int types) {
ImeTracker.Token statsToken = null;
if ((types & ime()) != 0) {
- statsToken = ImeTracker.forLogging().onRequestHide(null /* component */,
+ statsToken = ImeTracker.get().onRequestHide(null /* component */,
Process.myUid(), ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API);
}
@@ -1183,14 +1123,13 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
// no-op: already hidden or animating out (because window visibility is
// applied before starting animation).
if (isImeAnimation) {
- ImeTracker.forLogging().onCancelled(statsToken,
+ ImeTracker.get().onCancelled(statsToken,
ImeTracker.PHASE_CLIENT_APPLY_ANIMATION);
}
continue;
}
if (isImeAnimation) {
- ImeTracker.forLogging().onProgress(
- statsToken, ImeTracker.PHASE_CLIENT_APPLY_ANIMATION);
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_APPLY_ANIMATION);
}
typesReady |= type;
}
@@ -1262,19 +1201,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
@AnimationType int animationType,
@LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation,
boolean useInsetsAnimationThread, @Nullable ImeTracker.Token statsToken) {
- ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_CONTROL_ANIMATION);
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_CONTROL_ANIMATION);
if ((types & mTypesBeingCancelled) != 0) {
- final boolean monitoredAnimation =
- animationType == ANIMATION_TYPE_SHOW || animationType == ANIMATION_TYPE_HIDE;
- if (monitoredAnimation && (types & Type.ime()) != 0) {
- if (animationType == ANIMATION_TYPE_SHOW) {
- ImeTracker.forLatency().onShowCancelled(statsToken,
- PHASE_CLIENT_ANIMATION_CANCEL, ActivityThread::currentApplication);
- } else {
- ImeTracker.forLatency().onHideCancelled(statsToken,
- PHASE_CLIENT_ANIMATION_CANCEL, ActivityThread::currentApplication);
- }
- }
throw new IllegalStateException("Cannot start a new insets animation of "
+ Type.toString(types)
+ " while an existing " + Type.toString(mTypesBeingCancelled)
@@ -1286,7 +1214,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
types &= ~mDisabledUserAnimationInsetsTypes;
if ((disabledTypes & ime()) != 0) {
- ImeTracker.forLogging().onFailed(statsToken,
+ ImeTracker.get().onFailed(statsToken,
ImeTracker.PHASE_CLIENT_DISABLED_USER_ANIMATION);
if (fromIme && !mState.getSource(mImeSourceConsumer.getId()).isVisible()) {
@@ -1307,8 +1235,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.showRequestFromApiToImeReady", 0);
return;
}
- ImeTracker.forLogging().onProgress(statsToken,
- ImeTracker.PHASE_CLIENT_DISABLED_USER_ANIMATION);
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_DISABLED_USER_ANIMATION);
if (DEBUG) Log.d(TAG, "controlAnimation types: " + types);
mLastStartedAnimTypes |= types;
@@ -1375,11 +1302,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
if ((typesReady & WindowInsets.Type.ime()) != 0) {
ImeTracing.getInstance().triggerClientDump("InsetsAnimationControlImpl",
mHost.getInputMethodManager(), null /* icProto */);
- if (animationType == ANIMATION_TYPE_HIDE) {
- ImeTracker.forLatency().onHidden(statsToken, ActivityThread::currentApplication);
- }
}
- ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_ANIMATION_RUNNING);
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_ANIMATION_RUNNING);
mRunningAnimations.add(new RunningAnimation(runner, animationType));
if (DEBUG) Log.d(TAG, "Animation added to runner. useInsetsAnimationThread: "
+ useInsetsAnimationThread);
@@ -1419,8 +1343,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
private Pair<Integer, Boolean> collectSourceControls(boolean fromIme, @InsetsType int types,
SparseArray<InsetsSourceControl> controls, @AnimationType int animationType,
@Nullable ImeTracker.Token statsToken) {
- ImeTracker.forLogging().onProgress(statsToken,
- ImeTracker.PHASE_CLIENT_COLLECT_SOURCE_CONTROLS);
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_COLLECT_SOURCE_CONTROLS);
int typesReady = 0;
boolean imeReady = true;
@@ -1523,13 +1446,13 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
}
final ImeTracker.Token statsToken = runner.getStatsToken();
if (shown) {
- ImeTracker.forLogging().onProgress(statsToken,
+ ImeTracker.get().onProgress(statsToken,
ImeTracker.PHASE_CLIENT_ANIMATION_FINISHED_SHOW);
- ImeTracker.forLogging().onShown(statsToken);
+ ImeTracker.get().onShown(statsToken);
} else {
- ImeTracker.forLogging().onProgress(statsToken,
+ ImeTracker.get().onProgress(statsToken,
ImeTracker.PHASE_CLIENT_ANIMATION_FINISHED_HIDE);
- ImeTracker.forLogging().onHidden(statsToken);
+ ImeTracker.get().onHidden(statsToken);
}
reportRequestedVisibleTypes();
}
@@ -1555,13 +1478,13 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
private void cancelAnimation(InsetsAnimationControlRunner control, boolean invokeCallback) {
if (invokeCallback) {
- ImeTracker.forLogging().onCancelled(control.getStatsToken(),
- PHASE_CLIENT_ANIMATION_CANCEL);
+ ImeTracker.get().onCancelled(control.getStatsToken(),
+ ImeTracker.PHASE_CLIENT_ANIMATION_CANCEL);
control.cancel();
} else {
// Succeeds if invokeCallback is false (i.e. when called from notifyFinished).
- ImeTracker.forLogging().onProgress(control.getStatsToken(),
- PHASE_CLIENT_ANIMATION_CANCEL);
+ ImeTracker.get().onProgress(control.getStatsToken(),
+ ImeTracker.PHASE_CLIENT_ANIMATION_CANCEL);
}
if (DEBUG) {
Log.d(TAG, TextUtils.formatSimple(
@@ -1726,7 +1649,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
final InternalAnimationControlListener listener = new InternalAnimationControlListener(
show, hasAnimationCallbacks, types, mHost.getSystemBarsBehavior(),
skipAnim || mAnimationsDisabled, mHost.dipToPx(FLOATING_IME_BOTTOM_INSET_DP),
- mLoggingListener, mJankContext);
+ mLoggingListener);
// We are about to playing the default animation (show/hide). Passing a null frame indicates
// the controlled types should be animated regardless of the frame.
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 276360702063..175c76b7ec57 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -70,6 +70,7 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FIT_INSETS_CO
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OPTIMIZE_MEASURE;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
@@ -2781,7 +2782,7 @@ public final class ViewRootImpl implements ViewParent,
* TODO(b/260382739): Apply this to all windows.
*/
private static boolean shouldOptimizeMeasure(final WindowManager.LayoutParams lp) {
- return lp.type == TYPE_NOTIFICATION_SHADE;
+ return (lp.privateFlags & PRIVATE_FLAG_OPTIMIZE_MEASURE) != 0;
}
private Rect getWindowBoundsInsetSystemBars() {
@@ -5764,7 +5765,7 @@ public final class ViewRootImpl implements ViewParent,
}
case MSG_SHOW_INSETS: {
final ImeTracker.Token statsToken = (ImeTracker.Token) msg.obj;
- ImeTracker.forLogging().onProgress(statsToken,
+ ImeTracker.get().onProgress(statsToken,
ImeTracker.PHASE_CLIENT_HANDLE_SHOW_INSETS);
if (mView == null) {
Log.e(TAG,
@@ -5777,7 +5778,7 @@ public final class ViewRootImpl implements ViewParent,
}
case MSG_HIDE_INSETS: {
final ImeTracker.Token statsToken = (ImeTracker.Token) msg.obj;
- ImeTracker.forLogging().onProgress(statsToken,
+ ImeTracker.get().onProgress(statsToken,
ImeTracker.PHASE_CLIENT_HANDLE_HIDE_INSETS);
mInsetsController.hide(msg.arg1, msg.arg2 == 1, statsToken);
break;
@@ -10324,10 +10325,10 @@ public final class ViewRootImpl implements ViewParent,
null /* icProto */);
}
if (viewAncestor != null) {
- ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_SHOW_INSETS);
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_SHOW_INSETS);
viewAncestor.showInsets(types, fromIme, statsToken);
} else {
- ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_CLIENT_SHOW_INSETS);
+ ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_SHOW_INSETS);
}
}
@@ -10341,10 +10342,10 @@ public final class ViewRootImpl implements ViewParent,
null /* icProto */);
}
if (viewAncestor != null) {
- ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_HIDE_INSETS);
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_HIDE_INSETS);
viewAncestor.hideInsets(types, fromIme, statsToken);
} else {
- ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_CLIENT_HIDE_INSETS);
+ ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_HIDE_INSETS);
}
}
diff --git a/core/java/android/view/ViewRootInsetsControllerHost.java b/core/java/android/view/ViewRootInsetsControllerHost.java
index 037ce875a272..c59d83ec4c6e 100644
--- a/core/java/android/view/ViewRootInsetsControllerHost.java
+++ b/core/java/android/view/ViewRootInsetsControllerHost.java
@@ -21,7 +21,6 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_APPEARANCE_CO
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_BEHAVIOR_CONTROLLED;
import android.annotation.NonNull;
-import android.content.Context;
import android.content.res.CompatibilityInfo;
import android.os.Handler;
import android.os.IBinder;
@@ -247,11 +246,6 @@ public class ViewRootInsetsControllerHost implements InsetsController.Host {
}
@Override
- public Context getRootViewContext() {
- return mViewRoot != null ? mViewRoot.getView().getContext() : null;
- }
-
- @Override
public int dipToPx(int dips) {
if (mViewRoot != null) {
return mViewRoot.dipToPx(dips);
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 35f1787c0bb5..e437c1f03106 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -2663,6 +2663,15 @@ public interface WindowManager extends ViewManager {
public static final int PRIVATE_FLAG_SYSTEM_ERROR = 0x00000100;
/**
+ * Flag to indicate that the view hierarchy of the window can only be measured when
+ * necessary. If a window size can be known by the LayoutParams, we can use the size to
+ * relayout window, and we don't have to measure the view hierarchy before laying out the
+ * views. This reduces the chances to perform measure.
+ * {@hide}
+ */
+ public static final int PRIVATE_FLAG_OPTIMIZE_MEASURE = 0x00000200;
+
+ /**
* Flag that prevents the wallpaper behind the current window from receiving touch events.
*
* {@hide}
@@ -2864,6 +2873,7 @@ public interface WindowManager extends ViewManager {
PRIVATE_FLAG_NO_MOVE_ANIMATION,
PRIVATE_FLAG_COMPATIBLE_WINDOW,
PRIVATE_FLAG_SYSTEM_ERROR,
+ PRIVATE_FLAG_OPTIMIZE_MEASURE,
PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS,
PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR,
PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT,
@@ -2924,6 +2934,10 @@ public interface WindowManager extends ViewManager {
equals = PRIVATE_FLAG_SYSTEM_ERROR,
name = "SYSTEM_ERROR"),
@ViewDebug.FlagToString(
+ mask = PRIVATE_FLAG_OPTIMIZE_MEASURE,
+ equals = PRIVATE_FLAG_OPTIMIZE_MEASURE,
+ name = "OPTIMIZE_MEASURE"),
+ @ViewDebug.FlagToString(
mask = PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS,
equals = PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS,
name = "DISABLE_WALLPAPER_TOUCH_EVENTS"),
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 2ad01ed99b13..b5764c50ccc2 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -1235,6 +1235,42 @@ public final class AutofillManager {
}
/**
+ * Called when the virtual views are ready to the user for autofill.
+ *
+ * This method is used to notify autofill system the views are ready to the user. And then
+ * Autofill can do initialization if needed before the user starts to input. For example, do
+ * a pre-fill request for the
+ * <a href="/reference/android/service/autofill/Dataset.html#FillDialogUI">fill dialog</a>.
+ *
+ * @param view the host view that holds a virtual view hierarchy.
+ * @param infos extra information for the virtual views. The key is virtual id which represents
+ * the virtual view in the host view.
+ *
+ * @throws IllegalArgumentException if the {@code infos} was empty
+ */
+ public void notifyVirtualViewsReady(
+ @NonNull View view, @NonNull SparseArray<VirtualViewFillInfo> infos) {
+ Objects.requireNonNull(infos);
+ if (infos.size() == 0) {
+ throw new IllegalArgumentException("No VirtualViewInfo found");
+ }
+ if (AutofillFeatureFlags.isFillDialogDisabledForCredentialManager()
+ && view.isCredential()) {
+ if (sDebug) {
+ Log.d(TAG, "Ignoring Fill Dialog request since important for credMan:"
+ + view.getAutofillId().toString());
+ }
+ return;
+ }
+ for (int i = 0; i < infos.size(); i++) {
+ final VirtualViewFillInfo info = infos.valueAt(i);
+ final int virtualId = infos.indexOfKey(i);
+ notifyViewReadyInner(getAutofillId(view, virtualId),
+ (info == null) ? null : info.getAutofillHints());
+ }
+ }
+
+ /**
* The {@link AutofillFeatureFlags#DEVICE_CONFIG_AUTOFILL_DIALOG_ENABLED} is {@code true} or
* the view have the allowed autofill hints, performs a fill request to know there is any field
* supported fill dialog.
@@ -1245,9 +1281,6 @@ public final class AutofillManager {
if (sDebug) {
Log.d(TAG, "notifyViewEnteredForFillDialog:" + v.getAutofillId());
}
- if (!hasAutofillFeature()) {
- return;
- }
if (AutofillFeatureFlags.isFillDialogDisabledForCredentialManager()
&& v.isCredential()) {
if (sDebug) {
@@ -1256,6 +1289,13 @@ public final class AutofillManager {
}
return;
}
+ notifyViewReadyInner(v.getAutofillId(), v.getAutofillHints());
+ }
+
+ private void notifyViewReadyInner(AutofillId id, String[] autofillHints) {
+ if (!hasAutofillFeature()) {
+ return;
+ }
synchronized (mLock) {
if (mTrackedViews != null) {
@@ -1263,7 +1303,7 @@ public final class AutofillManager {
// different pages but in the same Activity. We need to reset the
// mIsFillRequested flag to allow asking for a new FillRequest when
// user switches to other page
- mTrackedViews.checkViewState(v.getAutofillId());
+ mTrackedViews.checkViewState(id);
}
}
@@ -1273,9 +1313,9 @@ public final class AutofillManager {
}
if (mIsFillDialogEnabled
- || ArrayUtils.containsAny(v.getAutofillHints(), mFillDialogEnabledHints)) {
+ || ArrayUtils.containsAny(autofillHints, mFillDialogEnabledHints)) {
if (sDebug) {
- Log.d(TAG, "Trigger fill request at view entered");
+ Log.d(TAG, "Trigger fill request when the view is ready.");
}
int flags = FLAG_SUPPORTS_FILL_DIALOG;
diff --git a/core/java/android/view/autofill/VirtualViewFillInfo.java b/core/java/android/view/autofill/VirtualViewFillInfo.java
new file mode 100644
index 000000000000..4bec5a4a0960
--- /dev/null
+++ b/core/java/android/view/autofill/VirtualViewFillInfo.java
@@ -0,0 +1,141 @@
+/*
+ * 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.view.autofill;
+
+
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.view.View;
+
+import com.android.internal.util.DataClass;
+
+
+/**
+ * Information for the virtual view to the autofill framework.
+ */
+@DataClass(genBuilder = true)
+public final class VirtualViewFillInfo {
+
+ /**
+ * Autofill hints of the virtual view.
+ *
+ * @see View#setAutofillHints(String...)
+ */
+ @Nullable
+ @SuppressLint("NullableCollection")
+ private String[] mAutofillHints;
+
+ private static String[] defaultAutofillHints() {
+ return null;
+ }
+
+
+
+ // 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/view/autofill/VirtualViewFillInfo.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ @DataClass.Generated.Member
+ /* package-private */ VirtualViewFillInfo(
+ @Nullable @SuppressLint("NullableCollection") String[] autofillHints) {
+ this.mAutofillHints = autofillHints;
+ com.android.internal.util.AnnotationValidations.validate(
+ SuppressLint.class, null, mAutofillHints,
+ "value", "NullableCollection");
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * Autofill hints of the virtual view.
+ *
+ * @see View#setAutofillHints(String...)
+ */
+ @DataClass.Generated.Member
+ public @Nullable @SuppressLint("NullableCollection") String[] getAutofillHints() {
+ return mAutofillHints;
+ }
+
+ /**
+ * A builder for {@link VirtualViewFillInfo}
+ */
+ @SuppressWarnings("WeakerAccess")
+ @DataClass.Generated.Member
+ public static final class Builder {
+
+ private @Nullable @SuppressLint("NullableCollection") String[] mAutofillHints;
+
+ private long mBuilderFieldsSet = 0L;
+
+ public Builder() {
+ }
+
+ /**
+ * Autofill hints of the virtual view.
+ *
+ * @see View#setAutofillHints(String...)
+ */
+ @DataClass.Generated.Member
+ public @android.annotation.NonNull Builder setAutofillHints(@SuppressLint("NullableCollection") @android.annotation.NonNull String... value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x1;
+ mAutofillHints = value;
+ return this;
+ }
+
+ /** Builds the instance. This builder should not be touched after calling this! */
+ public @android.annotation.NonNull VirtualViewFillInfo build() {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x2; // Mark builder used
+
+ if ((mBuilderFieldsSet & 0x1) == 0) {
+ mAutofillHints = defaultAutofillHints();
+ }
+ VirtualViewFillInfo o = new VirtualViewFillInfo(
+ mAutofillHints);
+ return o;
+ }
+
+ private void checkNotUsed() {
+ if ((mBuilderFieldsSet & 0x2) != 0) {
+ throw new IllegalStateException(
+ "This Builder should not be reused. Use a new Builder instance instead");
+ }
+ }
+ }
+
+ @DataClass.Generated(
+ time = 1674023010954L,
+ codegenVersion = "1.0.23",
+ sourceFile = "frameworks/base/core/java/android/view/autofill/VirtualViewFillInfo.java",
+ inputSignatures = "private @android.annotation.Nullable @android.annotation.SuppressLint java.lang.String[] mAutofillHints\nprivate static java.lang.String[] defaultAutofillHints()\nclass VirtualViewFillInfo extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genBuilder=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java
index 9ebaa67b11f3..4e5cec7b777d 100644
--- a/core/java/android/view/inputmethod/EditorInfo.java
+++ b/core/java/android/view/inputmethod/EditorInfo.java
@@ -568,6 +568,8 @@ public class EditorInfo implements InputType, Parcelable {
supportedTypes |= HandwritingGesture.GESTURE_TYPE_SELECT_RANGE;
} else if (gesture.equals(InsertGesture.class)) {
supportedTypes |= HandwritingGesture.GESTURE_TYPE_INSERT;
+ } else if (gesture.equals(InsertModeGesture.class)) {
+ supportedTypes |= HandwritingGesture.GESTURE_TYPE_INSERT_MODE;
} else if (gesture.equals(DeleteGesture.class)) {
supportedTypes |= HandwritingGesture.GESTURE_TYPE_DELETE;
} else if (gesture.equals(DeleteRangeGesture.class)) {
@@ -611,6 +613,10 @@ public class EditorInfo implements InputType, Parcelable {
== HandwritingGesture.GESTURE_TYPE_INSERT) {
list.add(InsertGesture.class);
}
+ if ((mSupportedHandwritingGestureTypes & HandwritingGesture.GESTURE_TYPE_INSERT_MODE)
+ == HandwritingGesture.GESTURE_TYPE_INSERT_MODE) {
+ list.add(InsertModeGesture.class);
+ }
if ((mSupportedHandwritingGestureTypes & HandwritingGesture.GESTURE_TYPE_DELETE)
== HandwritingGesture.GESTURE_TYPE_DELETE) {
list.add(DeleteGesture.class);
diff --git a/core/java/android/view/inputmethod/HandwritingGesture.java b/core/java/android/view/inputmethod/HandwritingGesture.java
index 1f4a7af43303..e7207fa0dfa0 100644
--- a/core/java/android/view/inputmethod/HandwritingGesture.java
+++ b/core/java/android/view/inputmethod/HandwritingGesture.java
@@ -142,6 +142,13 @@ public abstract class HandwritingGesture {
public static final int GESTURE_TYPE_DELETE_RANGE = 1 << 6;
/**
+ * Gesture of type {@link InsertModeGesture} to begin an insert mode at a designated point.
+ * @hide
+ */
+ @TestApi
+ public static final int GESTURE_TYPE_INSERT_MODE = 1 << 7;
+
+ /**
* Type of gesture like {@link #GESTURE_TYPE_SELECT}, {@link #GESTURE_TYPE_INSERT},
* or {@link #GESTURE_TYPE_DELETE}.
*/
@@ -150,6 +157,7 @@ public abstract class HandwritingGesture {
GESTURE_TYPE_SELECT,
GESTURE_TYPE_SELECT_RANGE,
GESTURE_TYPE_INSERT,
+ GESTURE_TYPE_INSERT_MODE,
GESTURE_TYPE_DELETE,
GESTURE_TYPE_DELETE_RANGE,
GESTURE_TYPE_REMOVE_SPACE,
@@ -168,6 +176,7 @@ public abstract class HandwritingGesture {
GESTURE_TYPE_SELECT,
GESTURE_TYPE_SELECT_RANGE,
GESTURE_TYPE_INSERT,
+ GESTURE_TYPE_INSERT_MODE,
GESTURE_TYPE_DELETE,
GESTURE_TYPE_DELETE_RANGE,
GESTURE_TYPE_REMOVE_SPACE,
diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java
index a07dedc8ca84..3b6ec800836a 100644
--- a/core/java/android/view/inputmethod/ImeTracker.java
+++ b/core/java/android/view/inputmethod/ImeTracker.java
@@ -16,36 +16,24 @@
package android.view.inputmethod;
-import static com.android.internal.inputmethod.InputMethodDebug.softInputDisplayReasonToString;
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_IME_INSETS_ANIMATION;
-import static com.android.internal.util.LatencyTracker.ACTION_REQUEST_IME_HIDDEN;
-import static com.android.internal.util.LatencyTracker.ACTION_REQUEST_IME_SHOWN;
-
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityThread;
-import android.content.Context;
import android.os.Binder;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemProperties;
import android.util.Log;
-import android.view.InsetsController.AnimationType;
-import android.view.SurfaceControl;
import com.android.internal.inputmethod.InputMethodDebug;
import com.android.internal.inputmethod.SoftInputShowHideReason;
-import com.android.internal.jank.InteractionJankMonitor;
-import com.android.internal.jank.InteractionJankMonitor.Configuration;
-import com.android.internal.util.LatencyTracker;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Field;
import java.util.Arrays;
-import java.util.Locale;
import java.util.Map;
import java.util.Random;
import java.util.stream.Collectors;
@@ -397,35 +385,15 @@ public interface ImeTracker {
void onHidden(@Nullable Token token);
/**
- * Get the singleton request tracker instance.
+ * Get the singleton instance of this class.
*
- * @return the singleton request tracker instance
+ * @return the singleton instance of this class
*/
@NonNull
- static ImeTracker forLogging() {
+ static ImeTracker get() {
return LOGGER;
}
- /**
- * Get the singleton jank tracker instance.
- *
- * @return the singleton jank tracker instance
- */
- @NonNull
- static ImeJankTracker forJank() {
- return JANK_TRACKER;
- }
-
- /**
- * Get the singleton latency tracker instance.
- *
- * @return the singleton latency tracker instance
- */
- @NonNull
- static ImeLatencyTracker forLatency() {
- return LATENCY_TRACKER;
- }
-
/** The singleton IME tracker instance. */
@NonNull
ImeTracker LOGGER = new ImeTracker() {
@@ -521,12 +489,6 @@ public interface ImeTracker {
}
};
- /** The singleton IME tracker instance for instrumenting jank metrics. */
- ImeJankTracker JANK_TRACKER = new ImeJankTracker();
-
- /** The singleton IME tracker instance for instrumenting latency metrics. */
- ImeLatencyTracker LATENCY_TRACKER = new ImeLatencyTracker();
-
/** A token that tracks the progress of an IME request. */
class Token implements Parcelable {
@@ -630,153 +592,4 @@ public interface ImeTracker {
}
}
}
-
- /**
- * Context related to {@link InteractionJankMonitor}.
- */
- interface InputMethodJankContext {
- /**
- * @return a context associated with a display
- */
- Context getDisplayContext();
-
- /**
- * @return a SurfaceControl is going to be monitored
- */
- SurfaceControl getTargetSurfaceControl();
-
- /**
- * @return the package name of the host
- */
- String getHostPackageName();
- }
-
- /**
- * Context related to {@link LatencyTracker}.
- */
- interface InputMethodLatencyContext {
- /**
- * @return a context associated with current application
- */
- Context getAppContext();
- }
-
- /**
- * A tracker instance which is in charge of communicating with {@link InteractionJankMonitor}.
- */
- final class ImeJankTracker {
-
- private ImeJankTracker() {
- }
-
- /**
- * Called when the animation, which is going to be monitored, starts.
- *
- * @param jankContext context which is needed by {@link InteractionJankMonitor}
- * @param animType {@link AnimationType}
- * @param useSeparatedThread {@code true} if the animation is handled by the app,
- * {@code false} if the animation will be scheduled on the
- * {@link android.view.InsetsAnimationThread}
- */
- public void onRequestAnimation(@NonNull InputMethodJankContext jankContext,
- @AnimationType int animType, boolean useSeparatedThread) {
- if (jankContext.getDisplayContext() == null
- || jankContext.getTargetSurfaceControl() == null) {
- return;
- }
- final Configuration.Builder builder = Configuration.Builder.withSurface(
- CUJ_IME_INSETS_ANIMATION,
- jankContext.getDisplayContext(),
- jankContext.getTargetSurfaceControl())
- .setTag(String.format(Locale.US, "%d@%d@%s", animType,
- useSeparatedThread ? 0 : 1, jankContext.getHostPackageName()));
- InteractionJankMonitor.getInstance().begin(builder);
- }
-
- /**
- * Called when the animation, which is going to be monitored, cancels.
- */
- public void onCancelAnimation() {
- InteractionJankMonitor.getInstance().cancel(CUJ_IME_INSETS_ANIMATION);
- }
-
- /**
- * Called when the animation, which is going to be monitored, ends.
- */
- public void onFinishAnimation() {
- InteractionJankMonitor.getInstance().end(CUJ_IME_INSETS_ANIMATION);
- }
- }
-
- /**
- * A tracker instance which is in charge of communicating with {@link LatencyTracker}.
- */
- final class ImeLatencyTracker {
-
- private ImeLatencyTracker() {
- }
-
- private boolean shouldMonitorLatency(@SoftInputShowHideReason int reason) {
- return reason == SoftInputShowHideReason.SHOW_SOFT_INPUT
- || reason == SoftInputShowHideReason.HIDE_SOFT_INPUT
- || reason == SoftInputShowHideReason.SHOW_SOFT_INPUT_BY_INSETS_API
- || reason == SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API
- || reason == SoftInputShowHideReason.SHOW_SOFT_INPUT_FROM_IME
- || reason == SoftInputShowHideReason.HIDE_SOFT_INPUT_FROM_IME;
- }
-
- public void onRequestShow(@Nullable Token token, @Origin int origin,
- @SoftInputShowHideReason int reason,
- @NonNull InputMethodLatencyContext latencyContext) {
- if (!shouldMonitorLatency(reason)) return;
- LatencyTracker.getInstance(latencyContext.getAppContext())
- .onActionStart(
- ACTION_REQUEST_IME_SHOWN,
- softInputDisplayReasonToString(reason));
- }
-
- public void onRequestHide(@Nullable Token token, @Origin int origin,
- @SoftInputShowHideReason int reason,
- @NonNull InputMethodLatencyContext latencyContext) {
- if (!shouldMonitorLatency(reason)) return;
- LatencyTracker.getInstance(latencyContext.getAppContext())
- .onActionStart(
- ACTION_REQUEST_IME_HIDDEN,
- softInputDisplayReasonToString(reason));
- }
-
- public void onShowFailed(@Nullable Token token, @Phase int phase,
- @NonNull InputMethodLatencyContext latencyContext) {
- onShowCancelled(token, phase, latencyContext);
- }
-
- public void onHideFailed(@Nullable Token token, @Phase int phase,
- @NonNull InputMethodLatencyContext latencyContext) {
- onHideCancelled(token, phase, latencyContext);
- }
-
- public void onShowCancelled(@Nullable Token token, @Phase int phase,
- @NonNull InputMethodLatencyContext latencyContext) {
- LatencyTracker.getInstance(latencyContext.getAppContext())
- .onActionCancel(ACTION_REQUEST_IME_SHOWN);
- }
-
- public void onHideCancelled(@Nullable Token token, @Phase int phase,
- @NonNull InputMethodLatencyContext latencyContext) {
- LatencyTracker.getInstance(latencyContext.getAppContext())
- .onActionCancel(ACTION_REQUEST_IME_HIDDEN);
- }
-
- public void onShown(@Nullable Token token,
- @NonNull InputMethodLatencyContext latencyContext) {
- LatencyTracker.getInstance(latencyContext.getAppContext())
- .onActionEnd(ACTION_REQUEST_IME_SHOWN);
- }
-
- public void onHidden(@Nullable Token token,
- @NonNull InputMethodLatencyContext latencyContext) {
- LatencyTracker.getInstance(latencyContext.getAppContext())
- .onActionEnd(ACTION_REQUEST_IME_HIDDEN);
- }
- }
}
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 642182b5ddfd..99bd02de6266 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -2045,11 +2045,10 @@ public final class InputMethodManager {
private boolean showSoftInput(View view, @Nullable ImeTracker.Token statsToken, int flags,
ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
if (statsToken == null) {
- statsToken = ImeTracker.forLogging().onRequestShow(null /* component */,
+ statsToken = ImeTracker.get().onRequestShow(null /* component */,
Process.myUid(), ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT, reason);
}
- ImeTracker.forLatency().onRequestShow(statsToken, ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT,
- reason, ActivityThread::currentApplication);
+
ImeTracing.getInstance().triggerClientDump("InputMethodManager#showSoftInput", this,
null /* icProto */);
// Re-dispatch if there is a context mismatch.
@@ -2061,15 +2060,12 @@ public final class InputMethodManager {
checkFocus();
synchronized (mH) {
if (!hasServedByInputMethodLocked(view)) {
- ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
- ImeTracker.forLatency().onShowFailed(
- statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED,
- ActivityThread::currentApplication);
+ ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
Log.w(TAG, "Ignoring showSoftInput() as view=" + view + " is not served.");
return false;
}
- ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
// Makes sure to call ImeInsetsSourceConsumer#onShowRequested on the UI thread.
// TODO(b/229426865): call WindowInsetsController#show instead.
@@ -2099,20 +2095,20 @@ public final class InputMethodManager {
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123768499)
public void showSoftInputUnchecked(int flags, ResultReceiver resultReceiver) {
synchronized (mH) {
- final ImeTracker.Token statsToken = ImeTracker.forLogging().onRequestShow(
- null /* component */, Process.myUid(), ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT,
+ final ImeTracker.Token statsToken = ImeTracker.get().onRequestShow(null /* component */,
+ Process.myUid(), ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT,
SoftInputShowHideReason.SHOW_SOFT_INPUT);
Log.w(TAG, "showSoftInputUnchecked() is a hidden method, which will be"
+ " removed soon. If you are using androidx.appcompat.widget.SearchView,"
+ " please update to version 26.0 or newer version.");
if (mCurRootView == null || mCurRootView.getView() == null) {
- ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
+ ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
Log.w(TAG, "No current root view, ignoring showSoftInputUnchecked()");
return;
}
- ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
// Makes sure to call ImeInsetsSourceConsumer#onShowRequested on the UI thread.
// TODO(b/229426865): call WindowInsetsController#show instead.
@@ -2190,24 +2186,20 @@ public final class InputMethodManager {
private boolean hideSoftInputFromWindow(IBinder windowToken, int flags,
ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
- final ImeTracker.Token statsToken = ImeTracker.forLogging().onRequestHide(
- null /* component */, Process.myUid(),
- ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT, reason);
- ImeTracker.forLatency().onRequestHide(statsToken, ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
- reason, ActivityThread::currentApplication);
+ final ImeTracker.Token statsToken = ImeTracker.get().onRequestHide(null /* component */,
+ Process.myUid(), ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT, reason);
+
ImeTracing.getInstance().triggerClientDump("InputMethodManager#hideSoftInputFromWindow",
this, null /* icProto */);
checkFocus();
synchronized (mH) {
final View servedView = getServedViewLocked();
if (servedView == null || servedView.getWindowToken() != windowToken) {
- ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
- ImeTracker.forLatency().onHideFailed(statsToken,
- ImeTracker.PHASE_CLIENT_VIEW_SERVED, ActivityThread::currentApplication);
+ ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
return false;
}
- ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
return IInputMethodManagerGlobalInvoker.hideSoftInput(mClient, windowToken, statsToken,
flags, resultReceiver, reason);
@@ -2843,22 +2835,18 @@ public final class InputMethodManager {
@UnsupportedAppUsage
void closeCurrentInput() {
- final ImeTracker.Token statsToken = ImeTracker.forLogging().onRequestHide(
- null /* component */, Process.myUid(), ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
+ final ImeTracker.Token statsToken = ImeTracker.get().onRequestHide(null /* component */,
+ Process.myUid(), ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
SoftInputShowHideReason.HIDE_SOFT_INPUT);
- ImeTracker.forLatency().onRequestHide(statsToken, ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
- SoftInputShowHideReason.HIDE_SOFT_INPUT, ActivityThread::currentApplication);
synchronized (mH) {
if (mCurRootView == null || mCurRootView.getView() == null) {
- ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
- ImeTracker.forLatency().onHideFailed(statsToken,
- ImeTracker.PHASE_CLIENT_VIEW_SERVED, ActivityThread::currentApplication);
+ ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
Log.w(TAG, "No current root view, ignoring closeCurrentInput()");
return;
}
- ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
IInputMethodManagerGlobalInvoker.hideSoftInput(
mClient,
@@ -2917,13 +2905,11 @@ public final class InputMethodManager {
synchronized (mH) {
final View servedView = getServedViewLocked();
if (servedView == null || servedView.getWindowToken() != windowToken) {
- ImeTracker.forLogging().onFailed(statsToken,
- ImeTracker.PHASE_CLIENT_REQUEST_IME_SHOW);
+ ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_REQUEST_IME_SHOW);
return false;
}
- ImeTracker.forLogging().onProgress(statsToken,
- ImeTracker.PHASE_CLIENT_REQUEST_IME_SHOW);
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_REQUEST_IME_SHOW);
showSoftInput(servedView, statsToken, 0 /* flags */, null /* resultReceiver */,
SoftInputShowHideReason.SHOW_SOFT_INPUT_BY_INSETS_API);
@@ -2941,25 +2927,21 @@ public final class InputMethodManager {
*/
public void notifyImeHidden(IBinder windowToken, @Nullable ImeTracker.Token statsToken) {
if (statsToken == null) {
- statsToken = ImeTracker.forLogging().onRequestHide(null /* component */,
+ statsToken = ImeTracker.get().onRequestHide(null /* component */,
Process.myUid(), ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API);
}
- ImeTracker.forLatency().onRequestHide(statsToken, ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
- SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API,
- ActivityThread::currentApplication);
+
ImeTracing.getInstance().triggerClientDump("InputMethodManager#notifyImeHidden", this,
null /* icProto */);
synchronized (mH) {
if (!isImeSessionAvailableLocked() || mCurRootView == null
|| mCurRootView.getWindowToken() != windowToken) {
- ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
- ImeTracker.forLatency().onHideFailed(statsToken,
- ImeTracker.PHASE_CLIENT_VIEW_SERVED, ActivityThread::currentApplication);
+ ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
return;
}
- ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
IInputMethodManagerGlobalInvoker.hideSoftInput(mClient, windowToken, statsToken,
0 /* flags */, null /* resultReceiver */,
diff --git a/core/java/android/view/inputmethod/InsertModeGesture.aidl b/core/java/android/view/inputmethod/InsertModeGesture.aidl
new file mode 100644
index 000000000000..a0790b88a812
--- /dev/null
+++ b/core/java/android/view/inputmethod/InsertModeGesture.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod;
+
+parcelable InsertModeGesture; \ No newline at end of file
diff --git a/core/java/android/view/inputmethod/InsertModeGesture.java b/core/java/android/view/inputmethod/InsertModeGesture.java
new file mode 100644
index 000000000000..6b9d7fbbc65b
--- /dev/null
+++ b/core/java/android/view/inputmethod/InsertModeGesture.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod;
+
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.graphics.PointF;
+import android.os.CancellationSignal;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+
+import java.util.Objects;
+
+/**
+ * A sub-class of {@link HandwritingGesture} for starting an insert mode which inserts a space in
+ * the editor to let users hand write freely at the designated insertion point.
+ * This class holds the information required for insertion of text in
+ * toolkit widgets like {@link TextView}.
+ *
+ * Once InsertMode gesture is started, it continues until IME calls
+ * {@link CancellationSignal#cancel()} and toolkit can receive cancel using
+ * {@link CancellationSignal#setOnCancelListener(CancellationSignal.OnCancelListener)} obtained from
+ * {@link #getCancellationSignal()}.
+ */
+public final class InsertModeGesture extends HandwritingGesture implements Parcelable {
+
+ private PointF mPoint;
+ private CancellationSignal mCancellationSignal;
+
+ private InsertModeGesture(PointF point, String fallbackText,
+ CancellationSignal cancellationSignal) {
+ mType = GESTURE_TYPE_INSERT_MODE;
+ mPoint = point;
+ mFallbackText = fallbackText;
+ mCancellationSignal = cancellationSignal;
+ }
+
+ private InsertModeGesture(final Parcel source) {
+ mType = GESTURE_TYPE_INSERT_MODE;
+ mFallbackText = source.readString8();
+ mPoint = source.readTypedObject(PointF.CREATOR);
+ }
+
+ /**
+ * Returns the {@link CancellationSignal} associated with finishing this gesture.
+ * Once InsertMode gesture is started, it continues until IME calls
+ * {@link CancellationSignal#cancel()} and toolkit can receive cancel using
+ * {@link CancellationSignal#setOnCancelListener(CancellationSignal.OnCancelListener)}.
+ */
+ @NonNull
+ public CancellationSignal getCancellationSignal() {
+ return mCancellationSignal;
+ }
+
+ /**
+ * Returns the insertion point {@link PointF} (in screen coordinates) where space will be
+ * created for additional text to be inserted.
+ */
+ @NonNull
+ public PointF getInsertionPoint() {
+ return mPoint;
+ }
+
+ /**
+ * Builder for {@link InsertModeGesture}. This class is not designed to be thread-safe.
+ */
+ public static final class Builder {
+ private PointF mPoint;
+ private String mFallbackText;
+ // TODO(b/254727073): implement CancellationSignal
+ private CancellationSignal mCancellationSignal;
+
+ /**
+ * Sets the insertion point (in screen coordinates) where space will be created for
+ * additional text to be inserted.
+ */
+ @NonNull
+ @SuppressLint("MissingGetterMatchingBuilder")
+ public Builder setInsertionPoint(@NonNull PointF point) {
+ mPoint = point;
+ return this;
+ }
+
+ /**
+ * Sets the {@link CancellationSignal} used to cancel the ongoing gesture.
+ * @param cancellationSignal signal to cancel an ongoing gesture.
+ */
+ @NonNull
+ @SuppressLint("MissingGetterMatchingBuilder")
+ public Builder setCancellationSignal(@NonNull CancellationSignal cancellationSignal) {
+ mCancellationSignal = cancellationSignal;
+ return this;
+ }
+
+ /**
+ * Set fallback text that will be committed at current cursor position if there is no
+ * applicable text beneath the area of gesture.
+ * @param fallbackText text to set
+ */
+ @NonNull
+ public Builder setFallbackText(@Nullable String fallbackText) {
+ mFallbackText = fallbackText;
+ return this;
+ }
+
+ /**
+ * Returns {@link InsertModeGesture} using parameters in this
+ * {@link InsertModeGesture.Builder}.
+ * @throws IllegalArgumentException if one or more positional parameters are not specified.
+ */
+ @NonNull
+ public InsertModeGesture build() {
+ if (mPoint == null) {
+ throw new IllegalArgumentException("Insertion point must be set.");
+ } else if (mCancellationSignal == null) {
+ throw new IllegalArgumentException("CancellationSignal must be set.");
+ }
+ return new InsertModeGesture(mPoint, mFallbackText, mCancellationSignal);
+ }
+ }
+
+ /**
+ * Used to make this class parcelable.
+ */
+ @NonNull
+ public static final Creator<InsertModeGesture> CREATOR = new Creator<>() {
+ @Override
+ public InsertModeGesture createFromParcel(Parcel source) {
+ return new InsertModeGesture(source);
+ }
+
+ @Override
+ public InsertModeGesture[] newArray(int size) {
+ return new InsertModeGesture[size];
+ }
+ };
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mPoint, mFallbackText);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof InsertModeGesture)) return false;
+
+ InsertModeGesture that = (InsertModeGesture) o;
+
+ if (!Objects.equals(mFallbackText, that.mFallbackText)) return false;
+ return Objects.equals(mPoint, that.mPoint);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Used to package this object into a {@link Parcel}.
+ *
+ * @param dest The {@link Parcel} to be written.
+ * @param flags The flags used for parceling.
+ */
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString8(mFallbackText);
+ dest.writeTypedObject(mPoint, flags);
+ }
+}
diff --git a/core/java/android/view/inputmethod/ParcelableHandwritingGesture.java b/core/java/android/view/inputmethod/ParcelableHandwritingGesture.java
index e4066fcfd61d..ae7df0f91f2e 100644
--- a/core/java/android/view/inputmethod/ParcelableHandwritingGesture.java
+++ b/core/java/android/view/inputmethod/ParcelableHandwritingGesture.java
@@ -70,6 +70,8 @@ public final class ParcelableHandwritingGesture implements Parcelable {
return SelectRangeGesture.CREATOR.createFromParcel(parcel);
case HandwritingGesture.GESTURE_TYPE_INSERT:
return InsertGesture.CREATOR.createFromParcel(parcel);
+ case HandwritingGesture.GESTURE_TYPE_INSERT_MODE:
+ return InsertModeGesture.CREATOR.createFromParcel(parcel);
case HandwritingGesture.GESTURE_TYPE_DELETE:
return DeleteGesture.CREATOR.createFromParcel(parcel);
case HandwritingGesture.GESTURE_TYPE_DELETE_RANGE:
diff --git a/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java
index 23167382ec3b..5e2eceb23789 100644
--- a/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java
+++ b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java
@@ -16,8 +16,8 @@
package com.android.internal.app;
import android.annotation.IntDef;
-import android.annotation.Nullable;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.AppGlobals;
import android.content.ContentResolver;
@@ -60,16 +60,19 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter {
private Set<Integer> mLoadedPages;
private final EmptyStateProvider mEmptyStateProvider;
private final UserHandle mWorkProfileUserHandle;
+ private final UserHandle mCloneUserHandle;
private final QuietModeManager mQuietModeManager;
AbstractMultiProfilePagerAdapter(Context context, int currentPage,
EmptyStateProvider emptyStateProvider,
QuietModeManager quietModeManager,
- UserHandle workProfileUserHandle) {
+ UserHandle workProfileUserHandle,
+ UserHandle cloneUserHandle) {
mContext = Objects.requireNonNull(context);
mCurrentPage = currentPage;
mLoadedPages = new HashSet<>();
mWorkProfileUserHandle = workProfileUserHandle;
+ mCloneUserHandle = cloneUserHandle;
mEmptyStateProvider = emptyStateProvider;
mQuietModeManager = quietModeManager;
}
@@ -160,6 +163,10 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter {
return null;
}
+ public UserHandle getCloneUserHandle() {
+ return mCloneUserHandle;
+ }
+
/**
* Returns the {@link ProfileDescriptor} relevant to the given <code>pageIndex</code>.
* <ul>
diff --git a/core/java/com/android/internal/app/AbstractResolverComparator.java b/core/java/com/android/internal/app/AbstractResolverComparator.java
index 975954035c17..930f6e0c2911 100644
--- a/core/java/com/android/internal/app/AbstractResolverComparator.java
+++ b/core/java/com/android/internal/app/AbstractResolverComparator.java
@@ -30,11 +30,16 @@ import android.os.UserHandle;
import android.util.Log;
import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
+import com.android.internal.app.chooser.TargetInfo;
+
+import com.google.android.collect.Lists;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Comparator;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
/**
* Used to sort resolved activities in {@link ResolverListController}.
@@ -48,8 +53,8 @@ public abstract class AbstractResolverComparator implements Comparator<ResolvedC
private static final String TAG = "AbstractResolverComp";
protected AfterCompute mAfterCompute;
- protected final PackageManager mPm;
- protected final UsageStatsManager mUsm;
+ protected final Map<UserHandle, PackageManager> mPmMap = new HashMap<>();
+ protected final Map<UserHandle, UsageStatsManager> mUsmMap = new HashMap<>();
protected String[] mAnnotations;
protected String mContentType;
@@ -98,14 +103,28 @@ public abstract class AbstractResolverComparator implements Comparator<ResolvedC
}
};
- public AbstractResolverComparator(Context context, Intent intent) {
+ // context here refers to the activity calling this comparator.
+ // targetUserSpace refers to the userSpace in which the targets to be ranked lie.
+ public AbstractResolverComparator(Context launchedFromContext, Intent intent,
+ UserHandle targetUserSpace) {
+ this(launchedFromContext, intent, Lists.newArrayList(targetUserSpace));
+ }
+
+ // context here refers to the activity calling this comparator.
+ // targetUserSpaceList refers to the userSpace(s) in which the targets to be ranked lie.
+ public AbstractResolverComparator(Context launchedFromContext, Intent intent,
+ List<UserHandle> targetUserSpaceList) {
String scheme = intent.getScheme();
mHttp = "http".equals(scheme) || "https".equals(scheme);
mContentType = intent.getType();
getContentAnnotations(intent);
- mPm = context.getPackageManager();
- mUsm = (UsageStatsManager) context.getSystemService(Context.USAGE_STATS_SERVICE);
- mAzComparator = new AzInfoComparator(context);
+ for (UserHandle user : targetUserSpaceList) {
+ Context userContext = launchedFromContext.createContextAsUser(user, 0);
+ mPmMap.put(user, userContext.getPackageManager());
+ mUsmMap.put(user,
+ (UsageStatsManager) userContext.getSystemService(Context.USAGE_STATS_SERVICE));
+ }
+ mAzComparator = new AzInfoComparator(launchedFromContext);
}
// get annotations of content from intent.
@@ -208,8 +227,8 @@ public abstract class AbstractResolverComparator implements Comparator<ResolvedC
/**
* Computes features for each target. This will be called before calls to {@link
- * #getScore(ComponentName)} or {@link #compare(Object, Object)}, in order to prepare the
- * comparator for those calls. Note that {@link #getScore(ComponentName)} uses {@link
+ * #getScore(TargetInfo)} or {@link #compare(ResolveInfo, ResolveInfo)}, in order to prepare the
+ * comparator for those calls. Note that {@link #getScore(TargetInfo)} uses {@link
* ComponentName}, so the implementation will have to be prepared to identify a {@link
* ResolvedComponentInfo} by {@link ComponentName}. {@link #beforeCompute()} will be called
* before doing any computing.
@@ -226,7 +245,7 @@ public abstract class AbstractResolverComparator implements Comparator<ResolvedC
* Returns the score that was calculated for the corresponding {@link ResolvedComponentInfo}
* when {@link #compute(List)} was called before this.
*/
- abstract float getScore(ComponentName name);
+ abstract float getScore(TargetInfo targetInfo);
/** Handles result message sent to mHandler. */
abstract void handleResultMessage(Message message);
@@ -234,9 +253,11 @@ public abstract class AbstractResolverComparator implements Comparator<ResolvedC
/**
* Reports to UsageStats what was chosen.
*/
- final void updateChooserCounts(String packageName, int userId, String action) {
- if (mUsm != null) {
- mUsm.reportChooserSelection(packageName, userId, mContentType, mAnnotations, action);
+ final void updateChooserCounts(String packageName, UserHandle user, String action) {
+ if (mUsmMap.containsKey(user)) {
+ mUsmMap.get(user)
+ .reportChooserSelection(packageName, user.getIdentifier(), mContentType,
+ mAnnotations, action);
}
}
@@ -246,9 +267,9 @@ public abstract class AbstractResolverComparator implements Comparator<ResolvedC
* <p>Default implementation does nothing, as we could have simple model that does not train
* online.
*
- * @param componentName the component that the user clicked
+ * @param targetInfo the target that the user clicked.
*/
- void updateModel(ComponentName componentName) {
+ void updateModel(TargetInfo targetInfo) {
}
/** Called before {@link #doCompute(List)}. Sets up 500ms timeout. */
diff --git a/core/java/com/android/internal/app/AppPredictionServiceResolverComparator.java b/core/java/com/android/internal/app/AppPredictionServiceResolverComparator.java
index 115a9d772658..b9f02365bbe7 100644
--- a/core/java/com/android/internal/app/AppPredictionServiceResolverComparator.java
+++ b/core/java/com/android/internal/app/AppPredictionServiceResolverComparator.java
@@ -31,6 +31,9 @@ import android.os.UserHandle;
import android.util.Log;
import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
+import com.android.internal.app.chooser.TargetInfo;
+
+import com.google.android.collect.Lists;
import java.util.ArrayList;
import java.util.Collections;
@@ -70,7 +73,7 @@ class AppPredictionServiceResolverComparator extends AbstractResolverComparator
AppPredictor appPredictor,
UserHandle user,
ChooserActivityLogger chooserActivityLogger) {
- super(context, intent);
+ super(context, intent, Lists.newArrayList(user));
mContext = context;
mIntent = intent;
mAppPredictor = appPredictor;
@@ -99,13 +102,13 @@ class AppPredictionServiceResolverComparator extends AbstractResolverComparator
}
@Override
- float getScore(ComponentName name) {
- return mComparatorModel.getScore(name);
+ float getScore(TargetInfo targetInfo) {
+ return mComparatorModel.getScore(targetInfo);
}
@Override
- void updateModel(ComponentName componentName) {
- mComparatorModel.notifyOnTargetSelected(componentName);
+ void updateModel(TargetInfo targetInfo) {
+ mComparatorModel.notifyOnTargetSelected(targetInfo);
}
@Override
@@ -158,9 +161,12 @@ class AppPredictionServiceResolverComparator extends AbstractResolverComparator
private void setupFallbackModel(List<ResolvedComponentInfo> targets) {
mResolverRankerService =
new ResolverRankerServiceResolverComparator(
- mContext, mIntent, mReferrerPackage,
+ mContext,
+ mIntent,
+ mReferrerPackage,
() -> mHandler.sendEmptyMessage(RANKER_SERVICE_RESULT),
- getChooserActivityLogger());
+ getChooserActivityLogger(),
+ mUser);
mComparatorModel = mModelBuilder.buildFallbackModel(mResolverRankerService);
mResolverRankerService.compute(targets);
}
@@ -224,13 +230,13 @@ class AppPredictionServiceResolverComparator extends AbstractResolverComparator
}
@Override
- public float getScore(ComponentName componentName) {
- return comparator.getScore(componentName);
+ public float getScore(TargetInfo targetInfo) {
+ return comparator.getScore(targetInfo);
}
@Override
- public void notifyOnTargetSelected(ComponentName componentName) {
- comparator.updateModel(componentName);
+ public void notifyOnTargetSelected(TargetInfo targetInfo) {
+ comparator.updateModel(targetInfo);
}
};
}
@@ -271,8 +277,8 @@ class AppPredictionServiceResolverComparator extends AbstractResolverComparator
}
@Override
- public float getScore(ComponentName name) {
- Integer rank = mTargetRanks.get(name);
+ public float getScore(TargetInfo targetInfo) {
+ Integer rank = mTargetRanks.get(targetInfo.getResolvedComponentName());
if (rank == null) {
Log.w(TAG, "Score requested for unknown component. Did you call compute yet?");
return 0f;
@@ -282,13 +288,14 @@ class AppPredictionServiceResolverComparator extends AbstractResolverComparator
}
@Override
- public void notifyOnTargetSelected(ComponentName componentName) {
+ public void notifyOnTargetSelected(TargetInfo targetInfo) {
mAppPredictor.notifyAppTargetEvent(
new AppTargetEvent.Builder(
new AppTarget.Builder(
- new AppTargetId(componentName.toString()),
- componentName.getPackageName(), mUser)
- .setClassName(componentName.getClassName()).build(),
+ new AppTargetId(targetInfo.getResolvedComponentName().toString()),
+ targetInfo.getResolvedComponentName().getPackageName(), mUser)
+ .setClassName(targetInfo.getResolvedComponentName()
+ .getClassName()).build(),
ACTION_LAUNCH).build());
}
}
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 9f283d4e81bf..f257f1c9b7ce 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -873,7 +873,7 @@ public class ChooserActivity extends ResolverActivity implements
return new NoCrossProfileEmptyStateProvider(getPersonalProfileUserHandle(),
noWorkToPersonalEmptyState, noPersonalToWorkEmptyState,
- createCrossProfileIntentsChecker(), createMyUserIdProvider());
+ createCrossProfileIntentsChecker(), getTabOwnerUserHandleForLaunch());
}
private ChooserMultiProfilePagerAdapter createChooserMultiProfilePagerAdapterForOneProfile(
@@ -886,13 +886,14 @@ public class ChooserActivity extends ResolverActivity implements
initialIntents,
rList,
filterLastUsed,
- /* userHandle */ UserHandle.of(UserHandle.myUserId()));
+ /* userHandle */ getPersonalProfileUserHandle());
return new ChooserMultiProfilePagerAdapter(
/* context */ this,
adapter,
createEmptyStateProvider(/* workProfileUserHandle= */ null),
mQuietModeManager,
/* workProfileUserHandle= */ null,
+ getCloneProfileUserHandle(),
mMaxTargetsPerRow);
}
@@ -923,13 +924,14 @@ public class ChooserActivity extends ResolverActivity implements
mQuietModeManager,
selectedProfile,
getWorkProfileUserHandle(),
+ getCloneProfileUserHandle(),
mMaxTargetsPerRow);
}
private int findSelectedProfile() {
int selectedProfile = getSelectedProfileExtra();
if (selectedProfile == -1) {
- selectedProfile = getProfileForUser(getUser());
+ selectedProfile = getProfileForUser(getTabOwnerUserHandleForLaunch());
}
return selectedProfile;
}
@@ -1800,8 +1802,12 @@ public class ChooserActivity extends ResolverActivity implements
targetList = new ArrayList<DisplayResolveInfo>();
targetList.add((DisplayResolveInfo) targetInfo);
}
+ // Adding userHandle from ResolveInfo allows the app icon in Dialog Box to be
+ // resolved correctly.
bundle.putParcelable(ChooserTargetActionsDialogFragment.USER_HANDLE_KEY,
- mChooserMultiProfilePagerAdapter.getCurrentUserHandle());
+ getResolveInfoUserHandle(
+ targetInfo.getResolveInfo(),
+ mChooserMultiProfilePagerAdapter.getCurrentUserHandle()));
bundle.putParcelableArrayList(ChooserTargetActionsDialogFragment.TARGET_INFOS_KEY,
targetList);
fragment.setArguments(bundle);
@@ -1865,8 +1871,11 @@ public class ChooserActivity extends ResolverActivity implements
if (!mti.hasSelected()) {
ChooserStackedAppDialogFragment f = new ChooserStackedAppDialogFragment();
Bundle b = new Bundle();
+ // Add userHandle based badge to the stackedAppDialogBox.
b.putParcelable(ChooserTargetActionsDialogFragment.USER_HANDLE_KEY,
- mChooserMultiProfilePagerAdapter.getCurrentUserHandle());
+ getResolveInfoUserHandle(
+ targetInfo.getResolveInfo(),
+ mChooserMultiProfilePagerAdapter.getCurrentUserHandle()));
b.putObject(ChooserStackedAppDialogFragment.MULTI_DRI_KEY,
mti);
b.putInt(ChooserStackedAppDialogFragment.WHICH_KEY, which);
@@ -2259,9 +2268,11 @@ public class ChooserActivity extends ResolverActivity implements
mChooserMultiProfilePagerAdapter.getActiveListAdapter();
if (currentListAdapter != null) {
sendImpressionToAppPredictor(info, currentListAdapter);
- currentListAdapter.updateModel(info.getResolvedComponentName());
- currentListAdapter.updateChooserCounts(ri.activityInfo.packageName,
- targetIntent.getAction());
+ currentListAdapter.updateModel(info);
+ currentListAdapter.updateChooserCounts(
+ ri.activityInfo.packageName,
+ targetIntent.getAction(),
+ ri.userHandle);
}
if (DEBUG) {
Log.d(TAG, "ResolveInfo Package is " + ri.activityInfo.packageName);
@@ -2386,7 +2397,10 @@ public class ChooserActivity extends ResolverActivity implements
*/
@Nullable
private AppPredictor getAppPredictorForShareActivitiesIfEnabled(UserHandle userHandle) {
- return USE_PREDICTION_MANAGER_FOR_SHARE_ACTIVITIES ? createAppPredictor(userHandle) : null;
+ // We cannot use APS service when clone profile is present as APS service cannot sort
+ // cross profile targets as of now.
+ return USE_PREDICTION_MANAGER_FOR_SHARE_ACTIVITIES && getCloneProfileUserHandle() == null
+ ? createAppPredictor(userHandle) : null;
}
void onRefinementResult(TargetInfo selectedTarget, Intent matchingIntent) {
@@ -2433,15 +2447,24 @@ public class ChooserActivity extends ResolverActivity implements
* Sort intents alphabetically based on display label.
*/
static class AzInfoComparator implements Comparator<DisplayResolveInfo> {
- Collator mCollator;
+ Comparator<DisplayResolveInfo> mComparator;
AzInfoComparator(Context context) {
- mCollator = Collator.getInstance(context.getResources().getConfiguration().locale);
+ Collator collator = Collator
+ .getInstance(context.getResources().getConfiguration().locale);
+ // Adding two stage comparator, first stage compares using displayLabel, next stage
+ // compares using resolveInfo.userHandle
+ mComparator = Comparator.comparing(DisplayResolveInfo::getDisplayLabel, collator)
+ .thenComparingInt(displayResolveInfo ->
+ getResolveInfoUserHandle(
+ displayResolveInfo.getResolveInfo(),
+ // TODO: User resolveInfo.userHandle, once its available.
+ UserHandle.SYSTEM).getIdentifier());
}
@Override
public int compare(
DisplayResolveInfo lhsp, DisplayResolveInfo rhsp) {
- return mCollator.compare(lhsp.getDisplayLabel(), rhsp.getDisplayLabel());
+ return mComparator.compare(lhsp, rhsp);
}
}
@@ -2466,9 +2489,10 @@ public class ChooserActivity extends ResolverActivity implements
String referrerPackageName,
int launchedFromUid,
UserHandle userId,
- AbstractResolverComparator resolverComparator) {
+ AbstractResolverComparator resolverComparator,
+ UserHandle queryIntentsAsUser) {
super(context, pm, targetIntent, referrerPackageName, launchedFromUid, userId,
- resolverComparator);
+ resolverComparator, queryIntentsAsUser);
}
@Override
@@ -2529,10 +2553,16 @@ public class ChooserActivity extends ResolverActivity implements
getReferrerPackageName(), appPredictor, userHandle, getChooserActivityLogger());
} else {
resolverComparator =
- new ResolverRankerServiceResolverComparator(this, getTargetIntent(),
- getReferrerPackageName(), null, getChooserActivityLogger());
+ new ResolverRankerServiceResolverComparator(
+ this,
+ getTargetIntent(),
+ getReferrerPackageName(),
+ null,
+ getChooserActivityLogger(),
+ getResolverRankerServiceUserHandleList(userHandle));
}
+ UserHandle queryIntentsUser = getQueryIntentsUser(userHandle);
return new ChooserListController(
this,
mPm,
@@ -2540,7 +2570,8 @@ public class ChooserActivity extends ResolverActivity implements
getReferrerPackageName(),
mLaunchedFromUid,
userHandle,
- resolverComparator);
+ resolverComparator,
+ queryIntentsUser == null ? userHandle : queryIntentsUser);
}
@VisibleForTesting
@@ -2741,17 +2772,16 @@ public class ChooserActivity extends ResolverActivity implements
}
/**
- * Returns {@link #PROFILE_PERSONAL}, {@link #PROFILE_WORK}, or -1 if the given user handle
- * does not match either the personal or work user handle.
+ * Returns {@link #PROFILE_WORK}, if the given user handle matches work user handle.
+ * Returns {@link #PROFILE_PERSONAL}, otherwise.
**/
private int getProfileForUser(UserHandle currentUserHandle) {
- if (currentUserHandle.equals(getPersonalProfileUserHandle())) {
- return PROFILE_PERSONAL;
- } else if (currentUserHandle.equals(getWorkProfileUserHandle())) {
+ if (currentUserHandle.equals(getWorkProfileUserHandle())) {
return PROFILE_WORK;
}
- Log.e(TAG, "User " + currentUserHandle + " does not belong to a personal or work profile.");
- return -1;
+ // We return personal profile, as it is the default when there is no work profile, personal
+ // profile represents rootUser, clonedUser & secondaryUser, covering all use cases.
+ return PROFILE_PERSONAL;
}
private ViewGroup getActiveEmptyStateView() {
diff --git a/core/java/com/android/internal/app/ChooserListAdapter.java b/core/java/com/android/internal/app/ChooserListAdapter.java
index 2ae2c09680bf..e0568cfe6560 100644
--- a/core/java/com/android/internal/app/ChooserListAdapter.java
+++ b/core/java/com/android/internal/app/ChooserListAdapter.java
@@ -197,6 +197,7 @@ public class ChooserListAdapter extends ResolverListAdapter {
ri.nonLocalizedLabel = li.getNonLocalizedLabel();
ri.icon = li.getIconResource();
ri.iconResourceId = ri.icon;
+ ri.userHandle = getUserHandle();
}
if (userManager.isManagedProfile()) {
ri.noResourceId = true;
@@ -351,7 +352,9 @@ public class ChooserListAdapter extends ResolverListAdapter {
Map<String, DisplayResolveInfo> consolidated = new HashMap<>();
for (DisplayResolveInfo info : allTargets) {
String resolvedTarget = info.getResolvedComponentName().getPackageName()
- + '#' + info.getDisplayLabel();
+ + '#' + info.getDisplayLabel()
+ + '#' + ResolverActivity.getResolveInfoUserHandle(
+ info.getResolveInfo(), getUserHandle()).getIdentifier();
DisplayResolveInfo multiDri = consolidated.get(resolvedTarget);
if (multiDri == null) {
consolidated.put(resolvedTarget, info);
@@ -367,7 +370,8 @@ public class ChooserListAdapter extends ResolverListAdapter {
}
List<DisplayResolveInfo> groupedTargets = new ArrayList<>();
groupedTargets.addAll(consolidated.values());
- Collections.sort(groupedTargets, new ChooserActivity.AzInfoComparator(mContext));
+ Collections.sort(groupedTargets,
+ new ChooserActivity.AzInfoComparator(mContext));
return groupedTargets;
}
@Override
diff --git a/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
index 0509b6754218..f56f8184a86b 100644
--- a/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
+++ b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
@@ -45,9 +45,10 @@ public class ChooserMultiProfilePagerAdapter extends AbstractMultiProfilePagerAd
EmptyStateProvider emptyStateProvider,
QuietModeManager quietModeManager,
UserHandle workProfileUserHandle,
+ UserHandle cloneUserHandle,
int maxTargetsPerRow) {
super(context, /* currentPage */ 0, emptyStateProvider, quietModeManager,
- workProfileUserHandle);
+ workProfileUserHandle, cloneUserHandle);
mItems = new ChooserProfileDescriptor[] {
createProfileDescriptor(adapter)
};
@@ -61,9 +62,10 @@ public class ChooserMultiProfilePagerAdapter extends AbstractMultiProfilePagerAd
QuietModeManager quietModeManager,
@Profile int defaultProfile,
UserHandle workProfileUserHandle,
+ UserHandle cloneUserHandle,
int maxTargetsPerRow) {
super(context, /* currentPage */ defaultProfile, emptyStateProvider,
- quietModeManager, workProfileUserHandle);
+ quietModeManager, workProfileUserHandle, cloneUserHandle);
mItems = new ChooserProfileDescriptor[] {
createProfileDescriptor(personalAdapter),
createProfileDescriptor(workAdapter)
@@ -110,11 +112,12 @@ public class ChooserMultiProfilePagerAdapter extends AbstractMultiProfilePagerAd
@Override
@Nullable
ChooserListAdapter getListAdapterForUserHandle(UserHandle userHandle) {
- if (getActiveListAdapter().getUserHandle().equals(userHandle)) {
- return getActiveListAdapter();
- } else if (getInactiveListAdapter() != null
- && getInactiveListAdapter().getUserHandle().equals(userHandle)) {
- return getInactiveListAdapter();
+ if (getPersonalListAdapter().getUserHandle().equals(userHandle)
+ || userHandle.equals(getCloneUserHandle())) {
+ return getPersonalListAdapter();
+ } else if (getWorkListAdapter() != null
+ && getWorkListAdapter().getUserHandle().equals(userHandle)) {
+ return getWorkListAdapter();
}
return null;
}
@@ -153,13 +156,13 @@ public class ChooserMultiProfilePagerAdapter extends AbstractMultiProfilePagerAd
}
@Override
- public ResolverListAdapter getPersonalListAdapter() {
+ public ChooserListAdapter getPersonalListAdapter() {
return getAdapterForIndex(PROFILE_PERSONAL).getListAdapter();
}
@Override
@Nullable
- public ResolverListAdapter getWorkListAdapter() {
+ public ChooserListAdapter getWorkListAdapter() {
return getAdapterForIndex(PROFILE_WORK).getListAdapter();
}
diff --git a/core/java/com/android/internal/app/LocalePickerWithRegion.java b/core/java/com/android/internal/app/LocalePickerWithRegion.java
index 3efd279c2639..3619c7b7965d 100644
--- a/core/java/com/android/internal/app/LocalePickerWithRegion.java
+++ b/core/java/com/android/internal/app/LocalePickerWithRegion.java
@@ -21,6 +21,7 @@ import android.app.FragmentTransaction;
import android.app.ListFragment;
import android.content.Context;
import android.os.Bundle;
+import android.os.LocaleList;
import android.text.TextUtils;
import android.view.Menu;
import android.view.MenuInflater;
@@ -102,15 +103,21 @@ public class LocalePickerWithRegion extends ListFragment implements SearchView.O
public static LocalePickerWithRegion createLanguagePicker(Context context,
LocaleSelectedListener listener, boolean translatedOnly) {
- return createLanguagePicker(context, listener, translatedOnly, null, null);
+ return createLanguagePicker(context, listener, translatedOnly, null, null, null);
}
public static LocalePickerWithRegion createLanguagePicker(Context context,
- LocaleSelectedListener listener, boolean translatedOnly, String appPackageName,
- OnActionExpandListener onActionExpandListener) {
+ LocaleSelectedListener listener, boolean translatedOnly, LocaleList explicitLocales) {
+ return createLanguagePicker(context, listener, translatedOnly, explicitLocales, null, null);
+ }
+
+ /** Creates language picker UI */
+ public static LocalePickerWithRegion createLanguagePicker(Context context,
+ LocaleSelectedListener listener, boolean translatedOnly, LocaleList explicitLocales,
+ String appPackageName, OnActionExpandListener onActionExpandListener) {
LocaleCollectorBase localePickerController;
if (TextUtils.isEmpty(appPackageName)) {
- localePickerController = new SystemLocaleCollector(context);
+ localePickerController = new SystemLocaleCollector(context, explicitLocales);
} else {
localePickerController = new AppLocaleCollector(context, appPackageName);
}
diff --git a/core/java/com/android/internal/app/LocaleStore.java b/core/java/com/android/internal/app/LocaleStore.java
index d2eee91d257f..bcff9078ddda 100644
--- a/core/java/com/android/internal/app/LocaleStore.java
+++ b/core/java/com/android/internal/app/LocaleStore.java
@@ -22,6 +22,7 @@ import android.content.Context;
import android.os.LocaleList;
import android.provider.Settings;
import android.telephony.TelephonyManager;
+import android.text.TextUtils;
import android.util.Log;
import android.view.inputmethod.InputMethodSubtype;
@@ -29,6 +30,7 @@ import com.android.internal.annotations.VisibleForTesting;
import java.io.Serializable;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IllformedLocaleException;
@@ -106,6 +108,9 @@ public class LocaleStore {
return mParent;
}
+ /**
+ * TODO: This method may rename to be more generic i.e. toLanguageTag().
+ */
@UnsupportedAppUsage
public String getId() {
return mId;
@@ -456,11 +461,30 @@ public class LocaleStore {
@UnsupportedAppUsage
public static Set<LocaleInfo> getLevelLocales(Context context, Set<String> ignorables,
LocaleInfo parent, boolean translatedOnly) {
+ return getLevelLocales(context, ignorables, parent, translatedOnly, null);
+ }
+
+ /**
+ * @param explicitLocales Indicates only the locales within this list should be shown in the
+ * locale picker.
+ *
+ * Returns a list of locales for language or region selection.
+ * If the parent is null, then it is the language list.
+ * If it is not null, then the list will contain all the locales that belong to that parent.
+ * Example: if the parent is "ar", then the region list will contain all Arabic locales.
+ * (this is not language based, but language-script, so that it works for zh-Hant and so on.
+ */
+ public static Set<LocaleInfo> getLevelLocales(Context context, Set<String> ignorables,
+ LocaleInfo parent, boolean translatedOnly, LocaleList explicitLocales) {
fillCache(context);
String parentId = parent == null ? null : parent.getId();
-
HashSet<LocaleInfo> result = new HashSet<>();
- for (LocaleStore.LocaleInfo li : sLocaleCache.values()) {
+ HashMap<String, LocaleInfo> supportedLcoaleInfos =
+ explicitLocales == null
+ ? sLocaleCache
+ : convertExplicitLocales(explicitLocales, sLocaleCache.values());
+
+ for (LocaleStore.LocaleInfo li : supportedLcoaleInfos.values()) {
int level = getLevel(ignorables, li, translatedOnly);
if (level == 2) {
if (parent != null) { // region selection
@@ -479,6 +503,61 @@ public class LocaleStore {
return result;
}
+ /** Converts string array of explicit locales to HashMap */
+ public static HashMap<String, LocaleInfo> convertExplicitLocales(
+ LocaleList explicitLocales, Collection<LocaleInfo> localeinfo) {
+ // Trys to find the matched locale within android supported locales. If there is no matched
+ // locale, it will still keep the unsupported lcoale in list.
+ // Note: This currently does not support unicode extension check.
+ LocaleList localeList = matchLocaleFromSupportedLocaleList(
+ explicitLocales, localeinfo);
+
+ HashMap<String, LocaleInfo> localeInfos = new HashMap<>();
+ for (int i = 0; i < localeList.size(); i++) {
+ Locale locale = localeList.get(i);
+ if (locale.toString().isEmpty()) {
+ throw new IllformedLocaleException("Bad locale entry");
+ }
+
+ LocaleInfo li = new LocaleInfo(locale);
+ if (localeInfos.containsKey(li.getId())) {
+ continue;
+ }
+ localeInfos.put(li.getId(), li);
+ Locale parent = li.getParent();
+ if (parent != null) {
+ String parentId = parent.toLanguageTag();
+ if (!localeInfos.containsKey(parentId)) {
+ localeInfos.put(parentId, new LocaleInfo(parent));
+ }
+ }
+ }
+ return localeInfos;
+ }
+
+ private static LocaleList matchLocaleFromSupportedLocaleList(
+ LocaleList explicitLocales, Collection<LocaleInfo> localeinfo) {
+ //TODO: Adds a function for unicode extension if needed.
+ Locale[] resultLocales = new Locale[explicitLocales.size()];
+ for (int i = 0; i < explicitLocales.size(); i++) {
+ Locale locale = explicitLocales.get(i).stripExtensions();
+ if (!TextUtils.isEmpty(locale.getCountry())) {
+ for (LocaleInfo localeInfo :localeinfo) {
+ if (LocaleList.matchesLanguageAndScript(locale, localeInfo.getLocale())
+ && TextUtils.equals(locale.getCountry(),
+ localeInfo.getLocale().getCountry())) {
+ resultLocales[i] = localeInfo.getLocale();
+ continue;
+ }
+ }
+ }
+ if (resultLocales[i] == null) {
+ resultLocales[i] = locale;
+ }
+ }
+ return new LocaleList(resultLocales);
+ }
+
@UnsupportedAppUsage
public static LocaleInfo getLocaleInfo(Locale locale) {
String id = locale.toLanguageTag();
diff --git a/core/java/com/android/internal/app/NoAppsAvailableEmptyStateProvider.java b/core/java/com/android/internal/app/NoAppsAvailableEmptyStateProvider.java
index 34249f2457c7..747780bd0c59 100644
--- a/core/java/com/android/internal/app/NoAppsAvailableEmptyStateProvider.java
+++ b/core/java/com/android/internal/app/NoAppsAvailableEmptyStateProvider.java
@@ -28,10 +28,9 @@ import android.content.pm.ResolveInfo;
import android.os.UserHandle;
import android.stats.devicepolicy.nano.DevicePolicyEnums;
+import com.android.internal.R;
import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyState;
import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyStateProvider;
-import com.android.internal.app.AbstractMultiProfilePagerAdapter.MyUserIdProvider;
-import com.android.internal.R;
import java.util.List;
@@ -50,16 +49,16 @@ public class NoAppsAvailableEmptyStateProvider implements EmptyStateProvider {
@NonNull
private final String mMetricsCategory;
@NonNull
- private final MyUserIdProvider mMyUserIdProvider;
+ private final UserHandle mTabOwnerUserHandleForLaunch;
public NoAppsAvailableEmptyStateProvider(Context context, UserHandle workProfileUserHandle,
UserHandle personalProfileUserHandle, String metricsCategory,
- MyUserIdProvider myUserIdProvider) {
+ UserHandle tabOwnerUserHandleForLaunch) {
mContext = context;
mWorkProfileUserHandle = workProfileUserHandle;
mPersonalProfileUserHandle = personalProfileUserHandle;
mMetricsCategory = metricsCategory;
- mMyUserIdProvider = myUserIdProvider;
+ mTabOwnerUserHandleForLaunch = tabOwnerUserHandleForLaunch;
}
@Nullable
@@ -69,7 +68,7 @@ public class NoAppsAvailableEmptyStateProvider implements EmptyStateProvider {
UserHandle listUserHandle = resolverListAdapter.getUserHandle();
if (mWorkProfileUserHandle != null
- && (mMyUserIdProvider.getMyUserId() == listUserHandle.getIdentifier()
+ && (mTabOwnerUserHandleForLaunch.equals(listUserHandle)
|| !hasAppsInOtherProfile(resolverListAdapter))) {
String title;
@@ -102,7 +101,7 @@ public class NoAppsAvailableEmptyStateProvider implements EmptyStateProvider {
return false;
}
List<ResolverActivity.ResolvedComponentInfo> resolversForIntent =
- adapter.getResolversForUser(UserHandle.of(mMyUserIdProvider.getMyUserId()));
+ adapter.getResolversForUser(mTabOwnerUserHandleForLaunch);
for (ResolverActivity.ResolvedComponentInfo info : resolversForIntent) {
ResolveInfo resolveInfo = info.getResolveInfoAt(0);
if (resolveInfo.targetUserId != UserHandle.USER_CURRENT) {
diff --git a/core/java/com/android/internal/app/NoCrossProfileEmptyStateProvider.java b/core/java/com/android/internal/app/NoCrossProfileEmptyStateProvider.java
index 2e7d5bf00e27..2046bfce6bf4 100644
--- a/core/java/com/android/internal/app/NoCrossProfileEmptyStateProvider.java
+++ b/core/java/com/android/internal/app/NoCrossProfileEmptyStateProvider.java
@@ -27,7 +27,6 @@ import android.os.UserHandle;
import com.android.internal.app.AbstractMultiProfilePagerAdapter.CrossProfileIntentsChecker;
import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyState;
import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyStateProvider;
-import com.android.internal.app.AbstractMultiProfilePagerAdapter.MyUserIdProvider;
/**
* Empty state provider that does not allow cross profile sharing, it will return a blocker
@@ -39,28 +38,28 @@ public class NoCrossProfileEmptyStateProvider implements EmptyStateProvider {
private final EmptyState mNoWorkToPersonalEmptyState;
private final EmptyState mNoPersonalToWorkEmptyState;
private final CrossProfileIntentsChecker mCrossProfileIntentsChecker;
- private final MyUserIdProvider mUserIdProvider;
+ private final UserHandle mTabOwnerUserHandleForLaunch;
public NoCrossProfileEmptyStateProvider(UserHandle personalUserHandle,
EmptyState noWorkToPersonalEmptyState,
EmptyState noPersonalToWorkEmptyState,
CrossProfileIntentsChecker crossProfileIntentsChecker,
- MyUserIdProvider myUserIdProvider) {
+ UserHandle preselectedTabOwnerUserHandle) {
mPersonalProfileUserHandle = personalUserHandle;
mNoWorkToPersonalEmptyState = noWorkToPersonalEmptyState;
mNoPersonalToWorkEmptyState = noPersonalToWorkEmptyState;
mCrossProfileIntentsChecker = crossProfileIntentsChecker;
- mUserIdProvider = myUserIdProvider;
+ mTabOwnerUserHandleForLaunch = preselectedTabOwnerUserHandle;
}
@Nullable
@Override
public EmptyState getEmptyState(ResolverListAdapter resolverListAdapter) {
boolean shouldShowBlocker =
- mUserIdProvider.getMyUserId() != resolverListAdapter.getUserHandle().getIdentifier()
+ !mTabOwnerUserHandleForLaunch.equals(resolverListAdapter.getUserHandle())
&& !mCrossProfileIntentsChecker
.hasCrossProfileIntents(resolverListAdapter.getIntents(),
- mUserIdProvider.getMyUserId(),
+ mTabOwnerUserHandleForLaunch.getIdentifier(),
resolverListAdapter.getUserHandle().getIdentifier());
if (!shouldShowBlocker) {
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index f098e2c6bd12..992e243060fc 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -166,6 +166,7 @@ public class ResolverActivity extends Activity implements
@UnsupportedAppUsage
protected PackageManager mPm;
protected int mLaunchedFromUid;
+ private UserHandle mLaunchedFromUserHandle;
private static final String TAG = "ResolverActivity";
private static final boolean DEBUG = false;
@@ -229,12 +230,15 @@ public class ResolverActivity extends Activity implements
private BroadcastReceiver mWorkProfileStateReceiver;
private UserHandle mHeaderCreatorUser;
-
+ private UserHandle mPersonalProfileUserHandle;
private UserHandle mWorkProfileUserHandle;
@Nullable
private OnSwitchOnWorkSelectedListener mOnSwitchOnWorkSelectedListener;
+ private UserHandle mCloneProfileUserHandle;
+ private UserHandle mTabOwnerUserHandleForLaunch;
+
protected final LatencyTracker mLatencyTracker = getLatencyTracker();
private LatencyTracker getLatencyTracker() {
@@ -400,6 +404,7 @@ public class ResolverActivity extends Activity implements
setProfileSwitchMessage(intent.getContentUserHint());
mLaunchedFromUid = getLaunchedFromUid();
+ mLaunchedFromUserHandle = UserHandle.getUserHandleForUid(mLaunchedFromUid);
if (mLaunchedFromUid < 0 || UserHandle.isIsolated(mLaunchedFromUid)) {
// Gulp!
finish();
@@ -416,15 +421,21 @@ public class ResolverActivity extends Activity implements
mDefaultTitleResId = defaultTitleRes;
mSupportsAlwaysUseOption = supportsAlwaysUseOption;
+ mPersonalProfileUserHandle = fetchPersonalProfileUserHandle();
mWorkProfileUserHandle = fetchWorkProfileUserProfile();
+ mCloneProfileUserHandle = fetchCloneProfileUserHandle();
+ mTabOwnerUserHandleForLaunch = fetchTabOwnerUserHandleForLaunch();
// The last argument of createResolverListAdapter is whether to do special handling
// of the last used choice to highlight it in the list. We need to always
// turn this off when running under voice interaction, since it results in
// a more complicated UI that the current voice interaction flow is not able
// to handle. We also turn it off when the work tab is shown to simplify the UX.
+ // We also turn it off when clonedProfile is present on the device, because we might have
+ // different "last chosen" activities in the different profiles, and PackageManager doesn't
+ // provide any more information to help us select between them.
boolean filterLastUsed = mSupportsAlwaysUseOption && !isVoiceInteraction()
- && !shouldShowTabs();
+ && !shouldShowTabs() && !hasCloneProfile();
mMultiProfilePagerAdapter = createMultiProfilePagerAdapter(initialIntents, rList, filterLastUsed);
if (configureContentView()) {
return;
@@ -566,9 +577,12 @@ public class ResolverActivity extends Activity implements
/* devicePolicyEventId= */ RESOLVER_EMPTY_STATE_NO_SHARING_TO_WORK,
/* devicePolicyEventCategory= */ ResolverActivity.METRICS_CATEGORY_RESOLVER);
- return new NoCrossProfileEmptyStateProvider(getPersonalProfileUserHandle(),
- noWorkToPersonalEmptyState, noPersonalToWorkEmptyState,
- createCrossProfileIntentsChecker(), createMyUserIdProvider());
+ return new NoCrossProfileEmptyStateProvider(
+ getPersonalProfileUserHandle(),
+ noWorkToPersonalEmptyState,
+ noPersonalToWorkEmptyState,
+ createCrossProfileIntentsChecker(),
+ getTabOwnerUserHandleForLaunch());
}
protected EmptyStateProvider createEmptyStateProvider(
@@ -589,7 +603,7 @@ public class ResolverActivity extends Activity implements
workProfileUserHandle,
getPersonalProfileUserHandle(),
getMetricsCategory(),
- createMyUserIdProvider()
+ getTabOwnerUserHandleForLaunch()
);
// Return composite provider, the order matters (the higher, the more priority)
@@ -609,14 +623,15 @@ public class ResolverActivity extends Activity implements
initialIntents,
rList,
filterLastUsed,
- /* userHandle */ UserHandle.of(UserHandle.myUserId()));
+ /* userHandle */ getPersonalProfileUserHandle());
QuietModeManager quietModeManager = createQuietModeManager();
return new ResolverMultiProfilePagerAdapter(
/* context */ this,
adapter,
createEmptyStateProvider(/* workProfileUserHandle= */ null),
quietModeManager,
- /* workProfileUserHandle= */ null);
+ /* workProfileUserHandle= */ null,
+ getCloneProfileUserHandle());
}
private UserHandle getIntentUser() {
@@ -634,7 +649,7 @@ public class ResolverActivity extends Activity implements
// this happens, we check for it here and set the current profile's tab.
int selectedProfile = getCurrentProfile();
UserHandle intentUser = getIntentUser();
- if (!getUser().equals(intentUser)) {
+ if (!getTabOwnerUserHandleForLaunch().equals(intentUser)) {
if (getPersonalProfileUserHandle().equals(intentUser)) {
selectedProfile = PROFILE_PERSONAL;
} else if (getWorkProfileUserHandle().equals(intentUser)) {
@@ -674,7 +689,8 @@ public class ResolverActivity extends Activity implements
createEmptyStateProvider(getWorkProfileUserHandle()),
quietModeManager,
selectedProfile,
- getWorkProfileUserHandle());
+ getWorkProfileUserHandle(),
+ getCloneProfileUserHandle());
}
protected int appliedThemeResId() {
@@ -701,20 +717,35 @@ public class ResolverActivity extends Activity implements
}
protected @Profile int getCurrentProfile() {
- return (UserHandle.myUserId() == UserHandle.USER_SYSTEM ? PROFILE_PERSONAL : PROFILE_WORK);
+ return (UserHandle.myUserId() == getPersonalProfileUserHandle().getIdentifier()
+ ? PROFILE_PERSONAL : PROFILE_WORK);
}
protected UserHandle getPersonalProfileUserHandle() {
- return UserHandle.of(ActivityManager.getCurrentUser());
+ return mPersonalProfileUserHandle;
}
protected @Nullable UserHandle getWorkProfileUserHandle() {
return mWorkProfileUserHandle;
}
+ protected @Nullable UserHandle getCloneProfileUserHandle() {
+ return mCloneProfileUserHandle;
+ }
+
+ protected UserHandle getTabOwnerUserHandleForLaunch() {
+ return mTabOwnerUserHandleForLaunch;
+ }
+
+ protected UserHandle fetchPersonalProfileUserHandle() {
+ mPersonalProfileUserHandle = UserHandle.of(ActivityManager.getCurrentUser());
+ return mPersonalProfileUserHandle;
+ }
+
protected @Nullable UserHandle fetchWorkProfileUserProfile() {
mWorkProfileUserHandle = null;
UserManager userManager = getSystemService(UserManager.class);
- for (final UserInfo userInfo : userManager.getProfiles(ActivityManager.getCurrentUser())) {
+ for (final UserInfo userInfo : userManager
+ .getProfiles(mPersonalProfileUserHandle.getIdentifier())) {
if (userInfo.isManagedProfile()) {
mWorkProfileUserHandle = userInfo.getUserHandle();
}
@@ -722,10 +753,38 @@ public class ResolverActivity extends Activity implements
return mWorkProfileUserHandle;
}
+ protected @Nullable UserHandle fetchCloneProfileUserHandle() {
+ mCloneProfileUserHandle = null;
+ UserManager userManager = getSystemService(UserManager.class);
+ for (final UserInfo userInfo :
+ userManager.getProfiles(mPersonalProfileUserHandle.getIdentifier())) {
+ if (userInfo.isCloneProfile()) {
+ mCloneProfileUserHandle = userInfo.getUserHandle();
+ }
+ }
+ return mCloneProfileUserHandle;
+ }
+
+ private UserHandle fetchTabOwnerUserHandleForLaunch() {
+ if (isLaunchedAsCloneProfile()) {
+ return getPersonalProfileUserHandle();
+ }
+ return mLaunchedFromUserHandle;
+ }
+
private boolean hasWorkProfile() {
return getWorkProfileUserHandle() != null;
}
+ private boolean hasCloneProfile() {
+ return getCloneProfileUserHandle() != null;
+ }
+
+ private boolean isLaunchedAsCloneProfile() {
+ return hasCloneProfile()
+ && (UserHandle.myUserId() == getCloneProfileUserHandle().getIdentifier());
+ }
+
protected boolean shouldShowTabs() {
return hasWorkProfile() && ENABLE_TABBED_VIEW;
}
@@ -1123,6 +1182,14 @@ public class ResolverActivity extends Activity implements
mAlwaysButton.setEnabled(false);
return;
}
+ // In case of clonedProfile being active, we do not allow the 'Always' option in the
+ // disambiguation dialog of Personal Profile as the package manager cannot distinguish
+ // between cross-profile preferred activities.
+ if (hasCloneProfile() && !mMultiProfilePagerAdapter
+ .getCurrentUserHandle().equals(mWorkProfileUserHandle)) {
+ mAlwaysButton.setEnabled(false);
+ return;
+ }
boolean enabled = false;
ResolveInfo ri = null;
if (hasValidSelection) {
@@ -1431,17 +1498,14 @@ public class ResolverActivity extends Activity implements
return true;
}
- @VisibleForTesting
- public void safelyStartActivity(TargetInfo cti) {
- // We're dispatching intents that might be coming from legacy apps, so
- // don't kill ourselves.
- StrictMode.disableDeathOnFileUriExposure();
- try {
- UserHandle currentUserHandle = mMultiProfilePagerAdapter.getCurrentUserHandle();
- safelyStartActivityInternal(cti, currentUserHandle, null);
- } finally {
- StrictMode.enableDeathOnFileUriExposure();
- }
+ /** Start the activity specified by the {@link TargetInfo}.*/
+ public final void safelyStartActivity(TargetInfo cti) {
+ // In case cloned apps are present, we would want to start those apps in cloned user
+ // space, which will not be same as adaptor's userHandle. resolveInfo.userHandle
+ // identifies the correct user space in such cases.
+ UserHandle activityUserHandle = getResolveInfoUserHandle(
+ cti.getResolveInfo(), mMultiProfilePagerAdapter.getCurrentUserHandle());
+ safelyStartActivityAsUser(cti, activityUserHandle, null);
}
/**
@@ -1449,11 +1513,12 @@ public class ResolverActivity extends Activity implements
* @param cti TargetInfo to be launched.
* @param user User to launch this activity as.
*/
- @VisibleForTesting
- public void safelyStartActivityAsUser(TargetInfo cti, UserHandle user) {
+ public final void safelyStartActivityAsUser(TargetInfo cti, UserHandle user) {
safelyStartActivityAsUser(cti, user, null);
}
+ // TODO: Make method public final.
+ @VisibleForTesting
protected void safelyStartActivityAsUser(
TargetInfo cti, UserHandle user, @Nullable Bundle options) {
// We're dispatching intents that might be coming from legacy apps, so
@@ -1466,7 +1531,8 @@ public class ResolverActivity extends Activity implements
}
}
- private void safelyStartActivityInternal(
+ @VisibleForTesting
+ protected void safelyStartActivityInternal(
TargetInfo cti, UserHandle user, @Nullable Bundle options) {
// If the target is suspended, the activity will not be successfully launched.
// Do not unregister from package manager updates in this case
@@ -1550,13 +1616,24 @@ public class ResolverActivity extends Activity implements
@VisibleForTesting
protected ResolverListController createListController(UserHandle userHandle) {
+ UserHandle queryIntentsUser = getQueryIntentsUser(userHandle);
+ ResolverRankerServiceResolverComparator resolverComparator =
+ new ResolverRankerServiceResolverComparator(
+ this,
+ getTargetIntent(),
+ getReferrerPackageName(),
+ null,
+ null,
+ getResolverRankerServiceUserHandleList(userHandle));
return new ResolverListController(
this,
mPm,
getTargetIntent(),
getReferrerPackageName(),
mLaunchedFromUid,
- userHandle);
+ userHandle,
+ resolverComparator,
+ queryIntentsUser);
}
/**
@@ -2170,16 +2247,10 @@ public class ResolverActivity extends Activity implements
public boolean useLayoutWithDefault() {
// We only use the default app layout when the profile of the active user has a
// filtered item. We always show the same default app even in the inactive user profile.
- boolean currentUserAdapterHasFilteredItem;
- if (mMultiProfilePagerAdapter.getCurrentUserHandle().getIdentifier()
- == UserHandle.myUserId()) {
- currentUserAdapterHasFilteredItem =
- mMultiProfilePagerAdapter.getActiveListAdapter().hasFilteredItem();
- } else {
- currentUserAdapterHasFilteredItem =
- mMultiProfilePagerAdapter.getInactiveListAdapter().hasFilteredItem();
- }
- return mSupportsAlwaysUseOption && currentUserAdapterHasFilteredItem;
+ boolean adapterForCurrentUserHasFilteredItem =
+ mMultiProfilePagerAdapter.getListAdapterForUserHandle(
+ getTabOwnerUserHandleForLaunch()).hasFilteredItem();
+ return mSupportsAlwaysUseOption && adapterForCurrentUserHasFilteredItem;
}
/**
@@ -2198,7 +2269,14 @@ public class ResolverActivity extends Activity implements
return lhs == null ? rhs == null
: lhs.activityInfo == null ? rhs.activityInfo == null
: Objects.equals(lhs.activityInfo.name, rhs.activityInfo.name)
- && Objects.equals(lhs.activityInfo.packageName, rhs.activityInfo.packageName);
+ && Objects.equals(lhs.activityInfo.packageName, rhs.activityInfo.packageName)
+ // Comparing against resolveInfo.userHandle in case cloned apps are present,
+ // as they will have the same activityInfo.
+ && Objects.equals(
+ getResolveInfoUserHandle(lhs,
+ mMultiProfilePagerAdapter.getActiveListAdapter().getUserHandle()),
+ getResolveInfoUserHandle(rhs,
+ mMultiProfilePagerAdapter.getActiveListAdapter().getUserHandle()));
}
protected String getMetricsCategory() {
@@ -2439,4 +2517,47 @@ public class ResolverActivity extends Activity implements
}
protected void maybeLogProfileChange() {}
+
+ /**
+ * Returns the {@link UserHandle} to use when querying resolutions for intents in a
+ * {@link ResolverListController} configured for the provided {@code userHandle}.
+ */
+ protected final UserHandle getQueryIntentsUser(UserHandle userHandle) {
+ // In case launching app is in clonedProfile, and we are building the personal tab, intent
+ // resolution will be attempted as clonedUser instead of user 0. This is because intent
+ // resolution from user 0 and clonedUser is not guaranteed to return same results.
+ // We do not care about the case when personal adapter is started with non-root user
+ // (secondary user case), as clone profile is guaranteed to be non-active in that case.
+ UserHandle queryIntentsUser = userHandle;
+ if (isLaunchedAsCloneProfile() && userHandle.equals(getPersonalProfileUserHandle())) {
+ queryIntentsUser = getCloneProfileUserHandle();
+ }
+ return queryIntentsUser;
+ }
+
+ /**
+ * This function is temporary in nature, and its usages will be replaced with just
+ * resolveInfo.userHandle, once it is available, once sharesheet is stable.
+ */
+ public static UserHandle getResolveInfoUserHandle(ResolveInfo resolveInfo,
+ UserHandle predictedHandle) {
+ return resolveInfo.userHandle;
+ }
+
+ /**
+ * Returns the {@link List} of {@link UserHandle} to pass on to the
+ * {@link ResolverRankerServiceResolverComparator} as per the provided {@code userHandle}.
+ */
+ protected final List<UserHandle> getResolverRankerServiceUserHandleList(UserHandle userHandle) {
+ List<UserHandle> userList = new ArrayList<>();
+ userList.add(userHandle);
+ // Add clonedProfileUserHandle to the list only if we are:
+ // a. Building the Personal Tab.
+ // b. CloneProfile exists on the device.
+ if (userHandle.equals(getPersonalProfileUserHandle())
+ && getCloneProfileUserHandle() != null) {
+ userList.add(getCloneProfileUserHandle());
+ }
+ return userList;
+ }
}
diff --git a/core/java/com/android/internal/app/ResolverComparatorModel.java b/core/java/com/android/internal/app/ResolverComparatorModel.java
index 3e8f64bf4ed3..a3900166821d 100644
--- a/core/java/com/android/internal/app/ResolverComparatorModel.java
+++ b/core/java/com/android/internal/app/ResolverComparatorModel.java
@@ -16,11 +16,11 @@
package com.android.internal.app;
-import android.content.ComponentName;
import android.content.pm.ResolveInfo;
+import com.android.internal.app.chooser.TargetInfo;
+
import java.util.Comparator;
-import java.util.List;
/**
* A ranking model for resolver targets, providing ordering and (optionally) numerical scoring.
@@ -45,7 +45,7 @@ interface ResolverComparatorModel {
* likelihood that the user will select that component as the target. Implementations that don't
* assign numerical scores are <em>recommended</em> to return a value of 0 for all components.
*/
- float getScore(ComponentName name);
+ float getScore(TargetInfo targetInfo);
/**
* Notify the model that the user selected a target. (Models may log this information, use it as
@@ -53,5 +53,5 @@ interface ResolverComparatorModel {
* {@code ResolverComparatorModel} instance is immutable, clients will need to get an up-to-date
* instance in order to see any changes in the ranking that might result from this feedback.
*/
- void notifyOnTargetSelected(ComponentName componentName);
+ void notifyOnTargetSelected(TargetInfo targetInfo);
}
diff --git a/core/java/com/android/internal/app/ResolverListAdapter.java b/core/java/com/android/internal/app/ResolverListAdapter.java
index 42b46cda6ba3..2df2b2bec2f8 100644
--- a/core/java/com/android/internal/app/ResolverListAdapter.java
+++ b/core/java/com/android/internal/app/ResolverListAdapter.java
@@ -21,7 +21,6 @@ import static android.content.Context.ACTIVITY_SERVICE;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.PermissionChecker;
@@ -157,17 +156,17 @@ public class ResolverListAdapter extends BaseAdapter {
/**
* Returns the app share score of the given {@code componentName}.
*/
- public float getScore(ComponentName componentName) {
- return mResolverListController.getScore(componentName);
+ public float getScore(TargetInfo targetInfo) {
+ return mResolverListController.getScore(targetInfo);
}
- public void updateModel(ComponentName componentName) {
- mResolverListController.updateModel(componentName);
+ public void updateModel(TargetInfo targetInfo) {
+ mResolverListController.updateModel(targetInfo);
}
- public void updateChooserCounts(String packageName, String action) {
+ public void updateChooserCounts(String packageName, String action, UserHandle userHandle) {
mResolverListController.updateChooserCounts(
- packageName, getUserHandle().getIdentifier(), action);
+ packageName, userHandle, action);
}
List<ResolvedComponentInfo> getUnfilteredResolveList() {
@@ -440,6 +439,7 @@ public class ResolverListAdapter extends BaseAdapter {
ri.nonLocalizedLabel = li.getNonLocalizedLabel();
ri.icon = li.getIconResource();
ri.iconResourceId = ri.icon;
+ ri.userHandle = getUserHandle();
}
if (userManager.isManagedProfile()) {
ri.noResourceId = true;
@@ -737,8 +737,10 @@ public class ResolverListAdapter extends BaseAdapter {
}
Drawable loadIconForResolveInfo(ResolveInfo ri) {
- // Load icons based on the current process. If in work profile icons should be badged.
- return makePresentationGetter(ri).getIcon(getUserHandle());
+ // Load icons based on userHandle from ResolveInfo. If in work profile/clone profile, icons
+ // should be badged.
+ return makePresentationGetter(ri)
+ .getIcon(ResolverActivity.getResolveInfoUserHandle(ri, getUserHandle()));
}
void loadFilteredItemIconTaskAsync(@NonNull ImageView iconView) {
diff --git a/core/java/com/android/internal/app/ResolverListController.java b/core/java/com/android/internal/app/ResolverListController.java
index 01dcf9624ed5..d9a19b0f0283 100644
--- a/core/java/com/android/internal/app/ResolverListController.java
+++ b/core/java/com/android/internal/app/ResolverListController.java
@@ -33,6 +33,7 @@ import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.chooser.DisplayResolveInfo;
+import com.android.internal.app.chooser.TargetInfo;
import java.util.ArrayList;
import java.util.Collections;
@@ -60,6 +61,7 @@ public class ResolverListController {
private AbstractResolverComparator mResolverComparator;
private boolean isComputed = false;
+ private final UserHandle mQueryIntentsAsUser;
public ResolverListController(
Context context,
@@ -67,10 +69,17 @@ public class ResolverListController {
Intent targetIntent,
String referrerPackage,
int launchedFromUid,
- UserHandle userHandle) {
+ UserHandle userHandle,
+ UserHandle queryIntentsAsUser) {
this(context, pm, targetIntent, referrerPackage, launchedFromUid, userHandle,
new ResolverRankerServiceResolverComparator(
- context, targetIntent, referrerPackage, null, null));
+ context,
+ targetIntent,
+ referrerPackage,
+ null,
+ null,
+ userHandle),
+ queryIntentsAsUser);
}
public ResolverListController(
@@ -80,7 +89,8 @@ public class ResolverListController {
String referrerPackage,
int launchedFromUid,
UserHandle userHandle,
- AbstractResolverComparator resolverComparator) {
+ AbstractResolverComparator resolverComparator,
+ UserHandle queryIntentsAsUser) {
mContext = context;
mpm = pm;
mLaunchedFromUid = launchedFromUid;
@@ -88,6 +98,7 @@ public class ResolverListController {
mReferrerPackage = referrerPackage;
mUserHandle = userHandle;
mResolverComparator = resolverComparator;
+ mQueryIntentsAsUser = queryIntentsAsUser;
}
@VisibleForTesting
@@ -113,7 +124,7 @@ public class ResolverListController {
boolean shouldGetOnlyDefaultActivities,
List<Intent> intents) {
return getResolversForIntentAsUser(shouldGetResolvedFilter, shouldGetActivityMetadata,
- shouldGetOnlyDefaultActivities, intents, mUserHandle);
+ shouldGetOnlyDefaultActivities, intents, mQueryIntentsAsUser);
}
public List<ResolverActivity.ResolvedComponentInfo> getResolversForIntentAsUser(
@@ -126,7 +137,8 @@ public class ResolverListController {
| PackageManager.MATCH_DIRECT_BOOT_AWARE
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE
| (shouldGetResolvedFilter ? PackageManager.GET_RESOLVED_FILTER : 0)
- | (shouldGetActivityMetadata ? PackageManager.GET_META_DATA : 0);
+ | (shouldGetActivityMetadata ? PackageManager.GET_META_DATA : 0)
+ | PackageManager.MATCH_CLONE_PROFILE;
return getResolversForIntentAsUserInternal(intents, userHandle, baseFlags);
}
@@ -170,6 +182,10 @@ public class ResolverListController {
final int intoCount = into.size();
for (int i = 0; i < fromCount; i++) {
final ResolveInfo newInfo = from.get(i);
+ if (newInfo.userHandle == null) {
+ Log.w(TAG, "Skipping ResolveInfo with no userHandle: " + newInfo);
+ continue;
+ }
boolean found = false;
// Only loop to the end of into as it was before we started; no dupes in from.
for (int j = 0; j < intoCount; j++) {
@@ -388,22 +404,22 @@ public class ResolverListController {
@VisibleForTesting
public float getScore(DisplayResolveInfo target) {
- return mResolverComparator.getScore(target.getResolvedComponentName());
+ return mResolverComparator.getScore(target);
}
/**
* Returns the app share score of the given {@code componentName}.
*/
- public float getScore(ComponentName componentName) {
- return mResolverComparator.getScore(componentName);
+ public float getScore(TargetInfo targetInfo) {
+ return mResolverComparator.getScore(targetInfo);
}
- public void updateModel(ComponentName componentName) {
- mResolverComparator.updateModel(componentName);
+ public void updateModel(TargetInfo targetInfo) {
+ mResolverComparator.updateModel(targetInfo);
}
- public void updateChooserCounts(String packageName, int userId, String action) {
- mResolverComparator.updateChooserCounts(packageName, userId, action);
+ public void updateChooserCounts(String packageName, UserHandle user, String action) {
+ mResolverComparator.updateChooserCounts(packageName, user, action);
}
public void destroy() {
diff --git a/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java
index 9922051c1b0b..1ecaf21a5148 100644
--- a/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java
+++ b/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java
@@ -41,9 +41,10 @@ public class ResolverMultiProfilePagerAdapter extends AbstractMultiProfilePagerA
ResolverListAdapter adapter,
EmptyStateProvider emptyStateProvider,
QuietModeManager quietModeManager,
- UserHandle workProfileUserHandle) {
+ UserHandle workProfileUserHandle,
+ UserHandle cloneUserHandle) {
super(context, /* currentPage */ 0, emptyStateProvider, quietModeManager,
- workProfileUserHandle);
+ workProfileUserHandle, cloneUserHandle);
mItems = new ResolverProfileDescriptor[] {
createProfileDescriptor(adapter)
};
@@ -55,9 +56,10 @@ public class ResolverMultiProfilePagerAdapter extends AbstractMultiProfilePagerA
EmptyStateProvider emptyStateProvider,
QuietModeManager quietModeManager,
@Profile int defaultProfile,
- UserHandle workProfileUserHandle) {
+ UserHandle workProfileUserHandle,
+ UserHandle cloneUserHandle) {
super(context, /* currentPage */ defaultProfile, emptyStateProvider, quietModeManager,
- workProfileUserHandle);
+ workProfileUserHandle, cloneUserHandle);
mItems = new ResolverProfileDescriptor[] {
createProfileDescriptor(personalAdapter),
createProfileDescriptor(workAdapter)
@@ -107,11 +109,12 @@ public class ResolverMultiProfilePagerAdapter extends AbstractMultiProfilePagerA
@Override
@Nullable
ResolverListAdapter getListAdapterForUserHandle(UserHandle userHandle) {
- if (getActiveListAdapter().getUserHandle().equals(userHandle)) {
- return getActiveListAdapter();
- } else if (getInactiveListAdapter() != null
- && getInactiveListAdapter().getUserHandle().equals(userHandle)) {
- return getInactiveListAdapter();
+ if (getPersonalListAdapter().getUserHandle().equals(userHandle)
+ || userHandle.equals(getCloneUserHandle())) {
+ return getPersonalListAdapter();
+ } else if (getWorkListAdapter() != null
+ && getWorkListAdapter().getUserHandle().equals(userHandle)) {
+ return getWorkListAdapter();
}
return null;
}
diff --git a/core/java/com/android/internal/app/ResolverRankerServiceResolverComparator.java b/core/java/com/android/internal/app/ResolverRankerServiceResolverComparator.java
index e7f80a7f6071..78c453dd4105 100644
--- a/core/java/com/android/internal/app/ResolverRankerServiceResolverComparator.java
+++ b/core/java/com/android/internal/app/ResolverRankerServiceResolverComparator.java
@@ -17,11 +17,13 @@
package com.android.internal.app;
+import android.annotation.Nullable;
import android.app.usage.UsageStats;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
+import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
@@ -38,12 +40,16 @@ import android.service.resolver.ResolverTarget;
import android.util.Log;
import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
+import com.android.internal.app.chooser.TargetInfo;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.google.android.collect.Lists;
+
import java.text.Collator;
import java.util.ArrayList;
import java.util.Comparator;
+import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -69,10 +75,10 @@ class ResolverRankerServiceResolverComparator extends AbstractResolverComparator
private static final int CONNECTION_COST_TIMEOUT_MILLIS = 200;
private final Collator mCollator;
- private final Map<String, UsageStats> mStats;
+ private final Map<UserHandle, Map<String, UsageStats>> mStatsPerUser;
private final long mCurrentTime;
private final long mSinceTime;
- private final LinkedHashMap<ComponentName, ResolverTarget> mTargetsDict = new LinkedHashMap<>();
+ private final Map<UserHandle, LinkedHashMap<ComponentName, ResolverTarget>> mTargetsDictPerUser;
private final String mReferrerPackage;
private final Object mLock = new Object();
private ArrayList<ResolverTarget> mTargets;
@@ -85,17 +91,34 @@ class ResolverRankerServiceResolverComparator extends AbstractResolverComparator
private CountDownLatch mConnectSignal;
private ResolverRankerServiceComparatorModel mComparatorModel;
- public ResolverRankerServiceResolverComparator(Context context, Intent intent,
+ // context here refers to the activity calling this comparator.
+ // targetUserSpace refers to the userSpace in which the targets to be ranked lie.
+ public ResolverRankerServiceResolverComparator(Context launchedFromContext, Intent intent,
String referrerPackage, AfterCompute afterCompute,
- ChooserActivityLogger chooserActivityLogger) {
- super(context, intent);
- mCollator = Collator.getInstance(context.getResources().getConfiguration().locale);
- mReferrerPackage = referrerPackage;
- mContext = context;
+ ChooserActivityLogger chooserActivityLogger, UserHandle targetUserSpace) {
+ this(launchedFromContext, intent, referrerPackage, afterCompute, chooserActivityLogger,
+ Lists.newArrayList(targetUserSpace));
+ }
+ // context here refers to the activity calling this comparator.
+ // targetUserSpaceList refers to the userSpace(s) in which the targets to be ranked lie.
+ public ResolverRankerServiceResolverComparator(Context launchedFromContext, Intent intent,
+ String referrerPackage, AfterCompute afterCompute,
+ ChooserActivityLogger chooserActivityLogger, List<UserHandle> targetUserSpaceList) {
+ super(launchedFromContext, intent, targetUserSpaceList);
+ mCollator = Collator.getInstance(launchedFromContext
+ .getResources().getConfiguration().locale);
+ mReferrerPackage = referrerPackage;
+ mContext = launchedFromContext;
mCurrentTime = System.currentTimeMillis();
mSinceTime = mCurrentTime - USAGE_STATS_PERIOD;
- mStats = mUsm.queryAndAggregateUsageStats(mSinceTime, mCurrentTime);
+ mStatsPerUser = new HashMap<>();
+ mTargetsDictPerUser = new HashMap<>();
+ for (UserHandle user : targetUserSpaceList) {
+ mStatsPerUser.put(user, mUsmMap.get(user)
+ .queryAndAggregateUsageStats(mSinceTime, mCurrentTime));
+ mTargetsDictPerUser.put(user, new LinkedHashMap<>());
+ }
mAction = intent.getAction();
mRankerServiceName = new ComponentName(mContext, this.getClass());
setCallBack(afterCompute);
@@ -147,57 +170,63 @@ class ResolverRankerServiceResolverComparator extends AbstractResolverComparator
for (ResolvedComponentInfo target : targets) {
final ResolverTarget resolverTarget = new ResolverTarget();
- mTargetsDict.put(target.name, resolverTarget);
- final UsageStats pkStats = mStats.get(target.name.getPackageName());
- if (pkStats != null) {
- // Only count recency for apps that weren't the caller
- // since the caller is always the most recent.
- // Persistent processes muck this up, so omit them too.
- if (!target.name.getPackageName().equals(mReferrerPackage)
- && !isPersistentProcess(target)) {
- final float recencyScore =
- (float) Math.max(pkStats.getLastTimeUsed() - recentSinceTime, 0);
- resolverTarget.setRecencyScore(recencyScore);
- if (recencyScore > mostRecencyScore) {
- mostRecencyScore = recencyScore;
+ final LinkedHashMap<ComponentName, ResolverTarget> targetsDict = mTargetsDictPerUser
+ .get(target.getResolveInfoAt(0).userHandle);
+ final Map<String, UsageStats> stats = mStatsPerUser
+ .get(target.getResolveInfoAt(0).userHandle);
+ if (targetsDict != null && stats != null) {
+ targetsDict.put(target.name, resolverTarget);
+ final UsageStats pkStats = stats.get(target.name.getPackageName());
+ if (pkStats != null) {
+ // Only count recency for apps that weren't the caller
+ // since the caller is always the most recent.
+ // Persistent processes muck this up, so omit them too.
+ if (!target.name.getPackageName().equals(mReferrerPackage)
+ && !isPersistentProcess(target)) {
+ final float recencyScore =
+ (float) Math.max(pkStats.getLastTimeUsed() - recentSinceTime, 0);
+ resolverTarget.setRecencyScore(recencyScore);
+ if (recencyScore > mostRecencyScore) {
+ mostRecencyScore = recencyScore;
+ }
+ }
+ final float timeSpentScore = (float) pkStats.getTotalTimeInForeground();
+ resolverTarget.setTimeSpentScore(timeSpentScore);
+ if (timeSpentScore > mostTimeSpentScore) {
+ mostTimeSpentScore = timeSpentScore;
+ }
+ final float launchScore = (float) pkStats.mLaunchCount;
+ resolverTarget.setLaunchScore(launchScore);
+ if (launchScore > mostLaunchScore) {
+ mostLaunchScore = launchScore;
}
- }
- final float timeSpentScore = (float) pkStats.getTotalTimeInForeground();
- resolverTarget.setTimeSpentScore(timeSpentScore);
- if (timeSpentScore > mostTimeSpentScore) {
- mostTimeSpentScore = timeSpentScore;
- }
- final float launchScore = (float) pkStats.mLaunchCount;
- resolverTarget.setLaunchScore(launchScore);
- if (launchScore > mostLaunchScore) {
- mostLaunchScore = launchScore;
- }
- float chooserScore = 0.0f;
- if (pkStats.mChooserCounts != null && mAction != null
- && pkStats.mChooserCounts.get(mAction) != null) {
- chooserScore = (float) pkStats.mChooserCounts.get(mAction)
- .getOrDefault(mContentType, 0);
- if (mAnnotations != null) {
- final int size = mAnnotations.length;
- for (int i = 0; i < size; i++) {
- chooserScore += (float) pkStats.mChooserCounts.get(mAction)
- .getOrDefault(mAnnotations[i], 0);
+ float chooserScore = 0.0f;
+ if (pkStats.mChooserCounts != null && mAction != null
+ && pkStats.mChooserCounts.get(mAction) != null) {
+ chooserScore = (float) pkStats.mChooserCounts.get(mAction)
+ .getOrDefault(mContentType, 0);
+ if (mAnnotations != null) {
+ final int size = mAnnotations.length;
+ for (int i = 0; i < size; i++) {
+ chooserScore += (float) pkStats.mChooserCounts.get(mAction)
+ .getOrDefault(mAnnotations[i], 0);
+ }
}
}
- }
- if (DEBUG) {
- if (mAction == null) {
- Log.d(TAG, "Action type is null");
- } else {
- Log.d(TAG, "Chooser Count of " + mAction + ":" +
- target.name.getPackageName() + " is " +
- Float.toString(chooserScore));
+ if (DEBUG) {
+ if (mAction == null) {
+ Log.d(TAG, "Action type is null");
+ } else {
+ Log.d(TAG, "Chooser Count of " + mAction + ":"
+ + target.name.getPackageName() + " is "
+ + Float.toString(chooserScore));
+ }
+ }
+ resolverTarget.setChooserScore(chooserScore);
+ if (chooserScore > mostChooserScore) {
+ mostChooserScore = chooserScore;
}
- }
- resolverTarget.setChooserScore(chooserScore);
- if (chooserScore > mostChooserScore) {
- mostChooserScore = chooserScore;
}
}
}
@@ -209,7 +238,11 @@ class ResolverRankerServiceResolverComparator extends AbstractResolverComparator
+ " mostChooserScore: " + mostChooserScore);
}
- mTargets = new ArrayList<>(mTargetsDict.values());
+ mTargets = new ArrayList<>();
+ for (UserHandle u : mTargetsDictPerUser.keySet()) {
+ mTargets.addAll(mTargetsDictPerUser.get(u).values());
+ }
+
for (ResolverTarget target : mTargets) {
final float recency = target.getRecencyScore() / mostRecencyScore;
setFeatures(target, recency * recency * RECENCY_MULTIPLIER,
@@ -232,15 +265,15 @@ class ResolverRankerServiceResolverComparator extends AbstractResolverComparator
}
@Override
- public float getScore(ComponentName name) {
- return mComparatorModel.getScore(name);
+ public float getScore(TargetInfo targetInfo) {
+ return mComparatorModel.getScore(targetInfo);
}
// update ranking model when the connection to it is valid.
@Override
- public void updateModel(ComponentName componentName) {
+ public void updateModel(TargetInfo targetInfo) {
synchronized (mLock) {
- mComparatorModel.notifyOnTargetSelected(componentName);
+ mComparatorModel.notifyOnTargetSelected(targetInfo);
}
}
@@ -281,7 +314,8 @@ class ResolverRankerServiceResolverComparator extends AbstractResolverComparator
// resolve the service for ranking.
private Intent resolveRankerService() {
Intent intent = new Intent(ResolverRankerService.SERVICE_INTERFACE);
- final List<ResolveInfo> resolveInfos = mPm.queryIntentServices(intent, 0);
+ final List<ResolveInfo> resolveInfos = mContext.getPackageManager()
+ .queryIntentServices(intent, 0);
for (ResolveInfo resolveInfo : resolveInfos) {
if (resolveInfo == null || resolveInfo.serviceInfo == null
|| resolveInfo.serviceInfo.applicationInfo == null) {
@@ -294,7 +328,8 @@ class ResolverRankerServiceResolverComparator extends AbstractResolverComparator
resolveInfo.serviceInfo.applicationInfo.packageName,
resolveInfo.serviceInfo.name);
try {
- final String perm = mPm.getServiceInfo(componentName, 0).permission;
+ final String perm = mContext.getPackageManager()
+ .getServiceInfo(componentName, 0).permission;
if (!ResolverRankerService.BIND_PERMISSION.equals(perm)) {
Log.w(TAG, "ResolverRankerService " + componentName + " does not require"
+ " permission " + ResolverRankerService.BIND_PERMISSION
@@ -305,9 +340,9 @@ class ResolverRankerServiceResolverComparator extends AbstractResolverComparator
+ " in the manifest.");
continue;
}
- if (PackageManager.PERMISSION_GRANTED != mPm.checkPermission(
- ResolverRankerService.HOLD_PERMISSION,
- resolveInfo.serviceInfo.packageName)) {
+ if (PackageManager.PERMISSION_GRANTED != mContext.getPackageManager()
+ .checkPermission(ResolverRankerService.HOLD_PERMISSION,
+ resolveInfo.serviceInfo.packageName)) {
Log.w(TAG, "ResolverRankerService " + componentName + " does not hold"
+ " permission " + ResolverRankerService.HOLD_PERMISSION
+ " - this service will not be queried for "
@@ -385,7 +420,9 @@ class ResolverRankerServiceResolverComparator extends AbstractResolverComparator
@Override
void beforeCompute() {
super.beforeCompute();
- mTargetsDict.clear();
+ for (UserHandle userHandle : mTargetsDictPerUser.keySet()) {
+ mTargetsDictPerUser.get(userHandle).clear();
+ }
mTargets = null;
mRankerServiceName = new ComponentName(mContext, this.getClass());
mComparatorModel = buildUpdatedModel();
@@ -465,14 +502,14 @@ class ResolverRankerServiceResolverComparator extends AbstractResolverComparator
// so the ResolverComparatorModel may provide inconsistent results. We should make immutable
// copies of the data (waiting for any necessary remaining data before creating the model).
return new ResolverRankerServiceComparatorModel(
- mStats,
- mTargetsDict,
+ mStatsPerUser,
+ mTargetsDictPerUser,
mTargets,
mCollator,
mRanker,
mRankerServiceName,
(mAnnotations != null),
- mPm);
+ mPmMap);
}
/**
@@ -481,35 +518,36 @@ class ResolverRankerServiceResolverComparator extends AbstractResolverComparator
* removing the complex legacy API.
*/
static class ResolverRankerServiceComparatorModel implements ResolverComparatorModel {
- private final Map<String, UsageStats> mStats; // Treat as immutable.
- private final Map<ComponentName, ResolverTarget> mTargetsDict; // Treat as immutable.
+ private final Map<UserHandle, Map<String, UsageStats>> mStatsPerUser; // Treat as immutable.
+ private final Map<UserHandle, LinkedHashMap<ComponentName,
+ ResolverTarget>> mTargetsDictPerUser; // Treat as immutable.
private final List<ResolverTarget> mTargets; // Treat as immutable.
private final Collator mCollator;
private final IResolverRankerService mRanker;
private final ComponentName mRankerServiceName;
private final boolean mAnnotationsUsed;
- private final PackageManager mPm;
+ private final Map<UserHandle, PackageManager> mPmMap;
// TODO: it doesn't look like we should have to pass both targets and targetsDict, but it's
// not written in a way that makes it clear whether we can derive one from the other (at
// least in this constructor).
ResolverRankerServiceComparatorModel(
- Map<String, UsageStats> stats,
- Map<ComponentName, ResolverTarget> targetsDict,
+ Map<UserHandle, Map<String, UsageStats>> statsPerUser,
+ Map<UserHandle, LinkedHashMap<ComponentName, ResolverTarget>> targetsDictPerUser,
List<ResolverTarget> targets,
Collator collator,
IResolverRankerService ranker,
ComponentName rankerServiceName,
boolean annotationsUsed,
- PackageManager pm) {
- mStats = stats;
- mTargetsDict = targetsDict;
+ Map<UserHandle, PackageManager> pmMap) {
+ mStatsPerUser = statsPerUser;
+ mTargetsDictPerUser = targetsDictPerUser;
mTargets = targets;
mCollator = collator;
mRanker = ranker;
mRankerServiceName = rankerServiceName;
mAnnotationsUsed = annotationsUsed;
- mPm = pm;
+ mPmMap = pmMap;
}
@Override
@@ -518,25 +556,29 @@ class ResolverRankerServiceResolverComparator extends AbstractResolverComparator
// a bug there, or do we have a way of knowing it will be non-null under certain
// conditions?
return (lhs, rhs) -> {
- if (mStats != null) {
- final ResolverTarget lhsTarget = mTargetsDict.get(new ComponentName(
- lhs.activityInfo.packageName, lhs.activityInfo.name));
- final ResolverTarget rhsTarget = mTargetsDict.get(new ComponentName(
- rhs.activityInfo.packageName, rhs.activityInfo.name));
-
- if (lhsTarget != null && rhsTarget != null) {
- final int selectProbabilityDiff = Float.compare(
- rhsTarget.getSelectProbability(), lhsTarget.getSelectProbability());
-
- if (selectProbabilityDiff != 0) {
- return selectProbabilityDiff > 0 ? 1 : -1;
- }
+ final ResolverTarget lhsTarget = getActivityResolverTargetForUser(lhs.activityInfo,
+ lhs.userHandle);
+ final ResolverTarget rhsTarget = getActivityResolverTargetForUser(rhs.activityInfo,
+ rhs.userHandle);
+
+ if (lhsTarget != null && rhsTarget != null) {
+ final int selectProbabilityDiff = Float.compare(
+ rhsTarget.getSelectProbability(), lhsTarget.getSelectProbability());
+
+ if (selectProbabilityDiff != 0) {
+ return selectProbabilityDiff > 0 ? 1 : -1;
}
}
- CharSequence sa = lhs.loadLabel(mPm);
+ CharSequence sa = null;
+ if (mPmMap.containsKey(lhs.userHandle)) {
+ sa = lhs.loadLabel(mPmMap.get(lhs.userHandle));
+ }
if (sa == null) sa = lhs.activityInfo.name;
- CharSequence sb = rhs.loadLabel(mPm);
+ CharSequence sb = null;
+ if (mPmMap.containsKey(rhs.userHandle)) {
+ sb = rhs.loadLabel(mPmMap.get(rhs.userHandle));
+ }
if (sb == null) sb = rhs.activityInfo.name;
return mCollator.compare(sa.toString().trim(), sb.toString().trim());
@@ -544,22 +586,28 @@ class ResolverRankerServiceResolverComparator extends AbstractResolverComparator
}
@Override
- public float getScore(ComponentName name) {
- final ResolverTarget target = mTargetsDict.get(name);
- if (target != null) {
- return target.getSelectProbability();
+ public float getScore(TargetInfo targetInfo) {
+ if (mTargetsDictPerUser.containsKey(targetInfo.getResolveInfo().userHandle)
+ && mTargetsDictPerUser.get(targetInfo.getResolveInfo().userHandle)
+ .get(targetInfo.getResolvedComponentName()) != null) {
+ return mTargetsDictPerUser.get(targetInfo.getResolveInfo().userHandle)
+ .get(targetInfo.getResolvedComponentName()).getSelectProbability();
}
return 0;
}
@Override
- public void notifyOnTargetSelected(ComponentName componentName) {
+ public void notifyOnTargetSelected(TargetInfo targetInfo) {
if (mRanker != null) {
try {
- int selectedPos = new ArrayList<ComponentName>(mTargetsDict.keySet())
- .indexOf(componentName);
+ int selectedPos = -1;
+ if (mTargetsDictPerUser.containsKey(targetInfo.getResolveInfo().userHandle)) {
+ selectedPos = new ArrayList<>(mTargetsDictPerUser
+ .get(targetInfo.getResolveInfo().userHandle).keySet())
+ .indexOf(targetInfo.getResolvedComponentName());
+ }
if (selectedPos >= 0 && mTargets != null) {
- final float selectedProbability = getScore(componentName);
+ final float selectedProbability = getScore(targetInfo);
int order = 0;
for (ResolverTarget target : mTargets) {
if (target.getSelectProbability() > selectedProbability) {
@@ -570,7 +618,8 @@ class ResolverRankerServiceResolverComparator extends AbstractResolverComparator
mRanker.train(mTargets, selectedPos);
} else {
if (DEBUG) {
- Log.d(TAG, "Selected a unknown component: " + componentName);
+ Log.d(TAG, "Selected a unknown component: " + targetInfo
+ .getResolvedComponentName());
}
}
} catch (RemoteException e) {
@@ -594,5 +643,16 @@ class ResolverRankerServiceResolverComparator extends AbstractResolverComparator
metricsLogger.write(log);
}
}
+
+ @Nullable
+ private ResolverTarget getActivityResolverTargetForUser(
+ ActivityInfo activity, UserHandle user) {
+ if ((mStatsPerUser == null) || !mTargetsDictPerUser.containsKey(user)) {
+ return null;
+ }
+ return mTargetsDictPerUser
+ .get(user)
+ .get(new ComponentName(activity.packageName, activity.name));
+ }
}
}
diff --git a/core/java/com/android/internal/app/SystemLocaleCollector.java b/core/java/com/android/internal/app/SystemLocaleCollector.java
index 9a6d4c192fdc..416f510b3230 100644
--- a/core/java/com/android/internal/app/SystemLocaleCollector.java
+++ b/core/java/com/android/internal/app/SystemLocaleCollector.java
@@ -26,9 +26,15 @@ import java.util.Set;
/** The Locale data collector for System language. */
class SystemLocaleCollector implements LocalePickerWithRegion.LocaleCollectorBase {
private final Context mContext;
+ private LocaleList mExplicitLocales;
SystemLocaleCollector(Context context) {
+ this(context, null);
+ }
+
+ SystemLocaleCollector(Context context, LocaleList explicitLocales) {
mContext = context;
+ mExplicitLocales = explicitLocales;
}
@Override
@@ -47,18 +53,16 @@ class SystemLocaleCollector implements LocalePickerWithRegion.LocaleCollectorBas
boolean translatedOnly, boolean isForCountryMode) {
Set<String> langTagsToIgnore = getIgnoredLocaleList(translatedOnly);
Set<LocaleStore.LocaleInfo> localeList;
-
if (isForCountryMode) {
localeList = LocaleStore.getLevelLocales(mContext,
- langTagsToIgnore, parent, translatedOnly);
+ langTagsToIgnore, parent, translatedOnly, mExplicitLocales);
} else {
localeList = LocaleStore.getLevelLocales(mContext, langTagsToIgnore,
- null /* no parent */, translatedOnly);
+ null /* no parent */, translatedOnly, mExplicitLocales);
}
return localeList;
}
-
@Override
public boolean hasSpecificPackageName() {
return false;
diff --git a/core/java/com/android/internal/content/InstallLocationUtils.java b/core/java/com/android/internal/content/InstallLocationUtils.java
index 4d9c09e92617..a173ce16feea 100644
--- a/core/java/com/android/internal/content/InstallLocationUtils.java
+++ b/core/java/com/android/internal/content/InstallLocationUtils.java
@@ -292,7 +292,7 @@ public class InstallLocationUtils {
// For new installations of a predefined size, check property to let it through
// regardless of the actual free space.
- if (bestCandidate != null && Integer.MAX_VALUE == params.sizeBytes
+ if (!volumePaths.isEmpty() && Integer.MAX_VALUE == params.sizeBytes
&& SystemProperties.getBoolean("debug.pm.install_skip_size_check_for_maxint",
false)) {
return bestCandidate;
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index 95a47ea47cd8..d9e9a5f19e2d 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -16,15 +16,11 @@
package com.android.internal.jank;
-import static android.Manifest.permission.READ_DEVICE_CONFIG;
-import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-
import static com.android.internal.jank.FrameTracker.REASON_CANCEL_NORMAL;
import static com.android.internal.jank.FrameTracker.REASON_CANCEL_TIMEOUT;
import static com.android.internal.jank.FrameTracker.REASON_END_NORMAL;
import static com.android.internal.jank.FrameTracker.REASON_END_UNKNOWN;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__BIOMETRIC_PROMPT_TRANSITION;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_ANIMATION;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_ALL_APPS_SCROLL;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_PIP;
@@ -93,19 +89,17 @@ import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_IN
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__VOLUME_CONTROL;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__WALLPAPER_TRANSITION;
-import android.Manifest;
import android.annotation.IntDef;
import android.annotation.NonNull;
-import android.annotation.RequiresPermission;
import android.annotation.UiThread;
import android.annotation.WorkerThread;
-import android.app.ActivityThread;
import android.content.Context;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.HandlerThread;
import android.provider.DeviceConfig;
+import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseArray;
@@ -238,7 +232,6 @@ public class InteractionJankMonitor {
public static final int CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS = 66;
public static final int CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE = 67;
public static final int CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME = 68;
- public static final int CUJ_IME_INSETS_ANIMATION = 69;
private static final int NO_STATSD_LOGGING = -1;
@@ -316,7 +309,6 @@ public class InteractionJankMonitor {
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_SWIPE_TO_RECENTS,
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_CLOSE_ALL_APPS_SWIPE,
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_CLOSE_ALL_APPS_TO_HOME,
- UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_ANIMATION,
};
private static class InstanceHolder {
@@ -409,8 +401,7 @@ public class InteractionJankMonitor {
CUJ_RECENTS_SCROLLING,
CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS,
CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE,
- CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME,
- CUJ_IME_INSETS_ANIMATION,
+ CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME
})
@Retention(RetentionPolicy.SOURCE)
public @interface CujType {
@@ -431,37 +422,29 @@ public class InteractionJankMonitor {
* @param worker the worker thread for the callbacks
*/
@VisibleForTesting
- @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
public InteractionJankMonitor(@NonNull HandlerThread worker) {
+ // Check permission early.
+ Settings.Config.enforceReadPermission(
+ DeviceConfig.NAMESPACE_INTERACTION_JANK_MONITOR);
+
mRunningTrackers = new SparseArray<>();
mTimeoutActions = new SparseArray<>();
mWorker = worker;
mWorker.start();
- mDisplayResolutionTracker = new DisplayResolutionTracker(worker.getThreadHandler());
mSamplingInterval = DEFAULT_SAMPLING_INTERVAL;
- mEnabled = DEFAULT_ENABLED;
+ mDisplayResolutionTracker = new DisplayResolutionTracker(worker.getThreadHandler());
- final Context context = ActivityThread.currentApplication();
- if (context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG) == PERMISSION_GRANTED) {
- // Post initialization to the background in case we're running on the main thread.
- mWorker.getThreadHandler().post(
- () -> mPropertiesChangedListener.onPropertiesChanged(
- DeviceConfig.getProperties(
- DeviceConfig.NAMESPACE_INTERACTION_JANK_MONITOR)));
- DeviceConfig.addOnPropertiesChangedListener(
- DeviceConfig.NAMESPACE_INTERACTION_JANK_MONITOR,
- new HandlerExecutor(mWorker.getThreadHandler()),
- mPropertiesChangedListener);
- } else {
- if (DEBUG) {
- Log.d(TAG, "Initialized the InteractionJankMonitor."
- + " (No READ_DEVICE_CONFIG permission to change configs)"
- + " enabled=" + mEnabled + ", interval=" + mSamplingInterval
- + ", missedFrameThreshold=" + mTraceThresholdMissedFrames
- + ", frameTimeThreshold=" + mTraceThresholdFrameTimeMillis
- + ", package=" + context.getPackageName());
- }
- }
+ // Post initialization to the background in case we're running on the main
+ // thread.
+ mWorker.getThreadHandler().post(
+ () -> mPropertiesChangedListener.onPropertiesChanged(
+ DeviceConfig.getProperties(
+ DeviceConfig.NAMESPACE_INTERACTION_JANK_MONITOR)));
+ DeviceConfig.addOnPropertiesChangedListener(
+ DeviceConfig.NAMESPACE_INTERACTION_JANK_MONITOR,
+ new HandlerExecutor(mWorker.getThreadHandler()),
+ mPropertiesChangedListener);
+ mEnabled = DEFAULT_ENABLED;
}
/**
@@ -940,8 +923,6 @@ public class InteractionJankMonitor {
return "LAUNCHER_CLOSE_ALL_APPS_SWIPE";
case CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME:
return "LAUNCHER_CLOSE_ALL_APPS_TO_HOME";
- case CUJ_IME_INSETS_ANIMATION:
- return "IME_INSETS_ANIMATION";
}
return "UNKNOWN";
}
@@ -1197,7 +1178,7 @@ public class InteractionJankMonitor {
*/
@VisibleForTesting
public int getDisplayId() {
- return mSurfaceOnly ? mContext.getDisplayId() : mView.getContext().getDisplayId();
+ return (mSurfaceOnly ? mContext.getDisplay() : mView.getDisplay()).getDisplayId();
}
}
diff --git a/core/java/com/android/internal/os/RoSystemProperties.java b/core/java/com/android/internal/os/RoSystemProperties.java
index af205d2a7e0b..40d5c4761dff 100644
--- a/core/java/com/android/internal/os/RoSystemProperties.java
+++ b/core/java/com/android/internal/os/RoSystemProperties.java
@@ -50,9 +50,6 @@ public class RoSystemProperties {
public static final boolean CONFIG_SMALL_BATTERY =
SystemProperties.getBoolean("ro.config.small_battery", false);
- // ------ ro.fw.* ------------ //
- public static final boolean FW_SYSTEM_USER_SPLIT =
- SystemProperties.getBoolean("ro.fw.system_user_split", false);
/**
* Indicates whether the device should run in headless system user mode,
* in which user 0 only runs the system, not a real user.
diff --git a/core/java/com/android/internal/protolog/ProtoLogGroup.java b/core/java/com/android/internal/protolog/ProtoLogGroup.java
index 8f943ef654f9..ad1fdbae037d 100644
--- a/core/java/com/android/internal/protolog/ProtoLogGroup.java
+++ b/core/java/com/android/internal/protolog/ProtoLogGroup.java
@@ -79,7 +79,9 @@ public enum ProtoLogGroup implements IProtoLogGroup {
Consts.TAG_WM),
WM_DEBUG_SYNC_ENGINE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
Consts.TAG_WM),
- WM_DEBUG_WINDOW_TRANSITIONS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
+ WM_DEBUG_WINDOW_TRANSITIONS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
+ Consts.TAG_WM),
+ WM_DEBUG_WINDOW_TRANSITIONS_MIN(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
Consts.TAG_WM),
WM_DEBUG_WINDOW_INSETS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
Consts.TAG_WM),
diff --git a/core/java/com/android/internal/util/LatencyTracker.java b/core/java/com/android/internal/util/LatencyTracker.java
index 2d5bb6c9f495..afb526aeea6f 100644
--- a/core/java/com/android/internal/util/LatencyTracker.java
+++ b/core/java/com/android/internal/util/LatencyTracker.java
@@ -14,10 +14,7 @@
package com.android.internal.util;
-import static android.Manifest.permission.READ_DEVICE_CONFIG;
-import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.Trace.TRACE_TAG_APP;
-import static android.provider.DeviceConfig.NAMESPACE_LATENCY_TRACKER;
import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_CHECK_CREDENTIAL;
import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_CHECK_CREDENTIAL_UNLOCKED;
@@ -27,8 +24,6 @@ import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPOR
import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_FOLD_TO_AOD;
import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_LOAD_SHARE_SHEET;
import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_LOCKSCREEN_UNLOCK;
-import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_REQUEST_IME_HIDDEN;
-import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_REQUEST_IME_SHOWN;
import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_ROTATE_SCREEN;
import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_ROTATE_SCREEN_CAMERA_CHECK;
import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_ROTATE_SCREEN_SENSOR;
@@ -47,12 +42,9 @@ import static com.android.internal.util.LatencyTracker.ActionProperties.LEGACY_T
import static com.android.internal.util.LatencyTracker.ActionProperties.SAMPLE_INTERVAL_SUFFIX;
import static com.android.internal.util.LatencyTracker.ActionProperties.TRACE_THRESHOLD_SUFFIX;
-import android.Manifest;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.RequiresPermission;
-import android.app.ActivityThread;
import android.content.Context;
import android.os.Build;
import android.os.ConditionVariable;
@@ -195,16 +187,6 @@ public class LatencyTracker {
*/
public static final int ACTION_SHOW_VOICE_INTERACTION = 19;
- /**
- * Time it takes to request IME shown animation.
- */
- public static final int ACTION_REQUEST_IME_SHOWN = 20;
-
- /**
- * Time it takes to request IME hidden animation.
- */
- public static final int ACTION_REQUEST_IME_HIDDEN = 21;
-
private static final int[] ACTIONS_ALL = {
ACTION_EXPAND_PANEL,
ACTION_TOGGLE_RECENTS,
@@ -226,8 +208,6 @@ public class LatencyTracker {
ACTION_SHOW_SELECTION_TOOLBAR,
ACTION_FOLD_TO_AOD,
ACTION_SHOW_VOICE_INTERACTION,
- ACTION_REQUEST_IME_SHOWN,
- ACTION_REQUEST_IME_HIDDEN,
};
/** @hide */
@@ -252,8 +232,6 @@ public class LatencyTracker {
ACTION_SHOW_SELECTION_TOOLBAR,
ACTION_FOLD_TO_AOD,
ACTION_SHOW_VOICE_INTERACTION,
- ACTION_REQUEST_IME_SHOWN,
- ACTION_REQUEST_IME_HIDDEN,
})
@Retention(RetentionPolicy.SOURCE)
public @interface Action {
@@ -281,8 +259,6 @@ public class LatencyTracker {
UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHOW_SELECTION_TOOLBAR,
UIACTION_LATENCY_REPORTED__ACTION__ACTION_FOLD_TO_AOD,
UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHOW_VOICE_INTERACTION,
- UIACTION_LATENCY_REPORTED__ACTION__ACTION_REQUEST_IME_SHOWN,
- UIACTION_LATENCY_REPORTED__ACTION__ACTION_REQUEST_IME_HIDDEN,
};
private static LatencyTracker sLatencyTracker;
@@ -308,30 +284,15 @@ public class LatencyTracker {
return sLatencyTracker;
}
- @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
@VisibleForTesting
public LatencyTracker() {
mEnabled = DEFAULT_ENABLED;
- final Context context = ActivityThread.currentApplication();
- if (context != null
- && context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG) == PERMISSION_GRANTED) {
- // Post initialization to the background in case we're running on the main thread.
- BackgroundThread.getHandler().post(() -> this.updateProperties(
- DeviceConfig.getProperties(NAMESPACE_LATENCY_TRACKER)));
- DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_LATENCY_TRACKER,
- BackgroundThread.getExecutor(), this::updateProperties);
- } else {
- if (DEBUG) {
- if (context == null) {
- Log.d(TAG, "No application for " + ActivityThread.currentActivityThread());
- } else {
- Log.d(TAG, "Initialized the LatencyTracker."
- + " (No READ_DEVICE_CONFIG permission to change configs)"
- + " enabled=" + mEnabled + ", package=" + context.getPackageName());
- }
- }
- }
+ // Post initialization to the background in case we're running on the main thread.
+ BackgroundThread.getHandler().post(() -> this.updateProperties(
+ DeviceConfig.getProperties(DeviceConfig.NAMESPACE_LATENCY_TRACKER)));
+ DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_LATENCY_TRACKER,
+ BackgroundThread.getExecutor(), this::updateProperties);
}
private void updateProperties(DeviceConfig.Properties properties) {
@@ -407,10 +368,6 @@ public class LatencyTracker {
return "ACTION_FOLD_TO_AOD";
case UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHOW_VOICE_INTERACTION:
return "ACTION_SHOW_VOICE_INTERACTION";
- case UIACTION_LATENCY_REPORTED__ACTION__ACTION_REQUEST_IME_SHOWN:
- return "ACTION_REQUEST_IME_SHOWN";
- case UIACTION_LATENCY_REPORTED__ACTION__ACTION_REQUEST_IME_HIDDEN:
- return "ACTION_REQUEST_IME_HIDDEN";
default:
throw new IllegalArgumentException("Invalid action");
}
diff --git a/core/java/com/android/internal/util/ScreenshotHelper.java b/core/java/com/android/internal/util/ScreenshotHelper.java
index 79c519645a24..3a393b689717 100644
--- a/core/java/com/android/internal/util/ScreenshotHelper.java
+++ b/core/java/com/android/internal/util/ScreenshotHelper.java
@@ -1,7 +1,7 @@
package com.android.internal.util;
import static android.content.Intent.ACTION_USER_SWITCHED;
-import static android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE;
+import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -11,29 +11,18 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
-import android.graphics.Bitmap;
-import android.graphics.ColorSpace;
-import android.graphics.Insets;
-import android.graphics.ParcelableColorSpace;
-import android.graphics.Rect;
-import android.hardware.HardwareBuffer;
import android.net.Uri;
-import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
-import android.os.Parcel;
-import android.os.Parcelable;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;
import android.view.WindowManager.ScreenshotSource;
-import android.view.WindowManager.ScreenshotType;
import com.android.internal.annotations.VisibleForTesting;
-import java.util.Objects;
import java.util.function.Consumer;
public class ScreenshotHelper {
@@ -41,212 +30,6 @@ public class ScreenshotHelper {
public static final int SCREENSHOT_MSG_URI = 1;
public static final int SCREENSHOT_MSG_PROCESS_COMPLETE = 2;
- /**
- * Describes a screenshot request.
- */
- public static class ScreenshotRequest implements Parcelable {
- @ScreenshotType
- private final int mType;
-
- @ScreenshotSource
- private final int mSource;
-
- private final Bundle mBitmapBundle;
- private final Rect mBoundsInScreen;
- private final Insets mInsets;
- private final int mTaskId;
- private final int mUserId;
- private final ComponentName mTopComponent;
-
-
- public ScreenshotRequest(@ScreenshotType int type, @ScreenshotSource int source) {
- this(type, source, /* topComponent */ null);
- }
-
- public ScreenshotRequest(@ScreenshotType int type, @ScreenshotSource int source,
- ComponentName topComponent) {
- this(type,
- source,
- /* bitmapBundle*/ null,
- /* boundsInScreen */ null,
- /* insets */ null,
- /* taskId */ -1,
- /* userId */ -1,
- topComponent);
- }
-
- public ScreenshotRequest(@ScreenshotType int type, @ScreenshotSource int source,
- Bundle bitmapBundle, Rect boundsInScreen, Insets insets, int taskId, int userId,
- ComponentName topComponent) {
- mType = type;
- mSource = source;
- mBitmapBundle = bitmapBundle;
- mBoundsInScreen = boundsInScreen;
- mInsets = insets;
- mTaskId = taskId;
- mUserId = userId;
- mTopComponent = topComponent;
- }
-
- ScreenshotRequest(Parcel in) {
- mType = in.readInt();
- mSource = in.readInt();
- if (in.readInt() == 1) {
- mBitmapBundle = in.readBundle(getClass().getClassLoader());
- mBoundsInScreen = in.readParcelable(Rect.class.getClassLoader(), Rect.class);
- mInsets = in.readParcelable(Insets.class.getClassLoader(), Insets.class);
- mTaskId = in.readInt();
- mUserId = in.readInt();
- mTopComponent = in.readParcelable(ComponentName.class.getClassLoader(),
- ComponentName.class);
- } else {
- mBitmapBundle = null;
- mBoundsInScreen = null;
- mInsets = null;
- mTaskId = -1;
- mUserId = -1;
- mTopComponent = null;
- }
- }
-
- @ScreenshotType
- public int getType() {
- return mType;
- }
-
- @ScreenshotSource
- public int getSource() {
- return mSource;
- }
-
- public Bundle getBitmapBundle() {
- return mBitmapBundle;
- }
-
- public Rect getBoundsInScreen() {
- return mBoundsInScreen;
- }
-
- public Insets getInsets() {
- return mInsets;
- }
-
- public int getTaskId() {
- return mTaskId;
- }
-
- public int getUserId() {
- return mUserId;
- }
-
- public ComponentName getTopComponent() {
- return mTopComponent;
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(mType);
- dest.writeInt(mSource);
- if (mBitmapBundle == null) {
- dest.writeInt(0);
- } else {
- dest.writeInt(1);
- dest.writeBundle(mBitmapBundle);
- dest.writeParcelable(mBoundsInScreen, 0);
- dest.writeParcelable(mInsets, 0);
- dest.writeInt(mTaskId);
- dest.writeInt(mUserId);
- dest.writeParcelable(mTopComponent, 0);
- }
- }
-
- @NonNull
- public static final Parcelable.Creator<ScreenshotRequest> CREATOR =
- new Parcelable.Creator<ScreenshotRequest>() {
-
- @Override
- public ScreenshotRequest createFromParcel(Parcel source) {
- return new ScreenshotRequest(source);
- }
-
- @Override
- public ScreenshotRequest[] newArray(int size) {
- return new ScreenshotRequest[size];
- }
- };
- }
-
- /**
- * Bundler used to convert between a hardware bitmap and a bundle without copying the internal
- * content. This is expected to be used together with {@link #provideScreenshot} to handle a
- * hardware bitmap as a screenshot.
- */
- public static final class HardwareBitmapBundler {
- private static final String KEY_BUFFER = "bitmap_util_buffer";
- private static final String KEY_COLOR_SPACE = "bitmap_util_color_space";
-
- private HardwareBitmapBundler() {
- }
-
- /**
- * Creates a Bundle that represents the given Bitmap.
- * <p>The Bundle will contain a wrapped version of the Bitmaps HardwareBuffer, so will avoid
- * copies when passing across processes, only pass to processes you trust.
- *
- * <p>Returns a new Bundle rather than modifying an exiting one to avoid key collisions, the
- * returned Bundle should be treated as a standalone object.
- *
- * @param bitmap to convert to bundle
- * @return a Bundle representing the bitmap, should only be parsed by
- * {@link #bundleToHardwareBitmap(Bundle)}
- */
- public static Bundle hardwareBitmapToBundle(Bitmap bitmap) {
- if (bitmap.getConfig() != Bitmap.Config.HARDWARE) {
- throw new IllegalArgumentException(
- "Passed bitmap must have hardware config, found: " + bitmap.getConfig());
- }
-
- // Bitmap assumes SRGB for null color space
- ParcelableColorSpace colorSpace =
- bitmap.getColorSpace() == null
- ? new ParcelableColorSpace(ColorSpace.get(ColorSpace.Named.SRGB))
- : new ParcelableColorSpace(bitmap.getColorSpace());
-
- Bundle bundle = new Bundle();
- bundle.putParcelable(KEY_BUFFER, bitmap.getHardwareBuffer());
- bundle.putParcelable(KEY_COLOR_SPACE, colorSpace);
-
- return bundle;
- }
-
- /**
- * Extracts the Bitmap added to a Bundle with {@link #hardwareBitmapToBundle(Bitmap)} .}
- *
- * <p>This Bitmap contains the HardwareBuffer from the original caller, be careful passing
- * this Bitmap on to any other source.
- *
- * @param bundle containing the bitmap
- * @return a hardware Bitmap
- */
- public static Bitmap bundleToHardwareBitmap(Bundle bundle) {
- if (!bundle.containsKey(KEY_BUFFER) || !bundle.containsKey(KEY_COLOR_SPACE)) {
- throw new IllegalArgumentException("Bundle does not contain a hardware bitmap");
- }
-
- HardwareBuffer buffer = bundle.getParcelable(KEY_BUFFER, HardwareBuffer.class);
- ParcelableColorSpace colorSpace = bundle.getParcelable(KEY_COLOR_SPACE,
- ParcelableColorSpace.class);
-
- return Bitmap.wrapHardwareBuffer(Objects.requireNonNull(buffer),
- colorSpace.getColorSpace());
- }
- }
-
private static final String TAG = "ScreenshotHelper";
// Time until we give up on the screenshot & show an error instead.
@@ -277,68 +60,53 @@ public class ScreenshotHelper {
/**
* Request a screenshot be taken.
* <p>
- * Added to support reducing unit test duration; the method variant without a timeout argument
- * is recommended for general use.
+ * Convenience method for taking a full screenshot with provided source.
*
- * @param type The type of screenshot, defined by {@link ScreenshotType}
- * @param source The source of the screenshot request, defined by {@link ScreenshotSource}
- * @param handler used to process messages received from the screenshot service
+ * @param source source of the screenshot request, defined by {@link
+ * ScreenshotSource}
+ * @param handler used to process messages received from the screenshot service
* @param completionConsumer receives the URI of the captured screenshot, once saved or
- * null if no screenshot was saved
+ * null if no screenshot was saved
*/
- public void takeScreenshot(@ScreenshotType int type, @ScreenshotSource int source,
- @NonNull Handler handler, @Nullable Consumer<Uri> completionConsumer) {
- ScreenshotRequest screenshotRequest = new ScreenshotRequest(type, source);
- takeScreenshot(handler, screenshotRequest, SCREENSHOT_TIMEOUT_MS,
- completionConsumer);
+ public void takeScreenshot(@ScreenshotSource int source, @NonNull Handler handler,
+ @Nullable Consumer<Uri> completionConsumer) {
+ ScreenshotRequest request =
+ new ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, source).build();
+ takeScreenshot(request, handler, completionConsumer);
}
/**
* Request a screenshot be taken.
* <p>
- * Added to support reducing unit test duration; the method variant without a timeout argument
- * is recommended for general use.
*
- * @param type The type of screenshot, defined by {@link ScreenshotType}
- * @param source The source of the screenshot request, defined by {@link ScreenshotSource}
- * @param handler used to process messages received from the screenshot service
- * @param timeoutMs time limit for processing, intended only for testing
+ * @param request description of the screenshot request, either for taking a
+ * screenshot or
+ * providing a bitmap
+ * @param handler used to process messages received from the screenshot service
* @param completionConsumer receives the URI of the captured screenshot, once saved or
- * null if no screenshot was saved
+ * null if no screenshot was saved
*/
- @VisibleForTesting
- public void takeScreenshot(@ScreenshotType int type, @ScreenshotSource int source,
- @NonNull Handler handler, long timeoutMs, @Nullable Consumer<Uri> completionConsumer) {
- ScreenshotRequest screenshotRequest = new ScreenshotRequest(type, source);
- takeScreenshot(handler, screenshotRequest, timeoutMs, completionConsumer);
+ public void takeScreenshot(ScreenshotRequest request, @NonNull Handler handler,
+ @Nullable Consumer<Uri> completionConsumer) {
+ takeScreenshotInternal(request, handler, completionConsumer, SCREENSHOT_TIMEOUT_MS);
}
/**
- * Request that provided image be handled as if it was a screenshot.
+ * Request a screenshot be taken.
+ * <p>
+ * Added to support reducing unit test duration; the method variant without a timeout argument
+ * is recommended for general use.
*
- * @param screenshotBundle Bundle containing the buffer and color space of the screenshot.
- * @param boundsInScreen The bounds in screen coordinates that the bitmap originated from.
- * @param insets The insets that the image was shown with, inside the screen bounds.
- * @param taskId The taskId of the task that the screen shot was taken of.
- * @param userId The userId of user running the task provided in taskId.
- * @param topComponent The component name of the top component running in the task.
- * @param source The source of the screenshot request, defined by {@link ScreenshotSource}
- * @param handler A handler used in case the screenshot times out
+ * @param request description of the screenshot request, either for taking a
+ * screenshot or providing a bitmap
+ * @param handler used to process messages received from the screenshot service
+ * @param timeoutMs time limit for processing, intended only for testing
* @param completionConsumer receives the URI of the captured screenshot, once saved or
- * null if no screenshot was saved
+ * null if no screenshot was saved
*/
- public void provideScreenshot(@NonNull Bundle screenshotBundle, @NonNull Rect boundsInScreen,
- @NonNull Insets insets, int taskId, int userId, ComponentName topComponent,
- @ScreenshotSource int source, @NonNull Handler handler,
- @Nullable Consumer<Uri> completionConsumer) {
- ScreenshotRequest screenshotRequest = new ScreenshotRequest(TAKE_SCREENSHOT_PROVIDED_IMAGE,
- source, screenshotBundle, boundsInScreen, insets, taskId, userId, topComponent);
- takeScreenshot(handler, screenshotRequest, SCREENSHOT_TIMEOUT_MS, completionConsumer);
- }
-
- private void takeScreenshot(@NonNull Handler handler,
- ScreenshotRequest screenshotRequest, long timeoutMs,
- @Nullable Consumer<Uri> completionConsumer) {
+ @VisibleForTesting
+ public void takeScreenshotInternal(ScreenshotRequest request, @NonNull Handler handler,
+ @Nullable Consumer<Uri> completionConsumer, long timeoutMs) {
synchronized (mScreenshotLock) {
final Runnable mScreenshotTimeout = () -> {
@@ -354,7 +122,7 @@ public class ScreenshotHelper {
}
};
- Message msg = Message.obtain(null, 0, screenshotRequest);
+ Message msg = Message.obtain(null, 0, request);
Handler h = new Handler(handler.getLooper()) {
@Override
@@ -471,5 +239,4 @@ public class ScreenshotHelper {
Intent.FLAG_RECEIVER_FOREGROUND);
mContext.sendBroadcastAsUser(errorIntent, UserHandle.CURRENT);
}
-
}
diff --git a/core/java/com/android/internal/util/ScreenshotRequest.aidl b/core/java/com/android/internal/util/ScreenshotRequest.aidl
new file mode 100644
index 000000000000..b08905dd0c9a
--- /dev/null
+++ b/core/java/com/android/internal/util/ScreenshotRequest.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.util;
+
+parcelable ScreenshotRequest; \ No newline at end of file
diff --git a/core/java/com/android/internal/util/ScreenshotRequest.java b/core/java/com/android/internal/util/ScreenshotRequest.java
new file mode 100644
index 000000000000..1902f80bd384
--- /dev/null
+++ b/core/java/com/android/internal/util/ScreenshotRequest.java
@@ -0,0 +1,332 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.util;
+
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.os.UserHandle.USER_NULL;
+import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN;
+import static android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.graphics.Bitmap;
+import android.graphics.ColorSpace;
+import android.graphics.Insets;
+import android.graphics.ParcelableColorSpace;
+import android.graphics.Rect;
+import android.hardware.HardwareBuffer;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+import android.view.WindowManager;
+
+import java.util.Objects;
+
+/**
+ * Describes a screenshot request.
+ */
+public class ScreenshotRequest implements Parcelable {
+ private static final String TAG = "ScreenshotRequest";
+
+ @WindowManager.ScreenshotType
+ private final int mType;
+ @WindowManager.ScreenshotSource
+ private final int mSource;
+ private final ComponentName mTopComponent;
+ private final int mTaskId;
+ private final int mUserId;
+ private final Bitmap mBitmap;
+ private final Rect mBoundsInScreen;
+ private final Insets mInsets;
+
+ private ScreenshotRequest(
+ @WindowManager.ScreenshotType int type, @WindowManager.ScreenshotSource int source,
+ ComponentName topComponent, int taskId, int userId,
+ Bitmap bitmap, Rect boundsInScreen, Insets insets) {
+ mType = type;
+ mSource = source;
+ mTopComponent = topComponent;
+ mTaskId = taskId;
+ mUserId = userId;
+ mBitmap = bitmap;
+ mBoundsInScreen = boundsInScreen;
+ mInsets = insets;
+ }
+
+ ScreenshotRequest(Parcel in) {
+ mType = in.readInt();
+ mSource = in.readInt();
+ mTopComponent = in.readTypedObject(ComponentName.CREATOR);
+ mTaskId = in.readInt();
+ mUserId = in.readInt();
+ mBitmap = HardwareBitmapBundler.bundleToHardwareBitmap(in.readTypedObject(Bundle.CREATOR));
+ mBoundsInScreen = in.readTypedObject(Rect.CREATOR);
+ mInsets = in.readTypedObject(Insets.CREATOR);
+ }
+
+ @WindowManager.ScreenshotType
+ public int getType() {
+ return mType;
+ }
+
+ @WindowManager.ScreenshotSource
+ public int getSource() {
+ return mSource;
+ }
+
+ public Bitmap getBitmap() {
+ return mBitmap;
+ }
+
+ public Rect getBoundsInScreen() {
+ return mBoundsInScreen;
+ }
+
+ public Insets getInsets() {
+ return mInsets;
+ }
+
+ public int getTaskId() {
+ return mTaskId;
+ }
+
+ public int getUserId() {
+ return mUserId;
+ }
+
+ public ComponentName getTopComponent() {
+ return mTopComponent;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mType);
+ dest.writeInt(mSource);
+ dest.writeTypedObject(mTopComponent, 0);
+ dest.writeInt(mTaskId);
+ dest.writeInt(mUserId);
+ dest.writeTypedObject(HardwareBitmapBundler.hardwareBitmapToBundle(mBitmap), 0);
+ dest.writeTypedObject(mBoundsInScreen, 0);
+ dest.writeTypedObject(mInsets, 0);
+ }
+
+ @NonNull
+ public static final Parcelable.Creator<ScreenshotRequest> CREATOR =
+ new Parcelable.Creator<ScreenshotRequest>() {
+
+ @Override
+ public ScreenshotRequest createFromParcel(Parcel source) {
+ return new ScreenshotRequest(source);
+ }
+
+ @Override
+ public ScreenshotRequest[] newArray(int size) {
+ return new ScreenshotRequest[size];
+ }
+ };
+
+ /**
+ * Builder class for {@link ScreenshotRequest} objects.
+ */
+ public static class Builder {
+ @WindowManager.ScreenshotType
+ private final int mType;
+
+ @WindowManager.ScreenshotSource
+ private final int mSource;
+
+ private Bitmap mBitmap;
+ private Rect mBoundsInScreen;
+ private Insets mInsets = Insets.NONE;
+ private int mTaskId = INVALID_TASK_ID;
+ private int mUserId = USER_NULL;
+ private ComponentName mTopComponent;
+
+ /**
+ * Begin building a ScreenshotRequest.
+ *
+ * @param type The type of the screenshot request, defined by {@link
+ * WindowManager.ScreenshotType}
+ * @param source The source of the screenshot request, defined by {@link
+ * WindowManager.ScreenshotSource}
+ */
+ public Builder(
+ @WindowManager.ScreenshotType int type,
+ @WindowManager.ScreenshotSource int source) {
+ mType = type;
+ mSource = source;
+ }
+
+ /**
+ * Construct a new {@link ScreenshotRequest} with the set parameters.
+ */
+ public ScreenshotRequest build() {
+ if (mType == TAKE_SCREENSHOT_FULLSCREEN && mBitmap != null) {
+ Log.w(TAG, "Bitmap provided, but request is fullscreen. Bitmap will be ignored.");
+ }
+ if (mType == TAKE_SCREENSHOT_PROVIDED_IMAGE && mBitmap == null) {
+ throw new IllegalStateException(
+ "Request is PROVIDED_IMAGE, but no bitmap is provided!");
+ }
+
+ return new ScreenshotRequest(mType, mSource, mTopComponent, mTaskId, mUserId, mBitmap,
+ mBoundsInScreen, mInsets);
+ }
+
+ /**
+ * Set the top component associated with this request.
+ *
+ * @param topComponent The component name of the top component running in the task.
+ */
+ public Builder setTopComponent(ComponentName topComponent) {
+ mTopComponent = topComponent;
+ return this;
+ }
+
+ /**
+ * Set the task id associated with this request.
+ *
+ * @param taskId The taskId of the task that the screenshot was taken of.
+ */
+ public Builder setTaskId(int taskId) {
+ mTaskId = taskId;
+ return this;
+ }
+
+ /**
+ * Set the user id associated with this request.
+ *
+ * @param userId The userId of user running the task provided in taskId.
+ */
+ public Builder setUserId(int userId) {
+ mUserId = userId;
+ return this;
+ }
+
+ /**
+ * Set the bitmap associated with this request.
+ *
+ * @param bitmap The provided screenshot.
+ */
+ public Builder setBitmap(Bitmap bitmap) {
+ mBitmap = bitmap;
+ return this;
+ }
+
+ /**
+ * Set the bounds for the provided bitmap.
+ *
+ * @param bounds The bounds in screen coordinates that the bitmap originated from.
+ */
+ public Builder setBoundsOnScreen(Rect bounds) {
+ mBoundsInScreen = bounds;
+ return this;
+ }
+
+ /**
+ * Set the insets for the provided bitmap.
+ *
+ * @param insets The insets that the image was shown with, inside the screen bounds.
+ */
+ public Builder setInsets(@NonNull Insets insets) {
+ mInsets = insets;
+ return this;
+ }
+ }
+
+ /**
+ * Bundler used to convert between a hardware bitmap and a bundle without copying the internal
+ * content. This is used together with a fully-defined ScreenshotRequest to handle a hardware
+ * bitmap as a screenshot.
+ */
+ private static final class HardwareBitmapBundler {
+ private static final String KEY_BUFFER = "bitmap_util_buffer";
+ private static final String KEY_COLOR_SPACE = "bitmap_util_color_space";
+
+ private HardwareBitmapBundler() {
+ }
+
+ /**
+ * Creates a Bundle that represents the given Bitmap.
+ * <p>The Bundle will contain a wrapped version of the Bitmaps HardwareBuffer, so will
+ * avoid
+ * copies when passing across processes, only pass to processes you trust.
+ *
+ * <p>Returns a new Bundle rather than modifying an exiting one to avoid key collisions,
+ * the
+ * returned Bundle should be treated as a standalone object.
+ *
+ * @param bitmap to convert to bundle
+ * @return a Bundle representing the bitmap, should only be parsed by
+ * {@link #bundleToHardwareBitmap(Bundle)}
+ */
+ private static Bundle hardwareBitmapToBundle(Bitmap bitmap) {
+ if (bitmap == null) {
+ return null;
+ }
+ if (bitmap.getConfig() != Bitmap.Config.HARDWARE) {
+ throw new IllegalArgumentException(
+ "Passed bitmap must have hardware config, found: "
+ + bitmap.getConfig());
+ }
+
+ // Bitmap assumes SRGB for null color space
+ ParcelableColorSpace colorSpace =
+ bitmap.getColorSpace() == null
+ ? new ParcelableColorSpace(ColorSpace.get(ColorSpace.Named.SRGB))
+ : new ParcelableColorSpace(bitmap.getColorSpace());
+
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(KEY_BUFFER, bitmap.getHardwareBuffer());
+ bundle.putParcelable(KEY_COLOR_SPACE, colorSpace);
+
+ return bundle;
+ }
+
+ /**
+ * Extracts the Bitmap added to a Bundle with {@link #hardwareBitmapToBundle(Bitmap)}.
+ *
+ * <p>This Bitmap contains the HardwareBuffer from the original caller, be careful
+ * passing
+ * this Bitmap on to any other source.
+ *
+ * @param bundle containing the bitmap
+ * @return a hardware Bitmap
+ */
+ private static Bitmap bundleToHardwareBitmap(Bundle bundle) {
+ if (bundle == null) {
+ return null;
+ }
+ if (!bundle.containsKey(KEY_BUFFER) || !bundle.containsKey(KEY_COLOR_SPACE)) {
+ throw new IllegalArgumentException("Bundle does not contain a hardware bitmap");
+ }
+
+ HardwareBuffer buffer = bundle.getParcelable(KEY_BUFFER, HardwareBuffer.class);
+ ParcelableColorSpace colorSpace = bundle.getParcelable(KEY_COLOR_SPACE,
+ ParcelableColorSpace.class);
+
+ return Bitmap.wrapHardwareBuffer(Objects.requireNonNull(buffer),
+ colorSpace.getColorSpace());
+ }
+ }
+}
diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java
index 4cf0ba19a887..635adcad0e7b 100644
--- a/core/java/com/android/internal/widget/ConversationLayout.java
+++ b/core/java/com/android/internal/widget/ConversationLayout.java
@@ -927,7 +927,8 @@ public class ConversationLayout extends FrameLayout
message = messages.get(i - histSize);
}
boolean isNewGroup = currentGroup == null;
- Person sender = message.getMessage().getSenderPerson();
+ Person sender =
+ message.getMessage() == null ? null : message.getMessage().getSenderPerson();
CharSequence key = getKey(sender);
isNewGroup |= !TextUtils.equals(key, currentSenderKey);
if (isNewGroup) {
@@ -1190,7 +1191,8 @@ public class ConversationLayout extends FrameLayout
return null;
}
final MessagingMessage messagingMessage = mMessages.get(mMessages.size() - 1);
- final CharSequence text = messagingMessage.getMessage().getText();
+ final CharSequence text = messagingMessage.getMessage() == null ? null
+ : messagingMessage.getMessage().getText();
if (text == null && messagingMessage instanceof MessagingImageMessage) {
final String unformatted =
getResources().getString(R.string.conversation_single_line_image_placeholder);
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 3c305f6e443e..86fd9569c61e 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -619,12 +619,11 @@ public class LockPatternUtils {
}
boolean disabledByDefault = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_disableLockscreenByDefault);
- boolean isSystemUser = UserManager.isSplitSystemUser() && userId == UserHandle.USER_SYSTEM;
UserInfo userInfo = getUserManager().getUserInfo(userId);
boolean isDemoUser = UserManager.isDeviceInDemoMode(mContext) && userInfo != null
&& userInfo.isDemo();
return getBoolean(DISABLE_LOCKSCREEN_KEY, false, userId)
- || (disabledByDefault && !isSystemUser)
+ || disabledByDefault
|| isDemoUser;
}
diff --git a/core/java/com/android/internal/widget/MessagingGroup.java b/core/java/com/android/internal/widget/MessagingGroup.java
index 146cb3fadaac..30e4099f9a6f 100644
--- a/core/java/com/android/internal/widget/MessagingGroup.java
+++ b/core/java/com/android/internal/widget/MessagingGroup.java
@@ -492,7 +492,9 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou
int color = mSendingSpinnerContainer.getVisibility() == View.VISIBLE
? mSendingTextColor : mTextColor;
for (MessagingMessage message : mMessages) {
- message.setColor(message.getMessage().isRemoteInputHistory() ? color : mTextColor);
+ final boolean isRemoteInputHistory =
+ message.getMessage() != null && message.getMessage().isRemoteInputHistory();
+ message.setColor(isRemoteInputHistory ? color : mTextColor);
}
}
}
diff --git a/core/java/com/android/internal/widget/MessagingLayout.java b/core/java/com/android/internal/widget/MessagingLayout.java
index 67b671e01413..9d142f689b98 100644
--- a/core/java/com/android/internal/widget/MessagingLayout.java
+++ b/core/java/com/android/internal/widget/MessagingLayout.java
@@ -470,7 +470,8 @@ public class MessagingLayout extends FrameLayout
message = messages.get(i - histSize);
}
boolean isNewGroup = currentGroup == null;
- Person sender = message.getMessage().getSenderPerson();
+ Person sender =
+ message.getMessage() == null ? null : message.getMessage().getSenderPerson();
CharSequence key = sender == null ? null
: sender.getKey() == null ? sender.getName() : sender.getKey();
isNewGroup |= !TextUtils.equals(key, currentSenderKey);
diff --git a/core/java/com/android/internal/widget/MessagingMessage.java b/core/java/com/android/internal/widget/MessagingMessage.java
index 2cc0d2305a78..5ecd3b82053d 100644
--- a/core/java/com/android/internal/widget/MessagingMessage.java
+++ b/core/java/com/android/internal/widget/MessagingMessage.java
@@ -68,6 +68,10 @@ public interface MessagingMessage extends MessagingLinearLayout.MessagingChild {
default boolean sameAs(Notification.MessagingStyle.Message message) {
Notification.MessagingStyle.Message ownMessage = getMessage();
+ // We have to make sure both messages are not null to go further comparison
+ if (message == null || ownMessage == null) {
+ return message == ownMessage;
+ }
if (!Objects.equals(message.getText(), ownMessage.getText())) {
return false;
}
diff --git a/core/jni/OWNERS b/core/jni/OWNERS
index a68040d4638e..bd85874b195f 100644
--- a/core/jni/OWNERS
+++ b/core/jni/OWNERS
@@ -72,6 +72,7 @@ per-file com_android_internal_net_* = file:/services/core/java/com/android/serve
per-file android_graphics_* = file:/graphics/java/android/graphics/OWNERS
per-file *HardwareBuffer* = file:/graphics/java/android/graphics/OWNERS
per-file android_hardware_SyncFence.cpp = file:/graphics/java/android/graphics/OWNERS
+per-file android_hardware_OverlayProperties.cpp = file:/graphics/java/android/graphics/OWNERS
per-file android_os_GraphicsEnvironment.cpp = file:platform/frameworks/native:/opengl/OWNERS
### Text ###
diff --git a/core/jni/android_view_InputDevice.cpp b/core/jni/android_view_InputDevice.cpp
index 7d379e5e69c2..0bc0878b7309 100644
--- a/core/jni/android_view_InputDevice.cpp
+++ b/core/jni/android_view_InputDevice.cpp
@@ -68,6 +68,7 @@ jobject android_view_InputDevice_create(JNIEnv* env, const InputDeviceInfo& devi
}
const InputDeviceIdentifier& ident = deviceInfo.getIdentifier();
+ const auto usiVersion = deviceInfo.getUsiVersion().value_or(InputDeviceUsiVersion{-1, -1});
ScopedLocalRef<jobject>
inputDeviceObj(env,
@@ -81,7 +82,9 @@ jobject android_view_InputDevice_create(JNIEnv* env, const InputDeviceInfo& devi
keyboardLanguageTagObj.get(), keyboardLayoutTypeObj.get(),
deviceInfo.hasVibrator(), deviceInfo.hasMic(),
deviceInfo.hasButtonUnderPad(), deviceInfo.hasSensor(),
- deviceInfo.hasBattery(), deviceInfo.supportsUsi()));
+ deviceInfo.hasBattery(), usiVersion.majorVersion,
+ usiVersion.minorVersion,
+ deviceInfo.getAssociatedDisplayId()));
// Note: We do not populate the Bluetooth address into the InputDevice object to avoid leaking
// it to apps that do not have the Bluetooth permission.
@@ -105,7 +108,7 @@ int register_android_view_InputDevice(JNIEnv* env)
gInputDeviceClassInfo.ctor = GetMethodIDOrDie(env, gInputDeviceClassInfo.clazz, "<init>",
"(IIILjava/lang/String;IILjava/lang/"
"String;ZIILandroid/view/KeyCharacterMap;Ljava/"
- "lang/String;Ljava/lang/String;ZZZZZZ)V");
+ "lang/String;Ljava/lang/String;ZZZZZIII)V");
gInputDeviceClassInfo.addMotionRange =
GetMethodIDOrDie(env, gInputDeviceClassInfo.clazz, "addMotionRange", "(IIFFFFF)V");
diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto
index e165b079f450..fd0d9c5d053b 100644
--- a/core/proto/android/providers/settings/global.proto
+++ b/core/proto/android/providers/settings/global.proto
@@ -160,7 +160,7 @@ message GlobalSettingsProto {
optional Bluetooth bluetooth = 21;
optional SettingProto boot_count = 22 [ (android.privacy).dest = DEST_AUTOMATIC ];
- optional SettingProto bugreport_in_power_menu = 23 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ reserved 23; // Moved to secure settings bugreport_in_power_menu
optional SettingProto cached_apps_freezer_enabled = 152 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto call_auto_retry = 24 [ (android.privacy).dest = DEST_AUTOMATIC ];
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index e6f942e49565..ea0ec79469d5 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -186,6 +186,7 @@ message SecureSettingsProto {
optional Backup backup = 10;
optional SettingProto bluetooth_on_while_driving = 11 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto bugreport_in_power_menu = 95 [ (android.privacy).dest = DEST_AUTOMATIC ];
message Camera {
option (android.msg_privacy).dest = DEST_EXPLICIT;
@@ -696,5 +697,5 @@ message SecureSettingsProto {
// Please insert fields in alphabetical order and group them into messages
// if possible (to avoid reaching the method limit).
- // Next tag = 95;
+ // Next tag = 96;
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 27f2b8a72901..bc40cefe6510 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -7050,6 +7050,19 @@
<permission android:name="android.permission.QUERY_CLONED_APPS"
android:protectionLevel="signature|privileged" />
+ <!-- @hide @SystemApi
+ Allows applications to capture bugreport directly without consent dialog when using the
+ bugreporting API on userdebug/eng build.
+ <p>The application still needs to hold {@link android.permission.DUMP} permission and be
+ bugreport-whitelisted to be able to capture a bugreport using the bugreporting API in the
+ first place. Then, when the corresponding app op of this permission is ALLOWED, the
+ bugreport can be captured directly without going through the consent dialog.
+ <p>Protection level: internal|appop
+ <p>Intended to only be used on userdebug/eng build.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD"
+ android:protectionLevel="internal|appop" />
+
<!-- Attribution for Geofencing service. -->
<attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
<!-- Attribution for Country Detector. -->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 0b5d66dd13c0..66163659554e 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -5802,6 +5802,7 @@
- config_roundedCornerDrawableArray (config in SystemUI resource)
- config_roundedCornerTopDrawableArray (config in SystemUI resource)
- config_roundedCornerBottomDrawableArray (config in SystemUI resource)
+ - config_displayUsiVersionArray
Leave this array empty for single display device and the system will load the default main
built-in related configs.
@@ -6160,4 +6161,21 @@
trusted certificate using the SHA-256 digest algorithm. -->
<string-array name="config_healthConnectMigrationKnownSigners">
</string-array>
+
+ <!-- The Universal Stylus Initiative (USI) protocol version supported by each display.
+ (@see https://universalstylus.org/).
+
+ The i-th value in this array corresponds to the supported USI version of the i-th display
+ listed in config_displayUniqueIdArray. On a single-display device, the
+ config_displayUniqueIdArray may be empty, in which case the only value in this array should
+ be the USI version for the main built-in display.
+
+ If the display does not support USI, the version value should be an empty string. If the
+ display supports USI, the version must be in the following format:
+ "<major-version>.<minor-version>"
+
+ For example, "", "1.0", and "2.0" are valid values. -->
+ <string-array name="config_displayUsiVersionArray" translatable="false">
+ <item>""</item>
+ </string-array>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index daedde1b7731..93d0873aedfc 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4779,6 +4779,7 @@
<java-symbol type="array" name="config_mainBuiltInDisplayWaterfallCutout" />
<java-symbol type="array" name="config_secondaryBuiltInDisplayWaterfallCutout" />
<java-symbol type="array" name="config_waterfallCutoutArray" />
+ <java-symbol type="array" name="config_displayUsiVersionArray" />
<java-symbol type="fraction" name="global_actions_vertical_padding_percentage" />
<java-symbol type="fraction" name="global_actions_horizontal_padding_percentage" />
diff --git a/core/tests/PackageInstallerSessions/src/android/content/pm/PackageSessionTests.kt b/core/tests/PackageInstallerSessions/src/android/content/pm/PackageSessionTests.kt
index 966d362f1fd1..9acb99a8c1c2 100644
--- a/core/tests/PackageInstallerSessions/src/android/content/pm/PackageSessionTests.kt
+++ b/core/tests/PackageInstallerSessions/src/android/content/pm/PackageSessionTests.kt
@@ -92,9 +92,9 @@ class PackageSessionTests {
}
fun makeIntentSender(sessionId: Int) = PendingIntent.getBroadcast(context, sessionId,
- Intent(INTENT_ACTION),
+ Intent(INTENT_ACTION).setPackage(context.packageName),
PendingIntent.FLAG_UPDATE_CURRENT
- or PendingIntent.FLAG_MUTABLE_UNAUDITED).intentSender
+ or PendingIntent.FLAG_MUTABLE).intentSender
fun getResult(unit: TimeUnit, timeout: Long) = results.poll(timeout, unit)
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index bcb13d2108b8..4548730fc487 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -248,7 +248,9 @@ public class NotificationTest {
@Test
public void allPendingIntents_containsCustomRemoteViews() {
- PendingIntent intent = PendingIntent.getActivity(mContext, 0, new Intent("test"), PendingIntent.FLAG_MUTABLE_UNAUDITED);
+ PendingIntent intent = PendingIntent.getActivity(mContext, 0,
+ new Intent("test").setPackage(mContext.getPackageName()),
+ PendingIntent.FLAG_MUTABLE);
RemoteViews contentView = new RemoteViews(mContext.getPackageName(), 0 /* layoutId */);
contentView.setOnClickPendingIntent(1 /* id */, intent);
@@ -1578,7 +1580,8 @@ public class NotificationTest {
* Creates a PendingIntent with the given action.
*/
private PendingIntent createPendingIntent(String action) {
- return PendingIntent.getActivity(mContext, 0, new Intent(action),
+ return PendingIntent.getActivity(mContext, 0,
+ new Intent(action).setPackage(mContext.getPackageName()),
PendingIntent.FLAG_MUTABLE);
}
}
diff --git a/core/tests/coretests/src/android/app/activity/IntentSenderTest.java b/core/tests/coretests/src/android/app/activity/IntentSenderTest.java
index 05775bca4196..1b52f8050f33 100644
--- a/core/tests/coretests/src/android/app/activity/IntentSenderTest.java
+++ b/core/tests/coretests/src/android/app/activity/IntentSenderTest.java
@@ -32,14 +32,16 @@ public class IntentSenderTest extends BroadcastTest {
registerMyReceiver(new IntentFilter(BROADCAST_REGISTERED), PERMISSION_GRANTED);
addIntermediate("after-register");
PendingIntent is = PendingIntent.getBroadcast(getContext(), 0,
- makeBroadcastIntent(BROADCAST_REGISTERED), PendingIntent.FLAG_MUTABLE_UNAUDITED);
+ makeBroadcastIntent(BROADCAST_REGISTERED).setPackage(getContext().getPackageName()),
+ PendingIntent.FLAG_MUTABLE);
is.send();
waitForResultOrThrow(BROADCAST_TIMEOUT);
is.cancel();
}
public void testRegisteredReceivePermissionDenied() throws Exception {
- final Intent intent = makeBroadcastIntent(BROADCAST_REGISTERED);
+ final Intent intent = makeBroadcastIntent(BROADCAST_REGISTERED)
+ .setPackage(getContext().getPackageName());
setExpectedReceivers(new String[]{RECEIVER_RESULTS});
registerMyReceiver(new IntentFilter(BROADCAST_REGISTERED), PERMISSION_DENIED);
@@ -52,7 +54,8 @@ public class IntentSenderTest extends BroadcastTest {
}
};
- PendingIntent is = PendingIntent.getBroadcast(getContext(), 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
+ PendingIntent is = PendingIntent.getBroadcast(getContext(), 0, intent,
+ PendingIntent.FLAG_MUTABLE);
is.send(Activity.RESULT_CANCELED, finish, null);
waitForResultOrThrow(BROADCAST_TIMEOUT);
is.cancel();
@@ -61,14 +64,16 @@ public class IntentSenderTest extends BroadcastTest {
public void testLocalReceivePermissionGranted() throws Exception {
setExpectedReceivers(new String[]{RECEIVER_LOCAL});
PendingIntent is = PendingIntent.getBroadcast(getContext(), 0,
- makeBroadcastIntent(BROADCAST_LOCAL_GRANTED), PendingIntent.FLAG_MUTABLE_UNAUDITED);
+ makeBroadcastIntent(BROADCAST_LOCAL_GRANTED)
+ .setPackage(getContext().getPackageName()), PendingIntent.FLAG_MUTABLE);
is.send();
waitForResultOrThrow(BROADCAST_TIMEOUT);
is.cancel();
}
public void testLocalReceivePermissionDenied() throws Exception {
- final Intent intent = makeBroadcastIntent(BROADCAST_LOCAL_DENIED);
+ final Intent intent = makeBroadcastIntent(BROADCAST_LOCAL_DENIED)
+ .setPackage(getContext().getPackageName());
setExpectedReceivers(new String[]{RECEIVER_RESULTS});
@@ -79,7 +84,8 @@ public class IntentSenderTest extends BroadcastTest {
}
};
- PendingIntent is = PendingIntent.getBroadcast(getContext(), 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
+ PendingIntent is = PendingIntent.getBroadcast(getContext(), 0, intent,
+ PendingIntent.FLAG_MUTABLE);
is.send(Activity.RESULT_CANCELED, finish, null);
waitForResultOrThrow(BROADCAST_TIMEOUT);
is.cancel();
diff --git a/core/tests/coretests/src/android/service/settings/suggestions/SuggestionTest.java b/core/tests/coretests/src/android/service/settings/suggestions/SuggestionTest.java
index 6186192daf32..e0eb197f437a 100644
--- a/core/tests/coretests/src/android/service/settings/suggestions/SuggestionTest.java
+++ b/core/tests/coretests/src/android/service/settings/suggestions/SuggestionTest.java
@@ -48,7 +48,8 @@ public class SuggestionTest {
public void setUp() {
final Context context = InstrumentationRegistry.getContext();
mTestIntent = PendingIntent.getActivity(context, 0 /* requestCode */,
- new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED /* flags */);
+ new Intent().setPackage(context.getPackageName()),
+ PendingIntent.FLAG_MUTABLE /* flags */);
mIcon = Icon.createWithBitmap(Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888));
}
diff --git a/core/tests/coretests/src/android/text/method/InsertModeTransformationMethodTest.java b/core/tests/coretests/src/android/text/method/InsertModeTransformationMethodTest.java
new file mode 100644
index 000000000000..7706d9aebdcc
--- /dev/null
+++ b/core/tests/coretests/src/android/text/method/InsertModeTransformationMethodTest.java
@@ -0,0 +1,796 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.text.method;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.platform.test.annotations.Presubmit;
+import android.text.SpannableString;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.style.ReplacementSpan;
+import android.view.View;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class InsertModeTransformationMethodTest {
+ private static View sView;
+ private static final String TEXT = "abc def";
+
+ @BeforeClass
+ public static void setupClass() {
+ final Context context = InstrumentationRegistry.getTargetContext();
+ sView = new View(context);
+ }
+
+ @Test
+ public void transformedText_charAt() {
+ for (int offset = 0; offset < TEXT.length(); ++offset) {
+ final InsertModeTransformationMethod transformationMethod =
+ new InsertModeTransformationMethod(offset, false, null);
+ final CharSequence transformedText =
+ transformationMethod.getTransformation(TEXT, sView);
+ final CharSequence expected =
+ TEXT.substring(0, offset) + "\n\n" + TEXT.substring(offset);
+
+ assertCharSequence(transformedText, expected);
+ }
+ }
+
+ @Test
+ public void transformedText_charAt_singleLine() {
+ for (int offset = 0; offset < TEXT.length(); ++offset) {
+ final InsertModeTransformationMethod transformationMethod =
+ new InsertModeTransformationMethod(offset, true, null);
+ final CharSequence transformedText =
+ transformationMethod.getTransformation(TEXT, sView);
+ final CharSequence expected =
+ TEXT.substring(0, offset) + "\uFFFD" + TEXT.substring(offset);
+
+ assertCharSequence(transformedText, expected);
+ }
+ }
+
+ @Test
+ public void transformedText_charAt_editing() {
+ transformedText_charAt_editing(false, "\n\n");
+ }
+
+ @Test
+ public void transformedText_charAt_singleLine_editing() {
+ transformedText_charAt_editing(true, "\uFFFD");
+ }
+
+ public void transformedText_charAt_editing(boolean singleLine, String placeholder) {
+ final SpannableStringBuilder text = new SpannableStringBuilder(TEXT);
+ final InsertModeTransformationMethod transformationMethod =
+ new InsertModeTransformationMethod(3, singleLine, null);
+ final CharSequence transformedText = transformationMethod.getTransformation(text, sView);
+ // TransformationMethod is set on the original text as a TextWatcher in the TextView.
+ text.setSpan(transformationMethod, 0, text.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+
+ assertCharSequence(transformedText, "abc" + placeholder + " def");
+
+ // original text is "abcxx def" after insertion.
+ text.insert(3, "xx");
+ assertCharSequence(transformedText, "abcxx" + placeholder + " def");
+
+ // original text is "abcxx vvdef" after insertion.
+ text.insert(6, "vv");
+ assertCharSequence(transformedText, "abcxx" + placeholder + " vvdef");
+
+ // original text is "abc vvdef" after deletion.
+ text.delete(3, 5);
+ assertCharSequence(transformedText, "abc" + placeholder + " vvdef");
+
+ // original text is "abc def" after deletion.
+ text.delete(4, 6);
+ assertCharSequence(transformedText, "abc" + placeholder + " def");
+
+ // original text is "abdef" after deletion.
+ // the placeholder is now inserted at index 2, since the deletion covers the index 3.
+ text.delete(2, 4);
+ assertCharSequence(transformedText, "ab" + placeholder + "def");
+
+ // original text is "axxdef" after replace.
+ text.replace(1, 2, "xx");
+ assertCharSequence(transformedText, "axx" + placeholder + "def");
+
+ // original text is "axxvvf" after replace.
+ text.replace(3, 5, "vv");
+ assertCharSequence(transformedText, "axx" + placeholder + "vvf");
+
+ // original text is "abc def" after replace.
+ // the placeholder is inserted at index 6 after the insertion, since the replacement covers
+ // the index 3.
+ text.replace(1, 5, "bc de");
+ assertCharSequence(transformedText, "abc de" + placeholder + "f");
+ }
+
+ @Test
+ public void transformedText_subSequence() {
+ for (int offset = 0; offset < TEXT.length(); ++offset) {
+ final InsertModeTransformationMethod transformationMethod =
+ new InsertModeTransformationMethod(offset, false, null);
+ final CharSequence transformedText =
+ transformationMethod.getTransformation(TEXT, sView);
+ final CharSequence expected =
+ TEXT.substring(0, offset) + "\n\n" + TEXT.substring(offset);
+
+ for (int start = 0; start < transformedText.length(); ++start) {
+ for (int end = start; end <= transformedText.length(); ++end) {
+ assertCharSequence(transformedText.subSequence(start, end),
+ expected.subSequence(start, end));
+ }
+ }
+ }
+ }
+
+ @Test
+ public void transformedText_subSequence_singleLine() {
+ for (int offset = 0; offset < TEXT.length(); ++offset) {
+ final InsertModeTransformationMethod transformationMethod =
+ new InsertModeTransformationMethod(offset, true, null);
+ final CharSequence transformedText =
+ transformationMethod.getTransformation(TEXT, sView);
+ final CharSequence expected =
+ TEXT.substring(0, offset) + "\uFFFD" + TEXT.substring(offset);
+
+ for (int start = 0; start < transformedText.length(); ++start) {
+ for (int end = start; end <= transformedText.length(); ++end) {
+ assertCharSequence(transformedText.subSequence(start, end),
+ expected.subSequence(start, end));
+ }
+ }
+ }
+ }
+
+ @Test
+ public void transformedText_toString() {
+ for (int offset = 0; offset < TEXT.length(); ++offset) {
+ final InsertModeTransformationMethod transformationMethod =
+ new InsertModeTransformationMethod(offset, false, null);
+ final CharSequence transformedText =
+ transformationMethod.getTransformation(TEXT, sView);
+ final String expected =
+ TEXT.substring(0, offset) + "\n\n" + TEXT.substring(offset);
+
+ assertThat(transformedText.toString()).isEqualTo(expected);
+ }
+ }
+
+ @Test
+ public void transformedText_toString_singleLine() {
+ for (int offset = 0; offset < TEXT.length(); ++offset) {
+ final InsertModeTransformationMethod transformationMethod =
+ new InsertModeTransformationMethod(offset, true, null);
+ final CharSequence transformedText =
+ transformationMethod.getTransformation(TEXT, sView);
+ final String expected =
+ TEXT.substring(0, offset) + "\uFFFD" + TEXT.substring(offset);
+
+ assertThat(transformedText.toString()).isEqualTo(expected);
+ }
+ }
+
+
+ @Test
+ public void transformedText_getSpans() {
+ final SpannableString text = new SpannableString(TEXT);
+ final TestSpan span1 = new TestSpan();
+ final TestSpan span2 = new TestSpan();
+ final TestSpan span3 = new TestSpan();
+
+ text.setSpan(span1, 0, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ text.setSpan(span2, 2, 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ text.setSpan(span3, 4, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+ // In the transformedText "abc\n\n def", the new ranges of the spans are:
+ // span1: [0, 3)
+ // span2: [2, 6)
+ // span3: [6, 7)
+ final InsertModeTransformationMethod transformationMethod =
+ new InsertModeTransformationMethod(3, false, null);
+ final Spanned transformedText =
+ (Spanned) transformationMethod.getTransformation(text, sView);
+
+ // only span1 is in the range of [0, 2).
+ final TestSpan[] spans0to2 = transformedText.getSpans(0, 2, TestSpan.class);
+ assertThat(spans0to2.length).isEqualTo(1);
+ assertThat(spans0to2[0]).isEqualTo(span1);
+
+ // span1 and span2 are in the range of [1, 6).
+ final TestSpan[] spans1to6 = transformedText.getSpans(1, 6, TestSpan.class);
+ assertThat(spans1to6.length).isEqualTo(2);
+ assertThat(spans1to6[0]).isEqualTo(span1);
+ assertThat(spans1to6[1]).isEqualTo(span2);
+
+ // only span2 is in the range of [4, 6).
+ final TestSpan[] spans4to6 = transformedText.getSpans(4, 6, TestSpan.class);
+ assertThat(spans4to6.length).isEqualTo(1);
+ assertThat(spans4to6[0]).isEqualTo(span2);
+
+ // span2 and span3 are in the range of [4, 7).
+ final TestSpan[] spans4to7 = transformedText.getSpans(4, 7, TestSpan.class);
+ assertThat(spans4to7.length).isEqualTo(2);
+ assertThat(spans4to7[0]).isEqualTo(span2);
+ assertThat(spans4to7[1]).isEqualTo(span3);
+
+ // only span3 is in the range of [6, 7).
+ final TestSpan[] spans6to7 = transformedText.getSpans(6, 7, TestSpan.class);
+ assertThat(spans6to7.length).isEqualTo(1);
+ assertThat(spans6to7[0]).isEqualTo(span3);
+
+ // there is no span in the range of [7, 9).
+ final TestSpan[] spans7to9 = transformedText.getSpans(7, 9, TestSpan.class);
+ assertThat(spans7to9.length).isEqualTo(0);
+ }
+
+ @Test
+ public void transformedText_getSpans_singleLine() {
+ final SpannableString text = new SpannableString(TEXT);
+ final TestSpan span1 = new TestSpan();
+ final TestSpan span2 = new TestSpan();
+ final TestSpan span3 = new TestSpan();
+
+ text.setSpan(span1, 0, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ text.setSpan(span2, 2, 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ text.setSpan(span3, 4, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+ // In the transformedText, the new ranges of the spans are:
+ // span1: [0, 3)
+ // span2: [2, 5)
+ // span3: [5, 6)
+ // There should also be a ReplacementSpan in the range [3, 4).
+ final InsertModeTransformationMethod transformationMethod =
+ new InsertModeTransformationMethod(3, true, null);
+ final Spanned transformedText =
+ (Spanned) transformationMethod.getTransformation(text, sView);
+
+ // only span1 is in the range of [0, 2).
+ final TestSpan[] spans0to2 = transformedText.getSpans(0, 2, TestSpan.class);
+ assertThat(spans0to2.length).isEqualTo(1);
+ assertThat(spans0to2[0]).isEqualTo(span1);
+
+ // span1 and span2 are in the range of [1, 5).
+ final TestSpan[] spans1to4 = transformedText.getSpans(1, 4, TestSpan.class);
+ assertThat(spans1to4.length).isEqualTo(2);
+ assertThat(spans1to4[0]).isEqualTo(span1);
+ assertThat(spans1to4[1]).isEqualTo(span2);
+
+ // only span2 is in the range of [3, 5).
+ final TestSpan[] spans3to5 = transformedText.getSpans(3, 5, TestSpan.class);
+ assertThat(spans3to5.length).isEqualTo(1);
+ assertThat(spans3to5[0]).isEqualTo(span2);
+
+ // span2 and span3 are in the range of [3, 6).
+ final TestSpan[] spans3to6 = transformedText.getSpans(3, 6, TestSpan.class);
+ assertThat(spans3to6.length).isEqualTo(2);
+ assertThat(spans3to6[0]).isEqualTo(span2);
+ assertThat(spans3to6[1]).isEqualTo(span3);
+
+ // only span3 is in the range of [5, 6).
+ final TestSpan[] spans5to6 = transformedText.getSpans(5, 6, TestSpan.class);
+ assertThat(spans5to6.length).isEqualTo(1);
+ assertThat(spans5to6[0]).isEqualTo(span3);
+
+ // there is no span in the range of [6, 8)
+ final TestSpan[] spans6to8 = transformedText.getSpans(6, 8, TestSpan.class);
+ assertThat(spans6to8.length).isEqualTo(0);
+
+ // When it's singleLine, there should be a ReplacementSpan in the range [3, 4)
+ final ReplacementSpan[] replacementSpans3to4 =
+ transformedText.getSpans(3, 4, ReplacementSpan.class);
+ assertThat(replacementSpans3to4.length).isEqualTo(1);
+
+ final ReplacementSpan[] replacementSpans0to3 =
+ transformedText.getSpans(0, 3, ReplacementSpan.class);
+ assertThat(replacementSpans0to3.length).isEqualTo(0);
+
+ final ReplacementSpan[] replacementSpans4to8 =
+ transformedText.getSpans(4, 8, ReplacementSpan.class);
+ assertThat(replacementSpans4to8.length).isEqualTo(0);
+ }
+
+ @Test
+ public void transformedText_getSpanStartAndEnd() {
+ final SpannableString text = new SpannableString(TEXT);
+ final TestSpan span1 = new TestSpan();
+ final TestSpan span2 = new TestSpan();
+ final TestSpan span3 = new TestSpan();
+
+ text.setSpan(span1, 0, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ text.setSpan(span2, 2, 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ text.setSpan(span3, 4, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+ // In the transformedText, the new ranges of the spans are:
+ // span1: [0, 3)
+ // span2: [2, 6)
+ // span3: [6, 7)
+ final InsertModeTransformationMethod transformationMethod =
+ new InsertModeTransformationMethod(3, false, null);
+ final Spanned transformedText =
+ (Spanned) transformationMethod.getTransformation(text, sView);
+
+ assertThat(transformedText.getSpanStart(span1)).isEqualTo(0);
+ assertThat(transformedText.getSpanEnd(span1)).isEqualTo(3);
+
+ assertThat(transformedText.getSpanStart(span2)).isEqualTo(2);
+ assertThat(transformedText.getSpanEnd(span2)).isEqualTo(6);
+
+ assertThat(transformedText.getSpanStart(span3)).isEqualTo(6);
+ assertThat(transformedText.getSpanEnd(span3)).isEqualTo(7);
+ }
+
+ @Test
+ public void transformedText_getSpanStartAndEnd_singleLine() {
+ final SpannableString text = new SpannableString(TEXT);
+ final TestSpan span1 = new TestSpan();
+ final TestSpan span2 = new TestSpan();
+ final TestSpan span3 = new TestSpan();
+
+ text.setSpan(span1, 0, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ text.setSpan(span2, 2, 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ text.setSpan(span3, 4, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+ // In the transformedText, the new ranges of the spans are:
+ // span1: [0, 3)
+ // span2: [2, 5)
+ // span3: [5, 6)
+ final InsertModeTransformationMethod transformationMethod =
+ new InsertModeTransformationMethod(3, true, null);
+ final Spanned transformedText =
+ (Spanned) transformationMethod.getTransformation(text, sView);
+
+ assertThat(transformedText.getSpanStart(span1)).isEqualTo(0);
+ assertThat(transformedText.getSpanEnd(span1)).isEqualTo(3);
+
+ assertThat(transformedText.getSpanStart(span2)).isEqualTo(2);
+ assertThat(transformedText.getSpanEnd(span2)).isEqualTo(5);
+
+ assertThat(transformedText.getSpanStart(span3)).isEqualTo(5);
+ assertThat(transformedText.getSpanEnd(span3)).isEqualTo(6);
+
+ final ReplacementSpan[] replacementSpans =
+ transformedText.getSpans(0, 8, ReplacementSpan.class);
+ assertThat(transformedText.getSpanStart(replacementSpans[0])).isEqualTo(3);
+ assertThat(transformedText.getSpanEnd(replacementSpans[0])).isEqualTo(4);
+ }
+
+ @Test
+ public void transformedText_getSpanFlag() {
+ transformedText_getSpanFlag(false);
+ }
+
+ @Test
+ public void transformedText_getSpanFlag_singleLine() {
+ transformedText_getSpanFlag(true);
+ }
+
+ public void transformedText_getSpanFlag(boolean singleLine) {
+ final SpannableString text = new SpannableString(TEXT);
+ final TestSpan span1 = new TestSpan();
+ final TestSpan span2 = new TestSpan();
+ final TestSpan span3 = new TestSpan();
+
+ text.setSpan(span1, 0, 2, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ text.setSpan(span2, 2, 4, Spanned.SPAN_EXCLUSIVE_INCLUSIVE);
+ text.setSpan(span3, 4, 5, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+
+ final InsertModeTransformationMethod transformationMethod =
+ new InsertModeTransformationMethod(3, singleLine, null);
+ final Spanned transformedText =
+ (Spanned) transformationMethod.getTransformation(text, sView);
+
+ assertThat(transformedText.getSpanFlags(span1)).isEqualTo(Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ assertThat(transformedText.getSpanFlags(span2)).isEqualTo(Spanned.SPAN_EXCLUSIVE_INCLUSIVE);
+ assertThat(transformedText.getSpanFlags(span3)).isEqualTo(Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+ }
+
+ @Test
+ public void transformedText_nextSpanTransition() {
+ final SpannableString text = new SpannableString(TEXT);
+ final TestSpan span1 = new TestSpan();
+ final TestSpan span2 = new TestSpan();
+ final TestSpan span3 = new TestSpan();
+
+
+ text.setSpan(span1, 0, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ text.setSpan(span2, 1, 4, Spanned.SPAN_EXCLUSIVE_INCLUSIVE);
+ text.setSpan(span3, 4, 5, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+
+ // In the transformedText, the new ranges of the spans are:
+ // span1: [0, 3)
+ // span2: [1, 6)
+ // span3: [6, 7)
+ final InsertModeTransformationMethod transformationMethod =
+ new InsertModeTransformationMethod(3, false, null);
+ final Spanned transformedText =
+ (Spanned) transformationMethod.getTransformation(text, sView);
+ final int[] expectedTransitions = new int[] { 0, 1, 3, 6, 7 };
+ assertNextSpanTransition(transformedText, expectedTransitions, TestSpan.class);
+ }
+
+ @Test
+ public void transformedText_nextSpanTransition_singleLine() {
+ final SpannableString text = new SpannableString(TEXT);
+ final TestSpan span1 = new TestSpan();
+ final TestSpan span2 = new TestSpan();
+ final TestSpan span3 = new TestSpan();
+
+
+ text.setSpan(span1, 0, 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ text.setSpan(span2, 1, 4, Spanned.SPAN_EXCLUSIVE_INCLUSIVE);
+ text.setSpan(span3, 4, 5, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+
+ // In the transformedText, the new ranges of the spans are:
+ // span1: [0, 3)
+ // span2: [1, 5)
+ // span3: [5, 6)
+ // there is also a ReplacementSpan at range [3, 4)
+ final InsertModeTransformationMethod transformationMethod =
+ new InsertModeTransformationMethod(3, true, null);
+ final Spanned transformedText =
+ (Spanned) transformationMethod.getTransformation(text, sView);
+ final int[] expectedTransitions = new int[] { 0, 1, 3, 4, 5, 6 };
+ assertNextSpanTransition(transformedText, expectedTransitions, Object.class);
+ }
+
+ @Test
+ public void transformedText_originalToTransformed() {
+ final InsertModeTransformationMethod transformationMethod =
+ new InsertModeTransformationMethod(2, false, null);
+ final OffsetMapping transformedText =
+ (OffsetMapping) transformationMethod.getTransformation(TEXT, sView);
+
+ // "abc def" is transformed to "ab\n\nc def".
+ final int[] mappedCharacterOffsets = new int[] { 0, 1, 4, 5, 6, 7, 8 };
+ assertOriginalToTransformed(transformedText, OffsetMapping.MAP_STRATEGY_CHARACTER,
+ mappedCharacterOffsets);
+
+ // "abc def" is transformed to "ab\n\nc def".
+ // the cursor before 'c' is mapped to index position before "\n\n".
+ final int[] mappedCursorOffsets = new int[] { 0, 1, 2, 5, 6, 7, 8 };
+ assertOriginalToTransformed(transformedText, OffsetMapping.MAP_STRATEGY_CURSOR,
+ mappedCursorOffsets);
+ }
+
+ @Test
+ public void transformedText_originalToTransformed_singleLine() {
+ final InsertModeTransformationMethod transformationMethod =
+ new InsertModeTransformationMethod(2, true, null);
+ final OffsetMapping transformedText =
+ (OffsetMapping) transformationMethod.getTransformation(TEXT, sView);
+
+ // "abc def" is transformed to "ab\uFFFDc def".
+ final int[] mappedCharacterOffsets = new int[] { 0, 1, 3, 4, 5, 6, 7 };
+ assertOriginalToTransformed(transformedText, OffsetMapping.MAP_STRATEGY_CHARACTER,
+ mappedCharacterOffsets);
+
+ // "abc def" is transformed to "ab\uFFFDc def".
+ // the cursor before 'c' is mapped to index position before "\uFFFD".
+ final int[] mappedCursorOffsets = new int[] { 0, 1, 3, 4, 5, 6, 7 };
+ assertOriginalToTransformed(transformedText, OffsetMapping.MAP_STRATEGY_CHARACTER,
+ mappedCursorOffsets);
+ }
+
+ @Test
+ public void transformedText_transformedToOriginal() {
+ final InsertModeTransformationMethod transformationMethod =
+ new InsertModeTransformationMethod(2, false, null);
+ final OffsetMapping transformedText =
+ (OffsetMapping) transformationMethod.getTransformation(TEXT, sView);
+
+ // "abc def" is transformed to "ab\n\nc def".
+ // the two '\n' characters have no corresponding character; map them to 'c'.
+ final int[] mappedCharacterOffsets = new int[] { 0, 1, 2, 2, 2, 3, 4, 5, 6 };
+ assertTransformedToOriginal(transformedText, OffsetMapping.MAP_STRATEGY_CHARACTER,
+ mappedCharacterOffsets);
+
+ // offset 2 and 3 (cursor positions before the two '\n' characters) are mapped to index 2
+ // (cursor position before 'c' in the original text)
+ final int[] mappedCursorOffsets = new int[] { 0, 1, 2, 2, 2, 3, 4, 5, 6 };
+ assertTransformedToOriginal(transformedText, OffsetMapping.MAP_STRATEGY_CURSOR,
+ mappedCursorOffsets);
+ }
+
+ @Test
+ public void transformedText_transformedToOriginal_singleLine() {
+ final InsertModeTransformationMethod transformationMethod =
+ new InsertModeTransformationMethod(2, true, null);
+ final OffsetMapping transformedText =
+ (OffsetMapping) transformationMethod.getTransformation(TEXT, sView);
+
+ // "abc def" is transformed to "ab\uFFFDc def".
+ // '\uFFFD' has no corresponding character; map it to 'c'.
+ final int[] mappedCharacterOffsets = new int[] { 0, 1, 2, 2, 3, 4, 5, 6 };
+ assertTransformedToOriginal(transformedText, OffsetMapping.MAP_STRATEGY_CHARACTER,
+ mappedCharacterOffsets);
+
+ // offset 2 (cursor positions before '\uFFFD') is mapped to index 2 (cursor position before
+ // 'c' in the original text)
+ final int[] mappedCursorOffsets = new int[] { 0, 1, 2, 2, 3, 4, 5, 6 };
+ assertTransformedToOriginal(transformedText, OffsetMapping.MAP_STRATEGY_CHARACTER,
+ mappedCursorOffsets);
+ }
+
+ @Test
+ public void transformedText_getHighlightStartAndEnd_insertion() {
+ transformedText_getHighlightStartAndEnd_insertion(false, "\n\n");
+ }
+
+ @Test
+ public void transformedText_getHighlightStartAndEnd_insertion_singleLine() {
+ transformedText_getHighlightStartAndEnd_insertion(true, "\uFDDD");
+ }
+
+ public void transformedText_getHighlightStartAndEnd_insertion(boolean singleLine,
+ String placeholder) {
+ final SpannableStringBuilder text = new SpannableStringBuilder(TEXT);
+ final InsertModeTransformationMethod transformationMethod =
+ new InsertModeTransformationMethod(3, singleLine, null);
+ final InsertModeTransformationMethod.TransformedText transformedText =
+ (InsertModeTransformationMethod.TransformedText) transformationMethod
+ .getTransformation(text, sView);
+ // TransformationMethod is set on the original text as a TextWatcher in the TextView.
+ text.setSpan(transformationMethod, 0, text.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+
+ // note: the placeholder text is also highlighted.
+ assertThat(transformedText.getHighlightStart()).isEqualTo(3);
+ assertThat(transformedText.getHighlightEnd()).isEqualTo(3 + placeholder.length());
+
+ // original text is "abcxx def" after insertion.
+ // the placeholder is now inserted at index 5.
+ // the highlight start is still 3.
+ // the highlight end now is 5 + placeholder.length(), including the newly inserted text.
+ text.insert(3, "xx");
+ assertThat(transformedText.getHighlightStart()).isEqualTo(3);
+ assertThat(transformedText.getHighlightEnd()).isEqualTo(5 + placeholder.length());
+
+ // original text is "abcxxvv def" after insertion.
+ // the placeholder is now inserted at index 7.
+ // the highlight start is still 3.
+ // the highlight end now is 7 + placeholder.length(), including the newly inserted text.
+ text.insert(5, "vv");
+ assertThat(transformedText.getHighlightStart()).isEqualTo(3);
+ assertThat(transformedText.getHighlightEnd()).isEqualTo(7 + placeholder.length());
+
+ // original text is "abzzcxxvv def" after insertion.
+ // the placeholder is now inserted at index 9.
+ // the highlight start is 5, since the insertion happens before the highlight range.
+ // the highlight end now is 9 + placeholder.length().
+ text.insert(2, "zz");
+ assertThat(transformedText.getHighlightStart()).isEqualTo(5);
+ assertThat(transformedText.getHighlightEnd()).isEqualTo(9 + placeholder.length());
+
+ // original text is "abzzcxxvv iidef" after insertion.
+ // the placeholder is still inserted at index 9.
+ // the highlight start is still 5, since the insertion happens after the highlight range.
+ // the highlight end now is 9 + placeholder.length().
+ text.insert(10, "ii");
+ assertThat(transformedText.getHighlightStart()).isEqualTo(5);
+ assertThat(transformedText.getHighlightEnd()).isEqualTo(9 + placeholder.length());
+
+ }
+
+ @Test
+ public void transformedText_getHighlightStartAndEnd_deletion() {
+ transformedText_getHighlightStartAndEnd_deletion(false, "\n\n");
+ }
+
+ @Test
+ public void transformedText_getHighlightStartAndEnd_insertion_deletion() {
+ transformedText_getHighlightStartAndEnd_deletion(true, "\uFDDD");
+ }
+
+ public void transformedText_getHighlightStartAndEnd_deletion(boolean singleLine,
+ String placeholder) {
+ final SpannableStringBuilder text = new SpannableStringBuilder(TEXT);
+ final InsertModeTransformationMethod transformationMethod =
+ new InsertModeTransformationMethod(3, singleLine, null);
+ final InsertModeTransformationMethod.TransformedText transformedText =
+ (InsertModeTransformationMethod.TransformedText) transformationMethod
+ .getTransformation(text, sView);
+ // TransformationMethod is set on the original text as a TextWatcher in the TextView.
+ text.setSpan(transformationMethod, 0, text.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+
+ // note: the placeholder text is also highlighted.
+ assertThat(transformedText.getHighlightStart()).isEqualTo(3);
+ assertThat(transformedText.getHighlightEnd()).isEqualTo(3 + placeholder.length());
+
+ // original text is "abcxxxxxx def" after insertion.
+ // the placeholder is now inserted at index 9.
+ // the highlight start is still 3.
+ // the highlight end now is 9 + placeholder.length().
+ text.insert(3, "xxxxxx");
+ assertThat(transformedText.getHighlightStart()).isEqualTo(3);
+ assertThat(transformedText.getHighlightEnd()).isEqualTo(9 + placeholder.length());
+
+ // original text is "abxxxxxx def" after deletion.
+ // the placeholder is now inserted at index 6.
+ // the highlight start is 2, since the deletion happens before the highlight range.
+ // the highlight end now is 8 + placeholder.length().
+ text.delete(2, 3);
+ assertThat(transformedText.getHighlightStart()).isEqualTo(2);
+ assertThat(transformedText.getHighlightEnd()).isEqualTo(8 + placeholder.length());
+
+ // original text is "abxxx def" after deletion.
+ // the placeholder is now inserted at index 5.
+ // the highlight start is still 2, since the deletion happens in the highlight range.
+ // the highlight end now is 5 + placeholder.length().
+ text.delete(2, 5);
+ assertThat(transformedText.getHighlightStart()).isEqualTo(2);
+ assertThat(transformedText.getHighlightEnd()).isEqualTo(5 + placeholder.length());
+
+ // original text is "abxxx d" after deletion.
+ // the placeholder is now inserted at index 5.
+ // the highlight start is still 2, since the deletion happens after the highlight range.
+ // the highlight end now is still 5 + placeholder.length().
+ text.delete(7, 9);
+ assertThat(transformedText.getHighlightStart()).isEqualTo(2);
+ assertThat(transformedText.getHighlightEnd()).isEqualTo(5 + placeholder.length());
+
+ // original text is "af" after deletion.
+ // the placeholder is now inserted at index 1.
+ // the highlight start is 1, since the deletion covers highlight range.
+ // the highlight end is 1 + placeholder.length().
+ text.delete(1, 5);
+ assertThat(transformedText.getHighlightStart()).isEqualTo(1);
+ assertThat(transformedText.getHighlightEnd()).isEqualTo(1 + placeholder.length());
+ }
+
+
+ @Test
+ public void transformedText_getHighlightStartAndEnd_replace() {
+ transformedText_getHighlightStartAndEnd_replace(false, "\n\n");
+ }
+
+ @Test
+ public void transformedText_getHighlightStartAndEnd_insertion__replace() {
+ transformedText_getHighlightStartAndEnd_replace(true, "\uFDDD");
+ }
+
+ public void transformedText_getHighlightStartAndEnd_replace(boolean singleLine,
+ String placeholder) {
+ final SpannableStringBuilder text = new SpannableStringBuilder(TEXT);
+ final InsertModeTransformationMethod transformationMethod =
+ new InsertModeTransformationMethod(3, singleLine, null);
+ final InsertModeTransformationMethod.TransformedText transformedText =
+ (InsertModeTransformationMethod.TransformedText) transformationMethod
+ .getTransformation(text, sView);
+ // TransformationMethod is set on the original text as a TextWatcher in the TextView.
+ text.setSpan(transformationMethod, 0, text.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+
+ // note: the placeholder text is also highlighted.
+ assertThat(transformedText.getHighlightStart()).isEqualTo(3);
+ assertThat(transformedText.getHighlightEnd()).isEqualTo(3 + placeholder.length());
+
+ // original text is "abcxxxxxx def" after insertion.
+ // the placeholder is now inserted at index 9.
+ // the highlight start is still 3.
+ // the highlight end now is 9 + placeholder.length().
+ text.insert(3, "xxxxxx");
+ assertThat(transformedText.getHighlightStart()).isEqualTo(3);
+ assertThat(transformedText.getHighlightEnd()).isEqualTo(9 + placeholder.length());
+
+ // original text is "abvvxxxxxx def" after replace.
+ // the replacement happens before the highlight range; highlight range is offset by 1
+ // the placeholder is now inserted at index 10,
+ // the highlight start is 4.
+ // the highlight end is 10 + placeholder.length().
+ text.replace(2, 3, "vv");
+ assertThat(transformedText.getHighlightStart()).isEqualTo(4);
+ assertThat(transformedText.getHighlightEnd()).isEqualTo(10 + placeholder.length());
+
+ // original text is "abvvxxx def" after replace.
+ // the replacement happens in the highlight range; highlight end is offset by -3
+ // the placeholder is now inserted at index 7,
+ // the highlight start is still 4.
+ // the highlight end is 7 + placeholder.length().
+ text.replace(5, 9, "x");
+ assertThat(transformedText.getHighlightStart()).isEqualTo(4);
+ assertThat(transformedText.getHighlightEnd()).isEqualTo(7 + placeholder.length());
+
+ // original text is "abvvxxxvvv" after replace.
+ // the replacement happens after the highlight range; highlight is not changed
+ // the placeholder is now inserted at index 7,
+ // the highlight start is still 4.
+ // the highlight end is 7 + placeholder.length().
+ text.replace(7, 11, "vvv");
+ assertThat(transformedText.getHighlightStart()).isEqualTo(4);
+ assertThat(transformedText.getHighlightEnd()).isEqualTo(7 + placeholder.length());
+
+ // original text is "abxxxxvvv" after replace.
+ // the replacement happens covers the highlight start; highlight start extends to the
+ // replacement start; highlight end is offset by -1
+ // the placeholder is now inserted at index 6,
+ // the highlight start is 2.
+ // the highlight end is 6 + placeholder.length().
+ text.replace(2, 5, "xx");
+ assertThat(transformedText.getHighlightStart()).isEqualTo(2);
+ assertThat(transformedText.getHighlightEnd()).isEqualTo(6 + placeholder.length());
+
+ // original text is "abxxxxxvv" after replace.
+ // the replacement happens covers the highlight end; highlight end extends to the
+ // replacement end; highlight start stays the same
+ // the placeholder is now inserted at index 7,
+ // the highlight start is 2.
+ // the highlight end is 7 + placeholder.length().
+ text.replace(5, 7, "xx");
+ assertThat(transformedText.getHighlightStart()).isEqualTo(2);
+ assertThat(transformedText.getHighlightEnd()).isEqualTo(7 + placeholder.length());
+
+ // original text is "axxv" after replace.
+ // the replacement happens covers the highlight range; highlight start is set to the
+ // replacement start; highlight end is set to the replacement end
+ // the placeholder is now inserted at index 3,
+ // the highlight start is 1.
+ // the highlight end is 3 + placeholder.length().
+ text.replace(1, 8, "xx");
+ assertThat(transformedText.getHighlightStart()).isEqualTo(1);
+ assertThat(transformedText.getHighlightEnd()).isEqualTo(3 + placeholder.length());
+ }
+
+ private static <T> void assertNextSpanTransition(Spanned spanned, int[] transitions,
+ Class<T> type) {
+ int currentTransition = 0;
+ for (int transition : transitions) {
+ assertThat(currentTransition).isEqualTo(transition);
+ currentTransition =
+ spanned.nextSpanTransition(currentTransition, spanned.length(), type);
+ }
+
+ // Make sure there is no transition after the currentTransition.
+ assertThat(currentTransition).isEqualTo(spanned.length());
+ }
+
+ private static void assertCharSequence(CharSequence actual, CharSequence expected) {
+ assertThat(actual.length()).isEqualTo(expected.length());
+ for (int index = 0; index < actual.length(); ++index) {
+ assertThat(actual.charAt(index)).isEqualTo(expected.charAt(index));
+ }
+ }
+
+ private static void assertOriginalToTransformed(OffsetMapping transformedText, int strategy,
+ int[] expected) {
+ for (int offset = 0; offset < expected.length; ++offset) {
+ assertThat(transformedText.originalToTransformed(offset, strategy))
+ .isEqualTo(expected[offset]);
+ }
+ }
+
+ private static void assertTransformedToOriginal(OffsetMapping transformedText, int strategy,
+ int[] expected) {
+ for (int offset = 0; offset < expected.length; ++offset) {
+ assertThat(transformedText.transformedToOriginal(offset, strategy))
+ .isEqualTo(expected[offset]);
+ }
+ }
+
+ private static class TestSpan { }
+}
diff --git a/core/tests/coretests/src/android/view/inputmethod/ParcelableHandwritingGestureTest.java b/core/tests/coretests/src/android/view/inputmethod/ParcelableHandwritingGestureTest.java
index 79aeaa39bc7c..90f7d06857c7 100644
--- a/core/tests/coretests/src/android/view/inputmethod/ParcelableHandwritingGestureTest.java
+++ b/core/tests/coretests/src/android/view/inputmethod/ParcelableHandwritingGestureTest.java
@@ -22,6 +22,7 @@ import static org.junit.Assert.assertThrows;
import android.annotation.NonNull;
import android.graphics.PointF;
import android.graphics.RectF;
+import android.os.CancellationSignal;
import android.os.Parcel;
import android.platform.test.annotations.Presubmit;
@@ -86,6 +87,14 @@ public class ParcelableHandwritingGestureTest {
}
@Test
+ public void testInsertModeGesture() {
+ verifyEqualityAfterUnparcel(new InsertModeGesture.Builder()
+ .setInsertionPoint(new PointF(1, 1)).setFallbackText("")
+ .setCancellationSignal(new CancellationSignal())
+ .build());
+ }
+
+ @Test
public void testDeleteGestureGesture() {
verifyEqualityAfterUnparcel(new DeleteGesture.Builder()
.setGranularity(HandwritingGesture.GRANULARITY_WORD)
diff --git a/core/tests/coretests/src/android/widget/RemoteViewsTest.java b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
index bbf9f3c99402..31c5a761ac1f 100644
--- a/core/tests/coretests/src/android/widget/RemoteViewsTest.java
+++ b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
@@ -324,7 +324,9 @@ public class RemoteViewsTest {
RemoteViews nested = new RemoteViews(mPackage, R.layout.remote_views_text);
nested.setOnClickPendingIntent(
R.id.text,
- PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE)
+ PendingIntent.getActivity(mContext, 0,
+ new Intent().setPackage(mContext.getPackageName()),
+ PendingIntent.FLAG_MUTABLE)
);
RemoteViews listItem = new RemoteViews(mPackage, R.layout.remote_view_host);
@@ -341,7 +343,9 @@ public class RemoteViewsTest {
RemoteViews inner = new RemoteViews(mPackage, R.layout.remote_views_text);
inner.setOnClickPendingIntent(
R.id.text,
- PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE)
+ PendingIntent.getActivity(mContext, 0,
+ new Intent().setPackage(mContext.getPackageName()),
+ PendingIntent.FLAG_MUTABLE)
);
RemoteViews listItem = new RemoteViews(inner, inner);
@@ -357,7 +361,9 @@ public class RemoteViewsTest {
RemoteViews inner = new RemoteViews(mPackage, R.layout.remote_views_text);
inner.setOnClickPendingIntent(
R.id.text,
- PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE)
+ PendingIntent.getActivity(mContext, 0,
+ new Intent().setPackage(mContext.getPackageName()),
+ PendingIntent.FLAG_MUTABLE)
);
RemoteViews listItem = new RemoteViews(
@@ -559,7 +565,9 @@ public class RemoteViewsTest {
RemoteViews views = new RemoteViews(mPackage, R.layout.remote_views_test);
for (int i = 1; i < 10; i++) {
PendingIntent pi = PendingIntent.getBroadcast(mContext, 0,
- new Intent("android.widget.RemoteViewsTest_" + i), PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
+ new Intent("android.widget.RemoteViewsTest_" + i)
+ .setPackage(mContext.getPackageName()),
+ PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_MUTABLE);
views.setOnClickPendingIntent(i, pi);
}
try {
@@ -575,7 +583,8 @@ public class RemoteViewsTest {
RemoteViews views = new RemoteViews(mPackage, R.layout.remote_views_test);
PendingIntent pi = PendingIntent.getBroadcast(mContext, 0,
- new Intent("test"), PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
+ new Intent("test").setPackage(mContext.getPackageName()),
+ PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_MUTABLE);
views.setOnClickPendingIntent(1, pi);
RemoteViews withCookie = parcelAndRecreateWithPendingIntentCookie(views, whitelistToken);
@@ -606,8 +615,9 @@ public class RemoteViewsTest {
public void sharedElement_pendingIntent_notifyParent() throws Exception {
RemoteViews views = new RemoteViews(mPackage, R.layout.remote_views_test);
PendingIntent pi = PendingIntent.getBroadcast(mContext, 0,
- new Intent("android.widget.RemoteViewsTest_shared_element"),
- PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
+ new Intent("android.widget.RemoteViewsTest_shared_element")
+ .setPackage(mContext.getPackageName()),
+ PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_MUTABLE);
views.setOnClickResponse(R.id.image, RemoteViews.RemoteResponse.fromPendingIntent(pi)
.addSharedElement(0, "e0")
.addSharedElement(1, "e1")
diff --git a/core/tests/coretests/src/com/android/internal/app/AbstractResolverComparatorTest.java b/core/tests/coretests/src/com/android/internal/app/AbstractResolverComparatorTest.java
index 3e640c1bad39..bdf42aca8edf 100644
--- a/core/tests/coretests/src/com/android/internal/app/AbstractResolverComparatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/AbstractResolverComparatorTest.java
@@ -18,6 +18,7 @@ package com.android.internal.app;
import static junit.framework.Assert.assertEquals;
+import android.app.Instrumentation;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -27,6 +28,10 @@ import android.os.Message;
import androidx.test.InstrumentationRegistry;
+import com.android.internal.app.chooser.TargetInfo;
+
+import com.google.android.collect.Lists;
+
import org.junit.Test;
import java.util.List;
@@ -96,7 +101,8 @@ public class AbstractResolverComparatorTest {
Intent intent = new Intent();
AbstractResolverComparator testComparator =
- new AbstractResolverComparator(context, intent) {
+ new AbstractResolverComparator(context, intent,
+ Lists.newArrayList(context.getUser())) {
@Override
int compare(ResolveInfo lhs, ResolveInfo rhs) {
@@ -109,7 +115,7 @@ public class AbstractResolverComparatorTest {
void doCompute(List<ResolverActivity.ResolvedComponentInfo> targets) {}
@Override
- float getScore(ComponentName name) {
+ float getScore(TargetInfo targetInfo) {
return 0;
}
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityOverrideData.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityOverrideData.java
index eead4edb6f90..5469843f3f9a 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityOverrideData.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityOverrideData.java
@@ -29,7 +29,6 @@ import android.os.UserHandle;
import android.util.Pair;
import com.android.internal.app.AbstractMultiProfilePagerAdapter.CrossProfileIntentsChecker;
-import com.android.internal.app.AbstractMultiProfilePagerAdapter.MyUserIdProvider;
import com.android.internal.app.AbstractMultiProfilePagerAdapter.QuietModeManager;
import com.android.internal.app.chooser.TargetInfo;
import com.android.internal.logging.MetricsLogger;
@@ -71,13 +70,14 @@ public class ChooserActivityOverrideData {
public int alternateProfileSetting;
public Resources resources;
public UserHandle workProfileUserHandle;
+ public UserHandle cloneProfileUserHandle;
+ public UserHandle tabOwnerUserHandleForLaunch;
public boolean hasCrossProfileIntents;
public boolean isQuietModeEnabled;
public boolean isWorkProfileUserRunning;
public boolean isWorkProfileUserUnlocked;
public Integer myUserId;
public QuietModeManager mQuietModeManager;
- public MyUserIdProvider mMyUserIdProvider;
public CrossProfileIntentsChecker mCrossProfileIntentsChecker;
public PackageManager packageManager;
@@ -98,6 +98,8 @@ public class ChooserActivityOverrideData {
alternateProfileSetting = 0;
resources = null;
workProfileUserHandle = null;
+ cloneProfileUserHandle = null;
+ tabOwnerUserHandleForLaunch = null;
hasCrossProfileIntents = true;
isQuietModeEnabled = false;
isWorkProfileUserRunning = true;
@@ -126,13 +128,6 @@ public class ChooserActivityOverrideData {
}
};
- mMyUserIdProvider = new MyUserIdProvider() {
- @Override
- public int getMyUserId() {
- return myUserId != null ? myUserId : UserHandle.myUserId();
- }
- };
-
mCrossProfileIntentsChecker = mock(CrossProfileIntentsChecker.class);
when(mCrossProfileIntentsChecker.hasCrossProfileIntents(any(), anyInt(), anyInt()))
.thenAnswer(invocation -> hasCrossProfileIntents);
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
index d656678c16ee..c06df9455e07 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
@@ -143,7 +143,8 @@ public class ChooserActivityTest {
* `Parameterized` runner.
* --------
*/
-
+ private static final UserHandle PERSONAL_USER_HANDLE = InstrumentationRegistry
+ .getInstrumentation().getTargetContext().getUser();
private static final Function<PackageManager, PackageManager> DEFAULT_PM = pm -> pm;
private static final Function<PackageManager, PackageManager> NO_APP_PREDICTION_SERVICE_PM =
pm -> {
@@ -472,7 +473,7 @@ public class ChooserActivityTest {
List<ResolvedComponentInfo> infosToStack = new ArrayList<>();
for (int i = 0; i < 4; i++) {
ResolveInfo resolveInfo = ResolverDataProvider.createResolveInfo(i,
- UserHandle.USER_CURRENT);
+ UserHandle.USER_CURRENT, PERSONAL_USER_HANDLE);
resolveInfo.activityInfo.applicationInfo.name = appName;
resolveInfo.activityInfo.applicationInfo.packageName = packageName;
resolveInfo.activityInfo.packageName = packageName;
@@ -544,13 +545,17 @@ public class ChooserActivityTest {
return true;
};
ResolveInfo toChoose = resolvedComponentInfos.get(0).getResolveInfoAt(0);
+ DisplayResolveInfo testDri =
+ activity.createTestDisplayResolveInfo(sendIntent, toChoose, "testLabel", "testInfo",
+ sendIntent,/* resolveInfoPresentationGetter */ null);
onView(withText(toChoose.activityInfo.name))
.perform(click());
waitForIdle();
verify(ChooserActivityOverrideData.getInstance().resolverListController, times(1))
- .updateChooserCounts(Mockito.anyString(), anyInt(), Mockito.anyString());
+ .updateChooserCounts(Mockito.anyString(), any(UserHandle.class),
+ Mockito.anyString());
verify(ChooserActivityOverrideData.getInstance().resolverListController, times(1))
- .updateModel(toChoose.activityInfo.getComponentName());
+ .updateModel(testDri);
assertThat(activity.getIsSelected(), is(true));
}
@@ -1329,7 +1334,11 @@ public class ChooserActivityTest {
final DisplayResolveInfo testDri =
activity.createTestDisplayResolveInfo(sendIntent,
- ResolverDataProvider.createResolveInfo(3, 0), "testLabel", "testInfo", sendIntent,
+ ResolverDataProvider.createResolveInfo(
+ 3, 0, PERSONAL_USER_HANDLE),
+ "testLabel",
+ "testInfo",
+ sendIntent,
/* resolveInfoPresentationGetter */ null);
final ChooserListAdapter adapter = activity.getAdapter();
@@ -1450,7 +1459,8 @@ public class ChooserActivityTest {
ArgumentCaptor<LogMaker> logMakerCaptor = ArgumentCaptor.forClass(LogMaker.class);
// Create direct share target
List<ChooserTarget> serviceTargets = createDirectShareTargets(1, "");
- ResolveInfo ri = ResolverDataProvider.createResolveInfo(3, 0);
+ ResolveInfo ri = ResolverDataProvider.createResolveInfo(3, 0,
+ PERSONAL_USER_HANDLE);
// Start activity
final IChooserWrapper activity = (IChooserWrapper)
@@ -1528,7 +1538,8 @@ public class ChooserActivityTest {
// Create direct share target
List<ChooserTarget> serviceTargets = createDirectShareTargets(1,
resolvedComponentInfos.get(0).getResolveInfoAt(0).activityInfo.packageName);
- ResolveInfo ri = ResolverDataProvider.createResolveInfo(3, 0);
+ ResolveInfo ri = ResolverDataProvider.createResolveInfo(3, 0,
+ PERSONAL_USER_HANDLE);
// Start activity
final IChooserWrapper activity = (IChooserWrapper)
@@ -1606,7 +1617,8 @@ public class ChooserActivityTest {
// Create direct share target
List<ChooserTarget> serviceTargets = createDirectShareTargets(2,
resolvedComponentInfos.get(0).getResolveInfoAt(0).activityInfo.packageName);
- ResolveInfo ri = ResolverDataProvider.createResolveInfo(3, 0);
+ ResolveInfo ri = ResolverDataProvider.createResolveInfo(3, 0,
+ PERSONAL_USER_HANDLE);
// Start activity
final ChooserActivity activity =
@@ -1679,7 +1691,8 @@ public class ChooserActivityTest {
// Create direct share target
List<ChooserTarget> serviceTargets = createDirectShareTargets(2,
resolvedComponentInfos.get(0).getResolveInfoAt(0).activityInfo.packageName);
- ResolveInfo ri = ResolverDataProvider.createResolveInfo(3, 0);
+ ResolveInfo ri = ResolverDataProvider.createResolveInfo(3, 0,
+ PERSONAL_USER_HANDLE);
// Start activity
final ChooserActivity activity =
@@ -1791,7 +1804,8 @@ public class ChooserActivityTest {
// Create direct share target
List<ChooserTarget> serviceTargets = createDirectShareTargets(1,
resolvedComponentInfos.get(14).getResolveInfoAt(0).activityInfo.packageName);
- ResolveInfo ri = ResolverDataProvider.createResolveInfo(16, 0);
+ ResolveInfo ri = ResolverDataProvider.createResolveInfo(16, 0,
+ PERSONAL_USER_HANDLE);
// Start activity
final IChooserWrapper activity = (IChooserWrapper)
@@ -2187,7 +2201,8 @@ public class ChooserActivityTest {
// Create direct share target
List<ChooserTarget> serviceTargets = createDirectShareTargets(1,
resolvedComponentInfos.get(0).getResolveInfoAt(0).activityInfo.packageName);
- ResolveInfo ri = ResolverDataProvider.createResolveInfo(3, 0);
+ ResolveInfo ri = ResolverDataProvider.createResolveInfo(3, 0,
+ PERSONAL_USER_HANDLE);
ChooserActivityOverrideData
.getInstance()
@@ -2721,7 +2736,7 @@ public class ChooserActivityTest {
new Intent[] {new Intent("action.fake")});
ChooserActivityOverrideData.getInstance().packageManager = mock(PackageManager.class);
ResolveInfo ri = ResolverDataProvider.createResolveInfo(0,
- UserHandle.USER_CURRENT);
+ UserHandle.USER_CURRENT, PERSONAL_USER_HANDLE);
when(
ChooserActivityOverrideData
.getInstance()
@@ -2886,6 +2901,53 @@ public class ChooserActivityTest {
assertEquals(3, wrapper.getWorkListAdapter().getCount());
}
+ @Test
+ public void testClonedProfilePresent_personalAdapterIsSetWithPersonalProfile() {
+ // enable cloneProfile
+ markCloneProfileUserAvailable();
+ List<ResolvedComponentInfo> resolvedComponentInfos =
+ createResolvedComponentsWithCloneProfileForTest(
+ 3,
+ PERSONAL_USER_HANDLE,
+ ChooserActivityOverrideData.getInstance().cloneProfileUserHandle);
+ when(ChooserActivityOverrideData.getInstance().resolverListController.getResolversForIntent(
+ Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class)))
+ .thenReturn(new ArrayList<>(resolvedComponentInfos));
+ Intent sendIntent = createSendTextIntent();
+
+ final IChooserWrapper activity = (IChooserWrapper) mActivityRule
+ .launchActivity(Intent.createChooser(sendIntent, "personalProfileTest"));
+ waitForIdle();
+
+ assertThat(activity.getPersonalListAdapter().getUserHandle(), is(PERSONAL_USER_HANDLE));
+ assertThat(activity.getAdapter().getCount(), is(3));
+ }
+
+ @Test
+ public void testClonedProfilePresent_personalTabUsesExpectedAdapter() {
+ // enable the work tab feature flag
+ ResolverActivity.ENABLE_TABBED_VIEW = true;
+ markWorkProfileUserAvailable();
+ markCloneProfileUserAvailable();
+ List<ResolvedComponentInfo> personalResolvedComponentInfos =
+ createResolvedComponentsForTest(3);
+ List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(
+ 4);
+ setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+ Intent sendIntent = createSendTextIntent();
+ sendIntent.setType(TEST_MIME_TYPE);
+
+
+ final IChooserWrapper activity = (IChooserWrapper)
+ mActivityRule.launchActivity(Intent.createChooser(sendIntent, "multi tab test"));
+ waitForIdle();
+
+ assertThat(activity.getCurrentUserHandle(), is(PERSONAL_USER_HANDLE));
+ }
+
private Intent createChooserIntent(Intent intent, Intent[] initialIntents) {
Intent chooserIntent = new Intent();
chooserIntent.setAction(Intent.ACTION_CHOOSER);
@@ -2906,6 +2968,7 @@ public class ChooserActivityTest {
ri.activityInfo.packageName = "fake.package.name";
ri.activityInfo.applicationInfo = new ApplicationInfo();
ri.activityInfo.applicationInfo.packageName = "fake.package.name";
+ ri.userHandle = UserHandle.CURRENT;
return ri;
}
@@ -2967,7 +3030,23 @@ public class ChooserActivityTest {
private List<ResolvedComponentInfo> createResolvedComponentsForTest(int numberOfResults) {
List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
for (int i = 0; i < numberOfResults; i++) {
- infoList.add(ResolverDataProvider.createResolvedComponentInfo(i));
+ infoList.add(ResolverDataProvider.createResolvedComponentInfo(i, PERSONAL_USER_HANDLE));
+ }
+ return infoList;
+ }
+
+ private List<ResolvedComponentInfo> createResolvedComponentsWithCloneProfileForTest(
+ int numberOfResults,
+ UserHandle resolvedForPersonalUser,
+ UserHandle resolvedForClonedUser) {
+ List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
+ for (int i = 0; i < 1; i++) {
+ infoList.add(ResolverDataProvider.createResolvedComponentInfo(i,
+ resolvedForPersonalUser));
+ }
+ for (int i = 1; i < numberOfResults; i++) {
+ infoList.add(ResolverDataProvider.createResolvedComponentInfo(i,
+ resolvedForClonedUser));
}
return infoList;
}
@@ -2977,9 +3056,11 @@ public class ChooserActivityTest {
List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
for (int i = 0; i < numberOfResults; i++) {
if (i == 0) {
- infoList.add(ResolverDataProvider.createResolvedComponentInfoWithOtherId(i));
+ infoList.add(ResolverDataProvider.createResolvedComponentInfoWithOtherId(i,
+ PERSONAL_USER_HANDLE));
} else {
- infoList.add(ResolverDataProvider.createResolvedComponentInfo(i));
+ infoList.add(ResolverDataProvider.createResolvedComponentInfo(i,
+ PERSONAL_USER_HANDLE));
}
}
return infoList;
@@ -2991,9 +3072,11 @@ public class ChooserActivityTest {
for (int i = 0; i < numberOfResults; i++) {
if (i == 0) {
infoList.add(
- ResolverDataProvider.createResolvedComponentInfoWithOtherId(i, userId));
+ ResolverDataProvider.createResolvedComponentInfoWithOtherId(i, userId,
+ PERSONAL_USER_HANDLE));
} else {
- infoList.add(ResolverDataProvider.createResolvedComponentInfo(i));
+ infoList.add(ResolverDataProvider.createResolvedComponentInfo(i,
+ PERSONAL_USER_HANDLE));
}
}
return infoList;
@@ -3003,7 +3086,8 @@ public class ChooserActivityTest {
int numberOfResults, int userId) {
List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
for (int i = 0; i < numberOfResults; i++) {
- infoList.add(ResolverDataProvider.createResolvedComponentInfoWithOtherId(i, userId));
+ infoList.add(ResolverDataProvider.createResolvedComponentInfoWithOtherId(i, userId,
+ PERSONAL_USER_HANDLE));
}
return infoList;
}
@@ -3097,6 +3181,10 @@ public class ChooserActivityTest {
ChooserActivityOverrideData.getInstance().workProfileUserHandle = UserHandle.of(10);
}
+ private void markCloneProfileUserAvailable() {
+ ChooserActivityOverrideData.getInstance().cloneProfileUserHandle = UserHandle.of(11);
+ }
+
private void setupResolverControllers(
List<ResolvedComponentInfo> personalResolvedComponentInfos,
List<ResolvedComponentInfo> workResolvedComponentInfos) {
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityWorkProfileTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityWorkProfileTest.java
index b6ea9dd3998d..db69cf2397fc 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityWorkProfileTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityWorkProfileTest.java
@@ -49,8 +49,8 @@ import androidx.test.espresso.NoMatchingViewException;
import androidx.test.rule.ActivityTestRule;
import com.android.internal.R;
-import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
import com.android.internal.app.ChooserActivityWorkProfileTest.TestCase.Tab;
+import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
import junit.framework.AssertionFailedError;
@@ -93,7 +93,7 @@ public class ChooserActivityWorkProfileTest {
public void testBlocker() {
setUpPersonalAndWorkComponentInfos();
sOverrides.hasCrossProfileIntents = mTestCase.hasCrossProfileIntents();
- sOverrides.myUserId = mTestCase.getMyUserHandle().getIdentifier();
+ sOverrides.tabOwnerUserHandleForLaunch = mTestCase.getMyUserHandle();
launchActivity(mTestCase.getIsSendAction());
switchToTab(mTestCase.getTab());
@@ -238,19 +238,21 @@ public class ChooserActivityWorkProfileTest {
}
private List<ResolvedComponentInfo> createResolvedComponentsForTestWithOtherProfile(
- int numberOfResults, int userId) {
+ int numberOfResults, int userId, UserHandle resolvedForUser) {
List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
for (int i = 0; i < numberOfResults; i++) {
infoList.add(
- ResolverDataProvider.createResolvedComponentInfoWithOtherId(i, userId));
+ ResolverDataProvider
+ .createResolvedComponentInfoWithOtherId(i, userId, resolvedForUser));
}
return infoList;
}
- private List<ResolvedComponentInfo> createResolvedComponentsForTest(int numberOfResults) {
+ private List<ResolvedComponentInfo> createResolvedComponentsForTest(int numberOfResults,
+ UserHandle resolvedForUser) {
List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
for (int i = 0; i < numberOfResults; i++) {
- infoList.add(ResolverDataProvider.createResolvedComponentInfo(i));
+ infoList.add(ResolverDataProvider.createResolvedComponentInfo(i, resolvedForUser));
}
return infoList;
}
@@ -262,9 +264,9 @@ public class ChooserActivityWorkProfileTest {
int workProfileTargets = 4;
List<ResolvedComponentInfo> personalResolvedComponentInfos =
createResolvedComponentsForTestWithOtherProfile(3,
- /* userId */ WORK_USER_HANDLE.getIdentifier());
+ /* userId */ WORK_USER_HANDLE.getIdentifier(), PERSONAL_USER_HANDLE);
List<ResolvedComponentInfo> workResolvedComponentInfos =
- createResolvedComponentsForTest(workProfileTargets);
+ createResolvedComponentsForTest(workProfileTargets, WORK_USER_HANDLE);
setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
}
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
index 5dc0c8b24218..d7a8b3a5c1d9 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
@@ -16,10 +16,6 @@
package com.android.internal.app;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import android.annotation.Nullable;
@@ -34,12 +30,12 @@ import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.net.Uri;
+import android.os.Bundle;
import android.os.UserHandle;
import android.util.Pair;
import android.util.Size;
import com.android.internal.app.AbstractMultiProfilePagerAdapter.CrossProfileIntentsChecker;
-import com.android.internal.app.AbstractMultiProfilePagerAdapter.MyUserIdProvider;
import com.android.internal.app.ResolverListAdapter.ResolveInfoPresentationGetter;
import com.android.internal.app.chooser.DisplayResolveInfo;
import com.android.internal.app.chooser.TargetInfo;
@@ -132,14 +128,6 @@ public class ChooserWrapperActivity extends ChooserActivity implements IChooserW
}
@Override
- protected MyUserIdProvider createMyUserIdProvider() {
- if (sOverrides.mMyUserIdProvider != null) {
- return sOverrides.mMyUserIdProvider;
- }
- return super.createMyUserIdProvider();
- }
-
- @Override
protected CrossProfileIntentsChecker createCrossProfileIntentsChecker() {
if (sOverrides.mCrossProfileIntentsChecker != null) {
return sOverrides.mCrossProfileIntentsChecker;
@@ -155,13 +143,15 @@ public class ChooserWrapperActivity extends ChooserActivity implements IChooserW
return super.createQuietModeManager();
}
+ // TODO: Remove this and override safelyStartActivityInternal() instead.
@Override
- public void safelyStartActivity(com.android.internal.app.chooser.TargetInfo cti) {
+ public void safelyStartActivityAsUser(TargetInfo cti, UserHandle user,
+ @Nullable Bundle options) {
if (sOverrides.onSafelyStartCallback != null &&
sOverrides.onSafelyStartCallback.apply(cti)) {
return;
}
- super.safelyStartActivity(cti);
+ super.safelyStartActivityAsUser(cti, user, options);
}
@Override
@@ -253,6 +243,14 @@ public class ChooserWrapperActivity extends ChooserActivity implements IChooserW
}
@Override
+ protected UserHandle getTabOwnerUserHandleForLaunch() {
+ if (sOverrides.tabOwnerUserHandleForLaunch == null) {
+ return super.getTabOwnerUserHandleForLaunch();
+ }
+ return sOverrides.tabOwnerUserHandleForLaunch;
+ }
+
+ @Override
public Context createContextAsUser(UserHandle user, int flags) {
// return the current context as a work profile doesn't really exist in these tests
return getApplicationContext();
diff --git a/core/tests/coretests/src/com/android/internal/app/FakeResolverComparatorModel.java b/core/tests/coretests/src/com/android/internal/app/FakeResolverComparatorModel.java
index fbbe57c8e325..573135ffa5a1 100644
--- a/core/tests/coretests/src/com/android/internal/app/FakeResolverComparatorModel.java
+++ b/core/tests/coretests/src/com/android/internal/app/FakeResolverComparatorModel.java
@@ -19,6 +19,8 @@ package com.android.internal.app;
import android.content.ComponentName;
import android.content.pm.ResolveInfo;
+import com.android.internal.app.chooser.TargetInfo;
+
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
@@ -45,14 +47,15 @@ public class FakeResolverComparatorModel implements ResolverComparatorModel {
}
@Override
- public float getScore(ComponentName name) {
+ public float getScore(TargetInfo targetInfo) {
return 0.0f; // Models are not required to provide numerical scores.
}
@Override
- public void notifyOnTargetSelected(ComponentName componentName) {
+ public void notifyOnTargetSelected(TargetInfo targetInfo) {
System.out.println(
- "User selected " + componentName + " under model " + System.identityHashCode(this));
+ "User selected " + targetInfo.getResolvedComponentName() + " under model "
+ + System.identityHashCode(this));
}
private FakeResolverComparatorModel(Comparator<ResolveInfo> comparator) {
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
index 92c05b0fe9fc..b82bc163bb40 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
@@ -79,6 +79,9 @@ import java.util.List;
*/
@RunWith(AndroidJUnit4.class)
public class ResolverActivityTest {
+
+ private static final UserHandle PERSONAL_USER_HANDLE = InstrumentationRegistry
+ .getInstrumentation().getTargetContext().getUser();
@Rule
public ActivityTestRule<ResolverWrapperActivity> mActivityRule =
new ActivityTestRule<>(ResolverWrapperActivity.class, false,
@@ -92,7 +95,8 @@ public class ResolverActivityTest {
@Test
public void twoOptionsAndUserSelectsOne() throws InterruptedException {
Intent sendIntent = createSendImageIntent();
- List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+ List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2,
+ PERSONAL_USER_HANDLE);
when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
Mockito.anyBoolean(),
@@ -124,7 +128,8 @@ public class ResolverActivityTest {
@Test
public void setMaxHeight() throws Exception {
Intent sendIntent = createSendImageIntent();
- List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+ List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2,
+ PERSONAL_USER_HANDLE);
when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
Mockito.anyBoolean(),
@@ -169,7 +174,8 @@ public class ResolverActivityTest {
@Test
public void setShowAtTopToTrue() throws Exception {
Intent sendIntent = createSendImageIntent();
- List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+ List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2,
+ PERSONAL_USER_HANDLE);
when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
Mockito.anyBoolean(),
@@ -201,7 +207,8 @@ public class ResolverActivityTest {
@Test
public void hasLastChosenActivity() throws Exception {
Intent sendIntent = createSendImageIntent();
- List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+ List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2,
+ PERSONAL_USER_HANDLE);
ResolveInfo toChoose = resolvedComponentInfos.get(0).getResolveInfoAt(0);
when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
@@ -234,10 +241,12 @@ public class ResolverActivityTest {
// enable the work tab feature flag
ResolverActivity.ENABLE_TABBED_VIEW = true;
List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(2, /* userId */ 10);
- List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
- setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+ createResolvedComponentsForTestWithOtherProfile(2, /* userId */ 10,
+ PERSONAL_USER_HANDLE);
markWorkProfileUserAvailable();
+ List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
+ sOverrides.workProfileUserHandle);
+ setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
ResolveInfo toChoose = personalResolvedComponentInfos.get(1).getResolveInfoAt(0);
Intent sendIntent = createSendImageIntent();
@@ -255,7 +264,8 @@ public class ResolverActivityTest {
};
// Make a stable copy of the components as the original list may be modified
List<ResolvedComponentInfo> stableCopy =
- createResolvedComponentsForTestWithOtherProfile(2, /* userId= */ 10);
+ createResolvedComponentsForTestWithOtherProfile(2, /* userId= */ 10,
+ PERSONAL_USER_HANDLE);
// We pick the first one as there is another one in the work profile side
onView(first(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name)))
.perform(click());
@@ -272,7 +282,7 @@ public class ResolverActivityTest {
Intent sendIntent = createSendImageIntent();
List<ResolvedComponentInfo> resolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(3);
+ createResolvedComponentsForTestWithOtherProfile(3, PERSONAL_USER_HANDLE);
ResolveInfo toChoose = resolvedComponentInfos.get(1).getResolveInfoAt(0);
when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
@@ -298,7 +308,7 @@ public class ResolverActivityTest {
// Make a stable copy of the components as the original list may be modified
List<ResolvedComponentInfo> stableCopy =
- createResolvedComponentsForTestWithOtherProfile(2);
+ createResolvedComponentsForTestWithOtherProfile(2, PERSONAL_USER_HANDLE);
onView(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name))
.perform(click());
@@ -317,7 +327,7 @@ public class ResolverActivityTest {
// chosen activity.
Intent sendIntent = createSendImageIntent();
List<ResolvedComponentInfo> resolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(3);
+ createResolvedComponentsForTestWithOtherProfile(3, PERSONAL_USER_HANDLE);
ResolveInfo toChoose = resolvedComponentInfos.get(1).getResolveInfoAt(0);
when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
@@ -345,7 +355,7 @@ public class ResolverActivityTest {
// Make a stable copy of the components as the original list may be modified
List<ResolvedComponentInfo> stableCopy =
- createResolvedComponentsForTestWithOtherProfile(2);
+ createResolvedComponentsForTestWithOtherProfile(2, PERSONAL_USER_HANDLE);
onView(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name))
.perform(click());
@@ -428,12 +438,14 @@ public class ResolverActivityTest {
// enable the work tab feature flag
ResolverActivity.ENABLE_TABBED_VIEW = true;
List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(3, /* userId = */ 10);
- List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
+ createResolvedComponentsForTestWithOtherProfile(3, /* userId = */ 10,
+ PERSONAL_USER_HANDLE);
+ markWorkProfileUserAvailable();
+ List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
+ sOverrides.workProfileUserHandle);
setupResolverControllers(personalResolvedComponentInfos,
new ArrayList<>(workResolvedComponentInfos));
Intent sendIntent = createSendImageIntent();
- markWorkProfileUserAvailable();
final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
waitForIdle();
@@ -448,11 +460,13 @@ public class ResolverActivityTest {
// enable the work tab feature flag
ResolverActivity.ENABLE_TABBED_VIEW = true;
List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10);
- List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
+ createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10,
+ PERSONAL_USER_HANDLE);
+ markWorkProfileUserAvailable();
+ List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
+ sOverrides.workProfileUserHandle);
setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
Intent sendIntent = createSendImageIntent();
- markWorkProfileUserAvailable();
final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
waitForIdle();
@@ -467,11 +481,12 @@ public class ResolverActivityTest {
// enable the work tab feature flag
ResolverActivity.ENABLE_TABBED_VIEW = true;
List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(3);
- List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
+ createResolvedComponentsForTestWithOtherProfile(3, PERSONAL_USER_HANDLE);
+ markWorkProfileUserAvailable();
+ List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
+ sOverrides.workProfileUserHandle);
setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
Intent sendIntent = createSendImageIntent();
- markWorkProfileUserAvailable();
final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
waitForIdle();
@@ -487,8 +502,10 @@ public class ResolverActivityTest {
ResolverActivity.ENABLE_TABBED_VIEW = true;
markWorkProfileUserAvailable();
List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10);
- List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
+ createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10,
+ PERSONAL_USER_HANDLE);
+ List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
+ sOverrides.workProfileUserHandle);
setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
Intent sendIntent = createSendImageIntent();
@@ -507,8 +524,10 @@ public class ResolverActivityTest {
ResolverActivity.ENABLE_TABBED_VIEW = true;
markWorkProfileUserAvailable();
List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10);
- List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
+ createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10,
+ PERSONAL_USER_HANDLE);
+ List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
+ sOverrides.workProfileUserHandle);
setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
Intent sendIntent = createSendImageIntent();
ResolveInfo[] chosen = new ResolveInfo[1];
@@ -539,8 +558,9 @@ public class ResolverActivityTest {
ResolverActivity.ENABLE_TABBED_VIEW = true;
markWorkProfileUserAvailable();
List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(1);
- List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
+ createResolvedComponentsForTestWithOtherProfile(1, PERSONAL_USER_HANDLE);
+ List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
+ sOverrides.workProfileUserHandle);
setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
Intent sendIntent = createSendImageIntent();
@@ -559,8 +579,9 @@ public class ResolverActivityTest {
ResolverActivity.ENABLE_TABBED_VIEW = true;
markWorkProfileUserAvailable();
List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(1);
- List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
+ createResolvedComponentsForTestWithOtherProfile(1, PERSONAL_USER_HANDLE);
+ List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
+ sOverrides.workProfileUserHandle);
setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
Intent sendIntent = createOpenWebsiteIntent();
@@ -578,8 +599,9 @@ public class ResolverActivityTest {
ResolverActivity.ENABLE_TABBED_VIEW = true;
markWorkProfileUserAvailable();
List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(1);
- List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
+ createResolvedComponentsForTestWithOtherProfile(1, PERSONAL_USER_HANDLE);
+ List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
+ sOverrides.workProfileUserHandle);
setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
Intent sendIntent = createOpenWebsiteIntent();
@@ -605,8 +627,10 @@ public class ResolverActivityTest {
ResolverActivity.ENABLE_TABBED_VIEW = true;
markWorkProfileUserAvailable();
List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(3, /* userId= */ 10);
- List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
+ createResolvedComponentsForTestWithOtherProfile(3, /* userId= */ 10,
+ PERSONAL_USER_HANDLE);
+ List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
+ sOverrides.workProfileUserHandle);
setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
Intent sendIntent = createSendImageIntent();
ResolveInfo[] chosen = new ResolveInfo[1];
@@ -639,9 +663,11 @@ public class ResolverActivityTest {
markWorkProfileUserAvailable();
int workProfileTargets = 4;
List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10);
+ createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10,
+ PERSONAL_USER_HANDLE);
List<ResolvedComponentInfo> workResolvedComponentInfos =
- createResolvedComponentsForTest(workProfileTargets);
+ createResolvedComponentsForTest(workProfileTargets,
+ sOverrides.workProfileUserHandle);
sOverrides.hasCrossProfileIntents = false;
setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
Intent sendIntent = createSendImageIntent();
@@ -665,9 +691,11 @@ public class ResolverActivityTest {
markWorkProfileUserAvailable();
int workProfileTargets = 4;
List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10);
+ createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10,
+ PERSONAL_USER_HANDLE);
List<ResolvedComponentInfo> workResolvedComponentInfos =
- createResolvedComponentsForTest(workProfileTargets);
+ createResolvedComponentsForTest(workProfileTargets,
+ sOverrides.workProfileUserHandle);
sOverrides.isQuietModeEnabled = true;
setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
Intent sendIntent = createSendImageIntent();
@@ -690,9 +718,9 @@ public class ResolverActivityTest {
ResolverActivity.ENABLE_TABBED_VIEW = true;
markWorkProfileUserAvailable();
List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTest(3);
+ createResolvedComponentsForTest(3, PERSONAL_USER_HANDLE);
List<ResolvedComponentInfo> workResolvedComponentInfos =
- createResolvedComponentsForTest(0);
+ createResolvedComponentsForTest(0, sOverrides.workProfileUserHandle);
setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
Intent sendIntent = createSendImageIntent();
sendIntent.setType("TestType");
@@ -714,9 +742,9 @@ public class ResolverActivityTest {
ResolverActivity.ENABLE_TABBED_VIEW = true;
markWorkProfileUserAvailable();
List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTest(3);
+ createResolvedComponentsForTest(3, PERSONAL_USER_HANDLE);
List<ResolvedComponentInfo> workResolvedComponentInfos =
- createResolvedComponentsForTest(0);
+ createResolvedComponentsForTest(0, sOverrides.workProfileUserHandle);
setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
Intent sendIntent = createSendImageIntent();
sendIntent.setType("TestType");
@@ -739,9 +767,9 @@ public class ResolverActivityTest {
ResolverActivity.ENABLE_TABBED_VIEW = true;
markWorkProfileUserAvailable();
List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTest(1);
+ createResolvedComponentsForTest(1, PERSONAL_USER_HANDLE);
List<ResolvedComponentInfo> workResolvedComponentInfos =
- createResolvedComponentsForTest(1);
+ createResolvedComponentsForTest(1, sOverrides.workProfileUserHandle);
// Personal profile only has a browser
personalResolvedComponentInfos.get(0).getResolveInfoAt(0).handleAllWebDataURI = true;
setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
@@ -758,9 +786,9 @@ public class ResolverActivityTest {
ResolverActivity.ENABLE_TABBED_VIEW = true;
markWorkProfileUserAvailable();
List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTest(0);
+ createResolvedComponentsForTest(0, PERSONAL_USER_HANDLE);
List<ResolvedComponentInfo> workResolvedComponentInfos =
- createResolvedComponentsForTest(1);
+ createResolvedComponentsForTest(1, sOverrides.workProfileUserHandle);
setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
Intent sendIntent = createSendImageIntent();
sendIntent.setType("TestType");
@@ -787,9 +815,9 @@ public class ResolverActivityTest {
ResolverActivity.ENABLE_TABBED_VIEW = true;
markWorkProfileUserAvailable();
List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTest(3);
+ createResolvedComponentsForTest(3, PERSONAL_USER_HANDLE);
List<ResolvedComponentInfo> workResolvedComponentInfos =
- createResolvedComponentsForTest(0);
+ createResolvedComponentsForTest(0, sOverrides.workProfileUserHandle);
setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
Intent sendIntent = createSendImageIntent();
sendIntent.setType("TestType");
@@ -810,7 +838,8 @@ public class ResolverActivityTest {
public void testAutolaunch_singleTarget_withWorkProfileAndTabbedViewOff_noAutolaunch() {
ResolverActivity.ENABLE_TABBED_VIEW = false;
List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(2, /* userId */ 10);
+ createResolvedComponentsForTestWithOtherProfile(2, /* userId */ 10,
+ PERSONAL_USER_HANDLE);
when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.anyBoolean(),
@@ -835,7 +864,7 @@ public class ResolverActivityTest {
public void testAutolaunch_singleTarget_noWorkProfile_autolaunch() {
ResolverActivity.ENABLE_TABBED_VIEW = false;
List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTest(1);
+ createResolvedComponentsForTest(1, PERSONAL_USER_HANDLE);
when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.anyBoolean(),
@@ -863,9 +892,11 @@ public class ResolverActivityTest {
markWorkProfileUserAvailable();
int workProfileTargets = 4;
List<ResolvedComponentInfo> personalResolvedComponentInfos =
- createResolvedComponentsForTestWithOtherProfile(2, /* userId */ 10);
+ createResolvedComponentsForTestWithOtherProfile(2, /* userId */ 10,
+ PERSONAL_USER_HANDLE);
List<ResolvedComponentInfo> workResolvedComponentInfos =
- createResolvedComponentsForTest(workProfileTargets);
+ createResolvedComponentsForTest(workProfileTargets,
+ sOverrides.workProfileUserHandle);
sOverrides.hasCrossProfileIntents = false;
setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
Intent sendIntent = createSendImageIntent();
@@ -892,7 +923,7 @@ public class ResolverActivityTest {
// chosen activity.
Intent sendIntent = createSendImageIntent();
List<ResolvedComponentInfo> resolvedComponentInfos =
- createResolvedComponentsForTest(2);
+ createResolvedComponentsForTest(2, PERSONAL_USER_HANDLE);
when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
Mockito.anyBoolean(),
@@ -911,6 +942,193 @@ public class ResolverActivityTest {
assertThat(activity.getAdapter().getPlaceholderCount(), is(2));
}
+ @Test
+ public void testClonedProfilePresent_personalAdapterIsSetWithPersonalProfile() {
+ // enable cloneProfile
+ markCloneProfileUserAvailable();
+ List<ResolvedComponentInfo> resolvedComponentInfos =
+ createResolvedComponentsWithCloneProfileForTest(
+ 3,
+ PERSONAL_USER_HANDLE,
+ sOverrides.cloneProfileUserHandle);
+ when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
+ Intent sendIntent = createSendImageIntent();
+
+ final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+ waitForIdle();
+
+ assertThat(activity.getCurrentUserHandle(), is(activity.getPersonalProfileUserHandle()));
+ assertThat(activity.getAdapter().getCount(), is(3));
+ }
+
+ @Test
+ public void testClonedProfilePresent_personalTabUsesExpectedAdapter() {
+ // enable the work tab feature flag
+ ResolverActivity.ENABLE_TABBED_VIEW = true;
+ markWorkProfileUserAvailable();
+ // enable cloneProfile
+ markCloneProfileUserAvailable();
+ List<ResolvedComponentInfo> personalResolvedComponentInfos =
+ createResolvedComponentsWithCloneProfileForTest(
+ 3,
+ PERSONAL_USER_HANDLE,
+ sOverrides.cloneProfileUserHandle);
+ List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4,
+ sOverrides.workProfileUserHandle);
+ setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+ Intent sendIntent = createSendImageIntent();
+
+ final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+ waitForIdle();
+
+ assertThat(activity.getCurrentUserHandle(), is(activity.getPersonalProfileUserHandle()));
+ assertThat(activity.getAdapter().getCount(), is(3));
+ }
+
+ @Test
+ public void testClonedProfilePresent_layoutWithDefault_neverShown() throws Exception {
+ // enable cloneProfile
+ markCloneProfileUserAvailable();
+ Intent sendIntent = createSendImageIntent();
+ List<ResolvedComponentInfo> resolvedComponentInfos =
+ createResolvedComponentsWithCloneProfileForTest(
+ 2,
+ PERSONAL_USER_HANDLE,
+ sOverrides.cloneProfileUserHandle);
+
+ when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
+ when(sOverrides.resolverListController.getLastChosen())
+ .thenReturn(resolvedComponentInfos.get(0).getResolveInfoAt(0));
+
+ final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+ Espresso.registerIdlingResources(activity.getAdapter().getLabelIdlingResource());
+ waitForIdle();
+
+ assertThat(activity.getAdapter().hasFilteredItem(), is(false));
+ assertThat(activity.getAdapter().getCount(), is(2));
+ assertThat(activity.getAdapter().getPlaceholderCount(), is(2));
+ }
+
+ @Test
+ public void testClonedProfilePresent_alwaysButtonDisabled() throws Exception {
+ // enable cloneProfile
+ markCloneProfileUserAvailable();
+ Intent sendIntent = createSendImageIntent();
+ List<ResolvedComponentInfo> resolvedComponentInfos =
+ createResolvedComponentsWithCloneProfileForTest(
+ 3,
+ PERSONAL_USER_HANDLE,
+ sOverrides.cloneProfileUserHandle);
+
+ when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
+ when(sOverrides.resolverListController.getLastChosen())
+ .thenReturn(resolvedComponentInfos.get(0).getResolveInfoAt(0));
+
+ final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+ waitForIdle();
+
+ // Confirm that the button bar is disabled by default
+ onView(withId(R.id.button_once)).check(matches(not(isEnabled())));
+ onView(withId(R.id.button_always)).check(matches(not(isEnabled())));
+
+ // Make a stable copy of the components as the original list may be modified
+ List<ResolvedComponentInfo> stableCopy =
+ createResolvedComponentsForTestWithOtherProfile(2, PERSONAL_USER_HANDLE);
+
+ onView(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name))
+ .perform(click());
+
+ onView(withId(R.id.button_once)).check(matches(isEnabled()));
+ onView(withId(R.id.button_always)).check(matches(not(isEnabled())));
+ }
+
+ @Test
+ public void testClonedProfilePresent_personalProfileActivityIsStartedInCorrectUser()
+ throws Exception {
+ // enable the work tab feature flag
+ ResolverActivity.ENABLE_TABBED_VIEW = true;
+ markWorkProfileUserAvailable();
+ // enable cloneProfile
+ markCloneProfileUserAvailable();
+
+ List<ResolvedComponentInfo> personalResolvedComponentInfos =
+ createResolvedComponentsWithCloneProfileForTest(
+ 3,
+ PERSONAL_USER_HANDLE,
+ sOverrides.cloneProfileUserHandle);
+ List<ResolvedComponentInfo> workResolvedComponentInfos =
+ createResolvedComponentsForTest(3, sOverrides.workProfileUserHandle);
+ sOverrides.hasCrossProfileIntents = false;
+ setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+ Intent sendIntent = createSendImageIntent();
+ sendIntent.setType("TestType");
+ final UserHandle[] selectedActivityUserHandle = new UserHandle[1];
+ sOverrides.onSafelyStartInternalCallback = userHandle -> {
+ selectedActivityUserHandle[0] = userHandle;
+ return true;
+ };
+
+ final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+ waitForIdle();
+ onView(first(allOf(withText(personalResolvedComponentInfos.get(0)
+ .getResolveInfoAt(0).activityInfo.applicationInfo.name), isCompletelyDisplayed())))
+ .perform(click());
+ onView(withId(R.id.button_once))
+ .perform(click());
+ waitForIdle();
+
+ assertThat(selectedActivityUserHandle[0], is(activity.getAdapter().getUserHandle()));
+ }
+
+ @Test
+ public void testClonedProfilePresent_workProfileActivityIsStartedInCorrectUser()
+ throws Exception {
+ // enable the work tab feature flag
+ ResolverActivity.ENABLE_TABBED_VIEW = true;
+ markWorkProfileUserAvailable();
+ // enable cloneProfile
+ markCloneProfileUserAvailable();
+
+ List<ResolvedComponentInfo> personalResolvedComponentInfos =
+ createResolvedComponentsWithCloneProfileForTest(
+ 3,
+ PERSONAL_USER_HANDLE,
+ sOverrides.cloneProfileUserHandle);
+ List<ResolvedComponentInfo> workResolvedComponentInfos =
+ createResolvedComponentsForTest(3, sOverrides.workProfileUserHandle);
+ setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+ Intent sendIntent = createSendImageIntent();
+ sendIntent.setType("TestType");
+ final UserHandle[] selectedActivityUserHandle = new UserHandle[1];
+ sOverrides.onSafelyStartInternalCallback = userHandle -> {
+ selectedActivityUserHandle[0] = userHandle;
+ return true;
+ };
+
+ final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+ waitForIdle();
+ onView(withText(R.string.resolver_work_tab))
+ .perform(click());
+ waitForIdle();
+ onView(first(allOf(withText(workResolvedComponentInfos.get(0)
+ .getResolveInfoAt(0).activityInfo.applicationInfo.name), isCompletelyDisplayed())))
+ .perform(click());
+ onView(withId(R.id.button_once))
+ .perform(click());
+ waitForIdle();
+
+ assertThat(selectedActivityUserHandle[0], is(activity.getAdapter().getUserHandle()));
+ }
+
private Intent createSendImageIntent() {
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
@@ -926,36 +1144,55 @@ public class ResolverActivityTest {
return sendIntent;
}
- private List<ResolvedComponentInfo> createResolvedComponentsForTest(int numberOfResults) {
+ private List<ResolvedComponentInfo> createResolvedComponentsForTest(int numberOfResults,
+ UserHandle resolvedForUser) {
List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
for (int i = 0; i < numberOfResults; i++) {
- infoList.add(ResolverDataProvider.createResolvedComponentInfo(i));
+ infoList.add(ResolverDataProvider.createResolvedComponentInfo(i, resolvedForUser));
+ }
+ return infoList;
+ }
+
+ private List<ResolvedComponentInfo> createResolvedComponentsWithCloneProfileForTest(
+ int numberOfResults,
+ UserHandle resolvedForPersonalUser,
+ UserHandle resolvedForClonedUser) {
+ List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
+ for (int i = 0; i < 1; i++) {
+ infoList.add(ResolverDataProvider.createResolvedComponentInfo(i,
+ resolvedForPersonalUser));
+ }
+ for (int i = 1; i < numberOfResults; i++) {
+ infoList.add(ResolverDataProvider.createResolvedComponentInfo(i,
+ resolvedForClonedUser));
}
return infoList;
}
private List<ResolvedComponentInfo> createResolvedComponentsForTestWithOtherProfile(
- int numberOfResults) {
+ int numberOfResults, UserHandle resolvedForUser) {
List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
for (int i = 0; i < numberOfResults; i++) {
if (i == 0) {
- infoList.add(ResolverDataProvider.createResolvedComponentInfoWithOtherId(i));
+ infoList.add(ResolverDataProvider.createResolvedComponentInfoWithOtherId(i,
+ resolvedForUser));
} else {
- infoList.add(ResolverDataProvider.createResolvedComponentInfo(i));
+ infoList.add(ResolverDataProvider.createResolvedComponentInfo(i, resolvedForUser));
}
}
return infoList;
}
private List<ResolvedComponentInfo> createResolvedComponentsForTestWithOtherProfile(
- int numberOfResults, int userId) {
+ int numberOfResults, int userId, UserHandle resolvedForUser) {
List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
for (int i = 0; i < numberOfResults; i++) {
if (i == 0) {
infoList.add(
- ResolverDataProvider.createResolvedComponentInfoWithOtherId(i, userId));
+ ResolverDataProvider.createResolvedComponentInfoWithOtherId(i, userId,
+ resolvedForUser));
} else {
- infoList.add(ResolverDataProvider.createResolvedComponentInfo(i));
+ infoList.add(ResolverDataProvider.createResolvedComponentInfo(i, resolvedForUser));
}
}
return infoList;
@@ -969,6 +1206,10 @@ public class ResolverActivityTest {
ResolverWrapperActivity.sOverrides.workProfileUserHandle = UserHandle.of(10);
}
+ private void markCloneProfileUserAvailable() {
+ ResolverWrapperActivity.sOverrides.cloneProfileUserHandle = UserHandle.of(11);
+ }
+
private void setupResolverControllers(
List<ResolvedComponentInfo> personalResolvedComponentInfos,
List<ResolvedComponentInfo> workResolvedComponentInfos) {
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverActivityWorkProfileTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverActivityWorkProfileTest.java
index 4a79f5ee3f88..9d16d85b30e6 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverActivityWorkProfileTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverActivityWorkProfileTest.java
@@ -92,7 +92,7 @@ public class ResolverActivityWorkProfileTest {
public void testBlocker() {
setUpPersonalAndWorkComponentInfos();
sOverrides.hasCrossProfileIntents = mTestCase.hasCrossProfileIntents();
- sOverrides.myUserId = mTestCase.getMyUserHandle().getIdentifier();
+ sOverrides.tabOwnerUserHandleForLaunch = mTestCase.getMyUserHandle();
launchActivity(/* callingUser= */ mTestCase.getExtraCallingUser());
switchToTab(mTestCase.getTab());
@@ -230,19 +230,21 @@ public class ResolverActivityWorkProfileTest {
}
private List<ResolvedComponentInfo> createResolvedComponentsForTestWithOtherProfile(
- int numberOfResults, int userId) {
+ int numberOfResults, int userId, UserHandle resolvedForUser) {
List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
for (int i = 0; i < numberOfResults; i++) {
infoList.add(
- ResolverDataProvider.createResolvedComponentInfoWithOtherId(i, userId));
+ ResolverDataProvider
+ .createResolvedComponentInfoWithOtherId(i, userId, resolvedForUser));
}
return infoList;
}
- private List<ResolvedComponentInfo> createResolvedComponentsForTest(int numberOfResults) {
+ private List<ResolvedComponentInfo> createResolvedComponentsForTest(int numberOfResults,
+ UserHandle resolvedForUser) {
List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
for (int i = 0; i < numberOfResults; i++) {
- infoList.add(ResolverDataProvider.createResolvedComponentInfo(i));
+ infoList.add(ResolverDataProvider.createResolvedComponentInfo(i, resolvedForUser));
}
return infoList;
}
@@ -254,9 +256,9 @@ public class ResolverActivityWorkProfileTest {
int workProfileTargets = 4;
List<ResolvedComponentInfo> personalResolvedComponentInfos =
createResolvedComponentsForTestWithOtherProfile(3,
- /* userId */ WORK_USER_HANDLE.getIdentifier());
+ /* userId */ WORK_USER_HANDLE.getIdentifier(), PERSONAL_USER_HANDLE);
List<ResolvedComponentInfo> workResolvedComponentInfos =
- createResolvedComponentsForTest(workProfileTargets);
+ createResolvedComponentsForTest(workProfileTargets, WORK_USER_HANDLE);
setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
}
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverDataProvider.java b/core/tests/coretests/src/com/android/internal/app/ResolverDataProvider.java
index d7db5f8e46eb..55318f3227fd 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverDataProvider.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverDataProvider.java
@@ -36,20 +36,24 @@ class ResolverDataProvider {
static private int USER_SOMEONE_ELSE = 10;
- static ResolverActivity.ResolvedComponentInfo createResolvedComponentInfo(int i) {
- return new ResolverActivity.ResolvedComponentInfo(createComponentName(i),
- createResolverIntent(i), createResolveInfo(i, UserHandle.USER_CURRENT));
+ static ResolverActivity.ResolvedComponentInfo createResolvedComponentInfo(int i,
+ UserHandle resolvedForUser) {
+ return new ResolverActivity.ResolvedComponentInfo(
+ createComponentName(i),
+ createResolverIntent(i),
+ createResolveInfo(i, UserHandle.USER_CURRENT, resolvedForUser));
}
- static ResolverActivity.ResolvedComponentInfo createResolvedComponentInfoWithOtherId(int i) {
+ static ResolverActivity.ResolvedComponentInfo createResolvedComponentInfoWithOtherId(int i,
+ UserHandle resolvedForUser) {
return new ResolverActivity.ResolvedComponentInfo(createComponentName(i),
- createResolverIntent(i), createResolveInfo(i, USER_SOMEONE_ELSE));
+ createResolverIntent(i), createResolveInfo(i, USER_SOMEONE_ELSE, resolvedForUser));
}
static ResolverActivity.ResolvedComponentInfo createResolvedComponentInfoWithOtherId(int i,
- int userId) {
+ int userId, UserHandle resolvedForUser) {
return new ResolverActivity.ResolvedComponentInfo(createComponentName(i),
- createResolverIntent(i), createResolveInfo(i, userId));
+ createResolverIntent(i), createResolveInfo(i, userId, resolvedForUser));
}
static ComponentName createComponentName(int i) {
@@ -57,10 +61,11 @@ class ResolverDataProvider {
return new ComponentName("foo.bar." + name, name);
}
- static ResolveInfo createResolveInfo(int i, int userId) {
+ static ResolveInfo createResolveInfo(int i, int userId, UserHandle resolvedForUser) {
final ResolveInfo resolveInfo = new ResolveInfo();
resolveInfo.activityInfo = createActivityInfo(i);
resolveInfo.targetUserId = userId;
+ resolveInfo.userHandle = resolvedForUser;
return resolveInfo;
}
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverListControllerTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverListControllerTest.java
index 42593f60094b..8f6cee33078e 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverListControllerTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverListControllerTest.java
@@ -44,6 +44,7 @@ import android.os.RemoteException;
import android.os.UserHandle;
import android.util.ArrayMap;
+import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
@@ -75,6 +76,8 @@ public class ResolverListControllerTest {
private ResolverListController mController;
private UsageStatsManager mUsm;
+ private static final UserHandle PERSONAL_USER_HANDLE = InstrumentationRegistry
+ .getInstrumentation().getTargetContext().getUser();
@Before
public void setUp() throws Exception {
@@ -83,6 +86,7 @@ public class ResolverListControllerTest {
config.locale = Locale.getDefault();
List<ResolveInfo> services = new ArrayList<>();
mUsm = new UsageStatsManager(mMockContext, mMockService);
+ when(mMockContext.createContextAsUser(any(), anyInt())).thenReturn(mMockContext);
when(mMockContext.getSystemService(Context.USAGE_STATS_SERVICE)).thenReturn(mUsm);
when(mMockPackageManager.queryIntentServices(any(), anyInt())).thenReturn(services);
when(mMockResources.getConfiguration()).thenReturn(config);
@@ -119,10 +123,11 @@ public class ResolverListControllerTest {
anyString(), anyInt(), anyString(), any(), anyString());
when(mMockContext.getOpPackageName()).thenReturn(refererPackage);
mController = new ResolverListController(mMockContext, mMockPackageManager, sendIntent,
- refererPackage, UserHandle.USER_CURRENT, /* userHandle */ UserHandle.SYSTEM);
+ refererPackage, UserHandle.USER_CURRENT, /* userHandle */ UserHandle.SYSTEM,
+ UserHandle.SYSTEM);
mController.sort(new ArrayList<ResolvedComponentInfo>());
long beforeReport = getCount(mUsm, packageName, action, annotation);
- mController.updateChooserCounts(packageName, UserHandle.USER_CURRENT, action);
+ mController.updateChooserCounts(packageName, UserHandle.SYSTEM, action);
long afterReport = getCount(mUsm, packageName, action, annotation);
assertThat(afterReport, is(beforeReport + 1l));
}
@@ -134,7 +139,8 @@ public class ResolverListControllerTest {
String refererPackage = "test_referer_package";
List<ResolvedComponentInfo> resolvedComponents = createResolvedComponentsForTest(10);
mController = new ResolverListController(mMockContext, mMockPackageManager, sendIntent,
- refererPackage, UserHandle.USER_CURRENT, /* userHandle */ UserHandle.SYSTEM);
+ refererPackage, UserHandle.USER_CURRENT, /* userHandle */ UserHandle.SYSTEM,
+ UserHandle.SYSTEM);
List<ResolvedComponentInfo> topKList = new ArrayList<>(resolvedComponents);
mController.topK(topKList, 5);
List<ResolvedComponentInfo> sortList = new ArrayList<>(topKList);
@@ -157,12 +163,13 @@ public class ResolverListControllerTest {
public void getResolversForIntent_usesResultsFromPackageManager() {
mockStats();
List<ResolveInfo> infos = new ArrayList<>();
- infos.add(ResolverDataProvider.createResolveInfo(0, UserHandle.USER_CURRENT));
+ infos.add(ResolverDataProvider.createResolveInfo(0, UserHandle.USER_CURRENT,
+ PERSONAL_USER_HANDLE));
when(mMockPackageManager.queryIntentActivitiesAsUser(any(), anyInt(),
any(UserHandle.class))).thenReturn(infos);
mController = new ResolverListController(mMockContext, mMockPackageManager,
createSendImageIntent("test"), null, UserHandle.USER_CURRENT,
- /* userHandle= */ UserHandle.SYSTEM);
+ /* userHandle= */ UserHandle.SYSTEM, UserHandle.SYSTEM);
List<Intent> intents = new ArrayList<>();
intents.add(createActionMainIntent());
@@ -181,12 +188,13 @@ public class ResolverListControllerTest {
public void getResolversForIntent_shouldGetOnlyDefaultActivitiesTrue_addsFlag() {
mockStats();
List<ResolveInfo> infos = new ArrayList<>();
- infos.add(ResolverDataProvider.createResolveInfo(0, UserHandle.USER_CURRENT));
+ infos.add(ResolverDataProvider.createResolveInfo(0, UserHandle.USER_CURRENT,
+ PERSONAL_USER_HANDLE));
when(mMockPackageManager.queryIntentActivitiesAsUser(any(), anyInt(),
any(UserHandle.class))).thenReturn(infos);
mController = new ResolverListController(mMockContext, mMockPackageManager,
createSendImageIntent("test"), null, UserHandle.USER_CURRENT,
- /* userHandle= */ UserHandle.SYSTEM);
+ /* userHandle= */ UserHandle.SYSTEM, UserHandle.SYSTEM);
List<Intent> intents = new ArrayList<>();
intents.add(createActionMainIntent());
@@ -205,12 +213,13 @@ public class ResolverListControllerTest {
public void getResolversForIntent_shouldGetOnlyDefaultActivitiesFalse_doesNotAddFlag() {
mockStats();
List<ResolveInfo> infos = new ArrayList<>();
- infos.add(ResolverDataProvider.createResolveInfo(0, UserHandle.USER_CURRENT));
+ infos.add(ResolverDataProvider.createResolveInfo(0, UserHandle.USER_CURRENT,
+ PERSONAL_USER_HANDLE));
when(mMockPackageManager.queryIntentActivitiesAsUser(any(), anyInt(),
any(UserHandle.class))).thenReturn(infos);
mController = new ResolverListController(mMockContext, mMockPackageManager,
createSendImageIntent("test"), null, UserHandle.USER_CURRENT,
- /* userHandle= */ UserHandle.SYSTEM);
+ /* userHandle= */ UserHandle.SYSTEM, UserHandle.SYSTEM);
List<Intent> intents = new ArrayList<>();
intents.add(createActionMainIntent());
@@ -225,6 +234,32 @@ public class ResolverListControllerTest {
doesNotContainFlag(PackageManager.MATCH_DEFAULT_ONLY), any());
}
+
+ @Test
+ public void testResolveInfoWithNoUserHandle_isNotAddedToResults()
+ throws Exception {
+ List<ResolveInfo> infos = new ArrayList<>();
+ infos.add(ResolverDataProvider.createResolveInfo(0, UserHandle.USER_CURRENT,
+ PERSONAL_USER_HANDLE));
+ infos.add(ResolverDataProvider.createResolveInfo(0, UserHandle.USER_CURRENT, null));
+ when(mMockPackageManager.queryIntentActivitiesAsUser(any(), anyInt(),
+ any(UserHandle.class))).thenReturn(infos);
+ mController = new ResolverListController(mMockContext, mMockPackageManager,
+ createSendImageIntent("test"), null, UserHandle.USER_CURRENT,
+ /* userHandle= */ UserHandle.SYSTEM, UserHandle.SYSTEM);
+ List<Intent> intents = new ArrayList<>();
+ intents.add(createActionMainIntent());
+
+ List<ResolverActivity.ResolvedComponentInfo> result = mController
+ .getResolversForIntent(
+ /* shouldGetResolvedFilter= */ true,
+ /* shouldGetActivityMetadata= */ true,
+ /* shouldGetOnlyDefaultActivities= */ false,
+ intents);
+
+ assertThat(result.size(), is(1));
+ }
+
private int containsFlag(int flag) {
return intThat(new FlagMatcher(flag, /* contains= */ true));
}
@@ -308,7 +343,7 @@ public class ResolverListControllerTest {
private List<ResolvedComponentInfo> createResolvedComponentsForTest(int numberOfResults) {
List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
for (int i = 0; i < numberOfResults; i++) {
- infoList.add(ResolverDataProvider.createResolvedComponentInfo(i));
+ infoList.add(ResolverDataProvider.createResolvedComponentInfo(i, PERSONAL_USER_HANDLE));
}
return infoList;
}
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
index c778dfeaf376..8f6f29d24ab9 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
@@ -21,6 +21,7 @@ import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
+import android.annotation.Nullable;
import android.app.usage.UsageStatsManager;
import android.content.Context;
import android.content.Intent;
@@ -30,7 +31,6 @@ import android.os.Bundle;
import android.os.UserHandle;
import com.android.internal.app.AbstractMultiProfilePagerAdapter.CrossProfileIntentsChecker;
-import com.android.internal.app.AbstractMultiProfilePagerAdapter.MyUserIdProvider;
import com.android.internal.app.AbstractMultiProfilePagerAdapter.QuietModeManager;
import com.android.internal.app.chooser.TargetInfo;
@@ -57,14 +57,6 @@ public class ResolverWrapperActivity extends ResolverActivity {
}
@Override
- protected MyUserIdProvider createMyUserIdProvider() {
- if (sOverrides.mMyUserIdProvider != null) {
- return sOverrides.mMyUserIdProvider;
- }
- return super.createMyUserIdProvider();
- }
-
- @Override
protected CrossProfileIntentsChecker createCrossProfileIntentsChecker() {
if (sOverrides.mCrossProfileIntentsChecker != null) {
return sOverrides.mCrossProfileIntentsChecker;
@@ -103,13 +95,25 @@ public class ResolverWrapperActivity extends ResolverActivity {
return super.isVoiceInteraction();
}
+ // TODO: Remove this and override safelyStartActivityInternal() instead.
@Override
- public void safelyStartActivity(TargetInfo cti) {
+ public void safelyStartActivityAsUser(TargetInfo cti, UserHandle user,
+ @Nullable Bundle options) {
if (sOverrides.onSafelyStartCallback != null &&
sOverrides.onSafelyStartCallback.apply(cti)) {
return;
}
- super.safelyStartActivity(cti);
+ super.safelyStartActivityAsUser(cti, user, options);
+ }
+
+ @Override
+ public void safelyStartActivityInternal(TargetInfo cti, UserHandle user,
+ @Nullable Bundle options) {
+ if (sOverrides.onSafelyStartInternalCallback != null
+ && sOverrides.onSafelyStartInternalCallback.apply(user)) {
+ return;
+ }
+ super.safelyStartActivityInternal(cti, user, options);
}
@Override
@@ -135,11 +139,29 @@ public class ResolverWrapperActivity extends ResolverActivity {
}
@Override
+ protected UserHandle getPersonalProfileUserHandle() {
+ return super.getPersonalProfileUserHandle();
+ }
+
+ @Override
protected UserHandle getWorkProfileUserHandle() {
return sOverrides.workProfileUserHandle;
}
@Override
+ protected UserHandle getCloneProfileUserHandle() {
+ return sOverrides.cloneProfileUserHandle;
+ }
+
+ @Override
+ protected UserHandle getTabOwnerUserHandleForLaunch() {
+ if (sOverrides.tabOwnerUserHandleForLaunch == null) {
+ return super.getTabOwnerUserHandleForLaunch();
+ }
+ return sOverrides.tabOwnerUserHandleForLaunch;
+ }
+
+ @Override
public void startActivityAsUser(Intent intent, Bundle options, UserHandle user) {
super.startActivityAsUser(intent, options, user);
}
@@ -153,24 +175,29 @@ public class ResolverWrapperActivity extends ResolverActivity {
@SuppressWarnings("Since15")
public Function<PackageManager, PackageManager> createPackageManager;
public Function<TargetInfo, Boolean> onSafelyStartCallback;
+ public Function<UserHandle, Boolean> onSafelyStartInternalCallback;
public ResolverListController resolverListController;
public ResolverListController workResolverListController;
public Boolean isVoiceInteraction;
public UserHandle workProfileUserHandle;
+ public UserHandle cloneProfileUserHandle;
+ public UserHandle tabOwnerUserHandleForLaunch;
public Integer myUserId;
public boolean hasCrossProfileIntents;
public boolean isQuietModeEnabled;
public QuietModeManager mQuietModeManager;
- public MyUserIdProvider mMyUserIdProvider;
public CrossProfileIntentsChecker mCrossProfileIntentsChecker;
public void reset() {
onSafelyStartCallback = null;
+ onSafelyStartInternalCallback = null;
isVoiceInteraction = null;
createPackageManager = null;
resolverListController = mock(ResolverListController.class);
workResolverListController = mock(ResolverListController.class);
workProfileUserHandle = null;
+ cloneProfileUserHandle = null;
+ tabOwnerUserHandleForLaunch = null;
myUserId = null;
hasCrossProfileIntents = true;
isQuietModeEnabled = false;
@@ -197,13 +224,6 @@ public class ResolverWrapperActivity extends ResolverActivity {
}
};
- mMyUserIdProvider = new MyUserIdProvider() {
- @Override
- public int getMyUserId() {
- return myUserId != null ? myUserId : UserHandle.myUserId();
- }
- };
-
mCrossProfileIntentsChecker = mock(CrossProfileIntentsChecker.class);
when(mCrossProfileIntentsChecker.hasCrossProfileIntents(any(), anyInt(), anyInt()))
.thenAnswer(invocation -> hasCrossProfileIntents);
diff --git a/core/tests/notificationtests/src/android/app/NotificationStressTest.java b/core/tests/notificationtests/src/android/app/NotificationStressTest.java
index e5000a43c47a..b2914d89e4f2 100644
--- a/core/tests/notificationtests/src/android/app/NotificationStressTest.java
+++ b/core/tests/notificationtests/src/android/app/NotificationStressTest.java
@@ -110,8 +110,9 @@ public class NotificationStressTest extends InstrumentationTestCase {
private void sendNotification(int id, CharSequence text) {
// Fill in arbitrary content
- Intent intent = new Intent(Intent.ACTION_VIEW);
- PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
+ Intent intent = new Intent(Intent.ACTION_VIEW).setPackage(mContext.getPackageName());
+ PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent,
+ PendingIntent.FLAG_MUTABLE);
CharSequence title = text + " " + id;
CharSequence subtitle = String.valueOf(System.currentTimeMillis());
// Create "typical" notification with random icon
diff --git a/core/tests/screenshothelpertests/Android.bp b/core/tests/screenshothelpertests/Android.bp
index 37af99c58d42..3c71e6e4247b 100644
--- a/core/tests/screenshothelpertests/Android.bp
+++ b/core/tests/screenshothelpertests/Android.bp
@@ -13,7 +13,7 @@ android_test {
srcs: [
"src/**/*.java",
],
-
+
static_libs: [
"frameworks-base-testutils",
"androidx.test.runner",
@@ -21,6 +21,7 @@ android_test {
"androidx.test.ext.junit",
"mockito-target-minus-junit4",
"platform-test-annotations",
+ "testng",
],
libs: [
diff --git a/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java b/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java
index 2719431a536e..5c9894ebd590 100644
--- a/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java
+++ b/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java
@@ -17,6 +17,7 @@
package com.android.internal.util;
import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN;
+import static android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.fail;
@@ -31,9 +32,11 @@ import static org.mockito.Mockito.mock;
import android.content.ComponentName;
import android.content.Context;
import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.ColorSpace;
import android.graphics.Insets;
import android.graphics.Rect;
-import android.os.Bundle;
+import android.hardware.HardwareBuffer;
import android.os.Handler;
import android.os.Looper;
import android.view.WindowManager;
@@ -79,30 +82,48 @@ public final class ScreenshotHelperTest {
@Test
public void testFullscreenScreenshot() {
- mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN,
+ mScreenshotHelper.takeScreenshot(
WindowManager.ScreenshotSource.SCREENSHOT_OTHER, mHandler, null);
}
@Test
+ public void testFullscreenScreenshotRequest() {
+ ScreenshotRequest request = new ScreenshotRequest.Builder(
+ TAKE_SCREENSHOT_FULLSCREEN, WindowManager.ScreenshotSource.SCREENSHOT_OTHER)
+ .build();
+ mScreenshotHelper.takeScreenshot(request, mHandler, null);
+ }
+
+ @Test
public void testProvidedImageScreenshot() {
- mScreenshotHelper.provideScreenshot(
- new Bundle(), new Rect(), Insets.of(0, 0, 0, 0), 1, 1, new ComponentName("", ""),
- WindowManager.ScreenshotSource.SCREENSHOT_OTHER, mHandler, null);
+ HardwareBuffer buffer = HardwareBuffer.create(
+ 10, 10, HardwareBuffer.RGBA_8888, 1, HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE);
+ Bitmap bitmap = Bitmap.wrapHardwareBuffer(buffer, ColorSpace.get(ColorSpace.Named.SRGB));
+ ScreenshotRequest request = new ScreenshotRequest.Builder(
+ TAKE_SCREENSHOT_PROVIDED_IMAGE, WindowManager.ScreenshotSource.SCREENSHOT_OTHER)
+ .setTopComponent(new ComponentName("", ""))
+ .setTaskId(1)
+ .setUserId(1)
+ .setBitmap(bitmap)
+ .setBoundsOnScreen(new Rect())
+ .setInsets(Insets.NONE)
+ .build();
+ mScreenshotHelper.takeScreenshot(request, mHandler, null);
}
@Test
public void testScreenshotTimesOut() {
long timeoutMs = 10;
+ ScreenshotRequest request = new ScreenshotRequest.Builder(
+ TAKE_SCREENSHOT_FULLSCREEN, WindowManager.ScreenshotSource.SCREENSHOT_OTHER)
+ .build();
CountDownLatch lock = new CountDownLatch(1);
- mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN,
- WindowManager.ScreenshotSource.SCREENSHOT_OTHER,
- mHandler,
- timeoutMs,
+ mScreenshotHelper.takeScreenshotInternal(request, mHandler,
uri -> {
assertNull(uri);
lock.countDown();
- });
+ }, timeoutMs);
try {
// Add tolerance for delay to prevent flakes.
diff --git a/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotRequestTest.java b/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotRequestTest.java
new file mode 100644
index 000000000000..30540a5f013b
--- /dev/null
+++ b/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotRequestTest.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.util;
+
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.os.UserHandle.USER_NULL;
+import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_OTHER;
+import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN;
+import static android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
+
+import static org.testng.Assert.assertThrows;
+
+import android.content.ComponentName;
+import android.graphics.Bitmap;
+import android.graphics.ColorSpace;
+import android.graphics.Insets;
+import android.graphics.Rect;
+import android.hardware.HardwareBuffer;
+import android.os.Parcel;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public final class ScreenshotRequestTest {
+ private final ComponentName mComponentName =
+ new ComponentName("android.test", "android.test.Component");
+
+ @Test
+ public void testSimpleScreenshot() {
+ ScreenshotRequest in =
+ new ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_OTHER).build();
+
+ Parcel parcel = Parcel.obtain();
+ in.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ ScreenshotRequest out = ScreenshotRequest.CREATOR.createFromParcel(parcel);
+
+ assertEquals(TAKE_SCREENSHOT_FULLSCREEN, out.getType());
+ assertEquals(SCREENSHOT_OTHER, out.getSource());
+ assertNull("Top component was expected to be null", out.getTopComponent());
+ assertEquals(INVALID_TASK_ID, out.getTaskId());
+ assertEquals(USER_NULL, out.getUserId());
+ assertNull("Bitmap was expected to be null", out.getBitmap());
+ assertNull("Bounds were expected to be null", out.getBoundsInScreen());
+ assertEquals(Insets.NONE, out.getInsets());
+ }
+
+ @Test
+ public void testProvidedScreenshot() {
+ Bitmap bitmap = makeHardwareBitmap(50, 50);
+ ScreenshotRequest in =
+ new ScreenshotRequest.Builder(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER)
+ .setTopComponent(mComponentName)
+ .setTaskId(2)
+ .setUserId(3)
+ .setBitmap(bitmap)
+ .setBoundsOnScreen(new Rect(10, 10, 60, 60))
+ .setInsets(Insets.of(2, 3, 4, 5))
+ .build();
+
+ Parcel parcel = Parcel.obtain();
+ in.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ ScreenshotRequest out = ScreenshotRequest.CREATOR.createFromParcel(parcel);
+
+ assertEquals(TAKE_SCREENSHOT_PROVIDED_IMAGE, out.getType());
+ assertEquals(SCREENSHOT_OTHER, out.getSource());
+ assertEquals(mComponentName, out.getTopComponent());
+ assertEquals(2, out.getTaskId());
+ assertEquals(3, out.getUserId());
+ assertTrue("Bitmaps should be equal", out.getBitmap().sameAs(bitmap));
+ assertEquals(new Rect(10, 10, 60, 60), out.getBoundsInScreen());
+ assertEquals(Insets.of(2, 3, 4, 5), out.getInsets());
+ }
+
+ @Test
+ public void testProvidedScreenshot_nullBitmap() {
+ ScreenshotRequest.Builder inBuilder =
+ new ScreenshotRequest.Builder(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER)
+ .setTopComponent(mComponentName)
+ .setTaskId(2)
+ .setUserId(3)
+ .setBoundsOnScreen(new Rect(10, 10, 60, 60))
+ .setInsets(Insets.of(2, 3, 4, 5));
+
+ assertThrows(IllegalStateException.class, inBuilder::build);
+ }
+
+ @Test
+ public void testFullScreenshot_withBitmap() {
+ // A bitmap added to a FULLSCREEN request will be ignored, but it's technically valid
+ Bitmap bitmap = makeHardwareBitmap(50, 50);
+ ScreenshotRequest in =
+ new ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_OTHER)
+ .setBitmap(bitmap)
+ .build();
+
+ Parcel parcel = Parcel.obtain();
+ in.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ ScreenshotRequest out = ScreenshotRequest.CREATOR.createFromParcel(parcel);
+
+ assertEquals(TAKE_SCREENSHOT_FULLSCREEN, out.getType());
+ assertEquals(SCREENSHOT_OTHER, out.getSource());
+ assertNull(out.getTopComponent());
+ assertEquals(INVALID_TASK_ID, out.getTaskId());
+ assertEquals(USER_NULL, out.getUserId());
+ assertTrue("Bitmaps should be equal", out.getBitmap().sameAs(bitmap));
+ assertNull("Bounds expected to be null", out.getBoundsInScreen());
+ assertEquals(Insets.NONE, out.getInsets());
+ }
+
+ private Bitmap makeHardwareBitmap(int width, int height) {
+ HardwareBuffer buffer = HardwareBuffer.create(
+ width, height, HardwareBuffer.RGBA_8888, 1, HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE);
+ return Bitmap.wrapHardwareBuffer(buffer, ColorSpace.get(ColorSpace.Named.SRGB));
+ }
+}
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index bc3af1daa204..8b7265e18d5c 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -2599,6 +2599,12 @@
"group": "WM_DEBUG_REMOTE_ANIMATIONS",
"at": "com\/android\/server\/wm\/RemoteAnimationController.java"
},
+ "273212558": {
+ "message": " info=%s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS_MIN",
+ "at": "com\/android\/server\/wm\/TransitionController.java"
+ },
"274773837": {
"message": "applyAnimation: anim=%s nextAppTransition=ANIM_CLIP_REVEAL transit=%s Callers=%s",
"level": "VERBOSE",
@@ -3937,6 +3943,12 @@
"group": "WM_DEBUG_STATES",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "1621562070": {
+ "message": " startWCT=%s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS_MIN",
+ "at": "com\/android\/server\/wm\/TransitionController.java"
+ },
"1628345525": {
"message": "Now opening app %s",
"level": "VERBOSE",
@@ -4345,6 +4357,12 @@
"group": "WM_DEBUG_ANIM",
"at": "com\/android\/server\/wm\/DisplayRotation.java"
},
+ "2021079047": {
+ "message": "%s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS_MIN",
+ "at": "com\/android\/server\/wm\/TransitionController.java"
+ },
"2022422429": {
"message": "createAnimationAdapter(): container=%s",
"level": "DEBUG",
@@ -4551,6 +4569,9 @@
"WM_DEBUG_WINDOW_TRANSITIONS": {
"tag": "WindowManager"
},
+ "WM_DEBUG_WINDOW_TRANSITIONS_MIN": {
+ "tag": "WindowManager"
+ },
"WM_ERROR": {
"tag": "WindowManager"
},
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
index 5d384944821c..38f1e28a62e5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
@@ -31,11 +31,12 @@ import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.RemoteException;
+import android.util.FloatProperty;
+import android.util.TypedValue;
import android.view.IRemoteAnimationFinishedCallback;
import android.view.IRemoteAnimationRunner;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
-import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.window.BackEvent;
@@ -43,9 +44,10 @@ import android.window.BackMotionEvent;
import android.window.BackProgressAnimator;
import android.window.IOnBackInvokedCallback;
+import com.android.internal.dynamicanimation.animation.SpringAnimation;
+import com.android.internal.dynamicanimation.animation.SpringForce;
import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.internal.protolog.common.ProtoLog;
-import com.android.wm.shell.animation.Interpolators;
import com.android.wm.shell.common.annotations.ShellMainThread;
/** Class that defines cross-activity animation. */
@@ -56,24 +58,40 @@ class CrossActivityAnimation {
*/
private static final float MIN_WINDOW_SCALE = 0.9f;
- /**
- * Minimum alpha of the closing/entering window.
- */
- private static final float CLOSING_MIN_WINDOW_ALPHA = 0.5f;
-
- /**
- * Progress value to fly out closing window and fly in entering window.
- */
- private static final float SWITCH_ENTERING_WINDOW_PROGRESS = 0.5f;
-
- /** Max window translation in the Y axis. */
- private static final int WINDOW_MAX_DELTA_Y = 160;
-
- /** Duration of fade in/out entering window. */
- private static final int FADE_IN_DURATION = 100;
/** Duration of post animation after gesture committed. */
private static final int POST_ANIMATION_DURATION = 350;
- private static final Interpolator INTERPOLATOR = Interpolators.EMPHASIZED;
+ private static final Interpolator INTERPOLATOR = new DecelerateInterpolator();
+ private static final FloatProperty<CrossActivityAnimation> ENTER_PROGRESS_PROP =
+ new FloatProperty<>("enter-alpha") {
+ @Override
+ public void setValue(CrossActivityAnimation anim, float value) {
+ anim.setEnteringProgress(value);
+ }
+
+ @Override
+ public Float get(CrossActivityAnimation object) {
+ return object.getEnteringProgress();
+ }
+ };
+ private static final FloatProperty<CrossActivityAnimation> LEAVE_PROGRESS_PROP =
+ new FloatProperty<>("leave-alpha") {
+ @Override
+ public void setValue(CrossActivityAnimation anim, float value) {
+ anim.setLeavingProgress(value);
+ }
+
+ @Override
+ public Float get(CrossActivityAnimation object) {
+ return object.getLeavingProgress();
+ }
+ };
+ private static final float MIN_WINDOW_ALPHA = 0.01f;
+ private static final float WINDOW_X_SHIFT_DP = 96;
+ private static final int SCALE_FACTOR = 100;
+ // TODO(b/264710590): Use the progress commit threshold from ViewConfiguration once it exists.
+ private static final float PROGRESS_COMMIT_THRESHOLD = 0.1f;
+ private static final float TARGET_COMMIT_PROGRESS = 0.5f;
+ private static final float ENTER_ALPHA_THRESHOLD = 0.22f;
private final Rect mStartTaskRect = new Rect();
private final float mCornerRadius;
@@ -84,12 +102,13 @@ class CrossActivityAnimation {
// The entering window properties.
private final Rect mEnteringStartRect = new Rect();
private final RectF mEnteringRect = new RectF();
+ private final SpringAnimation mEnteringProgressSpring;
+ private final SpringAnimation mLeavingProgressSpring;
+ // Max window x-shift in pixels.
+ private final float mWindowXShift;
- private float mCurrentAlpha = 1.0f;
-
- private float mEnteringMargin = 0;
- private ValueAnimator mEnteringAnimator;
- private boolean mEnteringWindowShow = false;
+ private float mEnteringProgress = 0f;
+ private float mLeavingProgress = 0f;
private final PointF mInitialTouchPos = new PointF();
@@ -115,14 +134,42 @@ class CrossActivityAnimation {
mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
mBackAnimationRunner = new BackAnimationRunner(new Callback(), new Runner());
mBackground = background;
+ mEnteringProgressSpring = new SpringAnimation(this, ENTER_PROGRESS_PROP);
+ mEnteringProgressSpring.setSpring(new SpringForce()
+ .setStiffness(SpringForce.STIFFNESS_MEDIUM)
+ .setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY));
+ mLeavingProgressSpring = new SpringAnimation(this, LEAVE_PROGRESS_PROP);
+ mLeavingProgressSpring.setSpring(new SpringForce()
+ .setStiffness(SpringForce.STIFFNESS_MEDIUM)
+ .setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY));
+ mWindowXShift = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, WINDOW_X_SHIFT_DP,
+ context.getResources().getDisplayMetrics());
}
- private static float mapRange(float value, float min, float max) {
- return min + (value * (max - min));
+ /**
+ * Returns 1 if x >= edge1, 0 if x <= edge0, and a smoothed value between the two.
+ * From https://en.wikipedia.org/wiki/Smoothstep
+ */
+ private static float smoothstep(float edge0, float edge1, float x) {
+ if (x < edge0) return 0;
+ if (x >= edge1) return 1;
+
+ x = (x - edge0) / (edge1 - edge0);
+ return x * x * (3 - 2 * x);
+ }
+
+ /**
+ * Linearly map x from range (a1, a2) to range (b1, b2).
+ */
+ private static float mapLinear(float x, float a1, float a2, float b1, float b2) {
+ return b1 + (x - a1) * (b2 - b1) / (a2 - a1);
}
- private float getInterpolatedProgress(float backProgress) {
- return INTERPOLATOR.getInterpolation(backProgress);
+ /**
+ * Linearly map a normalized value from (0, 1) to (min, max).
+ */
+ private static float mapRange(float value, float min, float max) {
+ return min + (value * (max - min));
}
private void startBackAnimation() {
@@ -169,9 +216,6 @@ class CrossActivityAnimation {
mBackInProgress = false;
mTransformMatrix.reset();
mInitialTouchPos.set(0, 0);
- mEnteringWindowShow = false;
- mEnteringMargin = 0;
- mEnteringAnimator = null;
if (mFinishCallback != null) {
try {
@@ -181,6 +225,10 @@ class CrossActivityAnimation {
}
mFinishCallback = null;
}
+ mEnteringProgressSpring.animateToFinalPosition(0);
+ mEnteringProgressSpring.skipToEnd();
+ mLeavingProgressSpring.animateToFinalPosition(0);
+ mLeavingProgressSpring.skipToEnd();
}
private void onGestureProgress(@NonNull BackEvent backEvent) {
@@ -190,84 +238,12 @@ class CrossActivityAnimation {
}
mTouchPos.set(backEvent.getTouchX(), backEvent.getTouchY());
- if (mEnteringTarget == null || mClosingTarget == null) {
- return;
- }
-
- final float progress = getInterpolatedProgress(backEvent.getProgress());
- final float touchY = mTouchPos.y;
-
- final int width = mStartTaskRect.width();
- final int height = mStartTaskRect.height();
-
- final float closingScale = mapRange(progress, 1, MIN_WINDOW_SCALE);
-
- final float closingWidth = closingScale * width;
- final float closingHeight = (float) height / width * closingWidth;
-
- // Move the window along the X axis.
- final float closingLeft = mStartTaskRect.left + (width - closingWidth) / 2;
-
- // Move the window along the Y axis.
- final float deltaYRatio = (touchY - mInitialTouchPos.y) / height;
- final float deltaY = (float) Math.sin(deltaYRatio * Math.PI * 0.5f) * WINDOW_MAX_DELTA_Y;
- final float closingTop = (height - closingHeight) * 0.5f + deltaY;
- mClosingRect.set(
- closingLeft, closingTop, closingLeft + closingWidth, closingTop + closingHeight);
- mEnteringRect.set(mClosingRect);
-
- // Switch closing/entering targets while reach to the threshold progress.
- if (showEnteringWindow(progress > SWITCH_ENTERING_WINDOW_PROGRESS)) {
- return;
- }
-
- // Present windows and update the alpha.
- mCurrentAlpha = Math.max(mapRange(progress, 1.0f, 0), CLOSING_MIN_WINDOW_ALPHA);
- mClosingRect.offset(mEnteringMargin, 0);
- mEnteringRect.offset(mEnteringMargin - width, 0);
-
- applyTransform(
- mClosingTarget.leash, mClosingRect, mEnteringWindowShow ? 0.01f : mCurrentAlpha);
- applyTransform(
- mEnteringTarget.leash, mEnteringRect, mEnteringWindowShow ? mCurrentAlpha : 0.01f);
- mTransaction.apply();
- }
-
- private boolean showEnteringWindow(boolean show) {
- if (mEnteringAnimator == null) {
- mEnteringAnimator = ValueAnimator.ofFloat(1f, 0f).setDuration(FADE_IN_DURATION);
- mEnteringAnimator.setInterpolator(new AccelerateInterpolator());
- mEnteringAnimator.addUpdateListener(animation -> {
- float progress = animation.getAnimatedFraction();
- final int width = mStartTaskRect.width();
- mEnteringMargin = width * progress;
- // We don't animate to 0 or the surface would become invisible and lose focus.
- final float alpha = progress >= 0.5f ? 0.01f
- : mapRange(progress * 2, mCurrentAlpha, 0.01f);
- mClosingRect.offset(mEnteringMargin, 0);
- mEnteringRect.offset(mEnteringMargin - width, 0);
-
- applyTransform(mClosingTarget.leash, mClosingRect, alpha);
- applyTransform(mEnteringTarget.leash, mEnteringRect, mCurrentAlpha);
- mTransaction.apply();
- });
- }
-
- if (mEnteringAnimator.isRunning()) {
- return true;
- }
-
- if (mEnteringWindowShow == show) {
- return false;
- }
-
- mEnteringWindowShow = show;
- if (show) {
- mEnteringAnimator.start();
- } else {
- mEnteringAnimator.reverse();
- }
- return true;
+ float progress = backEvent.getProgress();
+ float springProgress = (progress > PROGRESS_COMMIT_THRESHOLD
+ ? mapLinear(progress, 0.1f, 1, TARGET_COMMIT_PROGRESS, 1)
+ : mapLinear(progress, 0, 1f, 0, TARGET_COMMIT_PROGRESS)) * SCALE_FACTOR;
+ mLeavingProgressSpring.animateToFinalPosition(springProgress);
+ mEnteringProgressSpring.animateToFinalPosition(springProgress);
}
private void onGestureCommitted() {
@@ -275,11 +251,9 @@ class CrossActivityAnimation {
finishAnimation();
return;
}
-
- // End the fade in animation.
- if (mEnteringAnimator != null && mEnteringAnimator.isRunning()) {
- mEnteringAnimator.cancel();
- }
+ // End the fade animations
+ mLeavingProgressSpring.cancel();
+ mEnteringProgressSpring.cancel();
// We enter phase 2 of the animation, the starting coordinates for phase 2 are the current
// coordinate of the gesture driven phase.
@@ -309,12 +283,79 @@ class CrossActivityAnimation {
float top = mapRange(progress, mEnteringStartRect.top, mStartTaskRect.top);
float width = mapRange(progress, mEnteringStartRect.width(), mStartTaskRect.width());
float height = mapRange(progress, mEnteringStartRect.height(), mStartTaskRect.height());
- float alpha = mapRange(progress, mCurrentAlpha, 1.0f);
+ float alpha = mapRange(progress, mEnteringProgress, 1.0f);
mEnteringRect.set(left, top, left + width, top + height);
applyTransform(mEnteringTarget.leash, mEnteringRect, alpha);
}
+ private float getEnteringProgress() {
+ return mEnteringProgress * SCALE_FACTOR;
+ }
+
+ private void setEnteringProgress(float value) {
+ mEnteringProgress = value / SCALE_FACTOR;
+ if (mEnteringTarget != null && mEnteringTarget.leash != null) {
+ transformWithProgress(
+ mEnteringProgress,
+ Math.max(
+ smoothstep(ENTER_ALPHA_THRESHOLD, 1, mEnteringProgress),
+ MIN_WINDOW_ALPHA), /* alpha */
+ mEnteringTarget.leash,
+ mEnteringRect,
+ -mWindowXShift,
+ 0
+ );
+ }
+ }
+
+ private float getLeavingProgress() {
+ return mLeavingProgress * SCALE_FACTOR;
+ }
+
+ private void setLeavingProgress(float value) {
+ mLeavingProgress = value / SCALE_FACTOR;
+ if (mClosingTarget != null && mClosingTarget.leash != null) {
+ transformWithProgress(
+ mLeavingProgress,
+ Math.max(
+ 1 - smoothstep(0, ENTER_ALPHA_THRESHOLD, mLeavingProgress),
+ MIN_WINDOW_ALPHA),
+ mClosingTarget.leash,
+ mClosingRect,
+ 0,
+ mWindowXShift
+ );
+ }
+ }
+
+ private void transformWithProgress(float progress, float alpha, SurfaceControl surface,
+ RectF targetRect, float deltaXMin, float deltaXMax) {
+ final float touchY = mTouchPos.y;
+
+ final int width = mStartTaskRect.width();
+ final int height = mStartTaskRect.height();
+
+ final float interpolatedProgress = INTERPOLATOR.getInterpolation(progress);
+ final float closingScale = MIN_WINDOW_SCALE
+ + (1 - interpolatedProgress) * (1 - MIN_WINDOW_SCALE);
+ final float closingWidth = closingScale * width;
+ final float closingHeight = (float) height / width * closingWidth;
+
+ // Move the window along the X axis.
+ float closingLeft = mStartTaskRect.left + (width - closingWidth) / 2;
+ closingLeft += mapRange(interpolatedProgress, deltaXMin, deltaXMax);
+
+ // Move the window along the Y axis.
+ final float deltaYRatio = (touchY - mInitialTouchPos.y) / height;
+ final float closingTop = (height - closingHeight) * 0.5f;
+ targetRect.set(
+ closingLeft, closingTop, closingLeft + closingWidth, closingTop + closingHeight);
+
+ applyTransform(surface, targetRect, Math.max(alpha, MIN_WINDOW_ALPHA));
+ mTransaction.apply();
+ }
+
private final class Callback extends IOnBackInvokedCallback.Default {
@Override
public void onBackStarted(BackMotionEvent backEvent) {
@@ -330,10 +371,12 @@ class CrossActivityAnimation {
@Override
public void onBackCancelled() {
// End the fade in animation.
- if (mEnteringAnimator != null && mEnteringAnimator.isRunning()) {
- mEnteringAnimator.cancel();
- }
mProgressAnimator.onBackCancelled(CrossActivityAnimation.this::finishAnimation);
+ mEnteringProgressSpring.cancel();
+ mLeavingProgressSpring.cancel();
+ // TODO (b259608500): Let BackProgressAnimator could play cancel animation.
+ mProgressAnimator.reset();
+ finishAnimation();
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
index 9edfffce57cf..d0aef2023048 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
@@ -384,7 +384,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
@Nullable ImeTracker.Token statsToken) {
final InsetsSource imeSource = mInsetsState.getSource(InsetsState.ITYPE_IME);
if (imeSource == null || mImeSourceControl == null) {
- ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_WM_ANIMATION_CREATE);
+ ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_WM_ANIMATION_CREATE);
return;
}
final Rect newFrame = imeSource.getFrame();
@@ -407,8 +407,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
}
if ((!forceRestart && (mAnimationDirection == DIRECTION_SHOW && show))
|| (mAnimationDirection == DIRECTION_HIDE && !show)) {
- ImeTracker.forLogging().onCancelled(
- statsToken, ImeTracker.PHASE_WM_ANIMATION_CREATE);
+ ImeTracker.get().onCancelled(statsToken, ImeTracker.PHASE_WM_ANIMATION_CREATE);
return;
}
boolean seek = false;
@@ -452,7 +451,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
mTransactionPool.release(t);
});
mAnimation.setInterpolator(INTERPOLATOR);
- ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_WM_ANIMATION_CREATE);
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_WM_ANIMATION_CREATE);
mAnimation.addListener(new AnimatorListenerAdapter() {
private boolean mCancelled = false;
@Nullable
@@ -475,7 +474,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
: 1.f;
t.setAlpha(mImeSourceControl.getLeash(), alpha);
if (mAnimationDirection == DIRECTION_SHOW) {
- ImeTracker.forLogging().onProgress(mStatsToken,
+ ImeTracker.get().onProgress(mStatsToken,
ImeTracker.PHASE_WM_ANIMATION_RUNNING);
t.show(mImeSourceControl.getLeash());
}
@@ -512,15 +511,15 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
}
dispatchEndPositioning(mDisplayId, mCancelled, t);
if (mAnimationDirection == DIRECTION_HIDE && !mCancelled) {
- ImeTracker.forLogging().onProgress(mStatsToken,
+ ImeTracker.get().onProgress(mStatsToken,
ImeTracker.PHASE_WM_ANIMATION_RUNNING);
t.hide(mImeSourceControl.getLeash());
removeImeSurface();
- ImeTracker.forLogging().onHidden(mStatsToken);
+ ImeTracker.get().onHidden(mStatsToken);
} else if (mAnimationDirection == DIRECTION_SHOW && !mCancelled) {
- ImeTracker.forLogging().onShown(mStatsToken);
+ ImeTracker.get().onShown(mStatsToken);
} else if (mCancelled) {
- ImeTracker.forLogging().onCancelled(mStatsToken,
+ ImeTracker.get().onCancelled(mStatsToken,
ImeTracker.PHASE_WM_ANIMATION_RUNNING);
}
if (DEBUG_IME_VISIBILITY) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
index 9bdda14cf00b..8759301f695b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
@@ -162,12 +162,10 @@ public class DisplayInsetsController implements DisplayController.OnDisplaysChan
@Nullable ImeTracker.Token statsToken) {
CopyOnWriteArrayList<OnInsetsChangedListener> listeners = mListeners.get(mDisplayId);
if (listeners == null) {
- ImeTracker.forLogging().onFailed(
- statsToken, ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROLLER);
+ ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROLLER);
return;
}
- ImeTracker.forLogging().onProgress(
- statsToken, ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROLLER);
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROLLER);
for (OnInsetsChangedListener listener : listeners) {
listener.showInsets(types, fromIme, statsToken);
}
@@ -177,12 +175,10 @@ public class DisplayInsetsController implements DisplayController.OnDisplaysChan
@Nullable ImeTracker.Token statsToken) {
CopyOnWriteArrayList<OnInsetsChangedListener> listeners = mListeners.get(mDisplayId);
if (listeners == null) {
- ImeTracker.forLogging().onFailed(
- statsToken, ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROLLER);
+ ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROLLER);
return;
}
- ImeTracker.forLogging().onProgress(
- statsToken, ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROLLER);
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROLLER);
for (OnInsetsChangedListener listener : listeners) {
listener.hideInsets(types, fromIme, statsToken);
}
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml b/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml
index 06df9568e01a..4721741611cf 100644
--- a/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml
@@ -46,7 +46,7 @@
<uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
<!-- Allow the test to write directly to /sdcard/ -->
- <application android:requestLegacyExternalStorage="true">
+ <application android:requestLegacyExternalStorage="true" android:largeHeap="true">
<uses-library android:name="android.test.runner"/>
<service android:name=".NotificationListener"
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
index 8a694f770ab0..7aa40e708850 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
@@ -70,6 +70,18 @@ open class EnterPipOnUserLeaveHintTest(flicker: FlickerTest) : EnterPipTest(flic
@Presubmit
@Test
+ override fun pipAppWindowAlwaysVisible() {
+ // In gestural nav the pip will first move behind home and then above home. The visual
+ // appearance visible->invisible->visible is asserted by pipAppLayerAlwaysVisible().
+ // But the internal states of activity don't need to follow that, such as a temporary
+ // visibility state can be changed quickly outside a transaction so the test doesn't
+ // detect that. Hence, skip the case to avoid restricting the internal implementation.
+ Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
+ super.pipAppWindowAlwaysVisible()
+ }
+
+ @Presubmit
+ @Test
override fun pipAppLayerAlwaysVisible() {
if (!flicker.scenario.isGesturalNavigation) super.pipAppLayerAlwaysVisible()
else {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt
new file mode 100644
index 000000000000..e13344390584
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.pip
+
+import android.platform.test.annotations.Postsubmit
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import com.android.server.wm.traces.common.service.PlatformConsts
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/** Test minimizing a pip window via pinch in gesture. */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class PipPinchInTest(flicker: FlickerTest) : PipTransition(flicker) {
+ override val transition: FlickerBuilder.() -> Unit
+ get() = buildTransition { transitions { pipApp.pinchInPipWindow(wmHelper, 0.4f, 30) } }
+
+ /** Checks that the visible region area of [pipApp] always decreases during the animation. */
+ @Postsubmit
+ @Test
+ fun pipLayerAreaDecreases() {
+ flicker.assertLayers {
+ val pipLayerList = this.layers { pipApp.layerMatchesAnyOf(it) && it.isVisible }
+ pipLayerList.zipWithNext { previous, current ->
+ current.visibleRegion.notBiggerThan(previous.visibleRegion.region)
+ }
+ }
+ }
+
+ companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+ )
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
index ce31aacd88bb..415c27075a85 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
@@ -18,7 +18,7 @@ package com.android.wm.shell.flicker.pip
import android.app.Instrumentation
import android.content.Intent
-import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
import com.android.server.wm.flicker.FlickerBuilder
import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.helpers.PipAppHelper
@@ -80,7 +80,7 @@ abstract class PipTransition(flicker: FlickerTest) : BaseTest(flicker) {
}
}
- @Postsubmit
+ @Presubmit
@Test
fun hasAtMostOnePipDismissOverlayWindow() {
val matcher = ComponentNameMatcher("", "pip-dismiss-overlay")
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
index c08ad697b6ac..70a1523d5051 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
@@ -16,6 +16,7 @@
package com.android.wm.shell.flicker.splitscreen
+import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Presubmit
import androidx.test.filters.RequiresDevice
@@ -136,7 +137,7 @@ class CopyContentInSplit(flicker: FlickerTest) : SplitScreenBase(flicker) {
}
/** {@inheritDoc} */
- @Presubmit
+ @FlakyTest(bugId = 264241018)
@Test
override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
super.visibleWindowsShownMoreThanOneConsecutiveEntry()
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index 6affc6a81685..b0896daee2a1 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -138,7 +138,9 @@ bool Properties::load() {
skpCaptureEnabled = debuggingEnabled && base::GetBoolProperty(PROPERTY_CAPTURE_SKP_ENABLED, false);
SkAndroidFrameworkTraceUtil::setEnableTracing(
- base::GetBoolProperty(PROPERTY_SKIA_ATRACE_ENABLED, false));
+ base::GetBoolProperty(PROPERTY_SKIA_TRACING_ENABLED, false));
+ SkAndroidFrameworkTraceUtil::setUsePerfettoTrackEvents(
+ base::GetBoolProperty(PROPERTY_SKIA_USE_PERFETTO_TRACK_EVENTS, false));
runningInEmulator = base::GetBoolProperty(PROPERTY_IS_EMULATOR, false);
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index 96a517629eaa..ed7175e140e4 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -143,9 +143,32 @@ enum DebugLevel {
#define PROPERTY_CAPTURE_SKP_ENABLED "debug.hwui.capture_skp_enabled"
/**
- * Allows to record Skia drawing commands with systrace.
+ * Allows broad recording of Skia drawing commands.
+ *
+ * If disabled, a very minimal set of trace events *may* be recorded.
+ * If enabled, a much broader set of trace events *may* be recorded.
+ *
+ * In either case, trace events are only recorded if an appropriately configured tracing session is
+ * active.
+ *
+ * Use debug.hwui.skia_use_perfetto_track_events to determine if ATrace (default) or Perfetto is
+ * used as the tracing backend.
+ */
+#define PROPERTY_SKIA_TRACING_ENABLED "debug.hwui.skia_tracing_enabled"
+
+/**
+ * Switches Skia's tracing to use Perfetto's Track Event system instead of ATrace.
+ *
+ * If disabled, ATrace will be used by default, which will record trace events from any of Skia's
+ * tracing categories if overall system tracing is active and the "gfx" and "view" ATrace categories
+ * are enabled.
+ *
+ * If enabled, then Perfetto's Track Event system will be used instead, which will only record if an
+ * active Perfetto tracing session is targeting the correct apps and Skia tracing categories with
+ * the Track Event data source enabled. This approach may be used to selectively filter out
+ * undesired Skia tracing categories, and events will contain more data fields.
*/
-#define PROPERTY_SKIA_ATRACE_ENABLED "debug.hwui.skia_atrace_enabled"
+#define PROPERTY_SKIA_USE_PERFETTO_TRACK_EVENTS "debug.hwui.skia_use_perfetto_track_events"
/**
* Defines how many frames in a sequence to capture.
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index e1030b0faf8e..cb385d45dc5e 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -34,7 +34,6 @@
#include "SkImageFilter.h"
#include "SkImageInfo.h"
#include "SkLatticeIter.h"
-#include "SkMath.h"
#include "SkPaint.h"
#include "SkPicture.h"
#include "SkRRect.h"
diff --git a/libs/hwui/SkiaInterpolator.cpp b/libs/hwui/SkiaInterpolator.cpp
index 47bd0b96ebd3..b58f517834a3 100644
--- a/libs/hwui/SkiaInterpolator.cpp
+++ b/libs/hwui/SkiaInterpolator.cpp
@@ -16,7 +16,6 @@
#include "SkiaInterpolator.h"
-#include "include/core/SkMath.h"
#include "include/core/SkScalar.h"
#include "include/core/SkTypes.h"
#include "include/private/SkFixed.h"
diff --git a/libs/hwui/jni/BitmapFactory.cpp b/libs/hwui/jni/BitmapFactory.cpp
index c98b87a5b924..2f3e9bf29c81 100644
--- a/libs/hwui/jni/BitmapFactory.cpp
+++ b/libs/hwui/jni/BitmapFactory.cpp
@@ -14,7 +14,6 @@
#include "SkColorSpace.h"
#include "SkEncodedImageFormat.h"
#include "SkImageInfo.h"
-#include "SkMath.h"
#include "SkPaint.h"
#include "SkPixelRef.h"
#include "SkRect.h"
diff --git a/libs/hwui/jni/Graphics.cpp b/libs/hwui/jni/Graphics.cpp
index c8358497ad62..ae2e974e641d 100644
--- a/libs/hwui/jni/Graphics.cpp
+++ b/libs/hwui/jni/Graphics.cpp
@@ -14,7 +14,6 @@
#include "SkColorSpace.h"
#include "SkFontMetrics.h"
#include "SkImageInfo.h"
-#include "SkMath.h"
#include "SkPixelRef.h"
#include "SkPoint.h"
#include "SkRect.h"
diff --git a/libs/usb/tests/AccessoryChat/src/com/android/accessorychat/AccessoryChat.java b/libs/usb/tests/AccessoryChat/src/com/android/accessorychat/AccessoryChat.java
index 18cfce528205..c019a8ce0b44 100644
--- a/libs/usb/tests/AccessoryChat/src/com/android/accessorychat/AccessoryChat.java
+++ b/libs/usb/tests/AccessoryChat/src/com/android/accessorychat/AccessoryChat.java
@@ -83,7 +83,9 @@ public class AccessoryChat extends Activity implements Runnable, TextView.OnEdit
super.onCreate(savedInstanceState);
mUsbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
- mPermissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), PendingIntent.FLAG_MUTABLE_UNAUDITED);
+ mPermissionIntent = PendingIntent.getBroadcast(this, 0,
+ new Intent(ACTION_USB_PERMISSION).setPackage(this.getPackageName()),
+ PendingIntent.FLAG_MUTABLE);
IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
registerReceiver(mUsbReceiver, filter);
diff --git a/location/java/android/location/GnssAntennaInfo.java b/location/java/android/location/GnssAntennaInfo.java
index ce73be1ec11f..3558dd501d20 100644
--- a/location/java/android/location/GnssAntennaInfo.java
+++ b/location/java/android/location/GnssAntennaInfo.java
@@ -347,8 +347,8 @@ public final class GnssAntennaInfo implements Parcelable {
@Override
public String toString() {
return "SphericalCorrections{"
- + "Corrections=" + Arrays.toString(mCorrections)
- + ", CorrectionUncertainties=" + Arrays.toString(mCorrectionUncertainties)
+ + "Corrections=" + Arrays.deepToString(mCorrections)
+ + ", CorrectionUncertainties=" + Arrays.deepToString(mCorrectionUncertainties)
+ ", DeltaTheta=" + getDeltaTheta()
+ ", DeltaPhi=" + getDeltaPhi()
+ '}';
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 24c5b4172732..3fbada77667a 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -106,7 +106,6 @@ public class AudioManager {
private Context mOriginalContext;
private Context mApplicationContext;
private @Nullable VirtualDeviceManager mVirtualDeviceManager; // Lazy initialized.
- private long mVolumeKeyUpTime;
private static final String TAG = "AudioManager";
private static final boolean DEBUG = false;
private static final AudioPortEventHandler sAudioPortEventHandler = new AudioPortEventHandler();
@@ -911,7 +910,7 @@ public class AudioManager {
int keyCode = event.getKeyCode();
if (keyCode != KeyEvent.KEYCODE_VOLUME_DOWN && keyCode != KeyEvent.KEYCODE_VOLUME_UP
&& keyCode != KeyEvent.KEYCODE_VOLUME_MUTE
- && mVolumeKeyUpTime + AudioSystem.PLAY_SOUND_DELAY > SystemClock.uptimeMillis()) {
+ && AudioSystem.PLAY_SOUND_DELAY > SystemClock.uptimeMillis()) {
/*
* The user has hit another key during the delay (e.g., 300ms)
* since the last volume key up, so cancel any sounds.
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index fa74a9f12d7c..a7959c5dabdd 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -1515,6 +1515,16 @@ public final class MediaRouter2 {
}
/**
+ * Returns the current {@link RoutingSessionInfo} associated to this controller.
+ */
+ @NonNull
+ public RoutingSessionInfo getRoutingSessionInfo() {
+ synchronized (mControllerLock) {
+ return mSessionInfo;
+ }
+ }
+
+ /**
* Gets the information about how volume is handled on the session.
*
* <p>Please note that you may not control the volume of the session even when you can
@@ -1873,13 +1883,6 @@ public final class MediaRouter2 {
return result.toString();
}
- @NonNull
- RoutingSessionInfo getRoutingSessionInfo() {
- synchronized (mControllerLock) {
- return mSessionInfo;
- }
- }
-
void setRoutingSessionInfo(@NonNull RoutingSessionInfo info) {
synchronized (mControllerLock) {
mSessionInfo = info;
diff --git a/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java
index 1e270b14a5e1..40a4858d30ba 100644
--- a/media/java/android/media/RingtoneManager.java
+++ b/media/java/android/media/RingtoneManager.java
@@ -827,16 +827,18 @@ public class RingtoneManager {
ringtoneUri = ContentProvider.maybeAddUserId(ringtoneUri, context.getUserId());
}
- final String mimeType = resolver.getType(ringtoneUri);
- if (mimeType == null) {
- Log.e(TAG, "setActualDefaultRingtoneUri for URI:" + ringtoneUri
- + " ignored: failure to find mimeType (no access from this context?)");
- return;
- }
- if (!(mimeType.startsWith("audio/") || mimeType.equals("application/ogg"))) {
- Log.e(TAG, "setActualDefaultRingtoneUri for URI:" + ringtoneUri
- + " ignored: associated mimeType:" + mimeType + " is not an audio type");
- return;
+ if (ringtoneUri != null) {
+ final String mimeType = resolver.getType(ringtoneUri);
+ if (mimeType == null) {
+ Log.e(TAG, "setActualDefaultRingtoneUri for URI:" + ringtoneUri
+ + " ignored: failure to find mimeType (no access from this context?)");
+ return;
+ }
+ if (!(mimeType.startsWith("audio/") || mimeType.equals("application/ogg"))) {
+ Log.e(TAG, "setActualDefaultRingtoneUri for URI:" + ringtoneUri
+ + " ignored: associated mimeType:" + mimeType + " is not an audio type");
+ return;
+ }
}
Settings.System.putStringForUser(resolver, setting,
diff --git a/omapi/OWNERS b/omapi/OWNERS
index 5682fd3281f4..1dce1e0629c4 100644
--- a/omapi/OWNERS
+++ b/omapi/OWNERS
@@ -1,5 +1,6 @@
# Bug component: 456592
-zachoverflow@google.com
+sattiraju@google.com
+henrichataing@google.com
alisher@google.com
jackcwyu@google.com
diff --git a/omapi/java/android/se/OWNERS b/omapi/java/android/se/OWNERS
index 5682fd3281f4..1dce1e0629c4 100644
--- a/omapi/java/android/se/OWNERS
+++ b/omapi/java/android/se/OWNERS
@@ -1,5 +1,6 @@
# Bug component: 456592
-zachoverflow@google.com
+sattiraju@google.com
+henrichataing@google.com
alisher@google.com
jackcwyu@google.com
diff --git a/omapi/java/android/se/omapi/OWNERS b/omapi/java/android/se/omapi/OWNERS
index 5682fd3281f4..1dce1e0629c4 100644
--- a/omapi/java/android/se/omapi/OWNERS
+++ b/omapi/java/android/se/omapi/OWNERS
@@ -1,5 +1,6 @@
# Bug component: 456592
-zachoverflow@google.com
+sattiraju@google.com
+henrichataing@google.com
alisher@google.com
jackcwyu@google.com
diff --git a/packages/CarrierDefaultApp/AndroidManifest.xml b/packages/CarrierDefaultApp/AndroidManifest.xml
index 3f86aba26a1f..8f9730a9651b 100644
--- a/packages/CarrierDefaultApp/AndroidManifest.xml
+++ b/packages/CarrierDefaultApp/AndroidManifest.xml
@@ -34,8 +34,7 @@
android:label="@string/app_name"
android:directBootAware="true"
android:usesCleartextTraffic="true"
- android:icon="@mipmap/ic_launcher_android"
- android:debuggable="true">
+ android:icon="@mipmap/ic_launcher_android">
<receiver android:name="com.android.carrierdefaultapp.CarrierDefaultBroadcastReceiver"
android:exported="true">
<intent-filter>
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index 81505e1393cc..d69097199712 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -11,13 +11,34 @@
<string name="string_continue">Continue</string>
<!-- This is a label for a button that links to different places where the user can save their passkeys. [CHAR LIMIT=20] -->
<string name="string_more_options">More options</string>
+ <!-- This is a label for a button that links to additional information about passkeys. [CHAR LIMIT=20] -->
+ <string name="string_learn_more">Learn more</string>
<!-- This string introduces passkeys to the users for the first time they use this method. Tip: to avoid gendered language patterns, this header could be translated as if the original string were "More safety with passkeys". [CHAR LIMIT=200] -->
<string name="passkey_creation_intro_title">Safer with passkeys</string>
- <!-- These strings highlight passkey benefits. [CHAR LIMIT=200] -->
+ <!-- This string highlight passkey benefits related with the password. [CHAR LIMIT=200] -->
<string name="passkey_creation_intro_body_password">With passkeys, you don’t need to create or remember complex passwords</string>
+ <!-- This string highlight passkey benefits related with encrypted. [CHAR LIMIT=200] -->
<string name="passkey_creation_intro_body_fingerprint">Passkeys are encrypted digital keys you create using your fingerprint, face, or screen lock</string>
+ <!-- This string highlight passkey benefits related with signing with other devices. [CHAR LIMIT=200] -->
<string name="passkey_creation_intro_body_device">They are saved to a password manager, so you can sign in on other devices</string>
-
+ <!-- This string introduces passkeys in more detail to the users for the first time they use this method. [CHAR LIMIT=200] -->
+ <string name="more_about_passkeys_title">More about passkeys</string>
+ <!-- Title for subsection of "Learn more about passkeys" screen about passwordless technology. [CHAR LIMIT=80] -->
+ <string name="passwordless_technology_title">Passwordless technology</string>
+ <!-- Detail for subsection of "Learn more about passkeys" screen about passwordless technology. [CHAR LIMIT=500] -->
+ <string name="passwordless_technology_detail">Passkeys allow you to sign in without relying on passwords. You just need to use your fingerprint, face recognition, PIN, or swipe pattern to verify your identity and create a passkey.</string>
+ <!-- Title for subsection of "Learn more about passkeys" screen about public key cryptography. [CHAR LIMIT=80] -->
+ <string name="public_key_cryptography_title">Public key cryptography</string>
+ <!-- Detail for subsection of "Learn more about passkeys" screen about public key cryptography. [CHAR LIMIT=500] -->
+ <string name="public_key_cryptography_detail">Based on FIDO Alliance (which includes Google, Apple, Microsoft, and more) and W3C standards, passkeys use cryptographic key pairs. Unlike the username and string of characters we use for passwords, a private-public key pair is created for an app or website. The private key is safely stored on your device or password manager and it confirms your identity. The public key is shared with the app or website server. With corresponding keys, you can instantly register and sign in.</string>
+ <!-- Title for subsection of "Learn more about passkeys" screen about improved account security. [CHAR LIMIT=80] -->
+ <string name="improved_account_security_title">Improved account security</string>
+ <!-- Detail for subsection of "Learn more about passkeys" screen about improved account security. [CHAR LIMIT=500] -->
+ <string name="improved_account_security_detail">Each key is exclusively linked with the app or website they were created for, so you can never sign in to a fraudulent app or website by mistake. Plus, with servers only keeping public keys, hacking is a lot harder.</string>
+ <!-- Title for subsection of "Learn more about passkeys" screen about seamless transition. [CHAR LIMIT=80] -->
+ <string name="seamless_transition_title">Seamless transition</string>
+ <!-- Detail for subsection of "Learn more about passkeys" screen about seamless transition. [CHAR LIMIT=500] -->
+ <string name="seamless_transition_detail">As we move towards a passwordless future, passwords will still be available alongside passkeys.</string>
<!-- This appears as the title of the modal bottom sheet which provides all available providers for users to choose. [CHAR LIMIT=200] -->
<string name="choose_provider_title">Choose where to save your <xliff:g id="createTypes" example="passkeys">%1$s</xliff:g></string>
<!-- This appears as the description body of the modal bottom sheet which provides all available providers for users to choose. [CHAR LIMIT=200] -->
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index 7d433648df28..a1392ba8ac13 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -414,7 +414,7 @@ class CredentialManagerRepo(
credentialData,
// TODO: populate with actual data
/*candidateQueryData=*/ Bundle(),
- /*requireSystemProvider=*/ false
+ /*isSystemProviderRequired=*/ false
),
"com.google.android.youtube"
)
@@ -429,7 +429,7 @@ class CredentialManagerRepo(
data,
// TODO: populate with actual data
/*candidateQueryData=*/ Bundle(),
- /*requireSystemProvider=*/ false
+ /*isSystemProviderRequired=*/ false
),
"com.google.android.youtube"
)
@@ -443,7 +443,7 @@ class CredentialManagerRepo(
"other-sign-ins",
data,
/*candidateQueryData=*/ Bundle(),
- /*requireSystemProvider=*/ false
+ /*isSystemProviderRequired=*/ false
),
"com.google.android.youtube"
)
@@ -457,7 +457,7 @@ class CredentialManagerRepo(
)
.addGetCredentialOption(
GetCredentialOption(
- TYPE_PUBLIC_KEY_CREDENTIAL, Bundle(), Bundle(), /*requireSystemProvider=*/ false)
+ TYPE_PUBLIC_KEY_CREDENTIAL, Bundle(), Bundle(), /*isSystemProviderRequired=*/ false)
)
.build(),
"com.google.android.youtube"
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index 09f9b5eaaf6a..d9e4dc85fd77 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -269,7 +269,7 @@ class CreateFlowUtils {
val createCredentialRequest = requestInfo.createCredentialRequest
val createCredentialRequestJetpack = createCredentialRequest?.let {
CreateCredentialRequest.createFrom(
- it.type, it.credentialData, it.candidateQueryData, it.requireSystemProvider()
+ it.type, it.credentialData, it.candidateQueryData, it.isSystemProviderRequired()
)
}
when (createCredentialRequestJetpack) {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
index 498f0a193af1..03d7de5457a7 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -70,7 +70,7 @@ fun CreateCredentialScreen(
when (uiState.currentScreenState) {
CreateScreenState.PASSKEY_INTRO -> ConfirmationCard(
onConfirm = viewModel::onConfirmIntro,
- onCancel = viewModel::onCancel,
+ onLearnMore = viewModel::onLearnMore,
)
CreateScreenState.PROVIDER_SELECTION -> ProviderSelectionCard(
requestDisplayInfo = uiState.requestDisplayInfo,
@@ -115,7 +115,10 @@ fun CreateCredentialScreen(
activeRemoteEntry = uiState.activeEntry?.activeEntryInfo!!,
onOptionSelected = viewModel::onEntrySelected,
onConfirm = viewModel::onConfirmEntrySelected,
- onCancel = viewModel::onCancel,
+ )
+ CreateScreenState.MORE_ABOUT_PASSKEYS_INTRO -> MoreAboutPasskeysIntroCard(
+ onBackPasskeyIntroButtonSelected =
+ viewModel::onBackPasskeyIntroButtonSelected,
)
}
} else if (uiState.selectedEntry != null && !uiState.providerActivityPending) {
@@ -136,7 +139,7 @@ fun CreateCredentialScreen(
@Composable
fun ConfirmationCard(
onConfirm: () -> Unit,
- onCancel: () -> Unit,
+ onLearnMore: () -> Unit,
) {
ContainerCard() {
Column() {
@@ -223,8 +226,8 @@ fun ConfirmationCard(
modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
) {
ActionButton(
- stringResource(R.string.string_cancel),
- onClick = onCancel
+ stringResource(R.string.string_learn_more),
+ onClick = onLearnMore
)
ConfirmButton(
stringResource(R.string.string_continue),
@@ -396,8 +399,7 @@ fun MoreOptionsSelectionCard(
)
}
},
- colors = TopAppBarDefaults.smallTopAppBarColors
- (containerColor = Color.Transparent),
+ colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent),
modifier = Modifier.padding(top = 12.dp)
)
Divider(
@@ -634,7 +636,6 @@ fun ExternalOnlySelectionCard(
activeRemoteEntry: EntryInfo,
onOptionSelected: (EntryInfo) -> Unit,
onConfirm: () -> Unit,
- onCancel: () -> Unit,
) {
ContainerCard() {
Column() {
@@ -673,13 +674,9 @@ fun ExternalOnlySelectionCard(
color = Color.Transparent
)
Row(
- horizontalArrangement = Arrangement.SpaceBetween,
+ horizontalArrangement = Arrangement.End,
modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
) {
- ActionButton(
- stringResource(R.string.string_cancel),
- onClick = onCancel
- )
ConfirmButton(
stringResource(R.string.string_continue),
onClick = onConfirm
@@ -696,6 +693,92 @@ fun ExternalOnlySelectionCard(
@OptIn(ExperimentalMaterial3Api::class)
@Composable
+fun MoreAboutPasskeysIntroCard(
+ onBackPasskeyIntroButtonSelected: () -> Unit,
+) {
+ ContainerCard() {
+ Column() {
+ TopAppBar(
+ title = {
+ TextOnSurface(
+ text =
+ stringResource(
+ R.string.more_about_passkeys_title),
+ style = MaterialTheme.typography.titleMedium,
+ )
+ },
+ navigationIcon = {
+ IconButton(
+ onClick = onBackPasskeyIntroButtonSelected
+ ) {
+ Icon(
+ Icons.Filled.ArrowBack,
+ stringResource(R.string.accessibility_back_arrow_button)
+ )
+ }
+ },
+ colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent),
+ modifier = Modifier.padding(top = 12.dp)
+ )
+ Column(
+ modifier = Modifier.fillMaxWidth().padding(start = 24.dp, end = 68.dp)
+ ) {
+ TextOnSurfaceVariant(
+ text = stringResource(R.string.passwordless_technology_title),
+ style = MaterialTheme.typography.titleLarge,
+ )
+ TextSecondary(
+ text = stringResource(R.string.passwordless_technology_detail),
+ style = MaterialTheme.typography.bodyMedium,
+ )
+ Divider(
+ thickness = 24.dp,
+ color = Color.Transparent
+ )
+ TextOnSurfaceVariant(
+ text = stringResource(R.string.public_key_cryptography_title),
+ style = MaterialTheme.typography.titleLarge,
+ )
+ TextSecondary(
+ text = stringResource(R.string.public_key_cryptography_detail),
+ style = MaterialTheme.typography.bodyMedium,
+ )
+ Divider(
+ thickness = 24.dp,
+ color = Color.Transparent
+ )
+ TextOnSurfaceVariant(
+ text = stringResource(R.string.improved_account_security_title),
+ style = MaterialTheme.typography.titleLarge,
+ )
+ TextSecondary(
+ text = stringResource(R.string.improved_account_security_detail),
+ style = MaterialTheme.typography.bodyMedium,
+ )
+ Divider(
+ thickness = 24.dp,
+ color = Color.Transparent
+ )
+ TextOnSurfaceVariant(
+ text = stringResource(R.string.seamless_transition_title),
+ style = MaterialTheme.typography.titleLarge,
+ )
+ TextSecondary(
+ text = stringResource(R.string.seamless_transition_detail),
+ style = MaterialTheme.typography.bodyMedium,
+ )
+ }
+ Divider(
+ thickness = 18.dp,
+ color = Color.Transparent,
+ modifier = Modifier.padding(bottom = 24.dp)
+ )
+ }
+ }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
fun PrimaryCreateOptionRow(
requestDisplayInfo: RequestDisplayInfo,
entryInfo: EntryInfo,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt
index ac84503583a8..7a04a8e0e045 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt
@@ -122,6 +122,12 @@ class CreateCredentialViewModel(
)
}
+ fun onBackPasskeyIntroButtonSelected() {
+ uiState = uiState.copy(
+ currentScreenState = CreateScreenState.PASSKEY_INTRO,
+ )
+ }
+
fun onEntrySelectedFromMoreOptionScreen(activeEntry: ActiveEntry) {
uiState = uiState.copy(
currentScreenState = if (
@@ -150,6 +156,12 @@ class CreateCredentialViewModel(
dialogResult.tryEmit(DialogResult(ResultState.NORMAL_CANCELED))
}
+ fun onLearnMore() {
+ uiState = uiState.copy(
+ currentScreenState = CreateScreenState.MORE_ABOUT_PASSKEYS_INTRO,
+ )
+ }
+
fun onChangeDefaultSelected() {
uiState = uiState.copy(
currentScreenState = CreateScreenState.CREATION_OPTION_SELECTION,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
index 97477a75f8bf..957488ffcbcc 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
@@ -91,6 +91,7 @@ data class ActiveEntry (
/** The name of the current screen. */
enum class CreateScreenState {
PASSKEY_INTRO,
+ MORE_ABOUT_PASSKEYS_INTRO,
PROVIDER_SELECTION,
CREATION_OPTION_SELECTION,
MORE_OPTIONS_SELECTION,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index 03f39e1390c7..1cc1ac5c6913 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -63,6 +63,7 @@ import com.android.credentialmanager.common.material.ModalBottomSheetLayout
import com.android.credentialmanager.common.material.ModalBottomSheetValue
import com.android.credentialmanager.common.material.rememberModalBottomSheetState
import com.android.credentialmanager.common.ui.ActionButton
+import com.android.credentialmanager.common.ui.ConfirmButton
import com.android.credentialmanager.common.ui.Entry
import com.android.credentialmanager.common.ui.TextOnSurface
import com.android.credentialmanager.common.ui.TextSecondary
@@ -95,7 +96,10 @@ fun GetCredentialScreen(
PrimarySelectionCard(
requestDisplayInfo = uiState.requestDisplayInfo,
providerDisplayInfo = uiState.providerDisplayInfo,
+ providerInfoList = uiState.providerInfoList,
+ activeEntry = uiState.activeEntry,
onEntrySelected = viewModel::onEntrySelected,
+ onConfirm = viewModel::onConfirmEntrySelected,
onMoreOptionSelected = viewModel::onMoreOptionSelected,
)
} else {
@@ -133,7 +137,10 @@ fun GetCredentialScreen(
fun PrimarySelectionCard(
requestDisplayInfo: RequestDisplayInfo,
providerDisplayInfo: ProviderDisplayInfo,
+ providerInfoList: List<ProviderInfo>,
+ activeEntry: EntryInfo?,
onEntrySelected: (EntryInfo) -> Unit,
+ onConfirm: () -> Unit,
onMoreOptionSelected: () -> Unit,
) {
val sortedUserNameToCredentialEntryList =
@@ -217,13 +224,33 @@ fun PrimarySelectionCard(
thickness = 24.dp,
color = Color.Transparent
)
+ var totalEntriesCount = sortedUserNameToCredentialEntryList
+ .flatMap{ it.sortedCredentialEntryList}.size + authenticationEntryList
+ .size + providerInfoList.flatMap { it.actionEntryList }.size
+ if (providerDisplayInfo.remoteEntry != null) totalEntriesCount += 1
+ // Row horizontalArrangement differs on only one actionButton(should place on most
+ // left)/only one confirmButton(should place on most right)/two buttons exist the same
+ // time(should be one on the left, one on the right)
Row(
- horizontalArrangement = Arrangement.SpaceBetween,
+ horizontalArrangement =
+ if (totalEntriesCount <= 1 && activeEntry != null) Arrangement.End
+ else if (totalEntriesCount > 1 && activeEntry == null) Arrangement.Start
+ else Arrangement.SpaceBetween,
modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
) {
- ActionButton(
- stringResource(R.string.get_dialog_use_saved_passkey_for),
- onMoreOptionSelected)
+ if (totalEntriesCount > 1) {
+ ActionButton(
+ stringResource(R.string.get_dialog_use_saved_passkey_for),
+ onMoreOptionSelected
+ )
+ }
+ // Only one sign-in options exist
+ if (activeEntry != null) {
+ ConfirmButton(
+ stringResource(R.string.string_continue),
+ onClick = onConfirm
+ )
+ }
}
Divider(
thickness = 18.dp,
@@ -297,6 +324,12 @@ fun AllSignInOptionCard(
)
}
}
+ item {
+ Divider(
+ thickness = 8.dp,
+ color = Color.Transparent,
+ )
+ }
// From another device
val remoteEntry = providerDisplayInfo.remoteEntry
if (remoteEntry != null) {
@@ -307,6 +340,13 @@ fun AllSignInOptionCard(
)
}
}
+ item {
+ Divider(
+ thickness = 1.dp,
+ color = Color.LightGray,
+ modifier = Modifier.padding(top = 16.dp)
+ )
+ }
// Manage sign-ins (action chips)
item {
ActionChips(
@@ -335,7 +375,7 @@ fun ActionChips(
TextSecondary(
text = stringResource(R.string.get_dialog_heading_manage_sign_ins),
- style = MaterialTheme.typography.labelLarge,
+ style = MaterialTheme.typography.titleLarge,
modifier = Modifier.padding(vertical = 8.dp)
)
// TODO: tweak padding.
@@ -343,7 +383,7 @@ fun ActionChips(
modifier = Modifier.fillMaxWidth().wrapContentHeight(),
shape = MaterialTheme.shapes.medium,
) {
- Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
+ Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
actionChips.forEach {
ActionEntryRow(it, onEntrySelected)
}
@@ -358,7 +398,7 @@ fun RemoteEntryCard(
) {
TextSecondary(
text = stringResource(R.string.get_dialog_heading_from_another_device),
- style = MaterialTheme.typography.labelLarge,
+ style = MaterialTheme.typography.titleLarge,
modifier = Modifier.padding(vertical = 8.dp)
)
ContainerCard(
@@ -376,7 +416,7 @@ fun RemoteEntryCard(
painter = painterResource(R.drawable.ic_other_devices),
contentDescription = null,
tint = Color.Unspecified,
- modifier = Modifier.padding(start = 18.dp)
+ modifier = Modifier.padding(start = 16.dp)
)
},
label = {
@@ -427,7 +467,7 @@ fun PerUserNameCredentials(
text = stringResource(
R.string.get_dialog_heading_for_username, perUserNameCredentialEntryList.userName
),
- style = MaterialTheme.typography.labelLarge,
+ style = MaterialTheme.typography.titleLarge,
modifier = Modifier.padding(vertical = 8.dp)
)
ContainerCard(
@@ -554,7 +594,7 @@ fun ActionEntryRow(
TransparentBackgroundEntry(
icon = {
Image(
- modifier = Modifier.padding(start = 10.dp).size(32.dp),
+ modifier = Modifier.padding(start = 10.dp).size(24.dp),
bitmap = actionEntryInfo.icon.toBitmap().asImageBitmap(),
// TODO: add description.
contentDescription = ""
@@ -565,13 +605,13 @@ fun ActionEntryRow(
TextOnSurfaceVariant(
text = actionEntryInfo.title,
style = MaterialTheme.typography.titleLarge,
- modifier = Modifier.padding(start = 5.dp),
+ modifier = Modifier.padding(start = 8.dp),
)
if (actionEntryInfo.subTitle != null) {
TextSecondary(
text = actionEntryInfo.subTitle,
style = MaterialTheme.typography.bodyMedium,
- modifier = Modifier.padding(start = 5.dp),
+ modifier = Modifier.padding(start = 8.dp),
)
}
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt
index 6f0f76b72e50..9859c89b29d4 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt
@@ -41,6 +41,7 @@ data class GetCredentialUiState(
val currentScreenState: GetScreenState = toGetScreenState(providerInfoList),
val providerDisplayInfo: ProviderDisplayInfo = toProviderDisplayInfo(providerInfoList),
val selectedEntry: EntryInfo? = null,
+ val activeEntry: EntryInfo? = toActiveEntry(providerDisplayInfo),
val hidden: Boolean = false,
val providerActivityPending: Boolean = false,
val isNoAccount: Boolean = false,
@@ -73,6 +74,17 @@ class GetCredentialViewModel(private val credManRepo: CredentialManagerRepo) : V
}
}
+ fun onConfirmEntrySelected() {
+ val activeEntry = uiState.activeEntry
+ if (activeEntry != null) {
+ onEntrySelected(activeEntry)
+ } else {
+ Log.w("Account Selector",
+ "Illegal state: confirm is pressed but activeEntry isn't set.")
+ dialogResult.tryEmit(DialogResult(ResultState.COMPLETE))
+ }
+ }
+
fun launchProviderUi(
launcher: ManagedActivityResultLauncher<IntentSenderRequest, ActivityResult>
) {
@@ -198,6 +210,26 @@ private fun toProviderDisplayInfo(
)
}
+private fun toActiveEntry(
+ providerDisplayInfo: ProviderDisplayInfo,
+): EntryInfo? {
+ val sortedUserNameToCredentialEntryList =
+ providerDisplayInfo.sortedUserNameToCredentialEntryList
+ val authenticationEntryList = providerDisplayInfo.authenticationEntryList
+ var activeEntry: EntryInfo? = null
+ if (sortedUserNameToCredentialEntryList
+ .size == 1 && authenticationEntryList.isEmpty()
+ ) {
+ activeEntry = sortedUserNameToCredentialEntryList.first().sortedCredentialEntryList.first()
+ } else if (
+ sortedUserNameToCredentialEntryList
+ .isEmpty() && authenticationEntryList.size == 1
+ ) {
+ activeEntry = authenticationEntryList.first()
+ }
+ return activeEntry
+}
+
private fun toGetScreenState(
providerInfoList: List<ProviderInfo>
): GetScreenState {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialRequest.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialRequest.kt
index 18d5089828d6..5cb8d3b7bdde 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialRequest.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialRequest.kt
@@ -66,7 +66,7 @@ class GetCredentialRequest constructor(
it.type,
it.credentialRetrievalData,
it.candidateQueryData,
- it.requireSystemProvider()
+ it.isSystemProviderRequired()
)
}
)
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
index 21f4be0004f4..1bbdad5ddfc8 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
@@ -275,8 +275,11 @@ public class UninstallAlertDialogFragment extends DialogFragment implements
}
/**
- * Returns whether there is only one user on this device, not including
- * the system-only user.
+ * Returns whether there is only one "full" user on this device.
+ *
+ * <p><b>Note:</b> on devices that use {@link android.os.UserManager#isHeadlessSystemUserMode()
+ * headless system user mode}, the system user is not "full", so it's not be considered in the
+ * calculation.
*/
private boolean isSingleUser(UserManager userManager) {
final int userCount = userManager.getUserCount();
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAlertFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAlertFragment.java
index 5c5720a61186..cc2e3a600a7a 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAlertFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAlertFragment.java
@@ -128,8 +128,7 @@ public class UninstallAlertFragment extends GuidedStepFragment {
}
/**
- * Returns whether there is only one user on this device, not including
- * the system-only user.
+ * Returns whether there is only one user on this device.
*/
private boolean isSingleUser(UserManager userManager) {
final int userCount = userManager.getUserCount();
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/AnnotatedStringResource.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/AnnotatedStringResource.kt
new file mode 100644
index 000000000000..9ddd0c6d2c4c
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/AnnotatedStringResource.kt
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.util
+
+import android.content.res.Resources
+import android.graphics.Typeface
+import android.text.Spanned
+import android.text.style.StyleSpan
+import android.text.style.URLSpan
+import androidx.annotation.StringRes
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.SpanStyle
+import androidx.compose.ui.text.buildAnnotatedString
+import androidx.compose.ui.text.font.FontStyle
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.Density
+
+const val URLSPAN_TAG = "URLSPAN_TAG"
+
+@Composable
+fun annotatedStringResource(@StringRes id: Int, urlSpanColor: Color): AnnotatedString {
+ LocalConfiguration.current
+ val resources = LocalContext.current.resources
+ val density = LocalDensity.current
+ return remember(id) {
+ val text = resources.getText(id)
+ spannableStringToAnnotatedString(text, density, urlSpanColor)
+ }
+}
+
+private fun spannableStringToAnnotatedString(text: CharSequence, density: Density, urlSpanColor: Color): AnnotatedString {
+ return if (text is Spanned) {
+ with(density) {
+ buildAnnotatedString {
+ append((text.toString()))
+ text.getSpans(0, text.length, Any::class.java).forEach {
+ val start = text.getSpanStart(it)
+ val end = text.getSpanEnd(it)
+ when (it) {
+ is StyleSpan ->
+ when (it.style) {
+ Typeface.NORMAL -> addStyle(
+ SpanStyle(
+ fontWeight = FontWeight.Normal,
+ fontStyle = FontStyle.Normal
+ ),
+ start,
+ end
+ )
+ Typeface.BOLD -> addStyle(
+ SpanStyle(
+ fontWeight = FontWeight.Bold,
+ fontStyle = FontStyle.Normal
+ ),
+ start,
+ end
+ )
+ Typeface.ITALIC -> addStyle(
+ SpanStyle(
+ fontWeight = FontWeight.Normal,
+ fontStyle = FontStyle.Italic
+ ),
+ start,
+ end
+ )
+ Typeface.BOLD_ITALIC -> addStyle(
+ SpanStyle(
+ fontWeight = FontWeight.Bold,
+ fontStyle = FontStyle.Italic
+ ),
+ start,
+ end
+ )
+ }
+ is URLSpan -> {
+ addStyle(
+ SpanStyle(
+ color = urlSpanColor,
+ ),
+ start,
+ end
+ )
+ if (!it.url.isNullOrEmpty()) {
+ addStringAnnotation(
+ URLSPAN_TAG,
+ it.url,
+ start,
+ end
+ )
+ }
+ }
+ else -> addStyle(SpanStyle(), start, end)
+ }
+ }
+ }
+ }
+ } else {
+ AnnotatedString(text.toString())
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Footer.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Footer.kt
index 296cf3ba609f..d9d3b37a08cf 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Footer.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Footer.kt
@@ -34,15 +34,22 @@ import com.android.settingslib.spa.framework.theme.SettingsTheme
@Composable
fun Footer(footerText: String) {
if (footerText.isEmpty()) return
+ Footer {
+ SettingsBody(footerText)
+ }
+}
+
+@Composable
+fun Footer(content: @Composable () -> Unit) {
Column(Modifier.padding(SettingsDimension.itemPadding)) {
Icon(
- imageVector = Icons.Outlined.Info,
- contentDescription = null,
- modifier = Modifier.size(SettingsDimension.itemIconSize),
- tint = MaterialTheme.colorScheme.onSurfaceVariant,
+ imageVector = Icons.Outlined.Info,
+ contentDescription = null,
+ modifier = Modifier.size(SettingsDimension.itemIconSize),
+ tint = MaterialTheme.colorScheme.onSurfaceVariant,
)
Spacer(modifier = Modifier.height(SettingsDimension.itemPaddingVertical))
- SettingsBody(footerText)
+ content()
}
}
diff --git a/packages/SettingsLib/Spa/tests/res/values/strings.xml b/packages/SettingsLib/Spa/tests/res/values/strings.xml
index 1ca425c26f0a..cbfea060d157 100644
--- a/packages/SettingsLib/Spa/tests/res/values/strings.xml
+++ b/packages/SettingsLib/Spa/tests/res/values/strings.xml
@@ -25,4 +25,6 @@
=1 {There is one song found in {place}.}
other {There are # songs found in {place}.}
}</string>
+
+ <string name="test_annotated_string_resource">Annotated string with <b>bold</b> and <a href="https://www.google.com/">link</a>.</string>
</resources>
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/AnnotatedStringResourceTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/AnnotatedStringResourceTest.kt
new file mode 100644
index 000000000000..b65be42f50fe
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/AnnotatedStringResourceTest.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.util
+
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.SpanStyle
+import androidx.compose.ui.text.font.FontStyle
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.framework.util.URLSPAN_TAG
+import com.android.settingslib.spa.framework.util.annotatedStringResource
+import com.android.settingslib.spa.test.R
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class AnnotatedStringResourceTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ @Test
+ fun testAnnotatedStringResource() {
+ composeTestRule.setContent {
+ val annotatedString = annotatedStringResource(R.string.test_annotated_string_resource, Color.Blue)
+
+ val annotations = annotatedString.getStringAnnotations(0, annotatedString.length)
+ assertThat(annotations).hasSize(1)
+ assertThat(annotations[0].start).isEqualTo(31)
+ assertThat(annotations[0].end).isEqualTo(35)
+ assertThat(annotations[0].tag).isEqualTo(URLSPAN_TAG)
+ assertThat(annotations[0].item).isEqualTo("https://www.google.com/")
+
+ assertThat(annotatedString.spanStyles).hasSize(2)
+ assertThat(annotatedString.spanStyles[0].start).isEqualTo(22)
+ assertThat(annotatedString.spanStyles[0].end).isEqualTo(26)
+ assertThat(annotatedString.spanStyles[0].item).isEqualTo(
+ SpanStyle(fontWeight = FontWeight.Bold, fontStyle = FontStyle.Normal))
+
+ assertThat(annotatedString.spanStyles[1].start).isEqualTo(31)
+ assertThat(annotatedString.spanStyles[1].end).isEqualTo(35)
+ assertThat(annotatedString.spanStyles[1].item).isEqualTo(SpanStyle(color = Color.Blue))
+ }
+ }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfoPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfoPage.kt
index 0b45da67eaf7..21c9e349ef93 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfoPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfoPage.kt
@@ -29,6 +29,7 @@ fun AppInfoPage(
packageName: String,
userId: Int,
footerText: String,
+ footerContent: (@Composable () -> Unit)?,
packageManagers: IPackageManagers,
content: @Composable PackageInfo.() -> Unit,
) {
@@ -40,6 +41,10 @@ fun AppInfoPage(
packageInfo.content()
- Footer(footerText)
+ if (footerContent != null) {
+ Footer(footerContent)
+ } else {
+ Footer(footerText)
+ }
}
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
index e9fcbd2cfa68..7c689c62427e 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
@@ -141,6 +141,7 @@ internal fun TogglePermissionAppListModel<out AppRecord>.TogglePermissionAppInfo
packageName = packageName,
userId = userId,
footerText = stringResource(footerResId),
+ footerContent = footerContent(),
packageManagers = packageManagers,
) {
val model = createSwitchModel(applicationInfo)
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt
index 1ab623076f0a..f4b32043b1b1 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt
@@ -20,6 +20,7 @@ import android.content.Context
import android.content.pm.ApplicationInfo
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
+import androidx.compose.ui.text.AnnotatedString
import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
import com.android.settingslib.spa.framework.common.SettingsPageProvider
import com.android.settingslib.spa.framework.compose.rememberContext
@@ -36,7 +37,10 @@ interface TogglePermissionAppListModel<T : AppRecord> {
val footerResId: Int
val switchRestrictionKeys: List<String>
get() = emptyList()
-
+ @Composable
+ fun footerContent(): (@Composable () -> Unit)? {
+ return null
+ }
/**
* Loads the extra info for the App List, and generates the [AppRecord] List.
*
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
index ae061936d05d..c0818a80c757 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
@@ -39,7 +39,7 @@ public class GlobalSettings {
*/
public static final String[] SETTINGS_TO_BACKUP = {
Settings.Global.APPLY_RAMPING_RINGER,
- Settings.Global.BUGREPORT_IN_POWER_MENU,
+ Settings.Global.BUGREPORT_IN_POWER_MENU, // moved to secure
Settings.Global.STAY_ON_WHILE_PLUGGED_IN,
Settings.Global.APP_AUTO_RESTRICTION_ENABLED,
Settings.Global.AUTO_TIME,
@@ -70,8 +70,8 @@ public class GlobalSettings {
Settings.Global.ZEN_DURATION,
Settings.Global.CHARGING_VIBRATION_ENABLED,
Settings.Global.AWARE_ALLOWED,
- Settings.Global.CUSTOM_BUGREPORT_HANDLER_APP,
- Settings.Global.CUSTOM_BUGREPORT_HANDLER_USER,
+ Settings.Global.CUSTOM_BUGREPORT_HANDLER_APP, // moved to secure
+ Settings.Global.CUSTOM_BUGREPORT_HANDLER_USER, // moved to secure
Settings.Global.DEVELOPMENT_SETTINGS_ENABLED,
Settings.Global.USER_DISABLED_HDR_FORMATS,
Settings.Global.ARE_USER_DISABLED_HDR_FORMATS_ALLOWED,
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 06c34767b183..c1330979b0f2 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -29,7 +29,7 @@ public class SecureSettings {
*/
@UnsupportedAppUsage
public static final String[] SETTINGS_TO_BACKUP = {
- Settings.Secure.BUGREPORT_IN_POWER_MENU, // moved to global
+ Settings.Secure.BUGREPORT_IN_POWER_MENU,
Settings.Secure.ALLOW_MOCK_LOCATION,
Settings.Secure.USB_MASS_STORAGE_ENABLED, // moved to global
Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED,
@@ -225,6 +225,8 @@ public class SecureSettings {
Settings.Secure.ASSIST_LONG_PRESS_HOME_ENABLED,
Settings.Secure.BLUETOOTH_LE_BROADCAST_PROGRAM_INFO,
Settings.Secure.BLUETOOTH_LE_BROADCAST_CODE,
- Settings.Secure.BLUETOOTH_LE_BROADCAST_APP_SOURCE_NAME
+ Settings.Secure.BLUETOOTH_LE_BROADCAST_APP_SOURCE_NAME,
+ Settings.Secure.CUSTOM_BUGREPORT_HANDLER_APP,
+ Settings.Secure.CUSTOM_BUGREPORT_HANDLER_USER
};
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index a1a9e8c0db95..7b8ca4d36ed8 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -306,6 +306,13 @@ public class GlobalSettingsValidators {
VALIDATORS.put(Global.Wearable.COMPANION_OS_VERSION, ANY_INTEGER_VALIDATOR);
VALIDATORS.put(Global.Wearable.ENABLE_ALL_LANGUAGES, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.Wearable.OEM_SETUP_VERSION, ANY_INTEGER_VALIDATOR);
+ VALIDATORS.put(
+ Global.Wearable.OEM_SETUP_COMPLETED_STATUS,
+ new DiscreteValueValidator(
+ new String[] {
+ String.valueOf(Global.Wearable.OEM_SETUP_COMPLETED_FAILURE),
+ String.valueOf(Global.Wearable.OEM_SETUP_COMPLETED_SUCCESS),
+ }));
VALIDATORS.put(Global.Wearable.MASTER_GESTURES_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.Wearable.UNGAZE_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index d72d4d51136e..03921e18589b 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -357,5 +357,7 @@ public class SecureSettingsValidators {
VALIDATORS.put(Secure.BLUETOOTH_LE_BROADCAST_PROGRAM_INFO, ANY_STRING_VALIDATOR);
VALIDATORS.put(Secure.BLUETOOTH_LE_BROADCAST_CODE, ANY_STRING_VALIDATOR);
VALIDATORS.put(Secure.BLUETOOTH_LE_BROADCAST_APP_SOURCE_NAME, ANY_STRING_VALIDATOR);
+ VALIDATORS.put(Secure.CUSTOM_BUGREPORT_HANDLER_APP, ANY_STRING_VALIDATOR);
+ VALIDATORS.put(Secure.CUSTOM_BUGREPORT_HANDLER_USER, ANY_INTEGER_VALIDATOR);
}
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 8d4a35dc0d88..ede69be21564 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -363,9 +363,6 @@ class SettingsProtoDumpUtil {
Settings.Global.BOOT_COUNT,
GlobalSettingsProto.BOOT_COUNT);
dumpSetting(s, p,
- Settings.Global.BUGREPORT_IN_POWER_MENU,
- GlobalSettingsProto.BUGREPORT_IN_POWER_MENU);
- dumpSetting(s, p,
Settings.Global.CACHED_APPS_FREEZER_ENABLED,
GlobalSettingsProto.CACHED_APPS_FREEZER_ENABLED);
dumpSetting(s, p,
@@ -1629,6 +1626,7 @@ class SettingsProtoDumpUtil {
// Settings.Global.INSTALL_NON_MARKET_APPS intentionally excluded since it's deprecated.
// Settings.Global.APPLY_RAMPING_RINGER intentionally excluded since it's deprecated.
+ // Settings.Global.BUGREPORT_IN_POWER_MENU intentionally excluded since it's deprecated.
}
private static void dumpProtoConfigSettingsLocked(
@@ -1840,6 +1838,10 @@ class SettingsProtoDumpUtil {
Settings.Secure.ALLOWED_GEOLOCATION_ORIGINS,
SecureSettingsProto.ALLOWED_GEOLOCATION_ORIGINS);
+ dumpSetting(s, p,
+ Settings.Secure.BUGREPORT_IN_POWER_MENU,
+ SecureSettingsProto.BUGREPORT_IN_POWER_MENU);
+
final long aovToken = p.start(SecureSettingsProto.ALWAYS_ON_VPN);
dumpSetting(s, p,
Settings.Secure.ALWAYS_ON_VPN_APP,
@@ -2662,7 +2664,6 @@ class SettingsProtoDumpUtil {
p.end(token);
// Settings.Secure.DEVELOPMENT_SETTINGS_ENABLED intentionally excluded since it's deprecated.
- // Settings.Secure.BUGREPORT_IN_POWER_MENU intentionally excluded since it's deprecated.
// Settings.Secure.ADB_ENABLED intentionally excluded since it's deprecated.
// Settings.Secure.ALLOW_MOCK_LOCATION intentionally excluded since it's deprecated.
// Settings.Secure.DATA_ROAMING intentionally excluded since it's deprecated.
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 1356e1d4c3e6..f7403267c188 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -3681,7 +3681,7 @@ public class SettingsProvider extends ContentProvider {
}
private final class UpgradeController {
- private static final int SETTINGS_VERSION = 212;
+ private static final int SETTINGS_VERSION = 213;
private final int mUserId;
@@ -3791,6 +3791,7 @@ public class SettingsProvider extends ContentProvider {
* currentVersion = 119;
* }
*/
+ @GuardedBy("mLock")
private int onUpgradeLocked(int userId, int oldVersion, int newVersion) {
if (DEBUG) {
Slog.w(LOG_TAG, "Upgrading settings for user: " + userId + " from version: "
@@ -5588,6 +5589,32 @@ public class SettingsProvider extends ContentProvider {
currentVersion = 212;
}
+ if (currentVersion == 212) {
+ final SettingsState globalSettings = getGlobalSettingsLocked();
+ final SettingsState secureSettings = getSecureSettingsLocked(userId);
+
+ final Setting bugReportInPowerMenu = globalSettings.getSettingLocked(
+ Global.BUGREPORT_IN_POWER_MENU);
+
+ if (!bugReportInPowerMenu.isNull()) {
+ Slog.i(LOG_TAG, "Setting bugreport_in_power_menu to "
+ + bugReportInPowerMenu.getValue() + " in Secure settings.");
+ secureSettings.insertSettingLocked(
+ Secure.BUGREPORT_IN_POWER_MENU,
+ bugReportInPowerMenu.getValue(), null /* tag */,
+ false /* makeDefault */, SettingsState.SYSTEM_PACKAGE_NAME);
+
+ // set global bug_report_in_power_menu setting to null since it's deprecated
+ Slog.i(LOG_TAG, "Setting bugreport_in_power_menu to null"
+ + " in Global settings since it's deprecated.");
+ globalSettings.insertSettingLocked(
+ Global.BUGREPORT_IN_POWER_MENU, null /* value */, null /* tag */,
+ true /* makeDefault */, SettingsState.SYSTEM_PACKAGE_NAME);
+ }
+
+ currentVersion = 213;
+ }
+
// vXXX: Add new settings above this point.
if (currentVersion != newVersion) {
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 5ee36f373783..db7032eb757d 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -645,6 +645,7 @@ public class SettingsBackupTest {
Settings.Global.Wearable.ENABLE_ALL_LANGUAGES,
Settings.Global.Wearable.SETUP_LOCALE,
Settings.Global.Wearable.OEM_SETUP_VERSION,
+ Settings.Global.Wearable.OEM_SETUP_COMPLETED_STATUS,
Settings.Global.Wearable.MASTER_GESTURES_ENABLED,
Settings.Global.Wearable.UNGAZE_ENABLED,
Settings.Global.Wearable.BURN_IN_PROTECTION_ENABLED,
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 115cf7927f69..26a68bc13599 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -679,6 +679,20 @@
android:excludeFromRecents="true"
android:exported="true" />
+ <!-- started from Telecomm(CallsManager) -->
+ <activity
+ android:name=".telephony.ui.activity.SwitchToManagedProfileForCallActivity"
+ android:excludeFromRecents="true"
+ android:exported="true"
+ android:finishOnCloseSystemDialogs="true"
+ android:permission="android.permission.MODIFY_PHONE_STATE"
+ android:theme="@style/Theme.SystemUI.Dialog.Alert">
+ <intent-filter>
+ <action android:name="android.telecom.action.SHOW_SWITCH_TO_WORK_PROFILE_FOR_CALL_DIALOG" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+
<!-- platform logo easter egg activity -->
<activity
android:name=".DessertCase"
diff --git a/packages/SystemUI/README.md b/packages/SystemUI/README.md
index ee8d02301d5d..2910bba71341 100644
--- a/packages/SystemUI/README.md
+++ b/packages/SystemUI/README.md
@@ -5,46 +5,72 @@
SystemUI is a persistent process that provides UI for the system but outside
of the system_server process.
-The starting point for most of sysui code is a list of services that extend
-SystemUI that are started up by SystemUIApplication. These services then depend
-on some custom dependency injection provided by Dependency.
-
Inputs directed at sysui (as opposed to general listeners) generally come in
through IStatusBar. Outputs from sysui are through a variety of private APIs to
the android platform all over.
## SystemUIApplication
-When SystemUIApplication starts up, it will start up the services listed in
-config_systemUIServiceComponents or config_systemUIServiceComponentsPerUser.
+When SystemUIApplication starts up, it instantiates a Dagger graph from which
+various pieces of the application are built.
-Each of these services extend SystemUI. SystemUI provides them with a Context
-and gives them callbacks for onConfigurationChanged (this historically was
-the main path for onConfigurationChanged, now also happens through
-ConfigurationController). They also receive a callback for onBootCompleted
-since these objects may be started before the device has finished booting.
+To support customization, SystemUIApplication relies on the AndroidManifest.xml
+having an `android.app.AppComponentFactory` specified. Specifically, it relies
+on an `AppComponentFactory` that subclases `SystemUIAppComponentFactoryBase`.
+Implementations of this abstract base class must override
+`#createSystemUIInitializer(Context)` which returns a `SystemUIInitializer`.
+`SystemUIInitializer` primary job in turn is to intialize and return the Dagger
+root component back to the `SystemUIApplication`.
-Each SystemUI service is expected to be a major part of system ui and the
-goal is to minimize communication between them. So in general they should be
-relatively silo'd.
+Writing a custom `SystemUIAppComponentFactoryBase` and `SystemUIInitializer`,
+should be enough for most implementations to stand up a customized Dagger
+graph, and launch a custom version of SystemUI.
-## Dependencies
+## Dagger / Dependency Injection
-The first SystemUI service that is started should always be Dependency.
-Dependency provides a static method for getting a hold of dependencies that
-have a lifecycle that spans sysui. Dependency has code for how to create all
-dependencies manually added. SystemUIFactory is also capable of
-adding/replacing these dependencies.
+See [dagger.md](docs/dagger.md) and https://dagger.dev/.
-Dependencies are lazily initialized, so if a Dependency is never referenced at
-runtime, it will never be created.
+## CoreStartable
-If an instantiated dependency implements Dumpable it will be included in dumps
-of sysui (and bug reports), allowing it to include current state information.
-This is how \*Controllers dump state to bug reports.
+The starting point for most of SystemUI code is a list of classes that
+implement `CoreStartable` that are started up by SystemUIApplication.
+CoreStartables are like miniature services. They have their `#start` method
+called after being instantiated, and a reference to them is stored inside
+SystemUIApplication. They are in charge of their own behavior beyond this,
+registering and unregistering with the rest of the system as needed.
+
+`CoreStartable` also receives a callback for `#onBootCompleted`
+since these objects may be started before the device has finished booting.
-If an instantiated dependency implements ConfigurationChangeReceiver it will
-receive onConfigurationChange callbacks when the configuration changes.
+`CoreStartable` is an ideal place to add new features and functionality
+that does not belong directly under the umbrella of an existing feature.
+It is better to define a new `CoreStartable` than to stick unrelated
+initialization code together in catch-all methods.
+
+CoreStartables are tied to application startup via Dagger:
+
+```kotlin
+class FeatureStartable
+@Inject
+constructor(
+ /* ... */
+) : CoreStartable {
+ override fun start() {
+ // ...
+ }
+}
+
+@Module
+abstract class FeatureModule {
+ @Binds
+ @IntoMap
+ @ClassKey(FeatureStartable::class)
+ abstract fun bind(impl: FeatureStartable): CoreStartable
+}
+```
+
+Including `FeatureModule` in the Dagger graph such as this will ensure that
+`FeatureStartable` gets constructed and that its `#start` method is called.
## IStatusBar
@@ -64,12 +90,6 @@ across sysui. Such as when StatusBar calls CommandQueue#recomputeDisableFlags.
This is generally used a shortcut to directly trigger CommandQueue rather than
calling StatusManager and waiting for the call to come back to IStatusBar.
-## Default SystemUI services list
-
-### [com.android.systemui.Dependency](/packages/SystemUI/src/com/android/systemui/Dependency.java)
-
-Provides custom dependency injection.
-
### [com.android.systemui.util.NotificationChannels](/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java)
Creates/initializes the channels sysui uses when posting notifications.
@@ -88,11 +108,11 @@ activity. It provides this cached data to RecentsActivity when it is started.
Registers all the callbacks/listeners required to show the Volume dialog when
it should be shown.
-### [com.android.systemui.status.phone.StatusBar](/packages/SystemUI/src/com/android/systemui/status/phone/StatusBar.java)
+### [com.android.systemui.status.phone.CentralSurfaces](/packages/SystemUI/src/com/android/systemui/status/phone/CentralSurfaces.java)
This shows the UI for the status bar and the notification shade it contains.
It also contains a significant amount of other UI that interacts with these
-surfaces (keyguard, AOD, etc.). StatusBar also contains a notification listener
+surfaces (keyguard, AOD, etc.). CentralSurfaces also contains a notification listener
to receive notification callbacks.
### [com.android.systemui.usb.StorageNotification](/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java)
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java
index ed54f08b2b3d..76139c6b8099 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java
@@ -16,11 +16,19 @@
package com.android.systemui.accessibility.accessibilitymenu;
+import android.accessibilityservice.AccessibilityButtonController;
import android.accessibilityservice.AccessibilityService;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.content.res.Configuration;
import android.hardware.display.DisplayManager;
import android.os.Handler;
+import android.os.Looper;
+import android.os.SystemClock;
import android.view.Display;
+import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
@@ -28,11 +36,17 @@ import android.view.accessibility.AccessibilityEvent;
import com.android.systemui.accessibility.accessibilitymenu.view.A11yMenuOverlayLayout;
/** @hide */
-public class AccessibilityMenuService extends AccessibilityService implements View.OnTouchListener {
+public class AccessibilityMenuService extends AccessibilityService
+ implements View.OnTouchListener {
private static final String TAG = "A11yMenuService";
private static final long BUFFER_MILLISECONDS_TO_PREVENT_UPDATE_FAILURE = 100L;
+ private long mLastTimeTouchedOutside = 0L;
+ // Timeout used to ignore the A11y button onClick() when ACTION_OUTSIDE is also received on
+ // clicking on the A11y button.
+ public static final long BUTTON_CLICK_TIMEOUT = 200;
+
private A11yMenuOverlayLayout mA11yMenuLayout;
private static boolean sInitialized = false;
@@ -61,7 +75,7 @@ public class AccessibilityMenuService extends AccessibilityService implements Vi
};
// Update layout.
- private final Handler mHandler = new Handler(getMainLooper());
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
private final Runnable mOnConfigChangedRunnable = new Runnable() {
@Override
public void run() {
@@ -80,6 +94,28 @@ public class AccessibilityMenuService extends AccessibilityService implements Vi
@Override
public void onCreate() {
super.onCreate();
+
+ getAccessibilityButtonController().registerAccessibilityButtonCallback(
+ new AccessibilityButtonController.AccessibilityButtonCallback() {
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onClicked(AccessibilityButtonController controller) {
+ if (SystemClock.uptimeMillis() - mLastTimeTouchedOutside
+ > BUTTON_CLICK_TIMEOUT) {
+ mA11yMenuLayout.toggleVisibility();
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onAvailabilityChanged(AccessibilityButtonController controller,
+ boolean available) {}
+ }
+ );
}
@Override
@@ -95,8 +131,12 @@ public class AccessibilityMenuService extends AccessibilityService implements Vi
protected void onServiceConnected() {
mA11yMenuLayout = new A11yMenuOverlayLayout(this);
- // Temporary measure to force visibility
- mA11yMenuLayout.toggleVisibility();
+ registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mA11yMenuLayout.hideMenu();
+ }}, new IntentFilter(Intent.ACTION_SCREEN_OFF)
+ );
mDisplayManager = getSystemService(DisplayManager.class);
mDisplayManager.registerDisplayListener(mDisplayListener, null);
@@ -124,12 +164,34 @@ public class AccessibilityMenuService extends AccessibilityService implements Vi
mOnConfigChangedRunnable, BUFFER_MILLISECONDS_TO_PREVENT_UPDATE_FAILURE);
}
+ /**
+ * Handles click events of shortcuts.
+ *
+ * @param view the shortcut button being clicked.
+ */
+ public void handleClick(View view) {
+ mA11yMenuLayout.hideMenu();
+ }
+
@Override
public void onInterrupt() {
}
@Override
+ protected boolean onKeyEvent(KeyEvent event) {
+ if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
+ mA11yMenuLayout.hideMenu();
+ }
+ return false;
+ }
+
+ @Override
public boolean onTouch(View v, MotionEvent event) {
+ if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
+ if (mA11yMenuLayout.hideMenu()) {
+ mLastTimeTouchedOutside = SystemClock.uptimeMillis();
+ }
+ }
return false;
}
}
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuAdapter.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuAdapter.java
index e3401a9a7915..337814e84368 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuAdapter.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuAdapter.java
@@ -116,7 +116,7 @@ public class A11yMenuAdapter extends BaseAdapter {
shortcutIconButton.setOnClickListener(
(View v) -> {
// Handles shortcut click event by AccessibilityMenuService.
- // service.handleClick(v);
+ mService.handleClick(v);
});
}
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuViewPager.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuViewPager.java
index c510b876e847..cec503c39cba 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuViewPager.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuViewPager.java
@@ -66,7 +66,7 @@ public class A11yMenuViewPager {
public static final int LARGE_GRID_COLUMN_COUNT = 2;
/** Temporary measure to test both item types. */
- private static final boolean USE_LARGE_ITEMS = true;
+ private static final boolean USE_LARGE_ITEMS = false;
/**
* Returns the number of items in the grid view.
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
index 9a9236be9c8a..a3ed0856c60a 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
@@ -237,13 +237,17 @@ constructor(
openedDialogs.firstOrNull {
it.dialog.window.decorView.viewRootImpl == controller.viewRoot
}
- val animateFrom =
+ val controller =
animatedParent?.dialogContentWithBackground?.let {
Controller.fromView(it, controller.cuj)
}
?: controller
- if (animatedParent == null && animateFrom !is LaunchableView) {
+ if (
+ animatedParent == null &&
+ controller is ViewDialogLaunchAnimatorController &&
+ controller.source !is LaunchableView
+ ) {
// Make sure the View we launch from implements LaunchableView to avoid visibility
// issues. Given that we don't own dialog decorViews so we can't enforce it for launches
// from a dialog.
@@ -272,7 +276,7 @@ constructor(
launchAnimator,
callback,
interactionJankMonitor,
- animateFrom,
+ controller,
onDialogDismissed = { openedDialogs.remove(it) },
dialog = dialog,
animateBackgroundBoundsChange,
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt
index 46d5a5c0af8c..9257f99efe96 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewDialogLaunchAnimatorController.kt
@@ -25,7 +25,7 @@ import com.android.internal.jank.InteractionJankMonitor
/** A [DialogLaunchAnimator.Controller] that can animate a [View] from/to a dialog. */
class ViewDialogLaunchAnimatorController
internal constructor(
- private val source: View,
+ internal val source: View,
override val cuj: DialogCuj?,
) : DialogLaunchAnimator.Controller {
override val viewRoot: ViewRootImpl?
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/CleanArchitectureDependencyViolationDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/CleanArchitectureDependencyViolationDetector.kt
new file mode 100644
index 000000000000..1f6e60359eb8
--- /dev/null
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/CleanArchitectureDependencyViolationDetector.kt
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.internal.systemui.lint
+
+import com.android.tools.lint.client.api.UElementHandler
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import org.jetbrains.uast.UElement
+import org.jetbrains.uast.UFile
+import org.jetbrains.uast.UImportStatement
+
+/**
+ * Detects violations of the Dependency Rule of Clean Architecture.
+ *
+ * The rule states that code in each layer may only depend on code in the same layer or the layer
+ * directly "beneath" that layer in the layer diagram.
+ *
+ * In System UI, we have three layers; from top to bottom, they are: ui, domain, and data. As a
+ * convention, was used packages with those names to place code in the appropriate layer. We also
+ * make an exception and allow for shared models to live under a separate package named "shared" to
+ * avoid code duplication.
+ *
+ * For more information, please see go/sysui-arch.
+ */
+@Suppress("UnstableApiUsage")
+class CleanArchitectureDependencyViolationDetector : Detector(), Detector.UastScanner {
+ override fun getApplicableUastTypes(): List<Class<out UElement>> {
+ return listOf(UFile::class.java)
+ }
+
+ override fun createUastHandler(context: JavaContext): UElementHandler {
+ return object : UElementHandler() {
+ override fun visitFile(node: UFile) {
+ // Check which Clean Architecture layer this file belongs to:
+ matchingLayer(node.packageName)?.let { layer ->
+ // The file matches with a Clean Architecture layer. Let's check all of its
+ // imports.
+ node.imports.forEach { importStatement ->
+ visitImportStatement(context, layer, importStatement)
+ }
+ }
+ }
+ }
+ }
+
+ private fun visitImportStatement(
+ context: JavaContext,
+ layer: Layer,
+ importStatement: UImportStatement,
+ ) {
+ val importText = importStatement.importReference?.asSourceString() ?: return
+ val importedLayer = matchingLayer(importText) ?: return
+
+ // Now check whether the layer of the file may depend on the layer of the import.
+ if (!layer.mayDependOn(importedLayer)) {
+ context.report(
+ issue = ISSUE,
+ scope = importStatement,
+ location = context.getLocation(importStatement),
+ message =
+ "The ${layer.packageNamePart} layer may not depend on" +
+ " the ${importedLayer.packageNamePart} layer.",
+ )
+ }
+ }
+
+ private fun matchingLayer(packageName: String): Layer? {
+ val packageNameParts = packageName.split(".").toSet()
+ return Layer.values()
+ .filter { layer -> packageNameParts.contains(layer.packageNamePart) }
+ .takeIf { it.size == 1 }
+ ?.first()
+ }
+
+ private enum class Layer(
+ val packageNamePart: String,
+ val canDependOn: Set<Layer>,
+ ) {
+ SHARED(
+ packageNamePart = "shared",
+ canDependOn = emptySet(), // The shared layer may not depend on any other layer.
+ ),
+ DATA(
+ packageNamePart = "data",
+ canDependOn = setOf(SHARED),
+ ),
+ DOMAIN(
+ packageNamePart = "domain",
+ canDependOn = setOf(SHARED, DATA),
+ ),
+ UI(
+ packageNamePart = "ui",
+ canDependOn = setOf(DOMAIN, SHARED),
+ ),
+ ;
+
+ fun mayDependOn(otherLayer: Layer): Boolean {
+ return this == otherLayer || canDependOn.contains(otherLayer)
+ }
+ }
+
+ companion object {
+ @JvmStatic
+ val ISSUE =
+ Issue.create(
+ id = "CleanArchitectureDependencyViolation",
+ briefDescription = "Violation of the Clean Architecture Dependency Rule.",
+ explanation =
+ """
+ Following the \"Dependency Rule\" from Clean Architecture, every layer of code \
+ can only depend code in its own layer or code in the layer directly \
+ \"beneath\" it. Therefore, the UI layer can only depend on the" Domain layer \
+ and the Domain layer can only depend on the Data layer. We" do make an \
+ exception to allow shared models to exist and be shared across layers by \
+ placing them under shared/model, which should be done with care. For more \
+ information about Clean Architecture in System UI, please see go/sysui-arch. \
+ NOTE: if your code is not using Clean Architecture, please feel free to ignore \
+ this warning.
+ """,
+ category = Category.CORRECTNESS,
+ priority = 8,
+ severity = Severity.WARNING,
+ implementation =
+ Implementation(
+ CleanArchitectureDependencyViolationDetector::class.java,
+ Scope.JAVA_FILE_SCOPE,
+ ),
+ )
+ }
+}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
index 3f334c1cdb9c..254a6fb4714f 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
@@ -27,9 +27,11 @@ import com.google.auto.service.AutoService
class SystemUIIssueRegistry : IssueRegistry() {
override val issues: List<Issue>
- get() = listOf(
+ get() =
+ listOf(
BindServiceOnMainThreadDetector.ISSUE,
BroadcastSentViaContextDetector.ISSUE,
+ CleanArchitectureDependencyViolationDetector.ISSUE,
SlowUserQueryDetector.ISSUE_SLOW_USER_ID_QUERY,
SlowUserQueryDetector.ISSUE_SLOW_USER_INFO_QUERY,
NonInjectedMainThreadDetector.ISSUE,
@@ -37,7 +39,7 @@ class SystemUIIssueRegistry : IssueRegistry() {
SoftwareBitmapDetector.ISSUE,
NonInjectedServiceDetector.ISSUE,
StaticSettingsProviderDetector.ISSUE
- )
+ )
override val api: Int
get() = CURRENT_API
@@ -45,9 +47,9 @@ class SystemUIIssueRegistry : IssueRegistry() {
get() = 8
override val vendor: Vendor =
- Vendor(
- vendorName = "Android",
- feedbackUrl = "http://b/issues/new?component=78010",
- contact = "jernej@google.com"
- )
+ Vendor(
+ vendorName = "Android",
+ feedbackUrl = "http://b/issues/new?component=78010",
+ contact = "jernej@google.com"
+ )
}
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/CleanArchitectureDependencyViolationDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/CleanArchitectureDependencyViolationDetectorTest.kt
new file mode 100644
index 000000000000..a4b59fd8e086
--- /dev/null
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/CleanArchitectureDependencyViolationDetectorTest.kt
@@ -0,0 +1,296 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.internal.systemui.lint
+
+import com.android.tools.lint.checks.infrastructure.TestFiles
+import com.android.tools.lint.checks.infrastructure.TestMode
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+import org.junit.Ignore
+import org.junit.Test
+
+@Suppress("UnstableApiUsage")
+@Ignore("b/254533331")
+class CleanArchitectureDependencyViolationDetectorTest : SystemUILintDetectorTest() {
+ override fun getDetector(): Detector {
+ return CleanArchitectureDependencyViolationDetector()
+ }
+
+ override fun getIssues(): List<Issue> {
+ return listOf(
+ CleanArchitectureDependencyViolationDetector.ISSUE,
+ )
+ }
+
+ @Test
+ fun `No violations`() {
+ lint()
+ .files(
+ *LEGITIMATE_FILES,
+ )
+ .issues(
+ CleanArchitectureDependencyViolationDetector.ISSUE,
+ )
+ .run()
+ .expectWarningCount(0)
+ }
+
+ @Test
+ fun `Violation - domain depends on ui`() {
+ lint()
+ .files(
+ *LEGITIMATE_FILES,
+ TestFiles.kotlin(
+ """
+ package test.domain.interactor
+
+ import test.ui.viewmodel.ViewModel
+
+ class BadClass(
+ private val viewModel: ViewModel,
+ )
+ """.trimIndent()
+ )
+ )
+ .issues(
+ CleanArchitectureDependencyViolationDetector.ISSUE,
+ )
+ .testModes(TestMode.DEFAULT)
+ .run()
+ .expectWarningCount(1)
+ .expect(
+ expectedText =
+ """
+ src/test/domain/interactor/BadClass.kt:3: Warning: The domain layer may not depend on the ui layer. [CleanArchitectureDependencyViolation]
+ import test.ui.viewmodel.ViewModel
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ 0 errors, 1 warnings
+ """,
+ )
+ }
+
+ @Test
+ fun `Violation - ui depends on data`() {
+ lint()
+ .files(
+ *LEGITIMATE_FILES,
+ TestFiles.kotlin(
+ """
+ package test.ui.viewmodel
+
+ import test.data.repository.Repository
+
+ class BadClass(
+ private val repository: Repository,
+ )
+ """.trimIndent()
+ )
+ )
+ .issues(
+ CleanArchitectureDependencyViolationDetector.ISSUE,
+ )
+ .testModes(TestMode.DEFAULT)
+ .run()
+ .expectWarningCount(1)
+ .expect(
+ expectedText =
+ """
+ src/test/ui/viewmodel/BadClass.kt:3: Warning: The ui layer may not depend on the data layer. [CleanArchitectureDependencyViolation]
+ import test.data.repository.Repository
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ 0 errors, 1 warnings
+ """,
+ )
+ }
+
+ @Test
+ fun `Violation - shared depends on all other layers`() {
+ lint()
+ .files(
+ *LEGITIMATE_FILES,
+ TestFiles.kotlin(
+ """
+ package test.shared.model
+
+ import test.data.repository.Repository
+ import test.domain.interactor.Interactor
+ import test.ui.viewmodel.ViewModel
+
+ class BadClass(
+ private val repository: Repository,
+ private val interactor: Interactor,
+ private val viewmodel: ViewModel,
+ )
+ """.trimIndent()
+ )
+ )
+ .issues(
+ CleanArchitectureDependencyViolationDetector.ISSUE,
+ )
+ .testModes(TestMode.DEFAULT)
+ .run()
+ .expectWarningCount(3)
+ .expect(
+ expectedText =
+ """
+ src/test/shared/model/BadClass.kt:3: Warning: The shared layer may not depend on the data layer. [CleanArchitectureDependencyViolation]
+ import test.data.repository.Repository
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ src/test/shared/model/BadClass.kt:4: Warning: The shared layer may not depend on the domain layer. [CleanArchitectureDependencyViolation]
+ import test.domain.interactor.Interactor
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ src/test/shared/model/BadClass.kt:5: Warning: The shared layer may not depend on the ui layer. [CleanArchitectureDependencyViolation]
+ import test.ui.viewmodel.ViewModel
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ 0 errors, 3 warnings
+ """,
+ )
+ }
+
+ @Test
+ fun `Violation - data depends on domain`() {
+ lint()
+ .files(
+ *LEGITIMATE_FILES,
+ TestFiles.kotlin(
+ """
+ package test.data.repository
+
+ import test.domain.interactor.Interactor
+
+ class BadClass(
+ private val interactor: Interactor,
+ )
+ """.trimIndent()
+ )
+ )
+ .issues(
+ CleanArchitectureDependencyViolationDetector.ISSUE,
+ )
+ .testModes(TestMode.DEFAULT)
+ .run()
+ .expectWarningCount(1)
+ .expect(
+ expectedText =
+ """
+ src/test/data/repository/BadClass.kt:3: Warning: The data layer may not depend on the domain layer. [CleanArchitectureDependencyViolation]
+ import test.domain.interactor.Interactor
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ 0 errors, 1 warnings
+ """,
+ )
+ }
+
+ companion object {
+ private val MODEL_FILE =
+ TestFiles.kotlin(
+ """
+ package test.shared.model
+
+ import test.some.other.thing.SomeOtherThing
+
+ data class Model(
+ private val name: String,
+ )
+ """.trimIndent()
+ )
+ private val REPOSITORY_FILE =
+ TestFiles.kotlin(
+ """
+ package test.data.repository
+
+ import test.shared.model.Model
+ import test.some.other.thing.SomeOtherThing
+
+ class Repository {
+ private val models = listOf(
+ Model("one"),
+ Model("two"),
+ Model("three"),
+ )
+
+ fun getModels(): List<Model> {
+ return models
+ }
+ }
+ """.trimIndent()
+ )
+ private val INTERACTOR_FILE =
+ TestFiles.kotlin(
+ """
+ package test.domain.interactor
+
+ import test.data.repository.Repository
+ import test.shared.model.Model
+
+ class Interactor(
+ private val repository: Repository,
+ ) {
+ fun getModels(): List<Model> {
+ return repository.getModels()
+ }
+ }
+ """.trimIndent()
+ )
+ private val VIEW_MODEL_FILE =
+ TestFiles.kotlin(
+ """
+ package test.ui.viewmodel
+
+ import test.domain.interactor.Interactor
+ import test.some.other.thing.SomeOtherThing
+
+ class ViewModel(
+ private val interactor: Interactor,
+ ) {
+ fun getNames(): List<String> {
+ return interactor.getModels().map { model -> model.name }
+ }
+ }
+ """.trimIndent()
+ )
+ private val NON_CLEAN_ARCHITECTURE_FILE =
+ TestFiles.kotlin(
+ """
+ package test.some.other.thing
+
+ import test.data.repository.Repository
+ import test.domain.interactor.Interactor
+ import test.ui.viewmodel.ViewModel
+
+ class SomeOtherThing {
+ init {
+ val viewModel = ViewModel(
+ interactor = Interactor(
+ repository = Repository(),
+ ),
+ )
+ }
+ }
+ """.trimIndent()
+ )
+ private val LEGITIMATE_FILES =
+ arrayOf(
+ MODEL_FILE,
+ REPOSITORY_FILE,
+ INTERACTOR_FILE,
+ VIEW_MODEL_FILE,
+ NON_CLEAN_ARCHITECTURE_FILE,
+ )
+ }
+}
diff --git a/packages/SystemUI/docs/dagger.md b/packages/SystemUI/docs/dagger.md
index 89170139e21c..9b4c21efb27f 100644
--- a/packages/SystemUI/docs/dagger.md
+++ b/packages/SystemUI/docs/dagger.md
@@ -8,105 +8,110 @@ Go read about Dagger 2.
- [User's guide](https://google.github.io/dagger/users-guide)
-TODO: Add some links.
-
## State of the world
-Dagger 2 has been turned on for SystemUI and a early first pass has been taken
-for converting everything in [Dependency.java](packages/systemui/src/com/android/systemui/Dependency.java)
-to use Dagger. Since a lot of SystemUI depends on Dependency, stubs have been added to Dependency
-to proxy any gets through to the instances provided by dagger, this will allow migration of SystemUI
-through a number of CLs.
+Dagger 2 has been turned on for SystemUI and much of
+[Dependency.java](../src/com/android/systemui/Dependency.java)
+has been converted to use Dagger. Since a lot of SystemUI depends on Dependency,
+stubs have been added to Dependency to proxy any gets through to the instances
+provided by dagger, this will allow migration of SystemUI through a number of CLs.
### How it works in SystemUI
+There are three high level "scopes" of concern in SystemUI. They all represent
+singleton scopes, but serve different purposes.
+
+* `@Singleton` - Instances that are shared everywhere. There isn't a lot of
+ code in this scope. Things like the main thread, and Android Framework
+ provided instances mostly.
+* `@WMShell` - WindowManager related code in the SystemUI process. We don't
+ want this code relying on the rest of SystemUI, and we don't want the rest
+ of SystemUI peeking into its internals, so it runs in its own Subcomponent.
+* `@SysUISingleton` - Most of what would be considered "SystemUI". Most feature
+ work by SystemUI developers goes into this scope. Useful interfaces from
+ WindowManager are made available inside this Subcomponent.
+
+The root dagger graph is created by an instance of `SystemUIInitializer`.
+See [README.md](../README.md) for more details.
For the classes that we're using in Dependency and are switching to dagger, the
equivalent dagger version is using `@Singleton` and therefore only has one instance.
To have the single instance span all of SystemUI and be easily accessible for
other components, there is a single root `@Component` that exists that generates
-these. The component lives in [SystemUIFactory](packages/systemui/src/com/android/systemui/SystemUIFactory.java)
-and is called `SystemUIRootComponent`.
+these. The component lives in
+[ReferenceGlobalRootComponent.java](../src/com/android/systemui/dagger/ReferenceGlobalRootComponent.java).
-```java
+### Adding a new injectable object
-@Singleton
-@Component(modules = {SystemUIFactory.class, DependencyProvider.class, DependencyBinder.class,
- ContextHolder.class})
-public interface SystemUIRootComponent {
- @Singleton
- Dependency.DependencyInjector createDependency();
+First annotate the constructor with `@Inject`. Also annotate it with
+`@SysUISingleton` if only one instance should be created.
+
+```kotlin
+@SysUISingleton
+class FeatureStartable
+@Inject
+constructor(
+/* ... */
+) {
+ // ...
}
```
-The root component is composed of root modules, which in turn provide the global singleton
-dependencies across all of SystemUI.
-
-- `SystemUIFactory` `@Provides` dependencies that need to be overridden by SystemUI
-variants (like other form factors e.g. Car).
-
-- `DependencyBinder` creates the mapping from interfaces to implementation classes.
+If you have an interface class and an implementation class, Dagger needs to
+know how to map it. The simplest way to do this is to add an `@Binds` method
+in a module. The type of the return value tells dagger which dependency it's
+providing:
-- `DependencyProvider` provides or binds any remaining depedencies required.
-
-### Adding injection to a new SystemUI object
-
-SystemUI object are made injectable by adding an entry in `SystemUIBinder`. SystemUIApplication uses
-information in that file to locate and construct an instance of the requested SystemUI class.
-
-### Adding a new injectable object
-
-First tag the constructor with `@Inject`. Also tag it with `@Singleton` if only one
-instance should be created.
-
-```java
-@Singleton
-public class SomethingController {
- @Inject
- public SomethingController(Context context,
- @Named(MAIN_HANDLER_NAME) Handler mainHandler) {
- // context and mainHandler will be automatically populated.
- }
+```kotlin
+@Module
+abstract class FeatureModule {
+ @Binds
+ abstract fun bindsFeature(impl: FeatureImpl): Feature
}
```
-If you have an interface class and an implementation class, dagger needs to know
-how to map it. The simplest way to do this is to add an `@Provides` method to
-DependencyProvider. The type of the return value tells dagger which dependency it's providing.
-
-```java
-public class DependencyProvider {
- //...
- @Singleton
- @Provides
- public SomethingController provideSomethingController(Context context,
- @Named(MAIN_HANDLER_NAME) Handler mainHandler) {
- return new SomethingControllerImpl(context, mainHandler);
- }
+If you have a class that you want to make injectable that has can not
+be easily constructed by Dagger, write a `@Provides` method for it:
+
+```kotlin
+@Module
+abstract class FeatureModule {
+ @Module
+ companion object {
+ @Provides
+ fun providesFeature(ctx: Context): Feature {
+ return FeatureImpl.constructFromContext(ctx)
+ }
+ }
}
```
-If you need to access this from Dependency#get, then add an adapter to Dependency
-that maps to the instance provided by Dagger. The changes should be similar
-to the following diff.
+### Module Organization
-```java
-public class Dependency {
- //...
- @Inject Lazy<SomethingController> mSomethingController;
- //...
- public void start() {
- //...
- mProviders.put(SomethingController.class, mSomethingController::get);
- }
-}
-```
+Please define your modules on _at least_ per-package level. If the scope of a
+package grows to encompass a great number of features, create per-feature
+modules.
+
+**Do not create catch-all modules.** Those quickly grow unwieldy and
+unmaintainable. Any that exist today should be refactored into obsolescence.
+
+You can then include your module in one of three places:
+
+1) Within another module that depends on it. Ideally, this creates a clean
+ dependency graph between features and utilities.
+2) For features that should exist in all versions of SystemUI (AOSP and
+ any variants), include the module in
+ [SystemUIModule.java](../src/com/android/systemui/dagger/SystemUIModule.java).
+3) For features that should exist only in AOSP, include the module in
+ [ReferenceSystemUIModule.java](../src/com/android/systemui/dagger/ReferenceSystemUIModule.java).
+ Similarly, if you are working on a custom version of SystemUI and have code
+ specific to your version, include it in a module specific to your version.
### Using injection with Fragments
Fragments are created as part of the FragmentManager, so they need to be
setup so the manager knows how to create them. To do that, add a method
to com.android.systemui.fragments.FragmentService$FragmentCreator that
-returns your fragment class. Thats all thats required, once the method
+returns your fragment class. That is all that is required, once the method
exists, FragmentService will automatically pick it up and use injection
whenever your fragment needs to be created.
@@ -123,48 +128,11 @@ then the FragmentHostManager can do this for you.
FragmentHostManager.get(view).create(NavigationBarFragment.class);
```
-### Using injection with Views
-
-DO NOT ADD NEW VIEW INJECTION. VIEW INJECTION IS BEING ACTIVELY DEPRECATED.
-
-Needing to inject objects into your View's constructor generally implies you
-are doing more work in your presentation layer than is advisable.
-Instead, create an injected controller for you view, inject into the
-controller, and then attach the view to the controller after inflation.
-
-View injection generally causes headaches while testing, as inflating a view
-(which may in turn inflate other views) implicitly causes a Dagger graph to
-be stood up, which may or may not contain the appropriately
-faked/mocked/stubbed objects. It is a hard to control process.
-
## Updating Dagger2
We depend on the Dagger source found in external/dagger2. We should automatically pick up on updates
when that repository is updated.
-
-*Deprecated:*
-
-Binaries can be downloaded from https://repo1.maven.org/maven2/com/google/dagger/ and then loaded
-into
-[/prebuilts/tools/common/m2/repository/com/google/dagger/](http://cs/android/prebuilts/tools/common/m2/repository/com/google/dagger/)
-
-The following commands should work, substituting in the version that you are looking for:
-
-````
-cd prebuilts/tools/common/m2/repository/com/google/dagger/
-
-wget -r -np -nH --cut-dirs=4 -erobots=off -R "index.html*" -U "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36" https://repo1.maven.org/maven2/com/google/dagger/dagger/2.28.1/
-
-wget -r -np -nH --cut-dirs=4 -erobots=off -R "index.html*" -U "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36" https://repo1.maven.org/maven2/com/google/dagger/dagger-compiler/2.28.1/
-
-wget -r -np -nH --cut-dirs=4 -erobots=off -R "index.html*" -U "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36" https://repo1.maven.org/maven2/com/google/dagger/dagger-spi/2.28.1/
-
-wget -r -np -nH --cut-dirs=4 -erobots=off -R "index.html*" -U "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36" https://repo1.maven.org/maven2/com/google/dagger/dagger-producers/2.28.1/
-````
-
-Then update `prebuilts/tools/common/m2/Android.bp` to point at your new jars.
## TODO List
- - Eliminate usages of Dependency#get
- - Add links in above TODO
+ - Eliminate usages of Dependency#get: http://b/hotlists/3940788
diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
index dee0f5cd1979..314c736e2e44 100644
--- a/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
+++ b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
@@ -80,6 +80,7 @@ internal class HueSubtract(val amountDegrees: Double) : Hue {
internal class HueVibrantSecondary() : Hue {
val hueToRotations = listOf(Pair(0, 18), Pair(41, 15), Pair(61, 10), Pair(101, 12),
Pair(131, 15), Pair(181, 18), Pair(251, 15), Pair(301, 12), Pair(360, 12))
+
override fun get(sourceColor: Cam): Double {
return getHueRotation(sourceColor.hue, hueToRotations)
}
@@ -88,6 +89,7 @@ internal class HueVibrantSecondary() : Hue {
internal class HueVibrantTertiary() : Hue {
val hueToRotations = listOf(Pair(0, 35), Pair(41, 30), Pair(61, 20), Pair(101, 25),
Pair(131, 30), Pair(181, 35), Pair(251, 30), Pair(301, 25), Pair(360, 25))
+
override fun get(sourceColor: Cam): Double {
return getHueRotation(sourceColor.hue, hueToRotations)
}
@@ -96,6 +98,7 @@ internal class HueVibrantTertiary() : Hue {
internal class HueExpressiveSecondary() : Hue {
val hueToRotations = listOf(Pair(0, 45), Pair(21, 95), Pair(51, 45), Pair(121, 20),
Pair(151, 45), Pair(191, 90), Pair(271, 45), Pair(321, 45), Pair(360, 45))
+
override fun get(sourceColor: Cam): Double {
return getHueRotation(sourceColor.hue, hueToRotations)
}
@@ -104,6 +107,7 @@ internal class HueExpressiveSecondary() : Hue {
internal class HueExpressiveTertiary() : Hue {
val hueToRotations = listOf(Pair(0, 120), Pair(21, 120), Pair(51, 20), Pair(121, 45),
Pair(151, 20), Pair(191, 15), Pair(271, 20), Pair(321, 120), Pair(360, 120))
+
override fun get(sourceColor: Cam): Double {
return getHueRotation(sourceColor.hue, hueToRotations)
}
@@ -148,11 +152,11 @@ internal class TonalSpec(val hue: Hue = HueSource(), val chroma: Chroma) {
}
internal class CoreSpec(
- val a1: TonalSpec,
- val a2: TonalSpec,
- val a3: TonalSpec,
- val n1: TonalSpec,
- val n2: TonalSpec
+ val a1: TonalSpec,
+ val a2: TonalSpec,
+ val a3: TonalSpec,
+ val n1: TonalSpec,
+ val n2: TonalSpec
)
enum class Style(internal val coreSpec: CoreSpec) {
@@ -214,51 +218,86 @@ enum class Style(internal val coreSpec: CoreSpec) {
)),
}
+class TonalPalette {
+ val shadeKeys = listOf(10, 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000)
+ val allShades: List<Int>
+ val allShadesMapped: Map<Int, Int>
+ val baseColor: Int
+
+ internal constructor(spec: TonalSpec, seedColor: Int) {
+ val seedCam = Cam.fromInt(seedColor)
+ allShades = spec.shades(seedCam)
+ allShadesMapped = shadeKeys.zip(allShades).toMap()
+
+ val h = spec.hue.get(seedCam).toFloat()
+ val c = spec.chroma.get(seedCam).toFloat()
+ baseColor = ColorUtils.CAMToColor(h, c, CamUtils.lstarFromInt(seedColor))
+ }
+
+ val s10: Int get() = this.allShades[0]
+ val s50: Int get() = this.allShades[1]
+ val s100: Int get() = this.allShades[2]
+ val s200: Int get() = this.allShades[3]
+ val s300: Int get() = this.allShades[4]
+ val s400: Int get() = this.allShades[5]
+ val s500: Int get() = this.allShades[6]
+ val s600: Int get() = this.allShades[7]
+ val s700: Int get() = this.allShades[8]
+ val s800: Int get() = this.allShades[9]
+ val s900: Int get() = this.allShades[10]
+ val s1000: Int get() = this.allShades[11]
+}
+
class ColorScheme(
- @ColorInt val seed: Int,
- val darkTheme: Boolean,
- val style: Style = Style.TONAL_SPOT
+ @ColorInt val seed: Int,
+ val darkTheme: Boolean,
+ val style: Style = Style.TONAL_SPOT
) {
- val accent1: List<Int>
- val accent2: List<Int>
- val accent3: List<Int>
- val neutral1: List<Int>
- val neutral2: List<Int>
+ val accent1: TonalPalette
+ val accent2: TonalPalette
+ val accent3: TonalPalette
+ val neutral1: TonalPalette
+ val neutral2: TonalPalette
constructor(@ColorInt seed: Int, darkTheme: Boolean) :
this(seed, darkTheme, Style.TONAL_SPOT)
@JvmOverloads
constructor(
- wallpaperColors: WallpaperColors,
- darkTheme: Boolean,
- style: Style = Style.TONAL_SPOT
+ wallpaperColors: WallpaperColors,
+ darkTheme: Boolean,
+ style: Style = Style.TONAL_SPOT
) :
this(getSeedColor(wallpaperColors, style != Style.CONTENT), darkTheme, style)
+ val allHues: List<TonalPalette>
+ get() {
+ return listOf(accent1, accent2, accent3, neutral1, neutral2)
+ }
+
val allAccentColors: List<Int>
get() {
val allColors = mutableListOf<Int>()
- allColors.addAll(accent1)
- allColors.addAll(accent2)
- allColors.addAll(accent3)
+ allColors.addAll(accent1.allShades)
+ allColors.addAll(accent2.allShades)
+ allColors.addAll(accent3.allShades)
return allColors
}
val allNeutralColors: List<Int>
get() {
val allColors = mutableListOf<Int>()
- allColors.addAll(neutral1)
- allColors.addAll(neutral2)
+ allColors.addAll(neutral1.allShades)
+ allColors.addAll(neutral2.allShades)
return allColors
}
val backgroundColor
- get() = ColorUtils.setAlphaComponent(if (darkTheme) neutral1[8] else neutral1[0], 0xFF)
+ get() = ColorUtils.setAlphaComponent(if (darkTheme) neutral1.s700 else neutral1.s10, 0xFF)
val accentColor
- get() = ColorUtils.setAlphaComponent(if (darkTheme) accent1[2] else accent1[6], 0xFF)
+ get() = ColorUtils.setAlphaComponent(if (darkTheme) accent1.s100 else accent1.s500, 0xFF)
init {
val proposedSeedCam = Cam.fromInt(seed)
@@ -269,24 +308,26 @@ class ColorScheme(
} else {
seed
}
- val camSeed = Cam.fromInt(seedArgb)
- accent1 = style.coreSpec.a1.shades(camSeed)
- accent2 = style.coreSpec.a2.shades(camSeed)
- accent3 = style.coreSpec.a3.shades(camSeed)
- neutral1 = style.coreSpec.n1.shades(camSeed)
- neutral2 = style.coreSpec.n2.shades(camSeed)
+
+ accent1 = TonalPalette(style.coreSpec.a1, seedArgb)
+ accent2 = TonalPalette(style.coreSpec.a2, seedArgb)
+ accent3 = TonalPalette(style.coreSpec.a3, seedArgb)
+ neutral1 = TonalPalette(style.coreSpec.n1, seedArgb)
+ neutral2 = TonalPalette(style.coreSpec.n2, seedArgb)
}
+ val shadeCount get() = this.accent1.allShades.size
+
override fun toString(): String {
return "ColorScheme {\n" +
" seed color: ${stringForColor(seed)}\n" +
" style: $style\n" +
" palettes: \n" +
- " ${humanReadable("PRIMARY", accent1)}\n" +
- " ${humanReadable("SECONDARY", accent2)}\n" +
- " ${humanReadable("TERTIARY", accent3)}\n" +
- " ${humanReadable("NEUTRAL", neutral1)}\n" +
- " ${humanReadable("NEUTRAL VARIANT", neutral2)}\n" +
+ " ${humanReadable("PRIMARY", accent1.allShades)}\n" +
+ " ${humanReadable("SECONDARY", accent2.allShades)}\n" +
+ " ${humanReadable("TERTIARY", accent3.allShades)}\n" +
+ " ${humanReadable("NEUTRAL", neutral1.allShades)}\n" +
+ " ${humanReadable("NEUTRAL VARIANT", neutral2.allShades)}\n" +
"}"
}
@@ -385,7 +426,8 @@ class ColorScheme(
val existingSeedNearby = seeds.find {
val hueA = intToCam[int]!!.hue
val hueB = intToCam[it]!!.hue
- hueDiff(hueA, hueB) < i } != null
+ hueDiff(hueA, hueB) < i
+ } != null
if (existingSeedNearby) {
continue
}
@@ -460,9 +502,9 @@ class ColorScheme(
}
private fun huePopulations(
- camByColor: Map<Int, Cam>,
- populationByColor: Map<Int, Double>,
- filter: Boolean = true
+ camByColor: Map<Int, Cam>,
+ populationByColor: Map<Int, Double>,
+ filter: Boolean = true
): List<Double> {
val huePopulation = List(size = 360, init = { 0.0 }).toMutableList()
diff --git a/packages/SystemUI/res/drawable/ic_camera.xml b/packages/SystemUI/res/drawable/ic_camera.xml
new file mode 100644
index 000000000000..ef1406c1c58a
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_camera.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="48"
+ android:viewportHeight="48"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M7,42Q5.8,42 4.9,41.1Q4,40.2 4,39V13.35Q4,12.15 4.9,11.25Q5.8,10.35 7,10.35H14.35L18,6H30L33.65,10.35H41Q42.2,10.35 43.1,11.25Q44,12.15 44,13.35V39Q44,40.2 43.1,41.1Q42.2,42 41,42ZM7,39H41Q41,39 41,39Q41,39 41,39V13.35Q41,13.35 41,13.35Q41,13.35 41,13.35H7Q7,13.35 7,13.35Q7,13.35 7,13.35V39Q7,39 7,39Q7,39 7,39ZM7,39Q7,39 7,39Q7,39 7,39V13.35Q7,13.35 7,13.35Q7,13.35 7,13.35Q7,13.35 7,13.35Q7,13.35 7,13.35V39Q7,39 7,39Q7,39 7,39ZM24,34.7Q27.5,34.7 30,32.225Q32.5,29.75 32.5,26.2Q32.5,22.7 30,20.2Q27.5,17.7 24,17.7Q20.45,17.7 17.975,20.2Q15.5,22.7 15.5,26.2Q15.5,29.75 17.975,32.225Q20.45,34.7 24,34.7ZM24,26.2Q24,26.2 24,26.2Q24,26.2 24,26.2Q24,26.2 24,26.2Q24,26.2 24,26.2Q24,26.2 24,26.2Q24,26.2 24,26.2Q24,26.2 24,26.2Q24,26.2 24,26.2Z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_videocam.xml b/packages/SystemUI/res/drawable/ic_videocam.xml
new file mode 100644
index 000000000000..de2bc7bccdf1
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_videocam.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="48"
+ android:viewportHeight="48"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M7,40Q5.8,40 4.9,39.1Q4,38.2 4,37V11Q4,9.8 4.9,8.9Q5.8,8 7,8H33Q34.2,8 35.1,8.9Q36,9.8 36,11V21.75L44,13.75V34.25L36,26.25V37Q36,38.2 35.1,39.1Q34.2,40 33,40ZM7,37H33Q33,37 33,37Q33,37 33,37V11Q33,11 33,11Q33,11 33,11H7Q7,11 7,11Q7,11 7,11V37Q7,37 7,37Q7,37 7,37ZM7,37Q7,37 7,37Q7,37 7,37V11Q7,11 7,11Q7,11 7,11Q7,11 7,11Q7,11 7,11V37Q7,37 7,37Q7,37 7,37Z"/>
+</vector>
diff --git a/packages/SystemUI/res/layout/ongoing_privacy_chip.xml b/packages/SystemUI/res/layout/ongoing_privacy_chip.xml
index 5aa608084510..d1a2cf4c24b2 100644
--- a/packages/SystemUI/res/layout/ongoing_privacy_chip.xml
+++ b/packages/SystemUI/res/layout/ongoing_privacy_chip.xml
@@ -25,6 +25,7 @@
android:focusable="true"
android:clipChildren="false"
android:clipToPadding="false"
+ android:paddingStart="8dp"
>
<LinearLayout
diff --git a/packages/SystemUI/res/layout/status_bar_notification_footer.xml b/packages/SystemUI/res/layout/status_bar_notification_footer.xml
index bbb8df1c5a4a..db94c92738f2 100644
--- a/packages/SystemUI/res/layout/status_bar_notification_footer.xml
+++ b/packages/SystemUI/res/layout/status_bar_notification_footer.xml
@@ -26,6 +26,17 @@
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="wrap_content">
+ <TextView
+ android:id="@+id/unlock_prompt_footer"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="12dp"
+ android:layout_gravity="center_horizontal"
+ android:gravity="center"
+ android:drawablePadding="8dp"
+ android:visibility="gone"
+ android:textAppearance="?android:attr/textAppearanceButton"
+ android:text="@string/unlock_to_see_notif_text"/>
<com.android.systemui.statusbar.notification.row.FooterViewButton
style="@style/TextAppearance.NotificationSectionHeaderButton"
android:id="@+id/manage_text"
diff --git a/packages/SystemUI/res/values-sw600dp-land/dimens.xml b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
index 6c7cab51f440..5d78e4e1a44c 100644
--- a/packages/SystemUI/res/values-sw600dp-land/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
@@ -28,6 +28,7 @@
<!-- QS-->
<dimen name="qs_panel_padding_top">16dp</dimen>
+ <dimen name="qs_panel_padding">24dp</dimen>
<dimen name="qs_content_horizontal_padding">24dp</dimen>
<dimen name="qs_horizontal_margin">24dp</dimen>
<!-- in split shade qs_tiles_page_horizontal_margin should be equal of qs_horizontal_margin/2,
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 6043a3c9f463..3d3bb6e6c4ec 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1511,10 +1511,10 @@
<string name="notification_conversation_summary_low">No sound or vibration and appears lower in conversation section</string>
<!-- [CHAR LIMIT=150] Notification Importance title: normal importance level summary -->
- <string name="notification_channel_summary_default">May ring or vibrate based on phone settings</string>
+ <string name="notification_channel_summary_default">May ring or vibrate based on device settings</string>
<!-- [CHAR LIMIT=150] Conversation Notification Importance title: normal conversation level, with bubbling summary -->
- <string name="notification_channel_summary_default_with_bubbles">May ring or vibrate based on phone settings. Conversations from <xliff:g id="app_name" example="YouTube">%1$s</xliff:g> bubble by default.</string>
+ <string name="notification_channel_summary_default_with_bubbles">May ring or vibrate based on device settings. Conversations from <xliff:g id="app_name" example="YouTube">%1$s</xliff:g> bubble by default.</string>
<!-- [CHAR LIMIT=150] Notification Importance title: automatic importance level summary -->
<string name="notification_channel_summary_automatic">Have the system determine if this notification should make sound or vibration</string>
@@ -2898,4 +2898,25 @@
<string name="stylus_battery_low_percentage"><xliff:g id="percentage" example="16%">%s</xliff:g> battery remaining</string>
<!-- Subtitle for the notification sent when a stylus battery is low. [CHAR LIMIT=none]-->
<string name="stylus_battery_low_subtitle">Connect your stylus to a charger</string>
+
+ <!-- Title for notification of low stylus battery. [CHAR_LIMIT=NONE] -->
+ <string name="stylus_battery_low">Stylus battery low</string>
+
+ <!-- Label for a lock screen shortcut to start the camera in video mode. [CHAR_LIMIT=16] -->
+ <string name="video_camera">Video camera</string>
+
+ <!-- Switch to work profile dialer app for placing a call dialog. -->
+ <!-- Text for Switch to work profile dialog's Title. Switch to work profile dialog guide users to make call from work
+ profile dialer app as it's not possible to make call from current profile due to an admin policy. [CHAR LIMIT=60] -->
+ <string name="call_from_work_profile_title">Can\'t call from this profile</string>
+ <!-- Text for switch to work profile for call dialog to guide users to make call from work
+ profile dialer app as it's not possible to make call from current profile due to an admin policy. [CHAR LIMIT=NONE]
+ -->
+ <string name="call_from_work_profile_text">Your work policy allows you to make phone calls only from the work profile</string>
+ <!-- Label for the button to switch to work profile for placing call. Switch to work profile dialog guide users to make call from work
+ profile dialer app as it's not possible to make call from current profile due to an admin policy.[CHAR LIMIT=60] -->
+ <string name="call_from_work_profile_action">Switch to work profile</string>
+ <!-- Label for the close button on switch to work profile dialog. Switch to work profile dialog guide users to make call from work
+ profile dialer app as it's not possible to make call from current profile due to an admin policy.[CHAR LIMIT=60] -->
+ <string name="call_from_work_profile_close">Close</string>
</resources>
diff --git a/packages/SystemUI/res/xml/large_screen_shade_header.xml b/packages/SystemUI/res/xml/large_screen_shade_header.xml
index 06d425c57577..bf576dc5790b 100644
--- a/packages/SystemUI/res/xml/large_screen_shade_header.xml
+++ b/packages/SystemUI/res/xml/large_screen_shade_header.xml
@@ -1,5 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
+<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright (C) 2021 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
@@ -15,105 +14,73 @@
~ limitations under the License.
-->
-<ConstraintSet
- xmlns:android="http://schemas.android.com/apk/res/android"
+<ConstraintSet xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/large_screen_header_constraint">
- <Constraint
- android:id="@+id/clock">
+ <Constraint android:id="@+id/clock">
<Layout
android:layout_width="wrap_content"
android:layout_height="0dp"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintEnd_toStartOf="@id/date"
- app:layout_constraintHorizontal_bias="0"
- />
- <Transform
- android:scaleX="1"
- android:scaleY="1"
- />
+ app:layout_constraintStart_toEndOf="@id/begin_guide"
+ app:layout_constraintTop_toTopOf="parent" />
+ <PropertySet android:alpha="1" />
</Constraint>
- <Constraint
- android:id="@+id/date">
+ <Constraint android:id="@+id/date">
<Layout
android:layout_width="wrap_content"
android:layout_height="0dp"
- app:layout_constraintStart_toEndOf="@id/clock"
- app:layout_constraintEnd_toStartOf="@id/carrier_group"
- app:layout_constraintTop_toTopOf="parent"
+ android:layout_marginStart="8dp"
app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintHorizontal_bias="0"
- />
+ app:layout_constraintStart_toEndOf="@id/clock"
+ app:layout_constraintTop_toTopOf="parent" />
+ <PropertySet android:alpha="1" />
</Constraint>
- <Constraint
- android:id="@+id/carrier_group">
+ <Constraint android:id="@+id/carrier_group">
<Layout
- app:layout_constraintWidth_min="48dp"
android:layout_width="0dp"
android:layout_height="0dp"
- app:layout_constrainedWidth="true"
android:layout_gravity="end|center_vertical"
- android:layout_marginStart="8dp"
- app:layout_constraintStart_toEndOf="@id/date"
- app:layout_constraintEnd_toStartOf="@id/statusIcons"
- app:layout_constraintTop_toTopOf="@id/clock"
app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintHorizontal_bias="1"
- />
- <PropertySet
- android:alpha="1"
- />
+ app:layout_constraintEnd_toStartOf="@id/statusIcons"
+ app:layout_constraintStart_toEndOf="@id/date"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintWidth_default="wrap"
+ app:layout_constraintWidth_min="48dp" />
+ <PropertySet android:alpha="1" />
</Constraint>
- <Constraint
- android:id="@+id/statusIcons">
+ <Constraint android:id="@+id/statusIcons">
<Layout
- app:layout_constraintHeight_min="@dimen/large_screen_shade_header_min_height"
android:layout_width="wrap_content"
android:layout_height="@dimen/large_screen_shade_header_min_height"
- app:layout_constraintStart_toEndOf="@id/carrier_group"
- app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon"
- app:layout_constraintTop_toTopOf="@id/clock"
app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintHorizontal_bias="1"
- />
- <PropertySet
- android:alpha="1"
- />
+ app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintEnd_toEndOf="@id/carrier_group"/>
+ <PropertySet android:alpha="1" />
</Constraint>
- <Constraint
- android:id="@+id/batteryRemainingIcon">
+ <Constraint android:id="@+id/batteryRemainingIcon">
<Layout
android:layout_width="wrap_content"
android:layout_height="0dp"
app:layout_constraintHeight_min="@dimen/large_screen_shade_header_min_height"
- app:layout_constraintStart_toEndOf="@id/statusIcons"
- app:layout_constraintEnd_toStartOf="@id/privacy_container"
- app:layout_constraintTop_toTopOf="@id/clock"
app:layout_constraintBottom_toBottomOf="parent"
- />
- <PropertySet
- android:alpha="1"
- />
+ app:layout_constraintEnd_toStartOf="@id/privacy_container"
+ app:layout_constraintTop_toTopOf="parent" />
+ <PropertySet android:alpha="1" />
</Constraint>
- <Constraint
- android:id="@+id/privacy_container">
+ <Constraint android:id="@+id/privacy_container">
<Layout
android:layout_width="wrap_content"
android:layout_height="@dimen/large_screen_shade_header_min_height"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintTop_toTopOf="@id/date"
- app:layout_constraintBottom_toBottomOf="@id/date"
- app:layout_constraintStart_toEndOf="@id/batteryRemainingIcon"
- app:layout_constraintHorizontal_bias="1"
- />
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toStartOf="@id/end_guide"
+ app:layout_constraintTop_toTopOf="parent" />
</Constraint>
-
</ConstraintSet> \ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/hardware/InputDevice.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/hardware/InputDevice.kt
new file mode 100644
index 000000000000..a9a5cf974f95
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/hardware/InputDevice.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shared.hardware
+
+import android.view.InputDevice
+
+/**
+ * Returns true if [InputDevice] is electronic components to allow a user to use an active stylus in
+ * the host device or a passive stylus is detected by the host device.
+ */
+val InputDevice.isInternalStylusSource: Boolean
+ get() = isAnyStylusSource && !isExternal
+
+/** Returns true if [InputDevice] is an active stylus. */
+val InputDevice.isExternalStylusSource: Boolean
+ get() = isAnyStylusSource && isExternal
+
+/**
+ * Returns true if [InputDevice] supports any stylus source.
+ *
+ * @see InputDevice.isInternalStylusSource
+ * @see InputDevice.isExternalStylusSource
+ */
+val InputDevice.isAnyStylusSource: Boolean
+ get() = supportsSource(InputDevice.SOURCE_STYLUS)
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/hardware/InputManager.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/hardware/InputManager.kt
new file mode 100644
index 000000000000..f020b4ec4983
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/hardware/InputManager.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shared.hardware
+
+import android.hardware.input.InputManager
+import android.view.InputDevice
+
+/**
+ * Gets information about all input devices in the system and returns as a lazy [Sequence].
+ *
+ * For performance reasons, it is preferred to operate atop the returned [Sequence] to ensure each
+ * operation is executed on an element-per-element basis yet customizable.
+ *
+ * For example:
+ * ```kotlin
+ * val stylusDevices = inputManager.getInputDeviceSequence().filter {
+ * it.supportsSource(InputDevice.SOURCE_STYLUS)
+ * }
+ *
+ * val hasInternalStylus = stylusDevices.any { it.isInternal }
+ * val hasExternalStylus = stylusDevices.any { !it.isInternal }
+ * ```
+ *
+ * @return a [Sequence] of [InputDevice].
+ */
+fun InputManager.getInputDeviceSequence(): Sequence<InputDevice> =
+ inputDeviceIds.asSequence().mapNotNull { getInputDevice(it) }
+
+/**
+ * Returns the first [InputDevice] matching the given predicate, or null if no such [InputDevice]
+ * was found.
+ */
+fun InputManager.findInputDevice(predicate: (InputDevice) -> Boolean): InputDevice? =
+ getInputDeviceSequence().find { predicate(it) }
+
+/**
+ * Returns true if [any] [InputDevice] matches with [predicate].
+ *
+ * For example:
+ * ```kotlin
+ * val hasStylusSupport = inputManager.hasInputDevice { it.isStylusSupport() }
+ * val hasStylusPen = inputManager.hasInputDevice { it.isStylusPen() }
+ * ```
+ */
+fun InputManager.hasInputDevice(predicate: (InputDevice) -> Boolean): Boolean =
+ getInputDeviceSequence().any { predicate(it) }
+
+/** Returns true if host device has any [InputDevice] where [InputDevice.isInternalStylusSource]. */
+fun InputManager.hasInternalStylusSource(): Boolean = hasInputDevice { it.isInternalStylusSource }
+
+/** Returns true if host device has any [InputDevice] where [InputDevice.isExternalStylusSource]. */
+fun InputManager.hasExternalStylusSource(): Boolean = hasInputDevice { it.isExternalStylusSource }
+
+/** Returns true if host device has any [InputDevice] where [InputDevice.isAnyStylusSource]. */
+fun InputManager.hasAnyStylusSource(): Boolean = hasInputDevice { it.isAnyStylusSource }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
index 1c532fe7a529..b8bddd149d9a 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
@@ -22,6 +22,7 @@ import android.graphics.Rect;
import android.os.Bundle;
import android.os.UserHandle;
import android.view.MotionEvent;
+import com.android.internal.util.ScreenshotRequest;
import com.android.systemui.shared.recents.model.Task;
@@ -87,12 +88,6 @@ interface ISystemUiProxy {
void notifyPrioritizedRotation(int rotation) = 25;
/**
- * Handle the provided image as if it was a screenshot.
- */
- void handleImageBundleAsScreenshot(in Bundle screenImageBundle, in Rect locationInScreen,
- in Insets visibleInsets, in Task.TaskKey task) = 28;
-
- /**
* Notifies to expand notification panel.
*/
void expandNotificationPanel() = 29;
@@ -125,5 +120,10 @@ interface ISystemUiProxy {
*/
void toggleNotificationPanel() = 50;
- // Next id = 51
+ /**
+ * Handle the screenshot request.
+ */
+ void takeScreenshot(in ScreenshotRequest request) = 51;
+
+ // Next id = 52
}
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/TrustRepositoryLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/TrustRepositoryLogger.kt
new file mode 100644
index 000000000000..249b3fe97d81
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/logging/TrustRepositoryLogger.kt
@@ -0,0 +1,89 @@
+/*
+ * 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.keyguard.logging
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.shared.model.TrustModel
+import com.android.systemui.log.dagger.KeyguardUpdateMonitorLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
+import javax.inject.Inject
+
+/** Logging helper for trust repository. */
+@SysUISingleton
+class TrustRepositoryLogger
+@Inject
+constructor(
+ @KeyguardUpdateMonitorLog private val logBuffer: LogBuffer,
+) {
+ fun onTrustChanged(
+ enabled: Boolean,
+ newlyUnlocked: Boolean,
+ userId: Int,
+ flags: Int,
+ trustGrantedMessages: List<String>?
+ ) {
+ logBuffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ bool1 = enabled
+ bool2 = newlyUnlocked
+ int1 = userId
+ int2 = flags
+ str1 = trustGrantedMessages?.joinToString()
+ },
+ {
+ "onTrustChanged enabled: $bool1, newlyUnlocked: $bool2, " +
+ "userId: $int1, flags: $int2, grantMessages: $str1"
+ }
+ )
+ }
+
+ fun trustListenerRegistered() {
+ logBuffer.log(TAG, LogLevel.VERBOSE, "TrustRepository#registerTrustListener")
+ }
+
+ fun trustListenerUnregistered() {
+ logBuffer.log(TAG, LogLevel.VERBOSE, "TrustRepository#unregisterTrustListener")
+ }
+
+ fun trustModelEmitted(value: TrustModel) {
+ logBuffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ int1 = value.userId
+ bool1 = value.isTrusted
+ },
+ { "trustModel emitted: userId: $int1 isTrusted: $bool1" }
+ )
+ }
+
+ fun isCurrentUserTrusted(isCurrentUserTrusted: Boolean) {
+ logBuffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ { bool1 = isCurrentUserTrusted },
+ { "isCurrentUserTrusted emitted: $bool1" }
+ )
+ }
+
+ companion object {
+ const val TAG = "TrustRepositoryLog"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
index 50449b0936aa..f519796004f7 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
@@ -42,7 +42,6 @@ import android.view.IWindowManager;
import android.view.InputDevice;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
-import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.view.accessibility.AccessibilityManager;
@@ -343,6 +342,7 @@ public class SystemActions implements CoreStartable {
/**
* Register a system action.
+ *
* @param actionId the action ID to register.
*/
public void register(int actionId) {
@@ -440,6 +440,7 @@ public class SystemActions implements CoreStartable {
/**
* Unregister a system action.
+ *
* @param actionId the action ID to unregister.
*/
public void unregister(int actionId) {
@@ -475,7 +476,8 @@ public class SystemActions implements CoreStartable {
}
private void handleNotifications() {
- mCentralSurfacesOptionalLazy.get().ifPresent(CentralSurfaces::animateExpandNotificationsPanel);
+ mCentralSurfacesOptionalLazy.get().ifPresent(
+ CentralSurfaces::animateExpandNotificationsPanel);
}
private void handleQuickSettings() {
@@ -507,7 +509,7 @@ public class SystemActions implements CoreStartable {
private void handleTakeScreenshot() {
ScreenshotHelper screenshotHelper = new ScreenshotHelper(mContext);
- screenshotHelper.takeScreenshot(WindowManager.TAKE_SCREENSHOT_FULLSCREEN,
+ screenshotHelper.takeScreenshot(
SCREENSHOT_ACCESSIBILITY_ACTIONS, new Handler(Looper.getMainLooper()), null);
}
diff --git a/packages/SystemUI/src/com/android/systemui/camera/CameraIntents.kt b/packages/SystemUI/src/com/android/systemui/camera/CameraIntents.kt
index 867faf9843fe..cc43e7ed25a5 100644
--- a/packages/SystemUI/src/com/android/systemui/camera/CameraIntents.kt
+++ b/packages/SystemUI/src/com/android/systemui/camera/CameraIntents.kt
@@ -20,15 +20,13 @@ import android.content.Context
import android.content.Intent
import android.provider.MediaStore
import android.text.TextUtils
-
import com.android.systemui.R
class CameraIntents {
companion object {
- val DEFAULT_SECURE_CAMERA_INTENT_ACTION =
- MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE
- val DEFAULT_INSECURE_CAMERA_INTENT_ACTION =
- MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA
+ val DEFAULT_SECURE_CAMERA_INTENT_ACTION = MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE
+ val DEFAULT_INSECURE_CAMERA_INTENT_ACTION = MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA
+ private val VIDEO_CAMERA_INTENT_ACTION = MediaStore.INTENT_ACTION_VIDEO_CAMERA
const val EXTRA_LAUNCH_SOURCE = "com.android.systemui.camera_launch_source"
@JvmStatic
@@ -44,18 +42,14 @@ class CameraIntents {
@JvmStatic
fun getInsecureCameraIntent(context: Context): Intent {
val intent = Intent(DEFAULT_INSECURE_CAMERA_INTENT_ACTION)
- getOverrideCameraPackage(context)?.let {
- intent.setPackage(it)
- }
+ getOverrideCameraPackage(context)?.let { intent.setPackage(it) }
return intent
}
@JvmStatic
fun getSecureCameraIntent(context: Context): Intent {
val intent = Intent(DEFAULT_SECURE_CAMERA_INTENT_ACTION)
- getOverrideCameraPackage(context)?.let {
- intent.setPackage(it)
- }
+ getOverrideCameraPackage(context)?.let { intent.setPackage(it) }
return intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
}
@@ -68,5 +62,11 @@ class CameraIntents {
fun isInsecureCameraIntent(intent: Intent?): Boolean {
return intent?.getAction()?.equals(DEFAULT_INSECURE_CAMERA_INTENT_ACTION) ?: false
}
+
+ /** Returns an [Intent] that can be used to start the camera in video mode. */
+ @JvmStatic
+ fun getVideoCameraIntent(): Intent {
+ return Intent(VIDEO_CAMERA_INTENT_ACTION)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/camera/CameraIntentsWrapper.kt b/packages/SystemUI/src/com/android/systemui/camera/CameraIntentsWrapper.kt
index cf02f8fb4a3c..a434617f2da7 100644
--- a/packages/SystemUI/src/com/android/systemui/camera/CameraIntentsWrapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/camera/CameraIntentsWrapper.kt
@@ -21,7 +21,9 @@ import android.content.Intent
import javax.inject.Inject
/** Injectable wrapper around [CameraIntents]. */
-class CameraIntentsWrapper @Inject constructor(
+class CameraIntentsWrapper
+@Inject
+constructor(
private val context: Context,
) {
@@ -40,4 +42,9 @@ class CameraIntentsWrapper @Inject constructor(
fun getInsecureCameraIntent(): Intent {
return CameraIntents.getInsecureCameraIntent(context)
}
+
+ /** Returns an [Intent] that can be used to start the camera in video mode. */
+ fun getVideoCameraIntent(): Intent {
+ return CameraIntents.getVideoCameraIntent()
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index c53b4d11d997..25398280452a 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -102,6 +102,11 @@ object Flags {
val FILTER_UNSEEN_NOTIFS_ON_KEYGUARD =
unreleasedFlag(254647461, "filter_unseen_notifs_on_keyguard", teamfood = true)
+ // TODO(b/263414400): Tracking Bug
+ @JvmField
+ val NOTIFICATION_ANIMATE_BIG_PICTURE =
+ unreleasedFlag(120, "notification_animate_big_picture", teamfood = true)
+
// 200 - keyguard/lockscreen
// ** Flag retired **
// public static final BooleanFlag KEYGUARD_LAYOUT =
@@ -531,4 +536,9 @@ object Flags {
// TODO(b259590361): Tracking bug
val EXPERIMENTAL_FLAG = unreleasedFlag(2, "exp_flag_release")
+
+ // 2600 - keyboard shortcut
+ // TODO(b/259352579): Tracking Bug
+ @JvmField
+ val SHORTCUT_LIST_SEARCH_LAYOUT = unreleasedFlag(2600, "shortcut_list_search_layout")
}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index c3e163fbeecc..718b9bd07269 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -21,7 +21,6 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_GLOBAL_ACTIONS;
-import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST;
@@ -731,9 +730,10 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
}
@VisibleForTesting
- boolean shouldDisplayBugReport(UserInfo currentUser) {
- return mGlobalSettings.getInt(Settings.Global.BUGREPORT_IN_POWER_MENU, 0) != 0
- && (currentUser == null || currentUser.isAdmin());
+ boolean shouldDisplayBugReport(@Nullable UserInfo user) {
+ return user != null && user.isAdmin()
+ && mGlobalSettings.getIntForUser(Settings.Secure.BUGREPORT_IN_POWER_MENU, 0,
+ user.id) != 0;
}
@Override
@@ -959,8 +959,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
- mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN,
- SCREENSHOT_GLOBAL_ACTIONS, mHandler, null);
+ mScreenshotHelper.takeScreenshot(SCREENSHOT_GLOBAL_ACTIONS, mHandler, null);
mMetricsLogger.action(MetricsEvent.ACTION_SCREENSHOT_POWER_MENU);
mUiEventLogger.log(GlobalActionsEvent.GA_SCREENSHOT_PRESS);
}
@@ -1059,7 +1058,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
@Override
public boolean showBeforeProvisioning() {
return Build.isDebuggable() && mGlobalSettings.getIntForUser(
- Settings.Global.BUGREPORT_IN_POWER_MENU, 0, getCurrentUser().id) != 0
+ Settings.Secure.BUGREPORT_IN_POWER_MENU, 0, getCurrentUser().id) != 0
&& getCurrentUser().isAdmin();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 18854e513bed..a120e17f1126 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -148,12 +148,12 @@ import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.util.DeviceConfigProxy;
+import dagger.Lazy;
+
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.concurrent.Executor;
-import dagger.Lazy;
-
/**
* Mediates requests related to the keyguard. This includes queries about the
* state of the keyguard, power management events that effect whether the keyguard
@@ -589,12 +589,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
@Override
public void onDeviceProvisioned() {
sendUserPresentBroadcast();
- synchronized (KeyguardViewMediator.this) {
- // If system user is provisioned, we might want to lock now to avoid showing launcher
- if (mustNotUnlockCurrentUser()) {
- doKeyguardLocked(null);
- }
- }
}
@Override
@@ -1265,11 +1259,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
mPM.userActivity(SystemClock.uptimeMillis(), false);
}
- boolean mustNotUnlockCurrentUser() {
- return UserManager.isSplitSystemUser()
- && KeyguardUpdateMonitor.getCurrentUser() == UserHandle.USER_SYSTEM;
- }
-
private void setupLocked() {
mShowKeyguardWakeLock = mPM.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "show keyguard");
mShowKeyguardWakeLock.setReferenceCounted(false);
@@ -1947,31 +1936,28 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
}
}
- // In split system user mode, we never unlock system user.
- if (!mustNotUnlockCurrentUser()
- || !mUpdateMonitor.isDeviceProvisioned()) {
+ // if the setup wizard hasn't run yet, don't show
+ final boolean requireSim = !SystemProperties.getBoolean("keyguard.no_require_sim", false);
+ final boolean absent = SubscriptionManager.isValidSubscriptionId(
+ mUpdateMonitor.getNextSubIdForState(TelephonyManager.SIM_STATE_ABSENT));
+ final boolean disabled = SubscriptionManager.isValidSubscriptionId(
+ mUpdateMonitor.getNextSubIdForState(TelephonyManager.SIM_STATE_PERM_DISABLED));
+ final boolean lockedOrMissing = mUpdateMonitor.isSimPinSecure()
+ || ((absent || disabled) && requireSim);
- // if the setup wizard hasn't run yet, don't show
- final boolean requireSim = !SystemProperties.getBoolean("keyguard.no_require_sim", false);
- final boolean absent = SubscriptionManager.isValidSubscriptionId(
- mUpdateMonitor.getNextSubIdForState(TelephonyManager.SIM_STATE_ABSENT));
- final boolean disabled = SubscriptionManager.isValidSubscriptionId(
- mUpdateMonitor.getNextSubIdForState(TelephonyManager.SIM_STATE_PERM_DISABLED));
- final boolean lockedOrMissing = mUpdateMonitor.isSimPinSecure()
- || ((absent || disabled) && requireSim);
-
- if (!lockedOrMissing && shouldWaitForProvisioning()) {
- if (DEBUG) Log.d(TAG, "doKeyguard: not showing because device isn't provisioned"
- + " and the sim is not locked or missing");
- return;
+ if (!lockedOrMissing && shouldWaitForProvisioning()) {
+ if (DEBUG) {
+ Log.d(TAG, "doKeyguard: not showing because device isn't provisioned and the sim is"
+ + " not locked or missing");
}
+ return;
+ }
- boolean forceShow = options != null && options.getBoolean(OPTION_FORCE_SHOW, false);
- if (mLockPatternUtils.isLockScreenDisabled(KeyguardUpdateMonitor.getCurrentUser())
- && !lockedOrMissing && !forceShow) {
- if (DEBUG) Log.d(TAG, "doKeyguard: not showing because lockscreen is off");
- return;
- }
+ boolean forceShow = options != null && options.getBoolean(OPTION_FORCE_SHOW, false);
+ if (mLockPatternUtils.isLockScreenDisabled(KeyguardUpdateMonitor.getCurrentUser())
+ && !lockedOrMissing && !forceShow) {
+ if (DEBUG) Log.d(TAG, "doKeyguard: not showing because lockscreen is off");
+ return;
}
if (DEBUG) Log.d(TAG, "doKeyguard: showing the lock screen");
@@ -2539,15 +2525,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
synchronized (KeyguardViewMediator.this) {
if (DEBUG) Log.d(TAG, "handleHide");
- if (mustNotUnlockCurrentUser()) {
- // In split system user mode, we never unlock system user. The end user has to
- // switch to another user.
- // TODO: We should stop it early by disabling the swipe up flow. Right now swipe up
- // still completes and makes the screen blank.
- if (DEBUG) Log.d(TAG, "Split system user, quit unlocking.");
- mKeyguardExitAnimationRunner = null;
- return;
- }
mHiding = true;
if (mShowing && !mOccluded) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt
index ea5b4f43cc75..cd4dac0d59c5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt
@@ -30,5 +30,6 @@ object BuiltInKeyguardQuickAffordanceKeys {
const val HOME_CONTROLS = "home"
const val QR_CODE_SCANNER = "qr_code_scanner"
const val QUICK_ACCESS_WALLET = "wallet"
+ const val VIDEO_CAMERA = "video_camera"
// Please keep alphabetical order of const names to simplify future maintenance.
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt
index dbc376e62950..f6e6d6b7dc1b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt
@@ -26,6 +26,7 @@ import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.statusbar.StatusBarState
import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
@@ -46,7 +47,7 @@ constructor(
get() = context.getString(R.string.accessibility_camera_button)
override val pickerIconResourceId: Int
- get() = com.android.internal.R.drawable.perm_group_camera
+ get() = R.drawable.ic_camera
override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState>
get() =
@@ -54,12 +55,20 @@ constructor(
KeyguardQuickAffordanceConfig.LockScreenState.Visible(
icon =
Icon.Resource(
- com.android.internal.R.drawable.perm_group_camera,
+ R.drawable.ic_camera,
ContentDescription.Resource(R.string.accessibility_camera_button)
)
)
)
+ override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState {
+ return if (isLaunchable()) {
+ super.getPickerScreenState()
+ } else {
+ KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice
+ }
+ }
+
override fun onTriggered(
expandable: Expandable?
): KeyguardQuickAffordanceConfig.OnTriggeredResult {
@@ -68,4 +77,8 @@ constructor(
.launchCamera(StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE)
return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
}
+
+ private fun isLaunchable(): Boolean {
+ return cameraGestureHelper.get().canCameraGestureBeLaunched(StatusBarState.KEYGUARD)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt
index 71d01ebc8496..a1cce5c670ba 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt
@@ -39,6 +39,7 @@ interface KeyguardDataQuickAffordanceModule {
quickAccessWallet: QuickAccessWalletKeyguardQuickAffordanceConfig,
qrCodeScanner: QrCodeScannerKeyguardQuickAffordanceConfig,
camera: CameraQuickAffordanceConfig,
+ videoCamera: VideoCameraQuickAffordanceConfig,
): Set<KeyguardQuickAffordanceConfig> {
return setOf(
camera,
@@ -47,6 +48,7 @@ interface KeyguardDataQuickAffordanceModule {
home,
quickAccessWallet,
qrCodeScanner,
+ videoCamera,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
index 680c06bf2c64..4ba2eb913742 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
@@ -100,9 +100,9 @@ constructor(
override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState {
return when {
- !walletController.isWalletEnabled ->
+ !walletController.walletClient.isWalletServiceAvailable ->
KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice
- walletController.walletClient.tileIcon == null || queryCards().isEmpty() -> {
+ !walletController.isWalletEnabled || queryCards().isEmpty() -> {
val componentName =
walletController.walletClient.createWalletSettingsIntent().toComponentName()
val actionText =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfig.kt
new file mode 100644
index 000000000000..d9ec3b1c2f87
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfig.kt
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.quickaffordance
+
+import android.app.StatusBarManager
+import android.content.Context
+import android.content.Intent
+import com.android.systemui.ActivityIntentHelper
+import com.android.systemui.R
+import com.android.systemui.animation.Expandable
+import com.android.systemui.camera.CameraIntents
+import com.android.systemui.camera.CameraIntentsWrapper
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.settings.UserTracker
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+
+@SysUISingleton
+class VideoCameraQuickAffordanceConfig
+@Inject
+constructor(
+ @Application private val context: Context,
+ private val cameraIntents: CameraIntentsWrapper,
+ private val activityIntentHelper: ActivityIntentHelper,
+ private val userTracker: UserTracker,
+) : KeyguardQuickAffordanceConfig {
+
+ private val intent: Intent by lazy {
+ cameraIntents.getVideoCameraIntent().apply {
+ putExtra(
+ CameraIntents.EXTRA_LAUNCH_SOURCE,
+ StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE,
+ )
+ }
+ }
+
+ override val key: String
+ get() = BuiltInKeyguardQuickAffordanceKeys.VIDEO_CAMERA
+
+ override val pickerName: String
+ get() = context.getString(R.string.video_camera)
+
+ override val pickerIconResourceId: Int
+ get() = R.drawable.ic_videocam
+
+ override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState>
+ get() =
+ flowOf(
+ if (isLaunchable()) {
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+ icon =
+ Icon.Resource(
+ R.drawable.ic_videocam,
+ ContentDescription.Resource(R.string.video_camera)
+ )
+ )
+ } else {
+ KeyguardQuickAffordanceConfig.LockScreenState.Hidden
+ }
+ )
+
+ override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState {
+ return if (isLaunchable()) {
+ super.getPickerScreenState()
+ } else {
+ KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice
+ }
+ }
+
+ override fun onTriggered(
+ expandable: Expandable?
+ ): KeyguardQuickAffordanceConfig.OnTriggeredResult {
+ return KeyguardQuickAffordanceConfig.OnTriggeredResult.StartActivity(
+ intent = intent,
+ canShowWhileLocked = false,
+ )
+ }
+
+ private fun isLaunchable(): Boolean {
+ return activityIntentHelper.getTargetActivityInfo(
+ intent,
+ userTracker.userId,
+ true,
+ ) != null
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/TrustRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/TrustRepository.kt
new file mode 100644
index 000000000000..d90f328719bb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/TrustRepository.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import android.app.trust.TrustManager
+import com.android.keyguard.logging.TrustRepositoryLogger
+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.Application
+import com.android.systemui.keyguard.shared.model.TrustModel
+import com.android.systemui.user.data.repository.UserRepository
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.shareIn
+
+/** Encapsulates any state relevant to trust agents and trust grants. */
+interface TrustRepository {
+ /** Flow representing whether the current user is trusted. */
+ val isCurrentUserTrusted: Flow<Boolean>
+}
+
+@SysUISingleton
+class TrustRepositoryImpl
+@Inject
+constructor(
+ @Application private val applicationScope: CoroutineScope,
+ private val userRepository: UserRepository,
+ private val trustManager: TrustManager,
+ private val logger: TrustRepositoryLogger,
+) : TrustRepository {
+ private val latestTrustModelForUser = mutableMapOf<Int, TrustModel>()
+
+ private val trust =
+ conflatedCallbackFlow {
+ val callback =
+ object : TrustManager.TrustListener {
+ override fun onTrustChanged(
+ enabled: Boolean,
+ newlyUnlocked: Boolean,
+ userId: Int,
+ flags: Int,
+ grantMsgs: List<String>?
+ ) {
+ logger.onTrustChanged(enabled, newlyUnlocked, userId, flags, grantMsgs)
+ trySendWithFailureLogging(
+ TrustModel(enabled, userId),
+ TrustRepositoryLogger.TAG,
+ "onTrustChanged"
+ )
+ }
+
+ override fun onTrustError(message: CharSequence?) = Unit
+
+ override fun onTrustManagedChanged(enabled: Boolean, userId: Int) = Unit
+ }
+ trustManager.registerTrustListener(callback)
+ logger.trustListenerRegistered()
+ awaitClose {
+ logger.trustListenerUnregistered()
+ trustManager.unregisterTrustListener(callback)
+ }
+ }
+ .onEach {
+ latestTrustModelForUser[it.userId] = it
+ logger.trustModelEmitted(it)
+ }
+ .shareIn(applicationScope, started = SharingStarted.Eagerly, replay = 1)
+
+ override val isCurrentUserTrusted: Flow<Boolean>
+ get() =
+ combine(trust, userRepository.selectedUserInfo, ::Pair)
+ .map { latestTrustModelForUser[it.second.id]?.isTrusted ?: false }
+ .distinctUntilChanged()
+ .onEach { logger.isCurrentUserTrusted(it) }
+ .onStart { emit(false) }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index 7134ec0d64f0..81a58286aab7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
@@ -31,8 +31,10 @@ import javax.inject.Inject
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
@SysUISingleton
@@ -87,6 +89,9 @@ constructor(
private fun listenForDreamingToOccluded() {
scope.launch {
keyguardInteractor.isDreaming
+ // Add a slight delay, as dreaming and occluded events will arrive with a small gap
+ // in time. This prevents a transition to OCCLUSION happening prematurely.
+ .onEach { delay(50) }
.sample(
combine(
keyguardInteractor.isKeyguardOccluded,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
index 9203a9b924a7..14f918d78bc6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
@@ -50,10 +50,9 @@ constructor(
private fun listenForGoneToDreaming() {
scope.launch {
keyguardInteractor.isAbleToDream
- .sample(keyguardTransitionInteractor.finishedKeyguardState, ::Pair)
- .collect { pair ->
- val (isAbleToDream, keyguardState) = pair
- if (isAbleToDream && keyguardState == KeyguardState.GONE) {
+ .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
+ .collect { (isAbleToDream, lastStartedStep) ->
+ if (isAbleToDream && lastStartedStep.to == KeyguardState.GONE) {
keyguardTransitionRepository.startTransition(
TransitionInfo(
name,
@@ -72,15 +71,15 @@ constructor(
keyguardInteractor.wakefulnessModel
.sample(
combine(
- keyguardTransitionInteractor.finishedKeyguardState,
+ keyguardTransitionInteractor.startedKeyguardTransitionStep,
keyguardInteractor.isAodAvailable,
::Pair
),
::toTriple
)
- .collect { (wakefulnessState, keyguardState, isAodAvailable) ->
+ .collect { (wakefulnessState, lastStartedStep, isAodAvailable) ->
if (
- keyguardState == KeyguardState.GONE &&
+ lastStartedStep.to == KeyguardState.GONE &&
wakefulnessState.state == WakefulnessState.STARTING_TO_SLEEP
) {
keyguardTransitionRepository.startTransition(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
index a92540d733b5..6679b2221119 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
@@ -20,8 +20,6 @@ import android.content.res.ColorStateList
import android.hardware.biometrics.BiometricSourceType
import android.os.Handler
import android.os.Trace
-import android.os.UserHandle
-import android.os.UserManager
import android.view.View
import com.android.keyguard.KeyguardSecurityModel
import com.android.keyguard.KeyguardUpdateMonitor
@@ -134,12 +132,6 @@ constructor(
return
}
- val keyguardUserId = KeyguardUpdateMonitor.getCurrentUser()
- if (keyguardUserId == UserHandle.USER_SYSTEM && UserManager.isSplitSystemUser()) {
- // In split system user mode, we never unlock system user.
- return
- }
-
Trace.beginSection("KeyguardBouncer#show")
repository.setPrimaryScrimmed(isScrimmed)
if (isScrimmed) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TrustModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TrustModel.kt
new file mode 100644
index 000000000000..4fd14b1ce087
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TrustModel.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.shared.model
+
+/** Represents the trust state */
+data class TrustModel(
+ /** If true, the system believes the environment to be trusted. */
+ val isTrusted: Boolean,
+ /** The user, for which the trust changed. */
+ val userId: Int,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt
index 93be6a78ccd5..5c65c8bd5689 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt
@@ -162,8 +162,8 @@ internal constructor(
context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK ==
UI_MODE_NIGHT_YES
)
- colorScheme.accent1[2]
- else colorScheme.accent1[3]
+ colorScheme.accent1.s100
+ else colorScheme.accent1.s200
},
{ seamlessColor: Int ->
val accentColorList = ColorStateList.valueOf(seamlessColor)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaColorSchemes.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaColorSchemes.kt
index 82abf9bfcc80..2a8362b64cd6 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaColorSchemes.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaColorSchemes.kt
@@ -19,28 +19,28 @@ package com.android.systemui.media.controls.ui
import com.android.systemui.monet.ColorScheme
/** Returns the surface color for media controls based on the scheme. */
-internal fun surfaceFromScheme(scheme: ColorScheme) = scheme.accent2[9] // A2-800
+internal fun surfaceFromScheme(scheme: ColorScheme) = scheme.accent2.s800 // A2-800
/** Returns the primary accent color for media controls based on the scheme. */
-internal fun accentPrimaryFromScheme(scheme: ColorScheme) = scheme.accent1[2] // A1-100
+internal fun accentPrimaryFromScheme(scheme: ColorScheme) = scheme.accent1.s100 // A1-100
/** Returns the secondary accent color for media controls based on the scheme. */
-internal fun accentSecondaryFromScheme(scheme: ColorScheme) = scheme.accent1[3] // A1-200
+internal fun accentSecondaryFromScheme(scheme: ColorScheme) = scheme.accent1.s200 // A1-200
/** Returns the primary text color for media controls based on the scheme. */
-internal fun textPrimaryFromScheme(scheme: ColorScheme) = scheme.neutral1[1] // N1-50
+internal fun textPrimaryFromScheme(scheme: ColorScheme) = scheme.neutral1.s50 // N1-50
/** Returns the inverse of the primary text color for media controls based on the scheme. */
-internal fun textPrimaryInverseFromScheme(scheme: ColorScheme) = scheme.neutral1[10] // N1-900
+internal fun textPrimaryInverseFromScheme(scheme: ColorScheme) = scheme.neutral1.s900 // N1-900
/** Returns the secondary text color for media controls based on the scheme. */
-internal fun textSecondaryFromScheme(scheme: ColorScheme) = scheme.neutral2[3] // N2-200
+internal fun textSecondaryFromScheme(scheme: ColorScheme) = scheme.neutral2.s200 // N2-200
/** Returns the tertiary text color for media controls based on the scheme. */
-internal fun textTertiaryFromScheme(scheme: ColorScheme) = scheme.neutral2[5] // N2-400
+internal fun textTertiaryFromScheme(scheme: ColorScheme) = scheme.neutral2.s400 // N2-400
/** Returns the color for the start of the background gradient based on the scheme. */
-internal fun backgroundStartFromScheme(scheme: ColorScheme) = scheme.accent2[8] // A2-700
+internal fun backgroundStartFromScheme(scheme: ColorScheme) = scheme.accent2.s700 // A2-700
/** Returns the color for the end of the background gradient based on the scheme. */
-internal fun backgroundEndFromScheme(scheme: ColorScheme) = scheme.accent1[8] // A1-700
+internal fun backgroundEndFromScheme(scheme: ColorScheme) = scheme.accent1.s700 // A1-700
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index 5b137e91f9e7..9cf672b584bc 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -493,20 +493,20 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
ColorScheme mCurrentColorScheme = new ColorScheme(wallpaperColors,
isDarkTheme);
if (isDarkTheme) {
- mColorItemContent = mCurrentColorScheme.getAccent1().get(2); // A1-100
- mColorSeekbarProgress = mCurrentColorScheme.getAccent2().get(7); // A2-600
- mColorButtonBackground = mCurrentColorScheme.getAccent1().get(4); // A1-300
- mColorItemBackground = mCurrentColorScheme.getNeutral2().get(9); // N2-800
- mColorConnectedItemBackground = mCurrentColorScheme.getAccent2().get(9); // A2-800
- mColorPositiveButtonText = mCurrentColorScheme.getAccent2().get(9); // A2-800
- mColorDialogBackground = mCurrentColorScheme.getNeutral1().get(10); // N1-900
+ mColorItemContent = mCurrentColorScheme.getAccent1().getS100(); // A1-100
+ mColorSeekbarProgress = mCurrentColorScheme.getAccent2().getS600(); // A2-600
+ mColorButtonBackground = mCurrentColorScheme.getAccent1().getS300(); // A1-300
+ mColorItemBackground = mCurrentColorScheme.getNeutral2().getS800(); // N2-800
+ mColorConnectedItemBackground = mCurrentColorScheme.getAccent2().getS800(); // A2-800
+ mColorPositiveButtonText = mCurrentColorScheme.getAccent2().getS800(); // A2-800
+ mColorDialogBackground = mCurrentColorScheme.getNeutral1().getS900(); // N1-900
} else {
- mColorItemContent = mCurrentColorScheme.getAccent1().get(9); // A1-800
- mColorSeekbarProgress = mCurrentColorScheme.getAccent1().get(4); // A1-300
- mColorButtonBackground = mCurrentColorScheme.getAccent1().get(7); // A1-600
- mColorItemBackground = mCurrentColorScheme.getAccent2().get(1); // A2-50
- mColorConnectedItemBackground = mCurrentColorScheme.getAccent1().get(2); // A1-100
- mColorPositiveButtonText = mCurrentColorScheme.getNeutral1().get(1); // N1-50
+ mColorItemContent = mCurrentColorScheme.getAccent1().getS800(); // A1-800
+ mColorSeekbarProgress = mCurrentColorScheme.getAccent1().getS300(); // A1-300
+ mColorButtonBackground = mCurrentColorScheme.getAccent1().getS600(); // A1-600
+ mColorItemBackground = mCurrentColorScheme.getAccent2().getS50(); // A2-50
+ mColorConnectedItemBackground = mCurrentColorScheme.getAccent1().getS100(); // A1-100
+ mColorPositiveButtonText = mCurrentColorScheme.getNeutral1().getS50(); // N1-50
mColorDialogBackground = mCurrentColorScheme.getBackgroundColor();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 4d005bebd99e..543f1bdc70d0 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -21,7 +21,6 @@ import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.MotionEvent.ACTION_CANCEL;
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_UP;
-import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_OVERVIEW;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
import static com.android.internal.accessibility.common.ShortcutConstants.CHOOSER_PACKAGE_NAME;
@@ -44,8 +43,6 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
-import android.graphics.Insets;
-import android.graphics.Rect;
import android.graphics.Region;
import android.hardware.input.InputManager;
import android.os.Binder;
@@ -77,6 +74,7 @@ import com.android.internal.app.IVoiceInteractionSessionListener;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.internal.util.ScreenshotHelper;
+import com.android.internal.util.ScreenshotRequest;
import com.android.systemui.Dumpable;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
@@ -94,7 +92,6 @@ import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.shared.recents.IOverviewProxy;
import com.android.systemui.shared.recents.ISystemUiProxy;
-import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -322,18 +319,8 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
}
@Override
- public void handleImageBundleAsScreenshot(Bundle screenImageBundle, Rect locationInScreen,
- Insets visibleInsets, Task.TaskKey task) {
- mScreenshotHelper.provideScreenshot(
- screenImageBundle,
- locationInScreen,
- visibleInsets,
- task.id,
- task.userId,
- task.sourceComponent,
- SCREENSHOT_OVERVIEW,
- mHandler,
- null);
+ public void takeScreenshot(ScreenshotRequest request) {
+ mScreenshotHelper.takeScreenshot(request, mHandler, null);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
index 95cc0dcadfb4..f011aab9da0f 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
@@ -19,27 +19,26 @@ package com.android.systemui.screenshot
import android.graphics.Insets
import android.util.Log
import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
-import com.android.internal.util.ScreenshotHelper.HardwareBitmapBundler
-import com.android.internal.util.ScreenshotHelper.ScreenshotRequest
+import com.android.internal.util.ScreenshotRequest
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags.SCREENSHOT_WORK_PROFILE_POLICY
-import java.util.function.Consumer
-import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
+import java.util.function.Consumer
+import javax.inject.Inject
/**
* Processes a screenshot request sent from {@link ScreenshotHelper}.
*/
@SysUISingleton
class RequestProcessor @Inject constructor(
- private val capture: ImageCapture,
- private val policy: ScreenshotPolicy,
- private val flags: FeatureFlags,
- /** For the Java Async version, to invoke the callback. */
- @Application private val mainScope: CoroutineScope
+ private val capture: ImageCapture,
+ private val policy: ScreenshotPolicy,
+ private val flags: FeatureFlags,
+ /** For the Java Async version, to invoke the callback. */
+ @Application private val mainScope: CoroutineScope
) {
/**
* Inspects the incoming request, returning a potentially modified request depending on policy.
@@ -58,7 +57,7 @@ class RequestProcessor @Inject constructor(
// regardless of the managed profile status.
if (request.type != TAKE_SCREENSHOT_PROVIDED_IMAGE &&
- flags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)
+ flags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)
) {
val info = policy.findPrimaryContent(policy.getDefaultDisplayId())
@@ -66,17 +65,21 @@ class RequestProcessor @Inject constructor(
result = if (policy.isManagedProfile(info.user.identifier)) {
val image = capture.captureTask(info.taskId)
- ?: error("Task snapshot returned a null Bitmap!")
+ ?: error("Task snapshot returned a null Bitmap!")
// Provide the task snapshot as the screenshot
- ScreenshotRequest(
- TAKE_SCREENSHOT_PROVIDED_IMAGE, request.source,
- HardwareBitmapBundler.hardwareBitmapToBundle(image),
- info.bounds, Insets.NONE, info.taskId, info.user.identifier, info.component
- )
+ ScreenshotRequest.Builder(TAKE_SCREENSHOT_PROVIDED_IMAGE, request.source)
+ .setTopComponent(info.component)
+ .setTaskId(info.taskId)
+ .setUserId(info.user.identifier)
+ .setBitmap(image)
+ .setBoundsOnScreen(info.bounds)
+ .setInsets(Insets.NONE)
+ .build()
} else {
// Create a new request of the same type which includes the top component
- ScreenshotRequest(request.type, request.source, info.component)
+ ScreenshotRequest.Builder(request.type, request.source)
+ .setTopComponent(info.component).build()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index b21a4857c736..6d87922f49eb 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -416,10 +416,11 @@ public class ScreenshotController {
}
boolean showFlash = false;
- if (!aspectRatiosMatch(screenshot, visibleInsets, screenshotScreenBounds)) {
+ if (screenshotScreenBounds == null
+ || !aspectRatiosMatch(screenshot, visibleInsets, screenshotScreenBounds)) {
showFlash = true;
visibleInsets = Insets.NONE;
- screenshotScreenBounds.set(0, 0, screenshot.getWidth(), screenshot.getHeight());
+ screenshotScreenBounds = new Rect(0, 0, screenshot.getWidth(), screenshot.getHeight());
}
mCurrentRequestCallback = requestCallback;
saveScreenshot(screenshot, finisher, screenshotScreenBounds, visibleInsets, topComponent,
@@ -553,6 +554,10 @@ public class ScreenshotController {
Log.d(TAG, "adding OnComputeInternalInsetsListener");
}
mScreenshotView.getViewTreeObserver().addOnComputeInternalInsetsListener(mScreenshotView);
+ if (DEBUG_WINDOW) {
+ Log.d(TAG, "setContentView: " + mScreenshotView);
+ }
+ setContentView(mScreenshotView);
}
/**
@@ -634,6 +639,7 @@ public class ScreenshotController {
// The window is focusable by default
setWindowFocusable(true);
+ mScreenshotView.requestFocus();
// Wait until this window is attached to request because it is
// the reference used to locate the target window (below).
@@ -691,10 +697,7 @@ public class ScreenshotController {
mContext.getDrawable(R.drawable.overlay_badge_background), owner));
}
mScreenshotView.setScreenshot(mScreenBitmap, screenInsets);
- if (DEBUG_WINDOW) {
- Log.d(TAG, "setContentView: " + mScreenshotView);
- }
- setContentView(mScreenshotView);
+
// ignore system bar insets for the purpose of window layout
mWindow.getDecorView().setOnApplyWindowInsetsListener(
(v, insets) -> WindowInsets.CONSUMED);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
index 35e9f3e56723..7b271a886d68 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
@@ -54,7 +54,7 @@ import android.widget.Toast;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.UiEventLogger;
-import com.android.internal.util.ScreenshotHelper;
+import com.android.internal.util.ScreenshotRequest;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.flags.FeatureFlags;
@@ -186,8 +186,7 @@ public class TakeScreenshotService extends Service {
final Consumer<Uri> onSaved = (uri) -> reportUri(replyTo, uri);
RequestCallback callback = new RequestCallbackImpl(replyTo);
- ScreenshotHelper.ScreenshotRequest request =
- (ScreenshotHelper.ScreenshotRequest) msg.obj;
+ ScreenshotRequest request = (ScreenshotRequest) msg.obj;
handleRequest(request, onSaved, callback);
return true;
@@ -195,7 +194,7 @@ public class TakeScreenshotService extends Service {
@MainThread
@VisibleForTesting
- void handleRequest(ScreenshotHelper.ScreenshotRequest request, Consumer<Uri> onSaved,
+ void handleRequest(ScreenshotRequest request, Consumer<Uri> onSaved,
RequestCallback callback) {
// If the storage for this user is locked, we have no place to store
// the screenshot, so skip taking it instead of showing a misleading
@@ -226,7 +225,7 @@ public class TakeScreenshotService extends Service {
(r) -> dispatchToController(r, onSaved, callback));
}
- private void dispatchToController(ScreenshotHelper.ScreenshotRequest request,
+ private void dispatchToController(ScreenshotRequest request,
Consumer<Uri> uriConsumer, RequestCallback callback) {
ComponentName topComponent = request.getTopComponent();
@@ -244,8 +243,7 @@ public class TakeScreenshotService extends Service {
if (DEBUG_SERVICE) {
Log.d(TAG, "handleMessage: TAKE_SCREENSHOT_PROVIDED_IMAGE");
}
- Bitmap screenshot = ScreenshotHelper.HardwareBitmapBundler.bundleToHardwareBitmap(
- request.getBitmapBundle());
+ Bitmap screenshot = request.getBitmap();
Rect screenBounds = request.getBoundsInScreen();
Insets insets = request.getInsets();
int taskId = request.getTaskId();
diff --git a/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt
index 5011227ad2cc..b3d31f2986d1 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt
@@ -69,7 +69,8 @@ object CombinedShadeHeadersConstraintManagerImpl : CombinedShadeHeadersConstrain
}
return ConstraintsChanges(
qqsConstraintsChanges = change,
- qsConstraintsChanges = change
+ qsConstraintsChanges = change,
+ largeScreenConstraintsChanges = change,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
index e406be1ea0a3..88676371bd6b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
@@ -113,7 +113,7 @@ class LargeScreenShadeHeaderController @Inject constructor(
QQS_HEADER_CONSTRAINT -> "QQS Header"
QS_HEADER_CONSTRAINT -> "QS Header"
LARGE_SCREEN_HEADER_CONSTRAINT -> "Large Screen Header"
- else -> "Unknown state"
+ else -> "Unknown state $this"
}
}
@@ -296,6 +296,9 @@ class LargeScreenShadeHeaderController @Inject constructor(
override fun onViewAttached() {
privacyIconsController.chipVisibilityListener = chipVisibilityListener
+ updateVisibility()
+ updateTransition()
+
if (header is MotionLayout) {
header.setOnApplyWindowInsetsListener(insetListener)
clock.addOnLayoutChangeListener { v, _, _, _, _, _, _, _, _ ->
@@ -308,9 +311,6 @@ class LargeScreenShadeHeaderController @Inject constructor(
dumpManager.registerDumpable(this)
configurationController.addCallback(configurationControllerListener)
demoModeController.addCallback(demoModeReceiver)
-
- updateVisibility()
- updateTransition()
}
override fun onViewDetached() {
@@ -436,15 +436,14 @@ class LargeScreenShadeHeaderController @Inject constructor(
header as MotionLayout
if (largeScreenActive) {
logInstantEvent("Large screen constraints set")
- header.setTransition(HEADER_TRANSITION_ID)
- header.transitionToStart()
+ header.setTransition(LARGE_SCREEN_HEADER_TRANSITION_ID)
} else {
logInstantEvent("Small screen constraints set")
header.setTransition(HEADER_TRANSITION_ID)
- header.transitionToStart()
- updatePosition()
- updateScrollY()
}
+ header.jumpToState(header.startState)
+ updatePosition()
+ updateScrollY()
}
private fun updatePosition() {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index fd31e4975481..bf4a768c5710 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -450,6 +450,7 @@ public final class NotificationPanelViewController implements Dumpable {
private float mDownY;
private int mDisplayTopInset = 0; // in pixels
private int mDisplayRightInset = 0; // in pixels
+ private int mDisplayLeftInset = 0; // in pixels
private int mLargeScreenShadeHeaderHeight;
private int mSplitShadeNotificationsScrimMarginBottom;
@@ -3011,7 +3012,7 @@ public final class NotificationPanelViewController implements Dumpable {
// left bounds can ignore insets, it should always reach the edge of the screen
return 0;
} else {
- return mNotificationStackScrollLayoutController.getLeft();
+ return mNotificationStackScrollLayoutController.getLeft() + mDisplayLeftInset;
}
}
@@ -3019,7 +3020,7 @@ public final class NotificationPanelViewController implements Dumpable {
if (mIsFullWidth) {
return mView.getRight() + mDisplayRightInset;
} else {
- return mNotificationStackScrollLayoutController.getRight();
+ return mNotificationStackScrollLayoutController.getRight() + mDisplayLeftInset;
}
}
@@ -3143,8 +3144,8 @@ public final class NotificationPanelViewController implements Dumpable {
// Convert global clipping coordinates to local ones,
// relative to NotificationStackScrollLayout
- int nsslLeft = left - mNotificationStackScrollLayoutController.getLeft();
- int nsslRight = right - mNotificationStackScrollLayoutController.getLeft();
+ int nsslLeft = calculateNsslLeft(left);
+ int nsslRight = calculateNsslRight(right);
int nsslTop = getNotificationsClippingTopBounds(top);
int nsslBottom = bottom - mNotificationStackScrollLayoutController.getTop();
int bottomRadius = mSplitShadeEnabled ? radius : 0;
@@ -3153,6 +3154,22 @@ public final class NotificationPanelViewController implements Dumpable {
nsslLeft, nsslTop, nsslRight, nsslBottom, topRadius, bottomRadius);
}
+ private int calculateNsslLeft(int nsslLeftAbsolute) {
+ int left = nsslLeftAbsolute - mNotificationStackScrollLayoutController.getLeft();
+ if (mIsFullWidth) {
+ return left;
+ }
+ return left - mDisplayLeftInset;
+ }
+
+ private int calculateNsslRight(int nsslRightAbsolute) {
+ int right = nsslRightAbsolute - mNotificationStackScrollLayoutController.getLeft();
+ if (mIsFullWidth) {
+ return right;
+ }
+ return right - mDisplayLeftInset;
+ }
+
private int getNotificationsClippingTopBounds(int qsTop) {
if (mSplitShadeEnabled && mExpandingFromHeadsUp) {
// in split shade nssl has extra top margin so clipping at top 0 is not enough, we need
@@ -4550,6 +4567,7 @@ public final class NotificationPanelViewController implements Dumpable {
ipw.print("mDownY="); ipw.println(mDownY);
ipw.print("mDisplayTopInset="); ipw.println(mDisplayTopInset);
ipw.print("mDisplayRightInset="); ipw.println(mDisplayRightInset);
+ ipw.print("mDisplayLeftInset="); ipw.println(mDisplayLeftInset);
ipw.print("mLargeScreenShadeHeaderHeight="); ipw.println(mLargeScreenShadeHeaderHeight);
ipw.print("mSplitShadeNotificationsScrimMarginBottom=");
ipw.println(mSplitShadeNotificationsScrimMarginBottom);
@@ -5927,6 +5945,7 @@ public final class NotificationPanelViewController implements Dumpable {
Insets combinedInsets = insets.getInsetsIgnoringVisibility(insetTypes);
mDisplayTopInset = combinedInsets.top;
mDisplayRightInset = combinedInsets.right;
+ mDisplayLeftInset = combinedInsets.left;
mNavigationBarBottomHeight = insets.getStableInsetBottom();
updateMaxHeadsUpTranslation();
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index 26f8b6222dc1..ab2e692915ad 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -19,6 +19,7 @@ package com.android.systemui.shade;
import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_BEHAVIOR_CONTROLLED;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OPTIMIZE_MEASURE;
import static com.android.systemui.DejankUtils.whitelistIpcs;
import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT;
@@ -52,13 +53,13 @@ import com.android.systemui.biometrics.AuthController;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.dump.DumpsysTableLogger;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
@@ -72,10 +73,8 @@ import java.io.PrintWriter;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
-import java.util.HashSet;
import java.util.List;
import java.util.Objects;
-import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@@ -89,6 +88,7 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
Dumpable, ConfigurationListener {
private static final String TAG = "NotificationShadeWindowController";
+ private static final int MAX_STATE_CHANGES_BUFFER_SIZE = 100;
private final Context mContext;
private final WindowManager mWindowManager;
@@ -108,7 +108,7 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
private boolean mHasTopUi;
private boolean mHasTopUiChanged;
private float mScreenBrightnessDoze;
- private final State mCurrentState = new State();
+ private final NotificationShadeWindowState mCurrentState = new NotificationShadeWindowState();
private OtherwisedCollapsedListener mListener;
private ForcePluginOpenListener mForcePluginOpenListener;
private Consumer<Integer> mScrimsVisibilityListener;
@@ -125,6 +125,9 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
private int mDeferWindowLayoutParams;
private boolean mLastKeyguardRotationAllowed;
+ private final NotificationShadeWindowState.Buffer mStateBuffer =
+ new NotificationShadeWindowState.Buffer(MAX_STATE_CHANGES_BUFFER_SIZE);
+
@Inject
public NotificationShadeWindowControllerImpl(Context context, WindowManager windowManager,
IActivityManager activityManager, DozeParameters dozeParameters,
@@ -210,8 +213,8 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
@VisibleForTesting
void onShadeExpansionFullyChanged(Boolean isExpanded) {
- if (mCurrentState.mPanelExpanded != isExpanded) {
- mCurrentState.mPanelExpanded = isExpanded;
+ if (mCurrentState.panelExpanded != isExpanded) {
+ mCurrentState.panelExpanded = isExpanded;
apply(mCurrentState);
}
}
@@ -251,6 +254,7 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
mLp.setTitle("NotificationShade");
mLp.packageName = mContext.getPackageName();
mLp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+ mLp.privateFlags |= PRIVATE_FLAG_OPTIMIZE_MEASURE;
// We use BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE here, however, there is special logic in
// window manager which disables the transient show behavior.
@@ -296,10 +300,10 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
mNotificationShadeView.setSystemUiVisibility(vis);
}
- private void applyKeyguardFlags(State state) {
- final boolean keyguardOrAod = state.mKeyguardShowing
- || (state.mDozing && mDozeParameters.getAlwaysOn());
- if ((keyguardOrAod && !state.mBackdropShowing && !state.mLightRevealScrimOpaque)
+ private void applyKeyguardFlags(NotificationShadeWindowState state) {
+ final boolean keyguardOrAod = state.keyguardShowing
+ || (state.dozing && mDozeParameters.getAlwaysOn());
+ if ((keyguardOrAod && !state.mediaBackdropShowing && !state.lightRevealScrimOpaque)
|| mKeyguardViewMediator.isAnimatingBetweenKeyguardAndSurfaceBehind()) {
// Show the wallpaper if we're on keyguard/AOD and the wallpaper is not occluded by a
// solid backdrop. Also, show it if we are currently animating between the
@@ -310,15 +314,15 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
mLpChanged.flags &= ~LayoutParams.FLAG_SHOW_WALLPAPER;
}
- if (state.mDozing) {
+ if (state.dozing) {
mLpChanged.privateFlags |= LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
} else {
mLpChanged.privateFlags &= ~LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
}
if (mKeyguardPreferredRefreshRate > 0) {
- boolean onKeyguard = state.mStatusBarState == StatusBarState.KEYGUARD
- && !state.mKeyguardFadingAway && !state.mKeyguardGoingAway;
+ boolean onKeyguard = state.statusBarState == StatusBarState.KEYGUARD
+ && !state.keyguardFadingAway && !state.keyguardGoingAway;
if (onKeyguard
&& mAuthController.isUdfpsEnrolled(KeyguardUpdateMonitor.getCurrentUser())) {
// both max and min display refresh rate must be set to take effect:
@@ -332,9 +336,9 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
(long) mKeyguardPreferredRefreshRate);
} else if (mKeyguardMaxRefreshRate > 0) {
boolean bypassOnKeyguard = mKeyguardBypassController.getBypassEnabled()
- && state.mStatusBarState == StatusBarState.KEYGUARD
- && !state.mKeyguardFadingAway && !state.mKeyguardGoingAway;
- if (state.mDozing || bypassOnKeyguard) {
+ && state.statusBarState == StatusBarState.KEYGUARD
+ && !state.keyguardFadingAway && !state.keyguardGoingAway;
+ if (state.dozing || bypassOnKeyguard) {
mLpChanged.preferredMaxDisplayRefreshRate = mKeyguardMaxRefreshRate;
} else {
mLpChanged.preferredMaxDisplayRefreshRate = 0;
@@ -343,7 +347,7 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
(long) mLpChanged.preferredMaxDisplayRefreshRate);
}
- if (state.mBouncerShowing && !isDebuggable()) {
+ if (state.bouncerShowing && !isDebuggable()) {
mLpChanged.flags |= LayoutParams.FLAG_SECURE;
} else {
mLpChanged.flags &= ~LayoutParams.FLAG_SECURE;
@@ -354,8 +358,8 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
return Build.IS_DEBUGGABLE;
}
- private void adjustScreenOrientation(State state) {
- if (state.mBouncerShowing || state.isKeyguardShowingAndNotOccluded() || state.mDozing) {
+ private void adjustScreenOrientation(NotificationShadeWindowState state) {
+ if (state.bouncerShowing || state.isKeyguardShowingAndNotOccluded() || state.dozing) {
if (mKeyguardStateController.isKeyguardScreenRotationAllowed()) {
mLpChanged.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_USER;
} else {
@@ -366,10 +370,10 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
}
}
- private void applyFocusableFlag(State state) {
- boolean panelFocusable = state.mNotificationShadeFocusable && state.mPanelExpanded;
- if (state.mBouncerShowing && (state.mKeyguardOccluded || state.mKeyguardNeedsInput)
- || ENABLE_REMOTE_INPUT && state.mRemoteInputActive
+ private void applyFocusableFlag(NotificationShadeWindowState state) {
+ boolean panelFocusable = state.notificationShadeFocusable && state.panelExpanded;
+ if (state.bouncerShowing && (state.keyguardOccluded || state.keyguardNeedsInput)
+ || ENABLE_REMOTE_INPUT && state.remoteInputActive
// Make the panel focusable if we're doing the screen off animation, since the light
// reveal scrim is drawing in the panel and should consume touch events so that they
// don't go to the app behind.
@@ -379,7 +383,7 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
} else if (state.isKeyguardShowingAndNotOccluded() || panelFocusable) {
mLpChanged.flags &= ~LayoutParams.FLAG_NOT_FOCUSABLE;
// Make sure to remove FLAG_ALT_FOCUSABLE_IM when keyguard needs input.
- if (state.mKeyguardNeedsInput && state.isKeyguardShowingAndNotOccluded()) {
+ if (state.keyguardNeedsInput && state.isKeyguardShowingAndNotOccluded()) {
mLpChanged.flags &= ~LayoutParams.FLAG_ALT_FOCUSABLE_IM;
} else {
mLpChanged.flags |= LayoutParams.FLAG_ALT_FOCUSABLE_IM;
@@ -390,19 +394,19 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
}
}
- private void applyForceShowNavigationFlag(State state) {
- if (state.mPanelExpanded || state.mBouncerShowing
- || ENABLE_REMOTE_INPUT && state.mRemoteInputActive) {
+ private void applyForceShowNavigationFlag(NotificationShadeWindowState state) {
+ if (state.panelExpanded || state.bouncerShowing
+ || ENABLE_REMOTE_INPUT && state.remoteInputActive) {
mLpChanged.privateFlags |= LayoutParams.PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
} else {
mLpChanged.privateFlags &= ~LayoutParams.PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
}
}
- private void applyVisibility(State state) {
+ private void applyVisibility(NotificationShadeWindowState state) {
boolean visible = isExpanded(state);
mLogger.logApplyVisibility(visible);
- if (state.mForcePluginOpen) {
+ if (state.forcePluginOpen) {
if (mListener != null) {
mListener.setWouldOtherwiseCollapse(visible);
}
@@ -418,16 +422,16 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
}
}
- private boolean isExpanded(State state) {
- return !state.mForceCollapsed && (state.isKeyguardShowingAndNotOccluded()
- || state.mPanelVisible || state.mKeyguardFadingAway || state.mBouncerShowing
- || state.mHeadsUpShowing
- || state.mScrimsVisibility != ScrimController.TRANSPARENT)
- || state.mBackgroundBlurRadius > 0
- || state.mLaunchingActivity;
+ private boolean isExpanded(NotificationShadeWindowState state) {
+ return !state.forceWindowCollapsed && (state.isKeyguardShowingAndNotOccluded()
+ || state.panelVisible || state.keyguardFadingAway || state.bouncerShowing
+ || state.headsUpNotificationShowing
+ || state.scrimsVisibility != ScrimController.TRANSPARENT)
+ || state.backgroundBlurRadius > 0
+ || state.launchingActivityFromNotification;
}
- private void applyFitsSystemWindows(State state) {
+ private void applyFitsSystemWindows(NotificationShadeWindowState state) {
boolean fitsSystemWindows = !state.isKeyguardShowingAndNotOccluded();
if (mNotificationShadeView != null
&& mNotificationShadeView.getFitsSystemWindows() != fitsSystemWindows) {
@@ -436,21 +440,21 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
}
}
- private void applyUserActivityTimeout(State state) {
+ private void applyUserActivityTimeout(NotificationShadeWindowState state) {
if (state.isKeyguardShowingAndNotOccluded()
- && state.mStatusBarState == StatusBarState.KEYGUARD
- && !state.mQsExpanded) {
- mLpChanged.userActivityTimeout = state.mBouncerShowing
+ && state.statusBarState == StatusBarState.KEYGUARD
+ && !state.qsExpanded) {
+ mLpChanged.userActivityTimeout = state.bouncerShowing
? KeyguardViewMediator.AWAKE_INTERVAL_BOUNCER_MS : mLockScreenDisplayTimeout;
} else {
mLpChanged.userActivityTimeout = -1;
}
}
- private void applyInputFeatures(State state) {
+ private void applyInputFeatures(NotificationShadeWindowState state) {
if (state.isKeyguardShowingAndNotOccluded()
- && state.mStatusBarState == StatusBarState.KEYGUARD
- && !state.mQsExpanded && !state.mForceUserActivity) {
+ && state.statusBarState == StatusBarState.KEYGUARD
+ && !state.qsExpanded && !state.forceUserActivity) {
mLpChanged.inputFeatures |=
LayoutParams.INPUT_FEATURE_DISABLE_USER_ACTIVITY;
} else {
@@ -459,7 +463,7 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
}
}
- private void applyStatusBarColorSpaceAgnosticFlag(State state) {
+ private void applyStatusBarColorSpaceAgnosticFlag(NotificationShadeWindowState state) {
if (!isExpanded(state)) {
mLpChanged.privateFlags |= LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC;
} else {
@@ -485,8 +489,8 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
applyWindowLayoutParams();
}
- private void apply(State state) {
- mLogger.logNewState(state);
+ private void apply(NotificationShadeWindowState state) {
+ logState(state);
applyKeyguardFlags(state);
applyFocusableFlag(state);
applyForceShowNavigationFlag(state);
@@ -515,6 +519,38 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
notifyStateChangedCallbacks();
}
+ private void logState(NotificationShadeWindowState state) {
+ mStateBuffer.insert(
+ state.keyguardShowing,
+ state.keyguardOccluded,
+ state.keyguardNeedsInput,
+ state.panelVisible,
+ state.panelExpanded,
+ state.notificationShadeFocusable,
+ state.bouncerShowing,
+ state.keyguardFadingAway,
+ state.keyguardGoingAway,
+ state.qsExpanded,
+ state.headsUpNotificationShowing,
+ state.lightRevealScrimOpaque,
+ state.forceWindowCollapsed,
+ state.forceDozeBrightness,
+ state.forceUserActivity,
+ state.launchingActivityFromNotification,
+ state.mediaBackdropShowing,
+ state.wallpaperSupportsAmbientMode,
+ state.windowNotTouchable,
+ state.componentsForcingTopUi,
+ state.forceOpenTokens,
+ state.statusBarState,
+ state.remoteInputActive,
+ state.forcePluginOpen,
+ state.dozing,
+ state.scrimsVisibility,
+ state.backgroundBlurRadius
+ );
+ }
+
@Override
public void notifyStateChangedCallbacks() {
// Copy callbacks to separate ArrayList to avoid concurrent modification
@@ -523,36 +559,36 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
.filter(Objects::nonNull)
.collect(Collectors.toList());
for (StatusBarWindowCallback cb : activeCallbacks) {
- cb.onStateChanged(mCurrentState.mKeyguardShowing,
- mCurrentState.mKeyguardOccluded,
- mCurrentState.mBouncerShowing,
- mCurrentState.mDozing,
- mCurrentState.mPanelExpanded);
+ cb.onStateChanged(mCurrentState.keyguardShowing,
+ mCurrentState.keyguardOccluded,
+ mCurrentState.bouncerShowing,
+ mCurrentState.dozing,
+ mCurrentState.panelExpanded);
}
}
- private void applyModalFlag(State state) {
- if (state.mHeadsUpShowing) {
+ private void applyModalFlag(NotificationShadeWindowState state) {
+ if (state.headsUpNotificationShowing) {
mLpChanged.flags |= LayoutParams.FLAG_NOT_TOUCH_MODAL;
} else {
mLpChanged.flags &= ~LayoutParams.FLAG_NOT_TOUCH_MODAL;
}
}
- private void applyBrightness(State state) {
- if (state.mForceDozeBrightness) {
+ private void applyBrightness(NotificationShadeWindowState state) {
+ if (state.forceDozeBrightness) {
mLpChanged.screenBrightness = mScreenBrightnessDoze;
} else {
mLpChanged.screenBrightness = LayoutParams.BRIGHTNESS_OVERRIDE_NONE;
}
}
- private void applyHasTopUi(State state) {
- mHasTopUiChanged = !state.mComponentsForcingTopUi.isEmpty() || isExpanded(state);
+ private void applyHasTopUi(NotificationShadeWindowState state) {
+ mHasTopUiChanged = !state.componentsForcingTopUi.isEmpty() || isExpanded(state);
}
- private void applyNotTouchable(State state) {
- if (state.mNotTouchable) {
+ private void applyNotTouchable(NotificationShadeWindowState state) {
+ if (state.windowNotTouchable) {
mLpChanged.flags |= LayoutParams.FLAG_NOT_TOUCHABLE;
} else {
mLpChanged.flags &= ~LayoutParams.FLAG_NOT_TOUCHABLE;
@@ -574,88 +610,88 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
@Override
public void setKeyguardShowing(boolean showing) {
- mCurrentState.mKeyguardShowing = showing;
+ mCurrentState.keyguardShowing = showing;
apply(mCurrentState);
}
@Override
public void setKeyguardOccluded(boolean occluded) {
- mCurrentState.mKeyguardOccluded = occluded;
+ mCurrentState.keyguardOccluded = occluded;
apply(mCurrentState);
}
@Override
public void setKeyguardNeedsInput(boolean needsInput) {
- mCurrentState.mKeyguardNeedsInput = needsInput;
+ mCurrentState.keyguardNeedsInput = needsInput;
apply(mCurrentState);
}
@Override
public void setPanelVisible(boolean visible) {
- if (mCurrentState.mPanelVisible == visible
- && mCurrentState.mNotificationShadeFocusable == visible) {
+ if (mCurrentState.panelVisible == visible
+ && mCurrentState.notificationShadeFocusable == visible) {
return;
}
mLogger.logShadeVisibleAndFocusable(visible);
- mCurrentState.mPanelVisible = visible;
- mCurrentState.mNotificationShadeFocusable = visible;
+ mCurrentState.panelVisible = visible;
+ mCurrentState.notificationShadeFocusable = visible;
apply(mCurrentState);
}
@Override
public void setNotificationShadeFocusable(boolean focusable) {
mLogger.logShadeFocusable(focusable);
- mCurrentState.mNotificationShadeFocusable = focusable;
+ mCurrentState.notificationShadeFocusable = focusable;
apply(mCurrentState);
}
@Override
public void setBouncerShowing(boolean showing) {
- mCurrentState.mBouncerShowing = showing;
+ mCurrentState.bouncerShowing = showing;
apply(mCurrentState);
}
@Override
public void setBackdropShowing(boolean showing) {
- mCurrentState.mBackdropShowing = showing;
+ mCurrentState.mediaBackdropShowing = showing;
apply(mCurrentState);
}
@Override
public void setKeyguardFadingAway(boolean keyguardFadingAway) {
- mCurrentState.mKeyguardFadingAway = keyguardFadingAway;
+ mCurrentState.keyguardFadingAway = keyguardFadingAway;
apply(mCurrentState);
}
private void onQsExpansionChanged(Boolean expanded) {
- mCurrentState.mQsExpanded = expanded;
+ mCurrentState.qsExpanded = expanded;
apply(mCurrentState);
}
@Override
public void setForceUserActivity(boolean forceUserActivity) {
- mCurrentState.mForceUserActivity = forceUserActivity;
+ mCurrentState.forceUserActivity = forceUserActivity;
apply(mCurrentState);
}
@Override
public void setLaunchingActivity(boolean launching) {
- mCurrentState.mLaunchingActivity = launching;
+ mCurrentState.launchingActivityFromNotification = launching;
apply(mCurrentState);
}
@Override
public boolean isLaunchingActivity() {
- return mCurrentState.mLaunchingActivity;
+ return mCurrentState.launchingActivityFromNotification;
}
@Override
public void setScrimsVisibility(int scrimsVisibility) {
- if (scrimsVisibility == mCurrentState.mScrimsVisibility) {
+ if (scrimsVisibility == mCurrentState.scrimsVisibility) {
return;
}
boolean wasExpanded = isExpanded(mCurrentState);
- mCurrentState.mScrimsVisibility = scrimsVisibility;
+ mCurrentState.scrimsVisibility = scrimsVisibility;
if (wasExpanded != isExpanded(mCurrentState)) {
apply(mCurrentState);
}
@@ -669,31 +705,31 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
*/
@Override
public void setBackgroundBlurRadius(int backgroundBlurRadius) {
- if (mCurrentState.mBackgroundBlurRadius == backgroundBlurRadius) {
+ if (mCurrentState.backgroundBlurRadius == backgroundBlurRadius) {
return;
}
- mCurrentState.mBackgroundBlurRadius = backgroundBlurRadius;
+ mCurrentState.backgroundBlurRadius = backgroundBlurRadius;
apply(mCurrentState);
}
@Override
public void setHeadsUpShowing(boolean showing) {
- mCurrentState.mHeadsUpShowing = showing;
+ mCurrentState.headsUpNotificationShowing = showing;
apply(mCurrentState);
}
@Override
public void setLightRevealScrimOpaque(boolean opaque) {
- if (mCurrentState.mLightRevealScrimOpaque == opaque) {
+ if (mCurrentState.lightRevealScrimOpaque == opaque) {
return;
}
- mCurrentState.mLightRevealScrimOpaque = opaque;
+ mCurrentState.lightRevealScrimOpaque = opaque;
apply(mCurrentState);
}
@Override
public void setWallpaperSupportsAmbientMode(boolean supportsAmbientMode) {
- mCurrentState.mWallpaperSupportsAmbientMode = supportsAmbientMode;
+ mCurrentState.wallpaperSupportsAmbientMode = supportsAmbientMode;
apply(mCurrentState);
}
@@ -701,7 +737,7 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
* @param state The {@link StatusBarStateController} of the status bar.
*/
private void setStatusBarState(int state) {
- mCurrentState.mStatusBarState = state;
+ mCurrentState.statusBarState = state;
apply(mCurrentState);
}
@@ -712,13 +748,13 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
*/
@Override
public void setForceWindowCollapsed(boolean force) {
- mCurrentState.mForceCollapsed = force;
+ mCurrentState.forceWindowCollapsed = force;
apply(mCurrentState);
}
@Override
public void onRemoteInputActive(boolean remoteInputActive) {
- mCurrentState.mRemoteInputActive = remoteInputActive;
+ mCurrentState.remoteInputActive = remoteInputActive;
apply(mCurrentState);
}
@@ -728,32 +764,32 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
*/
@Override
public void setForceDozeBrightness(boolean forceDozeBrightness) {
- if (mCurrentState.mForceDozeBrightness == forceDozeBrightness) {
+ if (mCurrentState.forceDozeBrightness == forceDozeBrightness) {
return;
}
- mCurrentState.mForceDozeBrightness = forceDozeBrightness;
+ mCurrentState.forceDozeBrightness = forceDozeBrightness;
apply(mCurrentState);
}
@Override
public void setDozing(boolean dozing) {
- mCurrentState.mDozing = dozing;
+ mCurrentState.dozing = dozing;
apply(mCurrentState);
}
@Override
public void setForcePluginOpen(boolean forceOpen, Object token) {
if (forceOpen) {
- mCurrentState.mForceOpenTokens.add(token);
+ mCurrentState.forceOpenTokens.add(token);
} else {
- mCurrentState.mForceOpenTokens.remove(token);
+ mCurrentState.forceOpenTokens.remove(token);
}
- final boolean previousForceOpenState = mCurrentState.mForcePluginOpen;
- mCurrentState.mForcePluginOpen = !mCurrentState.mForceOpenTokens.isEmpty();
- if (previousForceOpenState != mCurrentState.mForcePluginOpen) {
+ final boolean previousForceOpenState = mCurrentState.forcePluginOpen;
+ mCurrentState.forcePluginOpen = !mCurrentState.forceOpenTokens.isEmpty();
+ if (previousForceOpenState != mCurrentState.forcePluginOpen) {
apply(mCurrentState);
if (mForcePluginOpenListener != null) {
- mForcePluginOpenListener.onChange(mCurrentState.mForcePluginOpen);
+ mForcePluginOpenListener.onChange(mCurrentState.forcePluginOpen);
}
}
}
@@ -763,12 +799,12 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
*/
@Override
public boolean getForcePluginOpen() {
- return mCurrentState.mForcePluginOpen;
+ return mCurrentState.forcePluginOpen;
}
@Override
public void setNotTouchable(boolean notTouchable) {
- mCurrentState.mNotTouchable = notTouchable;
+ mCurrentState.windowNotTouchable = notTouchable;
apply(mCurrentState);
}
@@ -777,7 +813,7 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
*/
@Override
public boolean getPanelExpanded() {
- return mCurrentState.mPanelExpanded;
+ return mCurrentState.panelExpanded;
}
@Override
@@ -800,11 +836,16 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
if (mNotificationShadeView != null && mNotificationShadeView.getViewRootImpl() != null) {
mNotificationShadeView.getViewRootImpl().dump(" ", pw);
}
+ new DumpsysTableLogger(
+ TAG,
+ NotificationShadeWindowState.TABLE_HEADERS,
+ mStateBuffer.toList()
+ ).printTableData(pw);
}
@Override
public boolean isShowingWallpaper() {
- return !mCurrentState.mBackdropShowing;
+ return !mCurrentState.mediaBackdropShowing;
}
@Override
@@ -834,7 +875,7 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
*/
@Override
public void setKeyguardGoingAway(boolean goingAway) {
- mCurrentState.mKeyguardGoingAway = goingAway;
+ mCurrentState.keyguardGoingAway = goingAway;
apply(mCurrentState);
}
@@ -846,87 +887,13 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
@Override
public void setRequestTopUi(boolean requestTopUi, String componentTag) {
if (requestTopUi) {
- mCurrentState.mComponentsForcingTopUi.add(componentTag);
+ mCurrentState.componentsForcingTopUi.add(componentTag);
} else {
- mCurrentState.mComponentsForcingTopUi.remove(componentTag);
+ mCurrentState.componentsForcingTopUi.remove(componentTag);
}
apply(mCurrentState);
}
- private static class State {
- boolean mKeyguardShowing;
- boolean mKeyguardOccluded;
- boolean mKeyguardNeedsInput;
- boolean mPanelVisible;
- boolean mPanelExpanded;
- boolean mNotificationShadeFocusable;
- boolean mBouncerShowing;
- boolean mKeyguardFadingAway;
- boolean mKeyguardGoingAway;
- boolean mQsExpanded;
- boolean mHeadsUpShowing;
- boolean mLightRevealScrimOpaque;
- boolean mForceCollapsed;
- boolean mForceDozeBrightness;
- boolean mForceUserActivity;
- boolean mLaunchingActivity;
- boolean mBackdropShowing;
- boolean mWallpaperSupportsAmbientMode;
- boolean mNotTouchable;
- Set<String> mComponentsForcingTopUi = new HashSet<>();
- Set<Object> mForceOpenTokens = new HashSet<>();
-
- /**
- * The status bar state from {@link CentralSurfaces}.
- */
- int mStatusBarState;
-
- boolean mRemoteInputActive;
- boolean mForcePluginOpen;
- boolean mDozing;
- int mScrimsVisibility;
- int mBackgroundBlurRadius;
-
- private boolean isKeyguardShowingAndNotOccluded() {
- return mKeyguardShowing && !mKeyguardOccluded;
- }
-
- @Override
- public String toString() {
- return new StringBuilder()
- .append("State{")
- .append(" mKeyguardShowing=").append(mKeyguardShowing)
- .append(", mKeyguardOccluded=").append(mKeyguardOccluded)
- .append(", mKeyguardNeedsInput=").append(mKeyguardNeedsInput)
- .append(", mPanelVisible=").append(mPanelVisible)
- .append(", mPanelExpanded=").append(mPanelExpanded)
- .append(", mNotificationShadeFocusable=").append(mNotificationShadeFocusable)
- .append(", mBouncerShowing=").append(mBouncerShowing)
- .append(", mKeyguardFadingAway=").append(mKeyguardFadingAway)
- .append(", mKeyguardGoingAway=").append(mKeyguardGoingAway)
- .append(", mQsExpanded=").append(mQsExpanded)
- .append(", mHeadsUpShowing=").append(mHeadsUpShowing)
- .append(", mLightRevealScrimOpaque=").append(mLightRevealScrimOpaque)
- .append(", mForceCollapsed=").append(mForceCollapsed)
- .append(", mForceDozeBrightness=").append(mForceDozeBrightness)
- .append(", mForceUserActivity=").append(mForceUserActivity)
- .append(", mLaunchingActivity=").append(mLaunchingActivity)
- .append(", mBackdropShowing=").append(mBackdropShowing)
- .append(", mWallpaperSupportsAmbientMode=")
- .append(mWallpaperSupportsAmbientMode)
- .append(", mNotTouchable=").append(mNotTouchable)
- .append(", mComponentsForcingTopUi=").append(mComponentsForcingTopUi)
- .append(", mForceOpenTokens=").append(mForceOpenTokens)
- .append(", mStatusBarState=").append(mStatusBarState)
- .append(", mRemoteInputActive=").append(mRemoteInputActive)
- .append(", mForcePluginOpen=").append(mForcePluginOpen)
- .append(", mDozing=").append(mDozing)
- .append(", mScrimsVisibility=").append(mScrimsVisibility)
- .append(", mBackgroundBlurRadius=").append(mBackgroundBlurRadius)
- .append('}').toString();
- }
- }
-
private final StateListener mStateListener = new StateListener() {
@Override
public void onStateChanged(int newState) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
new file mode 100644
index 000000000000..736404aa548a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade
+
+import com.android.systemui.dump.DumpsysTableLogger
+import com.android.systemui.dump.Row
+import com.android.systemui.plugins.util.RingBuffer
+import com.android.systemui.shade.NotificationShadeWindowState.Buffer
+import com.android.systemui.statusbar.StatusBarState
+
+/**
+ * Represents state of shade window, used by [NotificationShadeWindowControllerImpl].
+ * Contains nested class [Buffer] for pretty table logging in bug reports.
+ */
+class NotificationShadeWindowState(
+ @JvmField var keyguardShowing: Boolean = false,
+ @JvmField var keyguardOccluded: Boolean = false,
+ @JvmField var keyguardNeedsInput: Boolean = false,
+ @JvmField var panelVisible: Boolean = false,
+ /** shade panel is expanded (expansion fraction > 0) */
+ @JvmField var panelExpanded: Boolean = false,
+ @JvmField var notificationShadeFocusable: Boolean = false,
+ @JvmField var bouncerShowing: Boolean = false,
+ @JvmField var keyguardFadingAway: Boolean = false,
+ @JvmField var keyguardGoingAway: Boolean = false,
+ @JvmField var qsExpanded: Boolean = false,
+ @JvmField var headsUpNotificationShowing: Boolean = false,
+ @JvmField var lightRevealScrimOpaque: Boolean = false,
+ @JvmField var forceWindowCollapsed: Boolean = false,
+ @JvmField var forceDozeBrightness: Boolean = false,
+ // TODO: forceUserActivity seems to be unused, delete?
+ @JvmField var forceUserActivity: Boolean = false,
+ @JvmField var launchingActivityFromNotification: Boolean = false,
+ @JvmField var mediaBackdropShowing: Boolean = false,
+ @JvmField var wallpaperSupportsAmbientMode: Boolean = false,
+ @JvmField var windowNotTouchable: Boolean = false,
+ @JvmField var componentsForcingTopUi: MutableSet<String> = mutableSetOf(),
+ @JvmField var forceOpenTokens: MutableSet<Any> = mutableSetOf(),
+ /** one of [StatusBarState] */
+ @JvmField var statusBarState: Int = 0,
+ @JvmField var remoteInputActive: Boolean = false,
+ @JvmField var forcePluginOpen: Boolean = false,
+ @JvmField var dozing: Boolean = false,
+ @JvmField var scrimsVisibility: Int = 0,
+ @JvmField var backgroundBlurRadius: Int = 0,
+) {
+
+ fun isKeyguardShowingAndNotOccluded(): Boolean {
+ return keyguardShowing && !keyguardOccluded
+ }
+
+ /** List of [String] to be used as a [Row] with [DumpsysTableLogger]. */
+ val asStringList: List<String> by lazy {
+ listOf(
+ keyguardShowing.toString(),
+ keyguardOccluded.toString(),
+ keyguardNeedsInput.toString(),
+ panelVisible.toString(),
+ panelExpanded.toString(),
+ notificationShadeFocusable.toString(),
+ bouncerShowing.toString(),
+ keyguardFadingAway.toString(),
+ keyguardGoingAway.toString(),
+ qsExpanded.toString(),
+ headsUpNotificationShowing.toString(),
+ lightRevealScrimOpaque.toString(),
+ forceWindowCollapsed.toString(),
+ forceDozeBrightness.toString(),
+ forceUserActivity.toString(),
+ launchingActivityFromNotification.toString(),
+ mediaBackdropShowing.toString(),
+ wallpaperSupportsAmbientMode.toString(),
+ windowNotTouchable.toString(),
+ componentsForcingTopUi.toString(),
+ forceOpenTokens.toString(),
+ StatusBarState.toString(statusBarState),
+ remoteInputActive.toString(),
+ forcePluginOpen.toString(),
+ dozing.toString(),
+ scrimsVisibility.toString(),
+ backgroundBlurRadius.toString()
+ )
+ }
+
+ /**
+ * [RingBuffer] to store [NotificationShadeWindowState]. After the buffer is full, it will
+ * recycle old events.
+ */
+ class Buffer(capacity: Int) {
+
+ private val buffer = RingBuffer(capacity) { NotificationShadeWindowState() }
+
+ /** Insert a new element in the buffer. */
+ fun insert(
+ keyguardShowing: Boolean,
+ keyguardOccluded: Boolean,
+ keyguardNeedsInput: Boolean,
+ panelVisible: Boolean,
+ panelExpanded: Boolean,
+ notificationShadeFocusable: Boolean,
+ bouncerShowing: Boolean,
+ keyguardFadingAway: Boolean,
+ keyguardGoingAway: Boolean,
+ qsExpanded: Boolean,
+ headsUpShowing: Boolean,
+ lightRevealScrimOpaque: Boolean,
+ forceCollapsed: Boolean,
+ forceDozeBrightness: Boolean,
+ forceUserActivity: Boolean,
+ launchingActivity: Boolean,
+ backdropShowing: Boolean,
+ wallpaperSupportsAmbientMode: Boolean,
+ notTouchable: Boolean,
+ componentsForcingTopUi: MutableSet<String>,
+ forceOpenTokens: MutableSet<Any>,
+ statusBarState: Int,
+ remoteInputActive: Boolean,
+ forcePluginOpen: Boolean,
+ dozing: Boolean,
+ scrimsVisibility: Int,
+ backgroundBlurRadius: Int,
+ ) {
+ buffer.advance().apply {
+ this.keyguardShowing = keyguardShowing
+ this.keyguardOccluded = keyguardOccluded
+ this.keyguardNeedsInput = keyguardNeedsInput
+ this.panelVisible = panelVisible
+ this.panelExpanded = panelExpanded
+ this.notificationShadeFocusable = notificationShadeFocusable
+ this.bouncerShowing = bouncerShowing
+ this.keyguardFadingAway = keyguardFadingAway
+ this.keyguardGoingAway = keyguardGoingAway
+ this.qsExpanded = qsExpanded
+ this.headsUpNotificationShowing = headsUpShowing
+ this.lightRevealScrimOpaque = lightRevealScrimOpaque
+ this.forceWindowCollapsed = forceCollapsed
+ this.forceDozeBrightness = forceDozeBrightness
+ this.forceUserActivity = forceUserActivity
+ this.launchingActivityFromNotification = launchingActivity
+ this.mediaBackdropShowing = backdropShowing
+ this.wallpaperSupportsAmbientMode = wallpaperSupportsAmbientMode
+ this.windowNotTouchable = notTouchable
+ this.componentsForcingTopUi.clear()
+ this.componentsForcingTopUi.addAll(componentsForcingTopUi)
+ this.forceOpenTokens.clear()
+ this.forceOpenTokens.addAll(forceOpenTokens)
+ this.statusBarState = statusBarState
+ this.remoteInputActive = remoteInputActive
+ this.forcePluginOpen = forcePluginOpen
+ this.dozing = dozing
+ this.scrimsVisibility = scrimsVisibility
+ this.backgroundBlurRadius = backgroundBlurRadius
+ }
+ }
+
+ /**
+ * Returns the content of the buffer (sorted from latest to newest).
+ *
+ * @see [NotificationShadeWindowState.asStringList]
+ */
+ fun toList(): List<Row> {
+ return buffer.asSequence().map { it.asStringList }.toList()
+ }
+ }
+
+ companion object {
+ /** Headers for dumping a table using [DumpsysTableLogger]. */
+ @JvmField
+ val TABLE_HEADERS =
+ listOf(
+ "keyguardShowing",
+ "keyguardOccluded",
+ "keyguardNeedsInput",
+ "panelVisible",
+ "panelExpanded",
+ "notificationShadeFocusable",
+ "bouncerShowing",
+ "keyguardFadingAway",
+ "keyguardGoingAway",
+ "qsExpanded",
+ "headsUpShowing",
+ "lightRevealScrimOpaque",
+ "forceCollapsed",
+ "forceDozeBrightness",
+ "forceUserActivity",
+ "launchingActivity",
+ "backdropShowing",
+ "wallpaperSupportsAmbientMode",
+ "notTouchable",
+ "componentsForcingTopUi",
+ "forceOpenTokens",
+ "statusBarState",
+ "remoteInputActive",
+ "forcePluginOpen",
+ "dozing",
+ "scrimsVisibility",
+ "backgroundBlurRadius"
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 6e4ed7b0b668..755675066979 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -53,6 +53,7 @@ import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.RemoteException;
import android.util.Pair;
+import android.util.Slog;
import android.util.SparseArray;
import android.view.InsetsState.InternalInsetsType;
import android.view.WindowInsets.Type.InsetsType;
@@ -1270,7 +1271,8 @@ public class CommandQueue extends IStatusBar.Stub implements
public void showMediaOutputSwitcher(String packageName) {
int callingUid = Binder.getCallingUid();
if (callingUid != 0 && callingUid != Process.SYSTEM_UID) {
- throw new SecurityException("Call only allowed from system server.");
+ Slog.e(TAG, "Call only allowed from system server.");
+ return;
}
synchronized (mLock) {
SomeArgs args = SomeArgs.obtain();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutsModule.java
new file mode 100644
index 000000000000..a797d4a3a7b4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutsModule.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar;
+
+import android.content.BroadcastReceiver;
+
+import dagger.Binds;
+import dagger.Module;
+import dagger.multibindings.ClassKey;
+import dagger.multibindings.IntoMap;
+
+/**
+ * Module for {@link com.android.systemui.KeyboardShortcutsReceiver}.
+ */
+@Module
+public abstract class KeyboardShortcutsModule {
+
+ /**
+ *
+ */
+ @Binds
+ @IntoMap
+ @ClassKey(KeyboardShortcutsReceiver.class)
+ public abstract BroadcastReceiver bindKeyboardShortcutsReceiver(
+ KeyboardShortcutsReceiver broadcastReceiver);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index fbe88dff07f1..7addc8fe7a15 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -24,6 +24,7 @@ import android.graphics.Canvas;
import android.graphics.Point;
import android.util.AttributeSet;
import android.util.MathUtils;
+import android.view.Choreographer;
import android.view.MotionEvent;
import android.view.View;
import android.view.accessibility.AccessibilityManager;
@@ -492,12 +493,9 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
if (animationListener != null) {
mAppearAnimator.addListener(animationListener);
}
- if (delay > 0) {
- // we need to apply the initial state already to avoid drawn frames in the wrong state
- updateAppearAnimationAlpha();
- updateAppearRect();
- mAppearAnimator.setStartDelay(delay);
- }
+ // we need to apply the initial state already to avoid drawn frames in the wrong state
+ updateAppearAnimationAlpha();
+ updateAppearRect();
mAppearAnimator.addListener(new AnimatorListenerAdapter() {
private boolean mWasCancelled;
@@ -528,7 +526,20 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
mWasCancelled = true;
}
});
- mAppearAnimator.start();
+
+ // Cache the original animator so we can check if the animation should be started in the
+ // Choreographer callback. It's possible that the original animator (mAppearAnimator) is
+ // replaced with a new value before the callback is called.
+ ValueAnimator cachedAnimator = mAppearAnimator;
+ // Even when delay=0, starting the animation on the next frame is necessary to avoid jank.
+ // Not doing so will increase the chances our Animator will be forced to skip a value of
+ // the animation's progression, causing stutter.
+ Choreographer.getInstance().postFrameCallbackDelayed(
+ frameTimeNanos -> {
+ if (mAppearAnimator == cachedAnimator) {
+ mAppearAnimator.start();
+ }
+ }, delay);
}
private int getCujType(boolean isAppearing) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 9f50aef6de11..9275e2b603c3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -79,6 +79,8 @@ import com.android.internal.widget.CallLayout;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.classifier.FalsingCollector;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
@@ -177,6 +179,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
private PeopleNotificationIdentifier mPeopleNotificationIdentifier;
private Optional<BubblesManager> mBubblesManagerOptional;
private MetricsLogger mMetricsLogger;
+ private FeatureFlags mFeatureFlags;
private int mIconTransformContentShift;
private int mMaxHeadsUpHeightBeforeN;
private int mMaxHeadsUpHeightBeforeP;
@@ -277,7 +280,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
private boolean mChildIsExpanding;
private boolean mJustClicked;
- private boolean mIconAnimationRunning;
+ private boolean mAnimationRunning;
private boolean mShowNoBackground;
private ExpandableNotificationRow mNotificationParent;
private OnExpandClickListener mOnExpandClickListener;
@@ -451,10 +454,26 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
return mPublicLayout;
}
- public void setIconAnimationRunning(boolean running) {
- for (NotificationContentView l : mLayouts) {
- setIconAnimationRunning(running, l);
+ /**
+ * Sets animations running in the layouts of this row, including public, private, and children.
+ * @param running whether the animations should be started running or stopped.
+ */
+ public void setAnimationRunning(boolean running) {
+ // Sets animations running in the private/public layouts.
+ if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_ANIMATE_BIG_PICTURE)) {
+ for (NotificationContentView l : mLayouts) {
+ if (l != null) {
+ l.setContentAnimationRunning(running);
+ setIconAnimationRunning(running, l);
+ }
+ }
+ } else {
+ for (NotificationContentView l : mLayouts) {
+ setIconAnimationRunning(running, l);
+ }
}
+ // For groups summaries with children, we want to set the children containers
+ // animating as well.
if (mIsSummaryWithChildren) {
NotificationViewWrapper viewWrapper = mChildrenContainer.getNotificationViewWrapper();
if (viewWrapper != null) {
@@ -468,12 +487,18 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mChildrenContainer.getAttachedChildren();
for (int i = 0; i < notificationChildren.size(); i++) {
ExpandableNotificationRow child = notificationChildren.get(i);
- child.setIconAnimationRunning(running);
+ child.setAnimationRunning(running);
}
}
- mIconAnimationRunning = running;
+ mAnimationRunning = running;
}
+ /**
+ * Starts or stops animations of the icons in all potential content views (regardless of
+ * whether they're contracted, expanded, etc).
+ *
+ * @param running whether to start or stop the icon's animation.
+ */
private void setIconAnimationRunning(boolean running, NotificationContentView layout) {
if (layout != null) {
View contractedChild = layout.getContractedChild();
@@ -485,16 +510,29 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
}
+ /**
+ * Starts or stops animations of the icon in the provided view's icon and right icon.
+ *
+ * @param running whether to start or stop the icon's animation.
+ * @param child the view with the icon to start or stop.
+ */
private void setIconAnimationRunningForChild(boolean running, View child) {
if (child != null) {
ImageView icon = child.findViewById(com.android.internal.R.id.icon);
- setIconRunning(icon, running);
+ setImageViewAnimationRunning(icon, running);
ImageView rightIcon = child.findViewById(com.android.internal.R.id.right_icon);
- setIconRunning(rightIcon, running);
+ setImageViewAnimationRunning(rightIcon, running);
}
}
- private void setIconRunning(ImageView imageView, boolean running) {
+ /**
+ * Starts or stops the animation of a provided image view if it's an AnimationDrawable or an
+ * AnimatedVectorDrawable.
+ *
+ * @param imageView the image view on which to start/stop animation.
+ * @param running whether to start or stop the view's animation.
+ */
+ private void setImageViewAnimationRunning(ImageView imageView, boolean running) {
if (imageView != null) {
Drawable drawable = imageView.getDrawable();
if (drawable instanceof AnimationDrawable) {
@@ -561,8 +599,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mChildrenContainer.recreateNotificationHeader(mExpandClickListener, isConversation());
mChildrenContainer.onNotificationUpdated();
}
- if (mIconAnimationRunning) {
- setIconAnimationRunning(true);
+ if (mAnimationRunning) {
+ setAnimationRunning(true);
}
if (mLastChronometerRunning) {
setChronometerRunning(true);
@@ -1038,7 +1076,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
notifyHeightChanged(false /* needsAnimation */);
}
if (pinned) {
- setIconAnimationRunning(true);
+ setAnimationRunning(true);
mExpandedWhenPinned = false;
} else if (mExpandedWhenPinned) {
setUserExpanded(true);
@@ -1627,6 +1665,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
);
}
+ /**
+ * Constructs an ExpandableNotificationRow.
+ * @param context context passed to image resolver
+ * @param attrs attributes used to initialize parent view
+ */
public ExpandableNotificationRow(Context context, AttributeSet attrs) {
super(context, attrs);
mImageResolver = new NotificationInlineImageResolver(context,
@@ -1662,7 +1705,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
NotificationGutsManager gutsManager,
MetricsLogger metricsLogger,
SmartReplyConstants smartReplyConstants,
- SmartReplyController smartReplyController) {
+ SmartReplyController smartReplyController,
+ FeatureFlags featureFlags) {
mEntry = entry;
mAppName = appName;
if (mMenuRow == null) {
@@ -1697,6 +1741,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mBubblesManagerOptional = bubblesManagerOptional;
mNotificationGutsManager = gutsManager;
mMetricsLogger = metricsLogger;
+ mFeatureFlags = featureFlags;
}
private void initDimens() {
@@ -3588,11 +3633,13 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
@VisibleForTesting
protected void setPrivateLayout(NotificationContentView privateLayout) {
mPrivateLayout = privateLayout;
+ mLayouts = new NotificationContentView[]{mPrivateLayout, mPublicLayout};
}
@VisibleForTesting
protected void setPublicLayout(NotificationContentView publicLayout) {
mPublicLayout = publicLayout;
+ mLayouts = new NotificationContentView[]{mPrivateLayout, mPublicLayout};
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
index d1138608805b..bb92dfcdfcb5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
@@ -219,7 +219,8 @@ public class ExpandableNotificationRowController implements NotifViewController
mNotificationGutsManager,
mMetricsLogger,
mSmartReplyConstants,
- mSmartReplyController
+ mSmartReplyController,
+ mFeatureFlags
);
mView.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
if (mAllowLongPress) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java
index 49dc6550a51f..21f4cb566e28 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java
@@ -16,15 +16,22 @@
package com.android.systemui.statusbar.notification.row;
+import android.annotation.ColorInt;
+import android.annotation.DrawableRes;
+import android.annotation.StringRes;
import android.content.Context;
+import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.IndentingPrintWriter;
import android.view.View;
+import android.widget.TextView;
import androidx.annotation.NonNull;
+import com.android.settingslib.Utils;
import com.android.systemui.R;
import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
import com.android.systemui.statusbar.notification.stack.ViewState;
@@ -41,6 +48,11 @@ public class FooterView extends StackScrollerDecorView {
private String mManageNotificationText;
private String mManageNotificationHistoryText;
+ // Footer label
+ private TextView mSeenNotifsFooterTextView;
+ private @StringRes int mSeenNotifsFilteredText;
+ private int mUnlockIconSize;
+
public FooterView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@@ -73,10 +85,41 @@ public class FooterView extends StackScrollerDecorView {
super.onFinishInflate();
mClearAllButton = (FooterViewButton) findSecondaryView();
mManageButton = findViewById(R.id.manage_text);
+ mSeenNotifsFooterTextView = findViewById(R.id.unlock_prompt_footer);
updateResources();
updateText();
}
+ public void setFooterLabelTextAndIcon(@StringRes int text, @DrawableRes int icon) {
+ mSeenNotifsFilteredText = text;
+ if (mSeenNotifsFilteredText != 0) {
+ mSeenNotifsFooterTextView.setText(mSeenNotifsFilteredText);
+ } else {
+ mSeenNotifsFooterTextView.setText(null);
+ }
+ Drawable drawable;
+ if (icon == 0) {
+ drawable = null;
+ } else {
+ drawable = getResources().getDrawable(icon);
+ drawable.setBounds(0, 0, mUnlockIconSize, mUnlockIconSize);
+ }
+ mSeenNotifsFooterTextView.setCompoundDrawablesRelative(drawable, null, null, null);
+ updateFooterVisibilityMode();
+ }
+
+ private void updateFooterVisibilityMode() {
+ if (mSeenNotifsFilteredText != 0) {
+ mManageButton.setVisibility(View.GONE);
+ mClearAllButton.setVisibility(View.GONE);
+ mSeenNotifsFooterTextView.setVisibility(View.VISIBLE);
+ } else {
+ mManageButton.setVisibility(View.VISIBLE);
+ mClearAllButton.setVisibility(View.VISIBLE);
+ mSeenNotifsFooterTextView.setVisibility(View.GONE);
+ }
+ }
+
public void setManageButtonClickListener(OnClickListener listener) {
mManageButton.setOnClickListener(listener);
}
@@ -135,12 +178,19 @@ public class FooterView extends StackScrollerDecorView {
mClearAllButton.setTextColor(textColor);
mManageButton.setBackground(theme.getDrawable(R.drawable.notif_footer_btn_background));
mManageButton.setTextColor(textColor);
+ final @ColorInt int labelTextColor =
+ Utils.getColorAttrDefaultColor(mContext, android.R.attr.textColorPrimary);
+ mSeenNotifsFooterTextView.setTextColor(labelTextColor);
+ mSeenNotifsFooterTextView.setCompoundDrawableTintList(
+ ColorStateList.valueOf(labelTextColor));
}
private void updateResources() {
mManageNotificationText = getContext().getString(R.string.manage_notifications_text);
mManageNotificationHistoryText = getContext()
.getString(R.string.manage_notifications_history_text);
+ mUnlockIconSize = getResources()
+ .getDimensionPixelSize(R.dimen.notifications_unseen_footer_icon_size);
}
@Override
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 e46bf522acff..4a023c41388e 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
@@ -184,6 +184,8 @@ public class NotificationContentView extends FrameLayout implements Notification
private boolean mRemoteInputVisible;
private int mUnrestrictedContentHeight;
+ private boolean mContentAnimating;
+
public NotificationContentView(Context context, AttributeSet attrs) {
super(context, attrs);
mHybridGroupManager = new HybridGroupManager(getContext());
@@ -2129,8 +2131,49 @@ public class NotificationContentView extends FrameLayout implements Notification
return false;
}
+ /**
+ * Starts and stops animations in the underlying views.
+ * Avoids restarting the animations by checking whether they're already running first.
+ * Return value is used for testing.
+ *
+ * @param running whether to start animations running, or stop them.
+ * @return true if the state of animations changed.
+ */
+ public boolean setContentAnimationRunning(boolean running) {
+ boolean stateChangeRequired = (running != mContentAnimating);
+ if (stateChangeRequired) {
+ // Starts or stops the animations in the potential views.
+ if (mContractedWrapper != null) {
+ mContractedWrapper.setAnimationsRunning(running);
+ }
+ if (mExpandedWrapper != null) {
+ mExpandedWrapper.setAnimationsRunning(running);
+ }
+ if (mHeadsUpWrapper != null) {
+ mHeadsUpWrapper.setAnimationsRunning(running);
+ }
+ // Updates the state tracker.
+ mContentAnimating = running;
+ return true;
+ }
+ return false;
+ }
+
private static class RemoteInputViewData {
@Nullable RemoteInputView mView;
@Nullable RemoteInputViewController mController;
}
+
+ @VisibleForTesting
+ protected void setContractedWrapper(NotificationViewWrapper contractedWrapper) {
+ mContractedWrapper = contractedWrapper;
+ }
+ @VisibleForTesting
+ protected void setExpandedWrapper(NotificationViewWrapper expandedWrapper) {
+ mExpandedWrapper = expandedWrapper;
+ }
+ @VisibleForTesting
+ protected void setHeadsUpWrapper(NotificationViewWrapper headsUpWrapper) {
+ mHeadsUpWrapper = headsUpWrapper;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapper.java
index 8732696dc7a1..175ba15eebae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapper.java
@@ -18,11 +18,15 @@ package com.android.systemui.statusbar.notification.row.wrapper;
import android.app.Notification;
import android.content.Context;
+import android.graphics.drawable.AnimatedImageDrawable;
+import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.os.Bundle;
import android.service.notification.StatusBarNotification;
import android.view.View;
+import com.android.internal.R;
+import com.android.internal.widget.BigPictureNotificationImageView;
import com.android.systemui.statusbar.notification.ImageTransformState;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -31,6 +35,8 @@ import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
*/
public class NotificationBigPictureTemplateViewWrapper extends NotificationTemplateViewWrapper {
+ private BigPictureNotificationImageView mImageView;
+
protected NotificationBigPictureTemplateViewWrapper(Context ctx, View view,
ExpandableNotificationRow row) {
super(ctx, view, row);
@@ -39,9 +45,14 @@ public class NotificationBigPictureTemplateViewWrapper extends NotificationTempl
@Override
public void onContentUpdated(ExpandableNotificationRow row) {
super.onContentUpdated(row);
+ resolveViews();
updateImageTag(row.getEntry().getSbn());
}
+ private void resolveViews() {
+ mImageView = mView.findViewById(R.id.big_picture);
+ }
+
private void updateImageTag(StatusBarNotification sbn) {
final Bundle extras = sbn.getNotification().extras;
Icon bigLargeIcon = extras.getParcelable(Notification.EXTRA_LARGE_ICON_BIG, Icon.class);
@@ -54,4 +65,25 @@ public class NotificationBigPictureTemplateViewWrapper extends NotificationTempl
mRightIcon.setTag(ImageTransformState.ICON_TAG, getLargeIcon(sbn.getNotification()));
}
}
+
+ /**
+ * Starts or stops the animations in any drawables contained in this BigPicture Notification.
+ *
+ * @param running Whether the animations should be set to run.
+ */
+ @Override
+ public void setAnimationsRunning(boolean running) {
+ if (mImageView == null) {
+ return;
+ }
+ Drawable d = mImageView.getDrawable();
+ if (d instanceof AnimatedImageDrawable) {
+ AnimatedImageDrawable animatedImageDrawable = (AnimatedImageDrawable) d;
+ if (running) {
+ animatedImageDrawable.start();
+ } else {
+ animatedImageDrawable.stop();
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt
index e136055b80b3..10753f215103 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt
@@ -17,16 +17,20 @@
package com.android.systemui.statusbar.notification.row.wrapper
import android.content.Context
+import android.graphics.drawable.AnimatedImageDrawable
import android.view.View
import android.view.ViewGroup
import com.android.internal.widget.CachingIconView
import com.android.internal.widget.ConversationLayout
+import com.android.internal.widget.MessagingGroup
+import com.android.internal.widget.MessagingImageMessage
import com.android.internal.widget.MessagingLinearLayout
import com.android.systemui.R
import com.android.systemui.statusbar.notification.NotificationFadeAware
import com.android.systemui.statusbar.notification.NotificationUtils
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.wrapper.NotificationMessagingTemplateViewWrapper.setCustomImageMessageTransform
+import com.android.systemui.util.children
/**
* Wraps a notification containing a conversation template
@@ -49,6 +53,7 @@ class NotificationConversationTemplateViewWrapper constructor(
private lateinit var expandBtn: View
private lateinit var expandBtnContainer: View
private lateinit var imageMessageContainer: ViewGroup
+ private lateinit var messageContainers: ArrayList<MessagingGroup>
private lateinit var messagingLinearLayout: MessagingLinearLayout
private lateinit var conversationTitleView: View
private lateinit var importanceRing: View
@@ -60,6 +65,7 @@ class NotificationConversationTemplateViewWrapper constructor(
private fun resolveViews() {
messagingLinearLayout = conversationLayout.messagingLinearLayout
imageMessageContainer = conversationLayout.imageMessageContainer
+ messageContainers = conversationLayout.messagingGroups
with(conversationLayout) {
conversationIconContainer =
requireViewById(com.android.internal.R.id.conversation_icon_container)
@@ -146,4 +152,26 @@ class NotificationConversationTemplateViewWrapper constructor(
NotificationFadeAware.setLayerTypeForFaded(expandBtn, faded)
NotificationFadeAware.setLayerTypeForFaded(conversationIconContainer, faded)
}
+
+ // Starts or stops the animations in any drawables contained in this Conversation Notification.
+ override fun setAnimationsRunning(running: Boolean) {
+ // We apply to both the child message containers in a conversation group,
+ // and the top level image message container.
+ val containers = messageContainers.asSequence().map { it.messageContainer } +
+ sequenceOf(imageMessageContainer)
+ val drawables =
+ containers
+ .flatMap { it.children }
+ .mapNotNull { child ->
+ (child as? MessagingImageMessage)?.let { imageMessage ->
+ imageMessage.drawable as? AnimatedImageDrawable
+ }
+ }
+ drawables.toSet().forEach {
+ when {
+ running -> it.start()
+ !running -> it.stop()
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapper.java
index c587ce05b13f..4592fde69a93 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapper.java
@@ -17,9 +17,13 @@
package com.android.systemui.statusbar.notification.row.wrapper;
import android.content.Context;
+import android.graphics.drawable.AnimatedImageDrawable;
+import android.graphics.drawable.Drawable;
import android.view.View;
import android.view.ViewGroup;
+import com.android.internal.widget.MessagingGroup;
+import com.android.internal.widget.MessagingImageMessage;
import com.android.internal.widget.MessagingLayout;
import com.android.internal.widget.MessagingLinearLayout;
import com.android.systemui.R;
@@ -127,4 +131,40 @@ public class NotificationMessagingTemplateViewWrapper extends NotificationTempla
}
return super.getMinLayoutHeight();
}
+
+ /**
+ * Starts or stops the animations in any drawables contained in this Messaging Notification.
+ *
+ * @param running Whether the animations should be set to run.
+ */
+ @Override
+ public void setAnimationsRunning(boolean running) {
+ if (mMessagingLayout == null) {
+ return;
+ }
+
+ for (MessagingGroup group : mMessagingLayout.getMessagingGroups()) {
+ for (int i = 0; i < group.getMessageContainer().getChildCount(); i++) {
+ View view = group.getMessageContainer().getChildAt(i);
+ // We only need to set animations in MessagingImageMessages.
+ if (!(view instanceof MessagingImageMessage)) {
+ continue;
+ }
+ MessagingImageMessage imageMessage =
+ (com.android.internal.widget.MessagingImageMessage) view;
+
+ // If the drawable isn't an AnimatedImageDrawable, we can't set it to animate.
+ Drawable d = imageMessage.getDrawable();
+ if (!(d instanceof AnimatedImageDrawable)) {
+ continue;
+ }
+ AnimatedImageDrawable animatedImageDrawable = (AnimatedImageDrawable) d;
+ if (running) {
+ animatedImageDrawable.start();
+ } else {
+ animatedImageDrawable.stop();
+ }
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
index 1c22f0933236..ff5b9cbf3c23 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
@@ -403,4 +403,12 @@ public abstract class NotificationViewWrapper implements TransformableView {
NotificationFadeAware.setLayerTypeForFaded(getIcon(), faded);
NotificationFadeAware.setLayerTypeForFaded(getExpandButton(), faded);
}
+
+ /**
+ * Starts or stops the animations in any drawables contained in this Notification.
+ *
+ * @param running Whether the animations should be set to run.
+ */
+ public void setAnimationsRunning(boolean running) {
+ }
}
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 356ddfa2d482..aab36da66bdf 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
@@ -538,6 +538,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
private NotificationStackScrollLayoutController.TouchHandler mTouchHandler;
private final ScreenOffAnimationController mScreenOffAnimationController;
private boolean mShouldUseSplitNotificationShade;
+ private boolean mHasFilteredOutSeenNotifications;
private final ExpandableView.OnHeightChangedListener mOnChildHeightChangedListener =
new ExpandableView.OnHeightChangedListener() {
@@ -684,6 +685,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
updateFooter();
}
+ void setHasFilteredOutSeenNotifications(boolean hasFilteredOutSeenNotifications) {
+ mHasFilteredOutSeenNotifications = hasFilteredOutSeenNotifications;
+ }
+
@VisibleForTesting
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
public void updateFooter() {
@@ -3132,7 +3137,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
private void updateAnimationState(boolean running, View child) {
if (child instanceof ExpandableNotificationRow) {
ExpandableNotificationRow row = (ExpandableNotificationRow) child;
- row.setIconAnimationRunning(running);
+ row.setAnimationRunning(running);
}
}
@@ -4612,13 +4617,12 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
}
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
- void updateEmptyShadeView(
- boolean visible, boolean areNotificationsHiddenInShade, boolean areSeenNotifsFiltered) {
+ void updateEmptyShadeView(boolean visible, boolean areNotificationsHiddenInShade) {
mEmptyShadeView.setVisible(visible, mIsExpanded && mAnimationsEnabled);
if (areNotificationsHiddenInShade) {
updateEmptyShadeView(R.string.dnd_suppressing_shade_text, 0, 0);
- } else if (areSeenNotifsFiltered) {
+ } else if (mHasFilteredOutSeenNotifications) {
updateEmptyShadeView(
R.string.no_unseen_notif_text,
R.string.unlock_to_see_notif_text,
@@ -4657,13 +4661,20 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
public void updateFooterView(boolean visible, boolean showDismissView, boolean showHistory) {
- if (mFooterView == null) {
+ if (mFooterView == null || mNotificationStackSizeCalculator == null) {
return;
}
boolean animate = mIsExpanded && mAnimationsEnabled;
mFooterView.setVisible(visible, animate);
mFooterView.setSecondaryVisible(showDismissView, animate);
mFooterView.showHistory(showHistory);
+ if (mHasFilteredOutSeenNotifications) {
+ mFooterView.setFooterLabelTextAndIcon(
+ R.string.unlock_to_see_notif_text,
+ R.drawable.ic_friction_lock_closed);
+ } else {
+ mFooterView.setFooterLabelTextAndIcon(0, 0);
+ }
}
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 58919489496d..971dce89cf24 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
@@ -1242,11 +1242,7 @@ public class NotificationStackScrollLayoutController {
// For more details, see: b/228790482
&& !isInTransitionToKeyguard();
- mView.updateEmptyShadeView(
- shouldShow,
- mZenModeController.areNotificationsHiddenInShade(),
- mNotifPipelineFlags.getShouldFilterUnseenNotifsOnKeyguard()
- && mSeenNotificationsProvider.getHasFilteredOutSeenNotifications());
+ mView.updateEmptyShadeView(shouldShow, mZenModeController.areNotificationsHiddenInShade());
Trace.endSection();
}
@@ -1942,6 +1938,9 @@ public class NotificationStackScrollLayoutController {
@Override
public void setNotifStats(@NonNull NotifStats notifStats) {
mNotifStats = notifStats;
+ mView.setHasFilteredOutSeenNotifications(
+ mNotifPipelineFlags.getShouldFilterUnseenNotifsOnKeyguard()
+ && mSeenNotificationsProvider.getHasFilteredOutSeenNotifications());
updateFooter();
updateShowEmptyShadeView();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index d31875935dd3..6873de735015 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -26,8 +26,6 @@ import android.content.res.ColorStateList;
import android.hardware.biometrics.BiometricSourceType;
import android.os.Handler;
import android.os.Trace;
-import android.os.UserHandle;
-import android.os.UserManager;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
@@ -177,11 +175,6 @@ public class KeyguardBouncer {
*/
public void show(boolean resetSecuritySelection, boolean isScrimmed) {
final int keyguardUserId = KeyguardUpdateMonitor.getCurrentUser();
- if (keyguardUserId == UserHandle.USER_SYSTEM && UserManager.isSplitSystemUser()) {
- // In split system user mode, we never unlock system user.
- return;
- }
-
try {
Trace.beginSection("KeyguardBouncer#show");
@@ -212,9 +205,7 @@ public class KeyguardBouncer {
}
final int activeUserId = KeyguardUpdateMonitor.getCurrentUser();
- final boolean isSystemUser =
- UserManager.isSplitSystemUser() && activeUserId == UserHandle.USER_SYSTEM;
- final boolean allowDismissKeyguard = !isSystemUser && activeUserId == keyguardUserId;
+ final boolean allowDismissKeyguard = activeUserId == keyguardUserId;
// If allowed, try to dismiss the Keyguard. If no security auth (password/pin/pattern)
// is set, this will dismiss the whole Keyguard. Otherwise, show the bouncer.
diff --git a/packages/SystemUI/src/com/android/systemui/telephony/ui/activity/SwitchToManagedProfileForCallActivity.kt b/packages/SystemUI/src/com/android/systemui/telephony/ui/activity/SwitchToManagedProfileForCallActivity.kt
new file mode 100644
index 000000000000..e092f01d19f6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/telephony/ui/activity/SwitchToManagedProfileForCallActivity.kt
@@ -0,0 +1,81 @@
+/*
+ * 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.telephony.ui.activity
+
+import android.app.ActivityOptions
+import android.content.DialogInterface
+import android.content.Intent
+import android.net.Uri
+import android.os.Bundle
+import android.os.UserHandle
+import android.util.Log
+import android.view.WindowManager
+import com.android.internal.app.AlertActivity
+import com.android.systemui.R
+
+/** Dialog shown to the user to switch to managed profile for making a call using work SIM. */
+class SwitchToManagedProfileForCallActivity : AlertActivity(), DialogInterface.OnClickListener {
+ private lateinit var phoneNumber: Uri
+ private var managedProfileUserId = UserHandle.USER_NULL
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ window.addSystemFlags(
+ WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS
+ )
+ super.onCreate(savedInstanceState)
+
+ phoneNumber = intent.getData()
+ managedProfileUserId =
+ intent.getIntExtra(
+ "android.telecom.extra.MANAGED_PROFILE_USER_ID",
+ UserHandle.USER_NULL
+ )
+
+ mAlertParams.apply {
+ mTitle = getString(R.string.call_from_work_profile_title)
+ mMessage = getString(R.string.call_from_work_profile_text)
+ mPositiveButtonText = getString(R.string.call_from_work_profile_action)
+ mNegativeButtonText = getString(R.string.call_from_work_profile_close)
+ mPositiveButtonListener = this@SwitchToManagedProfileForCallActivity
+ mNegativeButtonListener = this@SwitchToManagedProfileForCallActivity
+ }
+ setupAlert()
+ }
+
+ override fun onClick(dialog: DialogInterface?, which: Int) {
+ if (which == BUTTON_POSITIVE) {
+ switchToManagedProfile()
+ }
+ finish()
+ }
+
+ private fun switchToManagedProfile() {
+ try {
+ applicationContext.startActivityAsUser(
+ Intent(Intent.ACTION_DIAL, phoneNumber),
+ ActivityOptions.makeOpenCrossProfileAppsAnimation().toBundle(),
+ UserHandle.of(managedProfileUserId)
+ )
+ } catch (e: Exception) {
+ Log.e(TAG, "Failed to launch activity", e)
+ }
+ }
+
+ companion object {
+ private const val TAG = "SwitchToManagedProfileForCallActivity"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 5894fd385ee3..8fff3b14be86 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -26,7 +26,6 @@ import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_COLOR_INDEX
import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_COLOR_SOURCE;
import static com.android.systemui.theme.ThemeOverlayApplier.TIMESTAMP_FIELD;
-import android.annotation.Nullable;
import android.app.WallpaperColors;
import android.app.WallpaperManager;
import android.app.WallpaperManager.OnColorsChangedListener;
@@ -70,6 +69,7 @@ import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.monet.ColorScheme;
import com.android.systemui.monet.Style;
+import com.android.systemui.monet.TonalPalette;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
@@ -511,39 +511,42 @@ public class ThemeOverlayController implements CoreStartable, Dumpable {
/**
* Given a color candidate, return an overlay definition.
*/
- protected @Nullable FabricatedOverlay getOverlay(int color, int type, Style style) {
+ protected FabricatedOverlay getOverlay(int color, int type, Style style) {
boolean nightMode = (mResources.getConfiguration().uiMode
& Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;
mColorScheme = new ColorScheme(color, nightMode, style);
- List<Integer> colorShades = type == ACCENT
- ? mColorScheme.getAllAccentColors() : mColorScheme.getAllNeutralColors();
String name = type == ACCENT ? "accent" : "neutral";
- int paletteSize = mColorScheme.getAccent1().size();
+
FabricatedOverlay.Builder overlay =
new FabricatedOverlay.Builder("com.android.systemui", name, "android");
- for (int i = 0; i < colorShades.size(); i++) {
- int luminosity = i % paletteSize;
- int paletteIndex = i / paletteSize + 1;
- String resourceName;
- switch (luminosity) {
- case 0:
- resourceName = "android:color/system_" + name + paletteIndex + "_10";
- break;
- case 1:
- resourceName = "android:color/system_" + name + paletteIndex + "_50";
- break;
- default:
- int l = luminosity - 1;
- resourceName = "android:color/system_" + name + paletteIndex + "_" + l + "00";
- }
- overlay.setResourceValue(resourceName, TypedValue.TYPE_INT_COLOR_ARGB8,
- ColorUtils.setAlphaComponent(colorShades.get(i), 0xFF));
+
+ if (type == ACCENT) {
+ assignTonalPaletteToOverlay("accent1", overlay, mColorScheme.getAccent1());
+ assignTonalPaletteToOverlay("accent2", overlay, mColorScheme.getAccent2());
+ assignTonalPaletteToOverlay("accent3", overlay, mColorScheme.getAccent3());
+ } else {
+ assignTonalPaletteToOverlay("neutral1", overlay, mColorScheme.getNeutral1());
+ assignTonalPaletteToOverlay("neutral2", overlay, mColorScheme.getNeutral2());
}
return overlay.build();
}
+ private void assignTonalPaletteToOverlay(String name,
+ FabricatedOverlay.Builder overlay, TonalPalette tonalPalette) {
+
+ String resourcePrefix = "android:color/system_" + name;
+ int colorDataType = TypedValue.TYPE_INT_COLOR_ARGB8;
+
+ tonalPalette.getAllShadesMapped().forEach((key, value) -> {
+ String resourceName = resourcePrefix + "_" + key;
+ int colorValue = ColorUtils.setAlphaComponent(value, 0xFF);
+ overlay.setResourceValue(resourceName, colorDataType,
+ colorValue);
+ });
+ }
+
/**
* Checks if the color scheme in mColorScheme matches the current system palettes.
* @param managedProfiles List of managed profiles for this user.
@@ -555,15 +558,15 @@ public class ThemeOverlayController implements CoreStartable, Dumpable {
Resources res = userHandle.isSystem()
? mResources : mContext.createContextAsUser(userHandle, 0).getResources();
if (!(res.getColor(android.R.color.system_accent1_500, mContext.getTheme())
- == mColorScheme.getAccent1().get(6)
+ == mColorScheme.getAccent1().getS500()
&& res.getColor(android.R.color.system_accent2_500, mContext.getTheme())
- == mColorScheme.getAccent2().get(6)
+ == mColorScheme.getAccent2().getS500()
&& res.getColor(android.R.color.system_accent3_500, mContext.getTheme())
- == mColorScheme.getAccent3().get(6)
+ == mColorScheme.getAccent3().getS500()
&& res.getColor(android.R.color.system_neutral1_500, mContext.getTheme())
- == mColorScheme.getNeutral1().get(6)
+ == mColorScheme.getNeutral1().getS500()
&& res.getColor(android.R.color.system_neutral2_500, mContext.getTheme())
- == mColorScheme.getNeutral2().get(6))) {
+ == mColorScheme.getNeutral2().getS500())) {
return false;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsController.kt b/packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsController.kt
new file mode 100644
index 000000000000..7b8235acb0a7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsController.kt
@@ -0,0 +1,115 @@
+/*
+ * 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.wallet.controller
+
+import android.Manifest
+import android.content.Context
+import android.content.IntentFilter
+import android.service.quickaccesswallet.GetWalletCardsError
+import android.service.quickaccesswallet.GetWalletCardsResponse
+import android.service.quickaccesswallet.QuickAccessWalletClient
+import android.service.quickaccesswallet.WalletCard
+import com.android.systemui.broadcast.BroadcastDispatcher
+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.Application
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.shareIn
+
+@SysUISingleton
+class WalletContextualSuggestionsController
+@Inject
+constructor(
+ @Application private val applicationCoroutineScope: CoroutineScope,
+ private val walletController: QuickAccessWalletController,
+ broadcastDispatcher: BroadcastDispatcher,
+ featureFlags: FeatureFlags
+) {
+ private val allWalletCards: Flow<List<WalletCard>> =
+ if (featureFlags.isEnabled(Flags.ENABLE_WALLET_CONTEXTUAL_LOYALTY_CARDS)) {
+ conflatedCallbackFlow {
+ val callback =
+ object : QuickAccessWalletClient.OnWalletCardsRetrievedCallback {
+ override fun onWalletCardsRetrieved(response: GetWalletCardsResponse) {
+ trySendWithFailureLogging(response.walletCards, TAG)
+ }
+
+ override fun onWalletCardRetrievalError(error: GetWalletCardsError) {
+ trySendWithFailureLogging(emptyList<WalletCard>(), TAG)
+ }
+ }
+
+ walletController.setupWalletChangeObservers(
+ callback,
+ QuickAccessWalletController.WalletChangeEvent.WALLET_PREFERENCE_CHANGE,
+ QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE
+ )
+ walletController.updateWalletPreference()
+ walletController.queryWalletCards(callback)
+
+ awaitClose {
+ walletController.unregisterWalletChangeObservers(
+ QuickAccessWalletController.WalletChangeEvent.WALLET_PREFERENCE_CHANGE,
+ QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE
+ )
+ }
+ }
+ } else {
+ emptyFlow()
+ }
+
+ private val contextualSuggestionsCardIds: Flow<Set<String>> =
+ if (featureFlags.isEnabled(Flags.ENABLE_WALLET_CONTEXTUAL_LOYALTY_CARDS)) {
+ broadcastDispatcher.broadcastFlow(
+ filter = IntentFilter(ACTION_UPDATE_WALLET_CONTEXTUAL_SUGGESTIONS),
+ permission = Manifest.permission.BIND_QUICK_ACCESS_WALLET_SERVICE,
+ flags = Context.RECEIVER_EXPORTED
+ ) { intent, _ ->
+ if (intent.hasExtra(UPDATE_CARD_IDS_EXTRA)) {
+ intent.getStringArrayListExtra(UPDATE_CARD_IDS_EXTRA).toSet()
+ } else {
+ emptySet()
+ }
+ }
+ } else {
+ emptyFlow()
+ }
+
+ val contextualSuggestionCards: Flow<List<WalletCard>> =
+ combine(allWalletCards, contextualSuggestionsCardIds) { cards, ids ->
+ cards.filter { card -> ids.contains(card.cardId) }
+ }
+ .shareIn(applicationCoroutineScope, replay = 1, started = SharingStarted.Eagerly)
+
+ companion object {
+ private const val ACTION_UPDATE_WALLET_CONTEXTUAL_SUGGESTIONS =
+ "com.android.systemui.wallet.UPDATE_CONTEXTUAL_SUGGESTIONS"
+
+ private const val UPDATE_CARD_IDS_EXTRA = "cardIds"
+
+ private const val TAG = "WalletSuggestions"
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
index c8a352dd7cd2..8795ac013bd3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
@@ -558,8 +558,8 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase {
UserInfo currentUser = mockCurrentUser(FLAG_ADMIN);
when(mGlobalActionsDialogLite.getCurrentUser()).thenReturn(currentUser);
- doReturn(1).when(mGlobalSettings)
- .getIntForUser(Settings.Global.BUGREPORT_IN_POWER_MENU, 0, currentUser.id);
+ when(mGlobalSettings.getIntForUser(Settings.Secure.BUGREPORT_IN_POWER_MENU,
+ 0, currentUser.id)).thenReturn(1);
GlobalActionsDialogLite.BugReportAction bugReportAction =
mGlobalActionsDialogLite.makeBugReportActionForTesting();
@@ -572,7 +572,7 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase {
when(mGlobalActionsDialogLite.getCurrentUser()).thenReturn(currentUser);
doReturn(1).when(mGlobalSettings)
- .getIntForUser(Settings.Global.BUGREPORT_IN_POWER_MENU, 0, currentUser.id);
+ .getIntForUser(Settings.Secure.BUGREPORT_IN_POWER_MENU, 0, currentUser.id);
GlobalActionsDialogLite.BugReportAction bugReportAction =
mGlobalActionsDialogLite.makeBugReportActionForTesting();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
index 7205f3068abb..8da4eae2f64a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
@@ -22,15 +22,21 @@ import android.content.Context
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.camera.CameraGestureHelper
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.mockito.Mock
+import org.mockito.Mockito.anyInt
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(JUnit4::class)
class CameraQuickAffordanceConfigTest : SysuiTestCase() {
@@ -62,4 +68,24 @@ class CameraQuickAffordanceConfigTest : SysuiTestCase() {
.launchCamera(StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE)
assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result)
}
+
+ @Test
+ fun `getPickerScreenState - default when launchable`() = runTest {
+ setLaunchable(true)
+
+ Truth.assertThat(underTest.getPickerScreenState())
+ .isInstanceOf(KeyguardQuickAffordanceConfig.PickerScreenState.Default::class.java)
+ }
+
+ @Test
+ fun `getPickerScreenState - unavailable when not launchable`() = runTest {
+ setLaunchable(false)
+
+ Truth.assertThat(underTest.getPickerScreenState())
+ .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice)
+ }
+
+ private fun setLaunchable(isLaunchable: Boolean) {
+ whenever(cameraGestureHelper.canCameraGestureBeLaunched(anyInt())).thenReturn(isLaunchable)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
index ca44fa18f6c4..8f56b9560ec3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
@@ -114,20 +114,8 @@ class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
}
@Test
- fun `affordance - missing icon - model is none`() = runBlockingTest {
- setUpState(hasWalletIcon = false)
- var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
-
- val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
-
- assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
-
- job.cancel()
- }
-
- @Test
fun `affordance - no selected card - model is none`() = runBlockingTest {
- setUpState(hasWalletIcon = false)
+ setUpState(hasSelectedCard = false)
var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
@@ -165,7 +153,7 @@ class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
@Test
fun `getPickerScreenState - unavailable`() = runTest {
setUpState(
- isWalletEnabled = false,
+ isWalletServiceAvailable = false,
)
assertThat(underTest.getPickerScreenState())
@@ -173,9 +161,9 @@ class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
}
@Test
- fun `getPickerScreenState - disabled when there is no icon`() = runTest {
+ fun `getPickerScreenState - disabled when the feature is not enabled`() = runTest {
setUpState(
- hasWalletIcon = false,
+ isWalletEnabled = false,
)
assertThat(underTest.getPickerScreenState())
@@ -194,20 +182,16 @@ class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
private fun setUpState(
isWalletEnabled: Boolean = true,
+ isWalletServiceAvailable: Boolean = true,
isWalletQuerySuccessful: Boolean = true,
- hasWalletIcon: Boolean = true,
hasSelectedCard: Boolean = true,
) {
whenever(walletController.isWalletEnabled).thenReturn(isWalletEnabled)
val walletClient: QuickAccessWalletClient = mock()
- val icon: Drawable? =
- if (hasWalletIcon) {
- ICON
- } else {
- null
- }
- whenever(walletClient.tileIcon).thenReturn(icon)
+ whenever(walletClient.tileIcon).thenReturn(ICON)
+ whenever(walletClient.isWalletServiceAvailable).thenReturn(isWalletServiceAvailable)
+
whenever(walletController.walletClient).thenReturn(walletClient)
whenever(walletController.queryWalletCards(any())).thenAnswer { invocation ->
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfigTest.kt
new file mode 100644
index 000000000000..805dcec0f5b1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfigTest.kt
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.quickaffordance
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.ActivityIntentHelper
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.camera.CameraIntentsWrapper
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.settings.FakeUserTracker
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class VideoCameraQuickAffordanceConfigTest : SysuiTestCase() {
+
+ @Mock private lateinit var activityIntentHelper: ActivityIntentHelper
+
+ private lateinit var underTest: VideoCameraQuickAffordanceConfig
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ underTest =
+ VideoCameraQuickAffordanceConfig(
+ context = context,
+ cameraIntents = CameraIntentsWrapper(context),
+ activityIntentHelper = activityIntentHelper,
+ userTracker = FakeUserTracker(),
+ )
+ }
+
+ @Test
+ fun `lockScreenState - visible when launchable`() = runTest {
+ setLaunchable(true)
+
+ val lockScreenState = collectLastValue(underTest.lockScreenState)
+
+ assertThat(lockScreenState())
+ .isInstanceOf(KeyguardQuickAffordanceConfig.LockScreenState.Visible::class.java)
+ }
+
+ @Test
+ fun `lockScreenState - hidden when not launchable`() = runTest {
+ setLaunchable(false)
+
+ val lockScreenState = collectLastValue(underTest.lockScreenState)
+
+ assertThat(lockScreenState())
+ .isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
+ }
+
+ @Test
+ fun `getPickerScreenState - default when launchable`() = runTest {
+ setLaunchable(true)
+
+ assertThat(underTest.getPickerScreenState())
+ .isInstanceOf(KeyguardQuickAffordanceConfig.PickerScreenState.Default::class.java)
+ }
+
+ @Test
+ fun `getPickerScreenState - unavailable when not launchable`() = runTest {
+ setLaunchable(false)
+
+ assertThat(underTest.getPickerScreenState())
+ .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice)
+ }
+
+ private fun setLaunchable(isLaunchable: Boolean) {
+ whenever(
+ activityIntentHelper.getTargetActivityInfo(
+ any(),
+ anyInt(),
+ anyBoolean(),
+ )
+ )
+ .thenReturn(
+ if (isLaunchable) {
+ mock()
+ } else {
+ null
+ }
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt
new file mode 100644
index 000000000000..4b069051423c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/TrustRepositoryTest.kt
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import android.app.trust.TrustManager
+import android.content.pm.UserInfo
+import androidx.test.filters.SmallTest
+import com.android.keyguard.logging.TrustRepositoryLogger
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogcatEchoTracker
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+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
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class TrustRepositoryTest : SysuiTestCase() {
+ @Mock private lateinit var trustManager: TrustManager
+ @Captor private lateinit var listenerCaptor: ArgumentCaptor<TrustManager.TrustListener>
+ private lateinit var userRepository: FakeUserRepository
+ private lateinit var testScope: TestScope
+ private val users = listOf(UserInfo(1, "user 1", 0), UserInfo(2, "user 1", 0))
+
+ private lateinit var underTest: TrustRepository
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ testScope = TestScope()
+ userRepository = FakeUserRepository()
+ userRepository.setUserInfos(users)
+
+ val logger =
+ TrustRepositoryLogger(
+ LogBuffer("TestBuffer", 1, mock(LogcatEchoTracker::class.java), false)
+ )
+ underTest =
+ TrustRepositoryImpl(testScope.backgroundScope, userRepository, trustManager, logger)
+ }
+
+ @Test
+ fun isCurrentUserTrusted_whenTrustChanges_emitsLatestValue() =
+ testScope.runTest {
+ runCurrent()
+ verify(trustManager).registerTrustListener(listenerCaptor.capture())
+ val listener = listenerCaptor.value
+
+ val currentUserId = users[0].id
+ userRepository.setSelectedUserInfo(users[0])
+ val isCurrentUserTrusted = collectLastValue(underTest.isCurrentUserTrusted)
+
+ listener.onTrustChanged(true, false, currentUserId, 0, emptyList())
+ assertThat(isCurrentUserTrusted()).isTrue()
+
+ listener.onTrustChanged(false, false, currentUserId, 0, emptyList())
+
+ assertThat(isCurrentUserTrusted()).isFalse()
+ }
+
+ @Test
+ fun isCurrentUserTrusted_isFalse_byDefault() =
+ testScope.runTest {
+ runCurrent()
+
+ val isCurrentUserTrusted = collectLastValue(underTest.isCurrentUserTrusted)
+
+ assertThat(isCurrentUserTrusted()).isFalse()
+ }
+
+ @Test
+ fun isCurrentUserTrusted_whenTrustChangesForDifferentUser_noop() =
+ testScope.runTest {
+ runCurrent()
+ verify(trustManager).registerTrustListener(listenerCaptor.capture())
+ userRepository.setSelectedUserInfo(users[0])
+ val listener = listenerCaptor.value
+
+ val isCurrentUserTrusted = collectLastValue(underTest.isCurrentUserTrusted)
+ // current user is trusted.
+ listener.onTrustChanged(true, true, users[0].id, 0, emptyList())
+ // some other user is not trusted.
+ listener.onTrustChanged(false, false, users[1].id, 0, emptyList())
+
+ assertThat(isCurrentUserTrusted()).isTrue()
+ }
+
+ @Test
+ fun isCurrentUserTrusted_whenTrustChangesForCurrentUser_emitsNewValue() =
+ testScope.runTest {
+ runCurrent()
+ verify(trustManager).registerTrustListener(listenerCaptor.capture())
+ val listener = listenerCaptor.value
+ userRepository.setSelectedUserInfo(users[0])
+
+ val isCurrentUserTrusted = collectLastValue(underTest.isCurrentUserTrusted)
+ listener.onTrustChanged(true, true, users[0].id, 0, emptyList())
+ assertThat(isCurrentUserTrusted()).isTrue()
+
+ listener.onTrustChanged(false, true, users[0].id, 0, emptyList())
+ assertThat(isCurrentUserTrusted()).isFalse()
+ }
+
+ @Test
+ fun isCurrentUserTrusted_whenUserChangesWithoutRecentTrustChange_defaultsToFalse() =
+ testScope.runTest {
+ runCurrent()
+ verify(trustManager).registerTrustListener(listenerCaptor.capture())
+ val listener = listenerCaptor.value
+ userRepository.setSelectedUserInfo(users[0])
+ listener.onTrustChanged(true, true, users[0].id, 0, emptyList())
+
+ val isCurrentUserTrusted = collectLastValue(underTest.isCurrentUserTrusted)
+ userRepository.setSelectedUserInfo(users[1])
+
+ assertThat(isCurrentUserTrusted()).isFalse()
+ }
+
+ @Test
+ fun isCurrentUserTrusted_trustChangesFirstBeforeUserInfoChanges_emitsCorrectValue() =
+ testScope.runTest {
+ runCurrent()
+ verify(trustManager).registerTrustListener(listenerCaptor.capture())
+ val listener = listenerCaptor.value
+ val isCurrentUserTrusted = collectLastValue(underTest.isCurrentUserTrusted)
+
+ listener.onTrustChanged(true, true, users[0].id, 0, emptyList())
+ assertThat(isCurrentUserTrusted()).isFalse()
+
+ userRepository.setSelectedUserInfo(users[0])
+
+ assertThat(isCurrentUserTrusted()).isTrue()
+ }
+}
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 a1b6d478d799..5a7a3d49b628 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
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.domain.interactor
import android.animation.ValueAnimator
+import androidx.test.filters.FlakyTest
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.Interpolators
@@ -56,6 +57,7 @@ import org.mockito.MockitoAnnotations
*/
@SmallTest
@RunWith(JUnit4::class)
+@FlakyTest(bugId = 265303901)
class KeyguardTransitionScenariosTest : SysuiTestCase() {
private lateinit var testScope: TestScope
diff --git a/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java b/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java
index 1bc4719c70b7..1a35502ceed6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.java
@@ -31,10 +31,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@@ -69,7 +66,7 @@ public class ColorSchemeTest extends SysuiTestCase {
// Expressive applies hue rotations to the theme color. The input theme color has hue
// 117, ensuring the hue changed significantly is a strong signal styles are being applied.
ColorScheme colorScheme = new ColorScheme(wallpaperColors, false, Style.EXPRESSIVE);
- Assert.assertEquals(357.77, Cam.fromInt(colorScheme.getAccent1().get(6)).getHue(), 0.1);
+ Assert.assertEquals(357.77, Cam.fromInt(colorScheme.getAccent1().getS500()).getHue(), 0.1);
}
@@ -111,7 +108,8 @@ public class ColorSchemeTest extends SysuiTestCase {
public void testTertiaryHueWrapsProperly() {
int colorInt = 0xffB3588A; // H350 C50 T50
ColorScheme colorScheme = new ColorScheme(colorInt, false /* darkTheme */);
- int tertiaryMid = colorScheme.getAccent3().get(colorScheme.getAccent3().size() / 2);
+ int tertiaryMid = colorScheme.getAccent3().getAllShades().get(
+ colorScheme.getShadeCount() / 2);
Cam cam = Cam.fromInt(tertiaryMid);
Assert.assertEquals(cam.getHue(), 50.0, 10.0);
}
@@ -121,7 +119,8 @@ public class ColorSchemeTest extends SysuiTestCase {
int colorInt = 0xffB3588A; // H350 C50 T50
ColorScheme colorScheme = new ColorScheme(colorInt, false /* darkTheme */,
Style.SPRITZ /* style */);
- int primaryMid = colorScheme.getAccent1().get(colorScheme.getAccent1().size() / 2);
+ int primaryMid = colorScheme.getAccent1().getAllShades().get(
+ colorScheme.getShadeCount() / 2);
Cam cam = Cam.fromInt(primaryMid);
Assert.assertEquals(cam.getChroma(), 12.0, 1.0);
}
@@ -131,7 +130,8 @@ public class ColorSchemeTest extends SysuiTestCase {
int colorInt = 0xffB3588A; // H350 C50 T50
ColorScheme colorScheme = new ColorScheme(colorInt, false /* darkTheme */,
Style.VIBRANT /* style */);
- int neutralMid = colorScheme.getNeutral1().get(colorScheme.getNeutral1().size() / 2);
+ int neutralMid = colorScheme.getNeutral1().getAllShades().get(
+ colorScheme.getShadeCount() / 2);
Cam cam = Cam.fromInt(neutralMid);
Assert.assertTrue("chroma was " + cam.getChroma(), Math.floor(cam.getChroma()) <= 12.0);
}
@@ -141,7 +141,8 @@ public class ColorSchemeTest extends SysuiTestCase {
int colorInt = 0xffB3588A; // H350 C50 T50
ColorScheme colorScheme = new ColorScheme(colorInt, false /* darkTheme */,
Style.EXPRESSIVE /* style */);
- int neutralMid = colorScheme.getNeutral1().get(colorScheme.getNeutral1().size() / 2);
+ int neutralMid = colorScheme.getNeutral1().getAllShades().get(
+ colorScheme.getShadeCount() / 2);
Cam cam = Cam.fromInt(neutralMid);
Assert.assertTrue(cam.getChroma() <= 8.0);
}
@@ -151,10 +152,11 @@ public class ColorSchemeTest extends SysuiTestCase {
int colorInt = 0xffB3588A; // H350 C50 T50
ColorScheme colorScheme = new ColorScheme(colorInt, false /* darkTheme */,
Style.MONOCHROMATIC /* style */);
- int neutralMid = colorScheme.getNeutral1().get(colorScheme.getNeutral1().size() / 2);
+ int neutralMid = colorScheme.getNeutral1().getAllShades().get(
+ colorScheme.getShadeCount() / 2);
Assert.assertTrue(
Color.red(neutralMid) == Color.green(neutralMid)
- && Color.green(neutralMid) == Color.blue(neutralMid)
+ && Color.green(neutralMid) == Color.blue(neutralMid)
);
}
@@ -190,15 +192,14 @@ public class ColorSchemeTest extends SysuiTestCase {
xml.append(" <").append(styleName).append(">");
List<String> colors = new ArrayList<>();
- for (Stream<Integer> stream: Arrays.asList(colorScheme.getAccent1().stream(),
- colorScheme.getAccent2().stream(),
- colorScheme.getAccent3().stream(),
- colorScheme.getNeutral1().stream(),
- colorScheme.getNeutral2().stream())) {
+
+ colorScheme.getAllHues().forEach(schemeHue -> {
colors.add("ffffff");
- colors.addAll(stream.map(Integer::toHexString).map(s -> s.substring(2)).collect(
- Collectors.toList()));
- }
+ schemeHue.getAllShades().forEach(tone -> {
+ colors.add(Integer.toHexString(tone).substring(2));
+ });
+ });
+
xml.append(String.join(",", colors));
xml.append("</").append(styleName).append(">\n");
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
index 46a502acba16..ed3f1a059e61 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
@@ -22,15 +22,12 @@ import android.graphics.ColorSpace
import android.graphics.Insets
import android.graphics.Rect
import android.hardware.HardwareBuffer
-import android.os.Bundle
import android.os.UserHandle
import android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD
import android.view.WindowManager.ScreenshotSource.SCREENSHOT_OTHER
import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN
import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
-import com.android.internal.util.ScreenshotHelper.HardwareBitmapBundler
-import com.android.internal.util.ScreenshotHelper.HardwareBitmapBundler.bundleToHardwareBitmap
-import com.android.internal.util.ScreenshotHelper.ScreenshotRequest
+import com.android.internal.util.ScreenshotRequest
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.screenshot.ScreenshotPolicy.DisplayContentInfo
@@ -49,7 +46,6 @@ class RequestProcessorTest {
private val bounds = Rect(25, 25, 75, 75)
private val scope = CoroutineScope(Dispatchers.Unconfined)
- private val dispatcher = Dispatchers.Unconfined
private val policy = FakeScreenshotPolicy()
private val flags = FakeFeatureFlags()
@@ -58,7 +54,8 @@ class RequestProcessorTest {
fun testProcessAsync() {
flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, false)
- val request = ScreenshotRequest(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD)
+ val request =
+ ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD).build()
val processor = RequestProcessor(imageCapture, policy, flags, scope)
var result: ScreenshotRequest? = null
@@ -80,7 +77,8 @@ class RequestProcessorTest {
fun testFullScreenshot_workProfilePolicyDisabled() = runBlocking {
flags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, false)
- val request = ScreenshotRequest(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD)
+ val request =
+ ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD).build()
val processor = RequestProcessor(imageCapture, policy, flags, scope)
val processedRequest = processor.process(request)
@@ -97,9 +95,11 @@ class RequestProcessorTest {
policy.setManagedProfile(USER_ID, false)
policy.setDisplayContentInfo(
policy.getDefaultDisplayId(),
- DisplayContentInfo(component, bounds, UserHandle.of(USER_ID), TASK_ID))
+ DisplayContentInfo(component, bounds, UserHandle.of(USER_ID), TASK_ID)
+ )
- val request = ScreenshotRequest(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_OTHER)
+ val request =
+ ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_OTHER).build()
val processor = RequestProcessor(imageCapture, policy, flags, scope)
val processedRequest = processor.process(request)
@@ -120,17 +120,20 @@ class RequestProcessorTest {
// Indicate that the primary content belongs to a manged profile
policy.setManagedProfile(USER_ID, true)
- policy.setDisplayContentInfo(policy.getDefaultDisplayId(),
- DisplayContentInfo(component, bounds, UserHandle.of(USER_ID), TASK_ID))
+ policy.setDisplayContentInfo(
+ policy.getDefaultDisplayId(),
+ DisplayContentInfo(component, bounds, UserHandle.of(USER_ID), TASK_ID)
+ )
- val request = ScreenshotRequest(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD)
+ val request =
+ ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD).build()
val processor = RequestProcessor(imageCapture, policy, flags, scope)
val processedRequest = processor.process(request)
// Expect a task snapshot is taken, overriding the full screen mode
assertThat(processedRequest.type).isEqualTo(TAKE_SCREENSHOT_PROVIDED_IMAGE)
- assertThat(bitmap.equalsHardwareBitmapBundle(processedRequest.bitmapBundle)).isTrue()
+ assertThat(bitmap.equalsHardwareBitmap(processedRequest.bitmap)).isTrue()
assertThat(processedRequest.boundsInScreen).isEqualTo(bounds)
assertThat(processedRequest.insets).isEqualTo(Insets.NONE)
assertThat(processedRequest.taskId).isEqualTo(TASK_ID)
@@ -147,10 +150,16 @@ class RequestProcessorTest {
val processor = RequestProcessor(imageCapture, policy, flags, scope)
val bitmap = makeHardwareBitmap(100, 100)
- val bitmapBundle = HardwareBitmapBundler.hardwareBitmapToBundle(bitmap)
- val request = ScreenshotRequest(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER,
- bitmapBundle, bounds, Insets.NONE, TASK_ID, USER_ID, component)
+ val request =
+ ScreenshotRequest.Builder(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER)
+ .setTopComponent(component)
+ .setTaskId(TASK_ID)
+ .setUserId(USER_ID)
+ .setBitmap(bitmap)
+ .setBoundsOnScreen(bounds)
+ .setInsets(Insets.NONE)
+ .build()
val processedRequest = processor.process(request)
@@ -168,10 +177,16 @@ class RequestProcessorTest {
policy.setManagedProfile(USER_ID, false)
val bitmap = makeHardwareBitmap(100, 100)
- val bitmapBundle = HardwareBitmapBundler.hardwareBitmapToBundle(bitmap)
- val request = ScreenshotRequest(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER,
- bitmapBundle, bounds, Insets.NONE, TASK_ID, USER_ID, component)
+ val request =
+ ScreenshotRequest.Builder(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER)
+ .setTopComponent(component)
+ .setTaskId(TASK_ID)
+ .setUserId(USER_ID)
+ .setBitmap(bitmap)
+ .setBoundsOnScreen(bounds)
+ .setInsets(Insets.NONE)
+ .build()
val processedRequest = processor.process(request)
@@ -190,10 +205,16 @@ class RequestProcessorTest {
policy.setManagedProfile(USER_ID, true)
val bitmap = makeHardwareBitmap(100, 100)
- val bitmapBundle = HardwareBitmapBundler.hardwareBitmapToBundle(bitmap)
- val request = ScreenshotRequest(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER,
- bitmapBundle, bounds, Insets.NONE, TASK_ID, USER_ID, component)
+ val request =
+ ScreenshotRequest.Builder(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER)
+ .setTopComponent(component)
+ .setTaskId(TASK_ID)
+ .setUserId(USER_ID)
+ .setBitmap(bitmap)
+ .setBoundsOnScreen(bounds)
+ .setInsets(Insets.NONE)
+ .build()
val processedRequest = processor.process(request)
@@ -202,14 +223,18 @@ class RequestProcessorTest {
}
private fun makeHardwareBitmap(width: Int, height: Int): Bitmap {
- val buffer = HardwareBuffer.create(width, height, HardwareBuffer.RGBA_8888, 1,
- HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE)
+ val buffer =
+ HardwareBuffer.create(
+ width,
+ height,
+ HardwareBuffer.RGBA_8888,
+ 1,
+ HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE
+ )
return Bitmap.wrapHardwareBuffer(buffer, ColorSpace.get(ColorSpace.Named.SRGB))!!
}
- private fun Bitmap.equalsHardwareBitmapBundle(bundle: Bundle): Boolean {
- val provided = bundleToHardwareBitmap(bundle)
- return provided.hardwareBuffer == this.hardwareBuffer &&
- provided.colorSpace == this.colorSpace
+ private fun Bitmap.equalsHardwareBitmap(bitmap: Bitmap): Boolean {
+ return bitmap.hardwareBuffer == this.hardwareBuffer && bitmap.colorSpace == this.colorSpace
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
index 99c79b0365ae..f93501928844 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
@@ -35,8 +35,7 @@ import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN
import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
import androidx.test.filters.SmallTest
import com.android.internal.logging.testing.UiEventLoggerFake
-import com.android.internal.util.ScreenshotHelper
-import com.android.internal.util.ScreenshotHelper.ScreenshotRequest
+import com.android.internal.util.ScreenshotRequest
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags.SCREENSHOT_WORK_PROFILE_POLICY
@@ -80,24 +79,39 @@ class TakeScreenshotServiceTest : SysuiTestCase() {
private val flags = FakeFeatureFlags()
private val topComponent = ComponentName(mContext, TakeScreenshotServiceTest::class.java)
- private val service = TakeScreenshotService(
- controller, userManager, devicePolicyManager, eventLogger,
- notificationsController, mContext, Runnable::run, flags, requestProcessor)
+ private val service =
+ TakeScreenshotService(
+ controller,
+ userManager,
+ devicePolicyManager,
+ eventLogger,
+ notificationsController,
+ mContext,
+ Runnable::run,
+ flags,
+ requestProcessor
+ )
@Before
fun setUp() {
whenever(devicePolicyManager.resources).thenReturn(devicePolicyResourcesManager)
- whenever(devicePolicyManager.getScreenCaptureDisabled(
- /* admin component (null: any admin) */ isNull(), eq(UserHandle.USER_ALL)))
+ whenever(
+ devicePolicyManager.getScreenCaptureDisabled(
+ /* admin component (null: any admin) */ isNull(),
+ eq(UserHandle.USER_ALL)
+ )
+ )
.thenReturn(false)
whenever(userManager.isUserUnlocked).thenReturn(true)
// Stub request processor as a synchronous no-op for tests with the flag enabled
doAnswer {
- val request: ScreenshotRequest = it.getArgument(0) as ScreenshotRequest
- val consumer: Consumer<ScreenshotRequest> = it.getArgument(1)
- consumer.accept(request)
- }.`when`(requestProcessor).processAsync(/* request= */ any(), /* callback= */ any())
+ val request: ScreenshotRequest = it.getArgument(0) as ScreenshotRequest
+ val consumer: Consumer<ScreenshotRequest> = it.getArgument(1)
+ consumer.accept(request)
+ }
+ .`when`(requestProcessor)
+ .processAsync(/* request= */ any(), /* callback= */ any())
// Flipped in selected test cases
flags.set(SCREENSHOT_WORK_PROFILE_POLICY, false)
@@ -108,7 +122,8 @@ class TakeScreenshotServiceTest : SysuiTestCase() {
/* className = */ null,
/* token = */ null,
application,
- /* activityManager = */ null)
+ /* activityManager = */ null
+ )
}
@Test
@@ -125,63 +140,89 @@ class TakeScreenshotServiceTest : SysuiTestCase() {
@Test
fun takeScreenshotFullscreen() {
- val request = ScreenshotRequest(
- TAKE_SCREENSHOT_FULLSCREEN,
- SCREENSHOT_KEY_CHORD,
- topComponent)
+ val request =
+ ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD)
+ .setTopComponent(topComponent)
+ .build()
- service.handleRequest(request, { /* onSaved */ }, callback)
+ service.handleRequest(request, { /* onSaved */}, callback)
- verify(controller, times(1)).takeScreenshotFullscreen(
- eq(topComponent),
- /* onSavedListener = */ any(),
- /* requestCallback = */ any())
+ verify(controller, times(1))
+ .takeScreenshotFullscreen(
+ eq(topComponent),
+ /* onSavedListener = */ any(),
+ /* requestCallback = */ any()
+ )
assertEquals("Expected one UiEvent", eventLogger.numLogs(), 1)
val logEvent = eventLogger.get(0)
- assertEquals("Expected SCREENSHOT_REQUESTED UiEvent",
- logEvent.eventId, SCREENSHOT_REQUESTED_KEY_CHORD.id)
- assertEquals("Expected supplied package name",
- topComponent.packageName, eventLogger.get(0).packageName)
+ assertEquals(
+ "Expected SCREENSHOT_REQUESTED UiEvent",
+ logEvent.eventId,
+ SCREENSHOT_REQUESTED_KEY_CHORD.id
+ )
+ assertEquals(
+ "Expected supplied package name",
+ topComponent.packageName,
+ eventLogger.get(0).packageName
+ )
}
@Test
fun takeScreenshotProvidedImage() {
val bounds = Rect(50, 50, 150, 150)
val bitmap = makeHardwareBitmap(100, 100)
- val bitmapBundle = ScreenshotHelper.HardwareBitmapBundler.hardwareBitmapToBundle(bitmap)
-
- val request = ScreenshotRequest(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OVERVIEW,
- bitmapBundle, bounds, Insets.NONE, TASK_ID, USER_ID, topComponent)
-
- service.handleRequest(request, { /* onSaved */ }, callback)
- verify(controller, times(1)).handleImageAsScreenshot(
- argThat { b -> b.equalsHardwareBitmap(bitmap) },
- eq(bounds),
- eq(Insets.NONE), eq(TASK_ID), eq(USER_ID), eq(topComponent),
- /* onSavedListener = */ any(), /* requestCallback = */ any())
+ val request =
+ ScreenshotRequest.Builder(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OVERVIEW)
+ .setTopComponent(topComponent)
+ .setTaskId(TASK_ID)
+ .setUserId(USER_ID)
+ .setBitmap(bitmap)
+ .setBoundsOnScreen(bounds)
+ .setInsets(Insets.NONE)
+ .build()
+
+ service.handleRequest(request, { /* onSaved */}, callback)
+
+ verify(controller, times(1))
+ .handleImageAsScreenshot(
+ argThat { b -> b.equalsHardwareBitmap(bitmap) },
+ eq(bounds),
+ eq(Insets.NONE),
+ eq(TASK_ID),
+ eq(USER_ID),
+ eq(topComponent),
+ /* onSavedListener = */ any(),
+ /* requestCallback = */ any()
+ )
assertEquals("Expected one UiEvent", eventLogger.numLogs(), 1)
val logEvent = eventLogger.get(0)
- assertEquals("Expected SCREENSHOT_REQUESTED_* UiEvent",
- logEvent.eventId, SCREENSHOT_REQUESTED_OVERVIEW.id)
- assertEquals("Expected supplied package name",
- topComponent.packageName, eventLogger.get(0).packageName)
+ assertEquals(
+ "Expected SCREENSHOT_REQUESTED_* UiEvent",
+ logEvent.eventId,
+ SCREENSHOT_REQUESTED_OVERVIEW.id
+ )
+ assertEquals(
+ "Expected supplied package name",
+ topComponent.packageName,
+ eventLogger.get(0).packageName
+ )
}
@Test
fun takeScreenshotFullscreen_userLocked() {
whenever(userManager.isUserUnlocked).thenReturn(false)
- val request = ScreenshotRequest(
- TAKE_SCREENSHOT_FULLSCREEN,
- SCREENSHOT_KEY_CHORD,
- topComponent)
+ val request =
+ ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD)
+ .setTopComponent(topComponent)
+ .build()
- service.handleRequest(request, { /* onSaved */ }, callback)
+ service.handleRequest(request, { /* onSaved */}, callback)
verify(notificationsController, times(1)).notifyScreenshotError(anyInt())
verify(callback, times(1)).reportError()
@@ -190,21 +231,24 @@ class TakeScreenshotServiceTest : SysuiTestCase() {
@Test
fun takeScreenshotFullscreen_screenCaptureDisabled_allUsers() {
- whenever(devicePolicyManager.getScreenCaptureDisabled(
- isNull(), eq(UserHandle.USER_ALL))
- ).thenReturn(true)
+ whenever(devicePolicyManager.getScreenCaptureDisabled(isNull(), eq(UserHandle.USER_ALL)))
+ .thenReturn(true)
- whenever(devicePolicyResourcesManager.getString(
- eq(SCREENSHOT_BLOCKED_BY_ADMIN),
- /* Supplier<String> */ any(),
- )).thenReturn("SCREENSHOT_BLOCKED_BY_ADMIN")
+ whenever(
+ devicePolicyResourcesManager.getString(
+ eq(SCREENSHOT_BLOCKED_BY_ADMIN),
+ /* Supplier<String> */
+ any(),
+ )
+ )
+ .thenReturn("SCREENSHOT_BLOCKED_BY_ADMIN")
- val request = ScreenshotRequest(
- TAKE_SCREENSHOT_FULLSCREEN,
- SCREENSHOT_KEY_CHORD,
- topComponent)
+ val request =
+ ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD)
+ .setTopComponent(topComponent)
+ .build()
- service.handleRequest(request, { /* onSaved */ }, callback)
+ service.handleRequest(request, { /* onSaved */}, callback)
// error shown: Toast.makeText(...).show(), untestable
verify(callback, times(1)).reportError()
@@ -214,14 +258,20 @@ class TakeScreenshotServiceTest : SysuiTestCase() {
private fun Bitmap.equalsHardwareBitmap(other: Bitmap): Boolean {
return config == HARDWARE &&
- other.config == HARDWARE &&
- hardwareBuffer == other.hardwareBuffer &&
- colorSpace == other.colorSpace
+ other.config == HARDWARE &&
+ hardwareBuffer == other.hardwareBuffer &&
+ colorSpace == other.colorSpace
}
/** A hardware Bitmap is mandated by use of ScreenshotHelper.HardwareBitmapBundler */
private fun makeHardwareBitmap(width: Int, height: Int): Bitmap {
- val buffer = HardwareBuffer.create(width, height, HardwareBuffer.RGBA_8888, 1,
- HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE)
+ val buffer =
+ HardwareBuffer.create(
+ width,
+ height,
+ HardwareBuffer.RGBA_8888,
+ 1,
+ HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE
+ )
return Bitmap.wrapHardwareBuffer(buffer, ColorSpace.get(ColorSpace.Named.SRGB))!!
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
index f802a5e09228..ed9baf5b1c9f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
@@ -109,11 +109,12 @@ class CombinedShadeHeaderConstraintsTest : SysuiTestCase() {
@Test
fun testEdgeElementsAlignedWithEdge_largeScreen() {
with(largeScreenConstraint) {
- assertThat(getConstraint(R.id.clock).layout.startToStart).isEqualTo(PARENT_ID)
- assertThat(getConstraint(R.id.clock).layout.horizontalBias).isEqualTo(0f)
+ assertThat(getConstraint(R.id.clock).layout.startToEnd).isEqualTo(R.id.begin_guide)
+ assertThat(getConstraint(R.id.clock).layout.horizontalBias).isEqualTo(0.5f)
- assertThat(getConstraint(R.id.privacy_container).layout.endToEnd).isEqualTo(PARENT_ID)
- assertThat(getConstraint(R.id.privacy_container).layout.horizontalBias).isEqualTo(1f)
+ assertThat(getConstraint(R.id.privacy_container).layout.endToStart)
+ .isEqualTo(R.id.end_guide)
+ assertThat(getConstraint(R.id.privacy_container).layout.horizontalBias).isEqualTo(0.5f)
}
}
@@ -219,7 +220,12 @@ class CombinedShadeHeaderConstraintsTest : SysuiTestCase() {
.isEqualTo(cutoutEnd - padding)
}
- assertThat(changes.largeScreenConstraintsChanges).isNull()
+ with(largeScreenConstraint) {
+ assertThat(getConstraint(R.id.begin_guide).layout.guideBegin)
+ .isEqualTo(cutoutStart - padding)
+ assertThat(getConstraint(R.id.end_guide).layout.guideEnd)
+ .isEqualTo(cutoutEnd - padding)
+ }
}
@Test
@@ -246,7 +252,10 @@ class CombinedShadeHeaderConstraintsTest : SysuiTestCase() {
assertThat(getConstraint(R.id.end_guide).layout.guideEnd).isEqualTo(0)
}
- assertThat(changes.largeScreenConstraintsChanges).isNull()
+ with(largeScreenConstraint) {
+ assertThat(getConstraint(R.id.begin_guide).layout.guideBegin).isEqualTo(0)
+ assertThat(getConstraint(R.id.end_guide).layout.guideEnd).isEqualTo(0)
+ }
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index 9d531a165f1f..4559a23a4f5c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -44,16 +44,23 @@ import static org.mockito.Mockito.when;
import android.app.Notification;
import android.app.NotificationChannel;
import android.graphics.Color;
+import android.graphics.drawable.AnimatedVectorDrawable;
+import android.graphics.drawable.AnimationDrawable;
+import android.graphics.drawable.Drawable;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
import android.util.DisplayMetrics;
import android.view.View;
+import android.widget.ImageView;
import androidx.test.filters.SmallTest;
import com.android.internal.R;
+import com.android.internal.widget.CachingIconView;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
@@ -61,6 +68,7 @@ import com.android.systemui.statusbar.notification.FeedbackIcon;
import com.android.systemui.statusbar.notification.SourceType;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableView.OnHeightChangedListener;
+import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer;
import org.junit.Assert;
@@ -72,6 +80,7 @@ import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
+import java.util.Arrays;
import java.util.List;
@SmallTest
@@ -96,6 +105,9 @@ public class ExpandableNotificationRowTest extends SysuiTestCase {
mDependency,
TestableLooper.get(this));
mNotificationTestHelper.setDefaultInflationFlags(FLAG_CONTENT_VIEW_ALL);
+ FakeFeatureFlags fakeFeatureFlags = new FakeFeatureFlags();
+ fakeFeatureFlags.set(Flags.NOTIFICATION_ANIMATE_BIG_PICTURE, true);
+ mNotificationTestHelper.setFeatureFlags(fakeFeatureFlags);
// create a standard private notification row
Notification normalNotif = mNotificationTestHelper.createNotification();
normalNotif.publicVersion = null;
@@ -559,4 +571,123 @@ public class ExpandableNotificationRowTest extends SysuiTestCase {
Assert.assertEquals(1f, mGroupRow.getBottomRoundness(), 0.001f);
Assert.assertEquals(1f, mGroupRow.getChildrenContainer().getBottomRoundness(), 0.001f);
}
+
+ @Test
+ public void testSetContentAnimationRunning_Run() throws Exception {
+ // Create views for the notification row.
+ NotificationContentView publicLayout = mock(NotificationContentView.class);
+ mNotifRow.setPublicLayout(publicLayout);
+ NotificationContentView privateLayout = mock(NotificationContentView.class);
+ mNotifRow.setPrivateLayout(privateLayout);
+
+ mNotifRow.setAnimationRunning(true);
+ verify(publicLayout, times(1)).setContentAnimationRunning(true);
+ verify(privateLayout, times(1)).setContentAnimationRunning(true);
+ }
+
+ @Test
+ public void testSetContentAnimationRunning_Stop() {
+ // Create views for the notification row.
+ NotificationContentView publicLayout = mock(NotificationContentView.class);
+ mNotifRow.setPublicLayout(publicLayout);
+ NotificationContentView privateLayout = mock(NotificationContentView.class);
+ mNotifRow.setPrivateLayout(privateLayout);
+
+ mNotifRow.setAnimationRunning(false);
+ verify(publicLayout, times(1)).setContentAnimationRunning(false);
+ verify(privateLayout, times(1)).setContentAnimationRunning(false);
+ }
+
+ @Test
+ public void testSetContentAnimationRunningInGroupChild_Run() {
+ // Creates parent views on mGroupRow.
+ NotificationContentView publicParentLayout = mock(NotificationContentView.class);
+ mGroupRow.setPublicLayout(publicParentLayout);
+ NotificationContentView privateParentLayout = mock(NotificationContentView.class);
+ mGroupRow.setPrivateLayout(privateParentLayout);
+
+ // Create child views on mNotifRow.
+ NotificationContentView publicChildLayout = mock(NotificationContentView.class);
+ mNotifRow.setPublicLayout(publicChildLayout);
+ NotificationContentView privateChildLayout = mock(NotificationContentView.class);
+ mNotifRow.setPrivateLayout(privateChildLayout);
+ when(mNotifRow.isGroupExpanded()).thenReturn(true);
+ setMockChildrenContainer(mGroupRow, mNotifRow);
+
+ mGroupRow.setAnimationRunning(true);
+ verify(publicParentLayout, times(1)).setContentAnimationRunning(true);
+ verify(privateParentLayout, times(1)).setContentAnimationRunning(true);
+ // The child layouts should be started too.
+ verify(publicChildLayout, times(1)).setContentAnimationRunning(true);
+ verify(privateChildLayout, times(1)).setContentAnimationRunning(true);
+ }
+
+
+ @Test
+ public void testSetIconAnimationRunningGroup_Run() {
+ // Create views for a group row.
+ NotificationContentView publicParentLayout = mock(NotificationContentView.class);
+ mGroupRow.setPublicLayout(publicParentLayout);
+ NotificationContentView privateParentLayout = mock(NotificationContentView.class);
+ mGroupRow.setPrivateLayout(privateParentLayout);
+ when(mGroupRow.isGroupExpanded()).thenReturn(true);
+
+ // Sets up mNotifRow as a child ExpandableNotificationRow.
+ NotificationContentView publicChildLayout = mock(NotificationContentView.class);
+ mNotifRow.setPublicLayout(publicChildLayout);
+ NotificationContentView privateChildLayout = mock(NotificationContentView.class);
+ mNotifRow.setPrivateLayout(privateChildLayout);
+ when(mNotifRow.isGroupExpanded()).thenReturn(true);
+
+ NotificationChildrenContainer mockContainer =
+ setMockChildrenContainer(mGroupRow, mNotifRow);
+
+ // Mock the children view wrappers, and give them each an icon.
+ NotificationViewWrapper mockViewWrapper = mock(NotificationViewWrapper.class);
+ when(mockContainer.getNotificationViewWrapper()).thenReturn(mockViewWrapper);
+ CachingIconView mockIcon = mock(CachingIconView.class);
+ when(mockViewWrapper.getIcon()).thenReturn(mockIcon);
+
+ NotificationViewWrapper mockLowPriorityViewWrapper = mock(NotificationViewWrapper.class);
+ when(mockContainer.getLowPriorityViewWrapper()).thenReturn(mockLowPriorityViewWrapper);
+ CachingIconView mockLowPriorityIcon = mock(CachingIconView.class);
+ when(mockLowPriorityViewWrapper.getIcon()).thenReturn(mockLowPriorityIcon);
+
+ // Give the icon image views drawables, so we can make sure they animate.
+ // We use both AnimationDrawables and AnimatedVectorDrawables to ensure both work.
+ AnimationDrawable drawable = mock(AnimationDrawable.class);
+ AnimatedVectorDrawable vectorDrawable = mock(AnimatedVectorDrawable.class);
+ setDrawableIconsInImageView(mockIcon, drawable, vectorDrawable);
+
+ AnimationDrawable lowPriDrawable = mock(AnimationDrawable.class);
+ AnimatedVectorDrawable lowPriVectorDrawable = mock(AnimatedVectorDrawable.class);
+ setDrawableIconsInImageView(mockLowPriorityIcon, lowPriDrawable, lowPriVectorDrawable);
+
+ mGroupRow.setAnimationRunning(true);
+ verify(drawable, times(1)).start();
+ verify(vectorDrawable, times(1)).start();
+ verify(lowPriDrawable, times(1)).start();
+ verify(lowPriVectorDrawable, times(1)).start();
+ }
+
+ private void setDrawableIconsInImageView(CachingIconView icon, Drawable iconDrawable,
+ Drawable rightIconDrawable) {
+ ImageView iconView = mock(ImageView.class);
+ when(icon.findViewById(com.android.internal.R.id.icon)).thenReturn(iconView);
+ when(iconView.getDrawable()).thenReturn(iconDrawable);
+
+ ImageView rightIconView = mock(ImageView.class);
+ when(icon.findViewById(com.android.internal.R.id.right_icon)).thenReturn(rightIconView);
+ when(rightIconView.getDrawable()).thenReturn(rightIconDrawable);
+ }
+
+ private NotificationChildrenContainer setMockChildrenContainer(
+ ExpandableNotificationRow parentRow, ExpandableNotificationRow childRow) {
+ List<ExpandableNotificationRow> rowList = Arrays.asList(childRow);
+ NotificationChildrenContainer mockContainer = mock(NotificationChildrenContainer.class);
+ when(mockContainer.getNotificationChildCount()).thenReturn(1);
+ when(mockContainer.getAttachedChildren()).thenReturn(rowList);
+ parentRow.setChildrenContainer(mockContainer);
+ return mockContainer;
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FooterViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FooterViewTest.java
index 1f92b0a42061..819a75bffc8e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FooterViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FooterViewTest.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.notification.row;
+import static com.google.common.truth.Truth.assertThat;
+
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue;
@@ -98,5 +100,16 @@ public class FooterViewTest extends SysuiTestCase {
mView.setSecondaryVisible(true /* visible */, true /* animate */);
}
+
+ @Test
+ public void testSetFooterLabelTextAndIcon() {
+ mView.setFooterLabelTextAndIcon(
+ R.string.unlock_to_see_notif_text,
+ R.drawable.ic_friction_lock_closed);
+ assertThat(mView.findViewById(R.id.manage_text).getVisibility()).isEqualTo(View.GONE);
+ assertThat(mView.findSecondaryView().getVisibility()).isEqualTo(View.GONE);
+ assertThat(mView.findViewById(R.id.unlock_prompt_footer).getVisibility())
+ .isEqualTo(View.VISIBLE);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
index 562b4dfb35ef..7b2051da4d15 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
@@ -34,9 +34,12 @@ import com.android.systemui.media.dialog.MediaOutputDialogFactory
import com.android.systemui.statusbar.notification.FeedbackIcon
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
+import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import junit.framework.Assert.assertEquals
+import junit.framework.Assert.assertFalse
+import junit.framework.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -44,6 +47,7 @@ import org.mockito.Mock
import org.mockito.Mockito.doReturn
import org.mockito.Mockito.never
import org.mockito.Mockito.spy
+import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations.initMocks
@@ -305,6 +309,86 @@ class NotificationContentViewTest : SysuiTestCase() {
assertEquals(0, getMarginBottom(actionListMarginTarget))
}
+ @Test
+ fun onSetAnimationRunning() {
+ // Given: contractedWrapper, enpandedWrapper, and headsUpWrapper being set
+ val mockContracted = mock<NotificationViewWrapper>()
+ val mockExpanded = mock<NotificationViewWrapper>()
+ val mockHeadsUp = mock<NotificationViewWrapper>()
+
+ view.setContractedWrapper(mockContracted)
+ view.setExpandedWrapper(mockExpanded)
+ view.setHeadsUpWrapper(mockHeadsUp)
+
+ // When: we set content animation running.
+ assertTrue(view.setContentAnimationRunning(true))
+
+ // Then: contractedChild, expandedChild, and headsUpChild should have setAnimationsRunning
+ // called on them.
+ verify(mockContracted, times(1)).setAnimationsRunning(true)
+ verify(mockExpanded, times(1)).setAnimationsRunning(true)
+ verify(mockHeadsUp, times(1)).setAnimationsRunning(true)
+
+ // When: we set content animation running true _again_.
+ assertFalse(view.setContentAnimationRunning(true))
+
+ // Then: the children should not have setAnimationRunning called on them again.
+ // Verify counts number of calls so far on the object, so these still register as 1.
+ verify(mockContracted, times(1)).setAnimationsRunning(true)
+ verify(mockExpanded, times(1)).setAnimationsRunning(true)
+ verify(mockHeadsUp, times(1)).setAnimationsRunning(true)
+ }
+
+ @Test
+ fun onSetAnimationStopped() {
+ // Given: contractedWrapper, expandedWrapper, and headsUpWrapper being set
+ val mockContracted = mock<NotificationViewWrapper>()
+ val mockExpanded = mock<NotificationViewWrapper>()
+ val mockHeadsUp = mock<NotificationViewWrapper>()
+
+ view.setContractedWrapper(mockContracted)
+ view.setExpandedWrapper(mockExpanded)
+ view.setHeadsUpWrapper(mockHeadsUp)
+
+ // When: we set content animation running.
+ assertTrue(view.setContentAnimationRunning(true))
+
+ // Then: contractedChild, expandedChild, and headsUpChild should have setAnimationsRunning
+ // called on them.
+ verify(mockContracted).setAnimationsRunning(true)
+ verify(mockExpanded).setAnimationsRunning(true)
+ verify(mockHeadsUp).setAnimationsRunning(true)
+
+ // When: we set content animation running false, the state changes, so the function
+ // returns true.
+ assertTrue(view.setContentAnimationRunning(false))
+
+ // Then: the children have their animations stopped.
+ verify(mockContracted).setAnimationsRunning(false)
+ verify(mockExpanded).setAnimationsRunning(false)
+ verify(mockHeadsUp).setAnimationsRunning(false)
+ }
+
+ @Test
+ fun onSetAnimationInitStopped() {
+ // Given: contractedWrapper, expandedWrapper, and headsUpWrapper being set
+ val mockContracted = mock<NotificationViewWrapper>()
+ val mockExpanded = mock<NotificationViewWrapper>()
+ val mockHeadsUp = mock<NotificationViewWrapper>()
+
+ view.setContractedWrapper(mockContracted)
+ view.setExpandedWrapper(mockExpanded)
+ view.setHeadsUpWrapper(mockHeadsUp)
+
+ // When: we try to stop the animations before they've been started.
+ assertFalse(view.setContentAnimationRunning(false))
+
+ // Then: the children should not have setAnimationRunning called on them again.
+ verify(mockContracted, never()).setAnimationsRunning(false)
+ verify(mockExpanded, never()).setAnimationsRunning(false)
+ verify(mockHeadsUp, never()).setAnimationsRunning(false)
+ }
+
private fun createMockContainingNotification(notificationEntry: NotificationEntry) =
mock<ExpandableNotificationRow>().apply {
whenever(this.entry).thenReturn(notificationEntry)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index 59d4720fdb6d..e6f6a8d696b8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -53,6 +53,7 @@ import com.android.internal.logging.UiEventLogger;
import com.android.systemui.TestableDependency;
import com.android.systemui.classifier.FalsingCollectorFake;
import com.android.systemui.classifier.FalsingManagerFake;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.media.controls.util.MediaFeatureFlag;
import com.android.systemui.media.dialog.MediaOutputDialogFactory;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -130,6 +131,7 @@ public class NotificationTestHelper {
public final OnUserInteractionCallback mOnUserInteractionCallback;
public final Runnable mFutureDismissalRunnable;
private @InflationFlag int mDefaultInflationFlags;
+ private FeatureFlags mFeatureFlags;
public NotificationTestHelper(
Context context,
@@ -191,12 +193,17 @@ public class NotificationTestHelper {
mFutureDismissalRunnable = mock(Runnable.class);
when(mOnUserInteractionCallback.registerFutureDismissal(any(), anyInt()))
.thenReturn(mFutureDismissalRunnable);
+ mFeatureFlags = mock(FeatureFlags.class);
}
public void setDefaultInflationFlags(@InflationFlag int defaultInflationFlags) {
mDefaultInflationFlags = defaultInflationFlags;
}
+ public void setFeatureFlags(FeatureFlags featureFlags) {
+ mFeatureFlags = featureFlags;
+ }
+
public ExpandableNotificationRowLogger getMockLogger() {
return mMockLogger;
}
@@ -559,7 +566,8 @@ public class NotificationTestHelper {
mock(NotificationGutsManager.class),
mock(MetricsLogger.class),
mock(SmartReplyConstants.class),
- mock(SmartReplyController.class));
+ mock(SmartReplyController.class),
+ mFeatureFlags);
row.setAboveShelfChangedListener(aboveShelf -> { });
mBindStage.getStageParams(entry).requireContentViews(extraInflationFlags);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapperTest.java
index 509ba4194e5e..8f88501a38f7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapperTest.java
@@ -16,11 +16,12 @@
package com.android.systemui.statusbar.notification.row.wrapper;
+import static org.junit.Assert.assertNotNull;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.verify;
import android.app.Notification;
-import android.content.Context;
+import android.graphics.drawable.AnimatedImageDrawable;
import android.graphics.drawable.Icon;
import android.os.Bundle;
import android.testing.AndroidTestingRunner;
@@ -28,11 +29,11 @@ import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
import android.view.LayoutInflater;
import android.view.View;
-import android.widget.LinearLayout;
-import android.widget.TextView;
import androidx.test.filters.SmallTest;
+import com.android.internal.R;
+import com.android.internal.widget.BigPictureNotificationImageView;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
@@ -73,4 +74,38 @@ public class NotificationBigPictureTemplateViewWrapperTest extends SysuiTestCase
Notification.EXTRA_LARGE_ICON_BIG, new Bundle());
wrapper.onContentUpdated(mRow);
}
+
+ @Test
+ public void setAnimationsRunning_Run() {
+ BigPictureNotificationImageView imageView = mView.findViewById(R.id.big_picture);
+ AnimatedImageDrawable mockDrawable = mock(AnimatedImageDrawable.class);
+
+ assertNotNull(imageView);
+ imageView.setImageDrawable(mockDrawable);
+
+ NotificationViewWrapper wrapper = new NotificationBigPictureTemplateViewWrapper(mContext,
+ mView, mRow);
+ // Required to re-initialize the imageView to the imageView created above.
+ wrapper.onContentUpdated(mRow);
+
+ wrapper.setAnimationsRunning(true);
+ verify(mockDrawable).start();
+ }
+
+ @Test
+ public void setAnimationsRunning_Stop() {
+ BigPictureNotificationImageView imageView = mView.findViewById(R.id.big_picture);
+ AnimatedImageDrawable mockDrawable = mock(AnimatedImageDrawable.class);
+
+ assertNotNull(imageView);
+ imageView.setImageDrawable(mockDrawable);
+
+ NotificationViewWrapper wrapper = new NotificationBigPictureTemplateViewWrapper(mContext,
+ mView, mRow);
+ // Required to re-initialize the imageView to the imageView created above.
+ wrapper.onContentUpdated(mRow);
+
+ wrapper.setAnimationsRunning(false);
+ verify(mockDrawable).stop();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapperTest.kt
new file mode 100644
index 000000000000..3fa68bb69da2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapperTest.kt
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row.wrapper
+
+import android.graphics.drawable.AnimatedImageDrawable
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.testing.TestableLooper.RunWithLooper
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.internal.R
+import com.android.internal.widget.CachingIconView
+import com.android.internal.widget.ConversationLayout
+import com.android.internal.widget.MessagingGroup
+import com.android.internal.widget.MessagingImageMessage
+import com.android.internal.widget.MessagingLinearLayout
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+import com.android.systemui.statusbar.notification.row.NotificationTestHelper
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class NotificationConversationTemplateViewWrapperTest : SysuiTestCase() {
+
+ private lateinit var mRow: ExpandableNotificationRow
+ private lateinit var helper: NotificationTestHelper
+
+ @Before
+ fun setUp() {
+ allowTestableLooperAsMainThread()
+ helper = NotificationTestHelper(mContext, mDependency, TestableLooper.get(this))
+ mRow = helper.createRow()
+ }
+
+ @Test
+ fun setAnimationsRunning_Run() {
+ // Creates a mocked out NotificationEntry of ConversationLayout type,
+ // with a mock imageMessage.drawable embedded in its MessagingImageMessages
+ // (both top level, and in a group).
+ val mockDrawable = mock<AnimatedImageDrawable>()
+ val mockDrawable2 = mock<AnimatedImageDrawable>()
+ val mockLayoutView: View = fakeConversationLayout(mockDrawable, mockDrawable2)
+
+ val wrapper: NotificationViewWrapper =
+ NotificationConversationTemplateViewWrapper(mContext, mockLayoutView, mRow)
+ wrapper.onContentUpdated(mRow)
+ wrapper.setAnimationsRunning(true)
+
+ // Verifies that each AnimatedImageDrawable is started animating.
+ verify(mockDrawable).start()
+ verify(mockDrawable2).start()
+ }
+
+ @Test
+ fun setAnimationsRunning_Stop() {
+ // Creates a mocked out NotificationEntry of ConversationLayout type,
+ // with a mock imageMessage.drawable embedded in its MessagingImageMessages
+ // (both top level, and in a group).
+ val mockDrawable = mock<AnimatedImageDrawable>()
+ val mockDrawable2 = mock<AnimatedImageDrawable>()
+ val mockLayoutView: View = fakeConversationLayout(mockDrawable, mockDrawable2)
+
+ val wrapper: NotificationViewWrapper =
+ NotificationConversationTemplateViewWrapper(mContext, mockLayoutView, mRow)
+ wrapper.onContentUpdated(mRow)
+ wrapper.setAnimationsRunning(false)
+
+ // Verifies that each AnimatedImageDrawable is started animating.
+ verify(mockDrawable).stop()
+ verify(mockDrawable2).stop()
+ }
+
+ private fun fakeConversationLayout(
+ mockDrawableGroupMessage: AnimatedImageDrawable,
+ mockDrawableImageMessage: AnimatedImageDrawable
+ ): View {
+ val mockMessagingImageMessage: MessagingImageMessage =
+ mock<MessagingImageMessage>().apply {
+ whenever(drawable).thenReturn(mockDrawableImageMessage)
+ }
+ val mockImageMessageContainer: MessagingLinearLayout =
+ mock<MessagingLinearLayout>().apply {
+ whenever(childCount).thenReturn(1)
+ whenever(getChildAt(any())).thenReturn(mockMessagingImageMessage)
+ }
+
+ val mockMessagingImageMessageForGroup: MessagingImageMessage =
+ mock<MessagingImageMessage>().apply {
+ whenever(drawable).thenReturn(mockDrawableGroupMessage)
+ }
+ val mockMessageContainer: MessagingLinearLayout =
+ mock<MessagingLinearLayout>().apply {
+ whenever(childCount).thenReturn(1)
+ whenever(getChildAt(any())).thenReturn(mockMessagingImageMessageForGroup)
+ }
+ val mockGroup: MessagingGroup =
+ mock<MessagingGroup>().apply {
+ whenever(messageContainer).thenReturn(mockMessageContainer)
+ }
+ val mockView: View =
+ mock<ConversationLayout>().apply {
+ whenever(messagingGroups).thenReturn(ArrayList<MessagingGroup>(listOf(mockGroup)))
+ whenever(imageMessageContainer).thenReturn(mockImageMessageContainer)
+ whenever(messagingLinearLayout).thenReturn(mockMessageContainer)
+
+ // These must be mocked as they're required to be nonnull.
+ whenever(requireViewById<View>(R.id.conversation_icon_container)).thenReturn(mock())
+ whenever(requireViewById<CachingIconView>(R.id.conversation_icon))
+ .thenReturn(mock())
+ whenever(findViewById<CachingIconView>(R.id.icon)).thenReturn(mock())
+ whenever(requireViewById<View>(R.id.conversation_icon_badge_bg)).thenReturn(mock())
+ whenever(requireViewById<View>(R.id.expand_button)).thenReturn(mock())
+ whenever(requireViewById<View>(R.id.expand_button_container)).thenReturn(mock())
+ whenever(requireViewById<View>(R.id.conversation_icon_badge_ring))
+ .thenReturn(mock())
+ whenever(requireViewById<View>(R.id.app_name_text)).thenReturn(mock())
+ whenever(requireViewById<View>(R.id.conversation_text)).thenReturn(mock())
+ }
+ return mockView
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapperTest.kt
new file mode 100644
index 000000000000..c0444b563a2c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapperTest.kt
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row.wrapper
+
+import android.graphics.drawable.AnimatedImageDrawable
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.testing.TestableLooper.RunWithLooper
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.internal.widget.MessagingGroup
+import com.android.internal.widget.MessagingImageMessage
+import com.android.internal.widget.MessagingLayout
+import com.android.internal.widget.MessagingLinearLayout
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+import com.android.systemui.statusbar.notification.row.NotificationTestHelper
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class NotificationMessagingTemplateViewWrapperTest : SysuiTestCase() {
+
+ private lateinit var mRow: ExpandableNotificationRow
+ private lateinit var helper: NotificationTestHelper
+
+ @Before
+ fun setUp() {
+ allowTestableLooperAsMainThread()
+ helper = NotificationTestHelper(mContext, mDependency, TestableLooper.get(this))
+ mRow = helper.createRow()
+ }
+
+ @Test
+ fun setAnimationsRunning_Run() {
+ // Creates a mocked out NotificationEntry of MessagingLayout/MessagingStyle type,
+ // with a mock imageMessage.drawable embedded in its MessagingImageMessage.
+ val mockDrawable = mock<AnimatedImageDrawable>()
+ val mockLayoutView: View = fakeMessagingLayout(mockDrawable)
+
+ val wrapper: NotificationViewWrapper =
+ NotificationMessagingTemplateViewWrapper(mContext, mockLayoutView, mRow)
+ wrapper.setAnimationsRunning(true)
+
+ // Verifies that each AnimatedImageDrawable is started animating.
+ verify(mockDrawable).start()
+ }
+
+ @Test
+ fun setAnimationsRunning_Stop() {
+ // Creates a mocked out NotificationEntry of MessagingLayout/MessagingStyle type,
+ // with a mock imageMessage.drawable embedded in its MessagingImageMessage.
+ val mockDrawable = mock<AnimatedImageDrawable>()
+ val mockLayoutView: View = fakeMessagingLayout(mockDrawable)
+
+ val wrapper: NotificationViewWrapper =
+ NotificationMessagingTemplateViewWrapper(mContext, mockLayoutView, mRow)
+ wrapper.setAnimationsRunning(false)
+
+ // Verifies that each AnimatedImageDrawable is started animating.
+ verify(mockDrawable).stop()
+ }
+
+ private fun fakeMessagingLayout(mockDrawable: AnimatedImageDrawable): View {
+ val mockMessagingImageMessage: MessagingImageMessage =
+ mock<MessagingImageMessage>().apply { whenever(drawable).thenReturn(mockDrawable) }
+ val mockMessageContainer: MessagingLinearLayout =
+ mock<MessagingLinearLayout>().apply {
+ whenever(childCount).thenReturn(1)
+ whenever(getChildAt(any())).thenReturn(mockMessagingImageMessage)
+ }
+ val mockGroup: MessagingGroup =
+ mock<MessagingGroup>().apply {
+ whenever(messageContainer).thenReturn(mockMessageContainer)
+ }
+ val mockView: View =
+ mock<MessagingLayout>().apply {
+ whenever(messagingGroups).thenReturn(ArrayList<MessagingGroup>(listOf(mockGroup)))
+ }
+ return mockView
+ }
+}
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 645052feee94..3d3b91d807c1 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
@@ -21,6 +21,7 @@ import static com.android.systemui.statusbar.StatusBarState.SHADE;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
@@ -65,6 +66,7 @@ import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.provider.SeenNotificationsProviderImpl;
import com.android.systemui.statusbar.notification.collection.provider.VisibilityLocationProviderDelegator;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
+import com.android.systemui.statusbar.notification.collection.render.NotifStats;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -139,6 +141,9 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
@Captor
private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerArgumentCaptor;
+ private final SeenNotificationsProviderImpl mSeenNotificationsProvider =
+ new SeenNotificationsProviderImpl();
+
private NotificationStackScrollLayoutController mController;
@Before
@@ -180,7 +185,7 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
mUiEventLogger,
mRemoteInputManager,
mVisibilityLocationProviderDelegator,
- new SeenNotificationsProviderImpl(),
+ mSeenNotificationsProvider,
mShadeController,
mJankMonitor,
mStackLogger,
@@ -233,16 +238,14 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
mController.updateShowEmptyShadeView();
verify(mNotificationStackScrollLayout).updateEmptyShadeView(
/* visible= */ true,
- /* notifVisibleInShade= */ true,
- /* areSeenNotifsFiltered= */false);
+ /* notifVisibleInShade= */ true);
setupShowEmptyShadeViewState(false);
reset(mNotificationStackScrollLayout);
mController.updateShowEmptyShadeView();
verify(mNotificationStackScrollLayout).updateEmptyShadeView(
/* visible= */ false,
- /* notifVisibleInShade= */ true,
- /* areSeenNotifsFiltered= */false);
+ /* notifVisibleInShade= */ true);
}
@Test
@@ -255,16 +258,14 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
mController.updateShowEmptyShadeView();
verify(mNotificationStackScrollLayout).updateEmptyShadeView(
/* visible= */ true,
- /* notifVisibleInShade= */ false,
- /* areSeenNotifsFiltered= */false);
+ /* notifVisibleInShade= */ false);
setupShowEmptyShadeViewState(false);
reset(mNotificationStackScrollLayout);
mController.updateShowEmptyShadeView();
verify(mNotificationStackScrollLayout).updateEmptyShadeView(
/* visible= */ false,
- /* notifVisibleInShade= */ false,
- /* areSeenNotifsFiltered= */false);
+ /* notifVisibleInShade= */ false);
}
@Test
@@ -283,16 +284,14 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
mController.updateShowEmptyShadeView();
verify(mNotificationStackScrollLayout).updateEmptyShadeView(
/* visible= */ true,
- /* notifVisibleInShade= */ false,
- /* areSeenNotifsFiltered= */false);
+ /* notifVisibleInShade= */ false);
mController.setQsFullScreen(true);
reset(mNotificationStackScrollLayout);
mController.updateShowEmptyShadeView();
verify(mNotificationStackScrollLayout).updateEmptyShadeView(
/* visible= */ true,
- /* notifVisibleInShade= */ false,
- /* areSeenNotifsFiltered= */false);
+ /* notifVisibleInShade= */ false);
}
@Test
@@ -400,6 +399,17 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
verify(mNotificationStackScrollLayout).setIsRemoteInputActive(true);
}
+ @Test
+ public void testSetNotifStats_updatesHasFilteredOutSeenNotifications() {
+ when(mNotifPipelineFlags.getShouldFilterUnseenNotifsOnKeyguard()).thenReturn(true);
+ mSeenNotificationsProvider.setHasFilteredOutSeenNotifications(true);
+ mController.attach(mNotificationStackScrollLayout);
+ mController.getNotifStackController().setNotifStats(NotifStats.getEmpty());
+ verify(mNotificationStackScrollLayout).setHasFilteredOutSeenNotifications(true);
+ verify(mNotificationStackScrollLayout).updateFooter();
+ verify(mNotificationStackScrollLayout).updateEmptyShadeView(anyBoolean(), anyBoolean());
+ }
+
private LogMaker logMatcher(int category, int type) {
return argThat(new LogMatcher(category, type));
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 7622549d29de..dd7143ae7e16 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -30,6 +30,7 @@ import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertFalse;
+import static org.mockito.AdditionalMatchers.not;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyFloat;
@@ -53,6 +54,7 @@ import android.util.MathUtils;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.TextView;
import androidx.test.annotation.UiThreadTest;
import androidx.test.filters.SmallTest;
@@ -328,7 +330,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
public void updateEmptyView_dndSuppressing() {
when(mEmptyShadeView.willBeGone()).thenReturn(true);
- mStackScroller.updateEmptyShadeView(true, true, false);
+ mStackScroller.updateEmptyShadeView(true, true);
verify(mEmptyShadeView).setText(R.string.dnd_suppressing_shade_text);
}
@@ -338,7 +340,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
mStackScroller.setEmptyShadeView(mEmptyShadeView);
when(mEmptyShadeView.willBeGone()).thenReturn(true);
- mStackScroller.updateEmptyShadeView(true, false, false);
+ mStackScroller.updateEmptyShadeView(true, false);
verify(mEmptyShadeView).setText(R.string.empty_shade_text);
}
@@ -347,10 +349,10 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
public void updateEmptyView_noNotificationsToDndSuppressing() {
mStackScroller.setEmptyShadeView(mEmptyShadeView);
when(mEmptyShadeView.willBeGone()).thenReturn(true);
- mStackScroller.updateEmptyShadeView(true, false, false);
+ mStackScroller.updateEmptyShadeView(true, false);
verify(mEmptyShadeView).setText(R.string.empty_shade_text);
- mStackScroller.updateEmptyShadeView(true, true, false);
+ mStackScroller.updateEmptyShadeView(true, true);
verify(mEmptyShadeView).setText(R.string.dnd_suppressing_shade_text);
}
@@ -818,6 +820,29 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
assertEquals(0f, mAmbientState.getStackY());
}
+ @Test
+ public void hasFilteredOutSeenNotifs_updateFooter() {
+ mStackScroller.setCurrentUserSetup(true);
+
+ // add footer
+ mStackScroller.inflateFooterView();
+ TextView footerLabel =
+ mStackScroller.mFooterView.requireViewById(R.id.unlock_prompt_footer);
+
+ mStackScroller.setHasFilteredOutSeenNotifications(true);
+ mStackScroller.updateFooter();
+
+ assertThat(footerLabel.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ @Test
+ public void hasFilteredOutSeenNotifs_updateEmptyShadeView() {
+ mStackScroller.setHasFilteredOutSeenNotifications(true);
+ mStackScroller.updateEmptyShadeView(true, false);
+
+ verify(mEmptyShadeView).setFooterText(not(0));
+ }
+
private void setBarStateForTest(int state) {
// Can't inject this through the listener or we end up on the actual implementation
// rather than the mock because the spy just coppied the anonymous inner /shruggie.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
index 2a93ffff87a5..d53e09d19048 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -790,15 +790,15 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
reset(mResources);
when(mResources.getColor(eq(android.R.color.system_accent1_500), any()))
- .thenReturn(mThemeOverlayController.mColorScheme.getAccent1().get(6));
+ .thenReturn(mThemeOverlayController.mColorScheme.getAccent1().getS500());
when(mResources.getColor(eq(android.R.color.system_accent2_500), any()))
- .thenReturn(mThemeOverlayController.mColorScheme.getAccent2().get(6));
+ .thenReturn(mThemeOverlayController.mColorScheme.getAccent2().getS500());
when(mResources.getColor(eq(android.R.color.system_accent3_500), any()))
- .thenReturn(mThemeOverlayController.mColorScheme.getAccent3().get(6));
+ .thenReturn(mThemeOverlayController.mColorScheme.getAccent3().getS500());
when(mResources.getColor(eq(android.R.color.system_neutral1_500), any()))
- .thenReturn(mThemeOverlayController.mColorScheme.getNeutral1().get(6));
+ .thenReturn(mThemeOverlayController.mColorScheme.getNeutral1().getS500());
when(mResources.getColor(eq(android.R.color.system_neutral2_500), any()))
- .thenReturn(mThemeOverlayController.mColorScheme.getNeutral2().get(6));
+ .thenReturn(mThemeOverlayController.mColorScheme.getNeutral2().getS500());
// Defers event because we already have initial colors.
verify(mThemeOverlayApplier, never())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsControllerTest.kt
new file mode 100644
index 000000000000..b52786178e71
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsControllerTest.kt
@@ -0,0 +1,243 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.wallet.controller
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.service.quickaccesswallet.GetWalletCardsResponse
+import android.service.quickaccesswallet.QuickAccessWalletClient
+import android.service.quickaccesswallet.WalletCard
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.capture
+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 java.util.ArrayList
+import kotlinx.coroutines.CoroutineScope
+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.junit.runners.JUnit4
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.isNull
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class WalletContextualSuggestionsControllerTest : SysuiTestCase() {
+
+ @Mock private lateinit var walletController: QuickAccessWalletController
+ @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
+ @Mock private lateinit var featureFlags: FeatureFlags
+ @Mock private lateinit var mockContext: Context
+ @Captor private lateinit var broadcastReceiver: ArgumentCaptor<BroadcastReceiver>
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ whenever(
+ broadcastDispatcher.broadcastFlow<List<String>?>(
+ any(),
+ isNull(),
+ any(),
+ any(),
+ any()
+ )
+ )
+ .thenCallRealMethod()
+
+ whenever(featureFlags.isEnabled(eq(Flags.ENABLE_WALLET_CONTEXTUAL_LOYALTY_CARDS)))
+ .thenReturn(true)
+
+ whenever(CARD_1.cardId).thenReturn(ID_1)
+ whenever(CARD_2.cardId).thenReturn(ID_2)
+ whenever(CARD_3.cardId).thenReturn(ID_3)
+ }
+
+ @Test
+ fun `state - has wallet cards - received contextual cards`() = runTest {
+ setUpWalletClient(listOf(CARD_1, CARD_2))
+ val latest =
+ collectLastValue(
+ createWalletContextualSuggestionsController(backgroundScope)
+ .contextualSuggestionCards,
+ )
+
+ runCurrent()
+ verifyRegistered()
+ broadcastReceiver.value.onReceive(
+ mockContext,
+ createContextualCardsIntent(listOf(ID_1, ID_2))
+ )
+
+ assertThat(latest()).containsExactly(CARD_1, CARD_2)
+ }
+
+ @Test
+ fun `state - no wallet cards - received contextual cards`() = runTest {
+ setUpWalletClient(emptyList())
+ val latest =
+ collectLastValue(
+ createWalletContextualSuggestionsController(backgroundScope)
+ .contextualSuggestionCards,
+ )
+
+ runCurrent()
+ verifyRegistered()
+ broadcastReceiver.value.onReceive(
+ mockContext,
+ createContextualCardsIntent(listOf(ID_1, ID_2))
+ )
+
+ assertThat(latest()).isEmpty()
+ }
+
+ @Test
+ fun `state - has wallet cards - no contextual cards`() = runTest {
+ setUpWalletClient(listOf(CARD_1, CARD_2))
+ val latest =
+ collectLastValue(
+ createWalletContextualSuggestionsController(backgroundScope)
+ .contextualSuggestionCards,
+ )
+
+ runCurrent()
+ verifyRegistered()
+ broadcastReceiver.value.onReceive(mockContext, createContextualCardsIntent(emptyList()))
+
+ assertThat(latest()).isEmpty()
+ }
+
+ @Test
+ fun `state - wallet cards error`() = runTest {
+ setUpWalletClient(shouldFail = true)
+ val latest =
+ collectLastValue(
+ createWalletContextualSuggestionsController(backgroundScope)
+ .contextualSuggestionCards,
+ )
+
+ runCurrent()
+ verifyRegistered()
+ broadcastReceiver.value.onReceive(
+ mockContext,
+ createContextualCardsIntent(listOf(ID_1, ID_2))
+ )
+
+ assertThat(latest()).isEmpty()
+ }
+
+ @Test
+ fun `state - no contextual cards extra`() = runTest {
+ setUpWalletClient(listOf(CARD_1, CARD_2))
+ val latest =
+ collectLastValue(
+ createWalletContextualSuggestionsController(backgroundScope)
+ .contextualSuggestionCards,
+ )
+
+ runCurrent()
+ verifyRegistered()
+ broadcastReceiver.value.onReceive(mockContext, Intent(INTENT_NAME))
+
+ assertThat(latest()).isEmpty()
+ }
+
+ @Test
+ fun `state - has wallet cards - received contextual cards - feature disabled`() = runTest {
+ whenever(featureFlags.isEnabled(eq(Flags.ENABLE_WALLET_CONTEXTUAL_LOYALTY_CARDS)))
+ .thenReturn(false)
+ setUpWalletClient(listOf(CARD_1, CARD_2))
+ val latest =
+ collectLastValue(
+ createWalletContextualSuggestionsController(backgroundScope)
+ .contextualSuggestionCards,
+ )
+
+ runCurrent()
+ verify(broadcastDispatcher, never()).broadcastFlow(any(), isNull(), any(), any())
+ assertThat(latest()).isNull()
+ }
+
+ private fun createWalletContextualSuggestionsController(
+ scope: CoroutineScope
+ ): WalletContextualSuggestionsController {
+ return WalletContextualSuggestionsController(
+ scope,
+ walletController,
+ broadcastDispatcher,
+ featureFlags
+ )
+ }
+
+ private fun verifyRegistered() {
+ verify(broadcastDispatcher)
+ .registerReceiver(capture(broadcastReceiver), any(), isNull(), isNull(), any(), any())
+ }
+
+ private fun createContextualCardsIntent(
+ ids: List<String> = emptyList(),
+ ): Intent {
+ val intent = Intent(INTENT_NAME)
+ intent.putStringArrayListExtra("cardIds", ArrayList(ids))
+ return intent
+ }
+
+ private fun setUpWalletClient(
+ cards: List<WalletCard> = emptyList(),
+ shouldFail: Boolean = false
+ ) {
+ whenever(walletController.queryWalletCards(any())).thenAnswer { invocation ->
+ with(
+ invocation.arguments[0] as QuickAccessWalletClient.OnWalletCardsRetrievedCallback
+ ) {
+ if (shouldFail) {
+ onWalletCardRetrievalError(mock())
+ } else {
+ onWalletCardsRetrieved(GetWalletCardsResponse(cards, 0))
+ }
+ }
+ }
+ }
+
+ companion object {
+ private const val ID_1: String = "123"
+ private val CARD_1: WalletCard = mock()
+ private const val ID_2: String = "456"
+ private val CARD_2: WalletCard = mock()
+ private const val ID_3: String = "789"
+ private val CARD_3: WalletCard = mock()
+ private val INTENT_NAME: String = "WalletSuggestionsIntent"
+ }
+}
diff --git a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
index 5ee30fb728ad..e549b61ac491 100644
--- a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
+++ b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
@@ -31,7 +31,6 @@ import android.content.SharedPreferences;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import android.graphics.Rect;
-import android.os.Environment;
import android.os.FileUtils;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
@@ -43,8 +42,6 @@ import android.util.Xml;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.content.PackageMonitor;
-import libcore.io.IoUtils;
-
import org.xmlpull.v1.XmlPullParser;
import java.io.File;
@@ -52,39 +49,60 @@ import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
+/**
+ * Backs up and restores wallpaper and metadata related to it.
+ *
+ * This agent has its own package because it does full backup as opposed to SystemBackupAgent
+ * which does key/value backup.
+ *
+ * This class stages wallpaper files for backup by copying them into its own directory because of
+ * the following reasons:
+ *
+ * <ul>
+ * <li>Non-system users don't have permission to read the directory that the system stores
+ * the wallpaper files in</li>
+ * <li>{@link BackupAgent} enforces that backed up files must live inside the package's
+ * {@link Context#getFilesDir()}</li>
+ * </ul>
+ *
+ * There are 3 files to back up:
+ * <ul>
+ * <li>The "wallpaper info" file which contains metadata like the crop applied to the
+ * wallpaper or the live wallpaper component name.</li>
+ * <li>The "system" wallpaper file.</li>
+ * <li>An optional "lock" wallpaper, which is shown on the lockscreen instead of the system
+ * wallpaper if set.</li>
+ * </ul>
+ *
+ * On restore, the metadata file is parsed and {@link WallpaperManager} APIs are used to set the
+ * wallpaper. Note that if there's a live wallpaper, the live wallpaper package name will be
+ * part of the metadata file and the wallpaper will be applied when the package it's installed.
+ */
public class WallpaperBackupAgent extends BackupAgent {
private static final String TAG = "WallpaperBackup";
private static final boolean DEBUG = false;
- // NB: must be kept in sync with WallpaperManagerService but has no
- // compile-time visibility.
-
- // Target filenames within the system's wallpaper directory
- static final String WALLPAPER = "wallpaper_orig";
- static final String WALLPAPER_LOCK = "wallpaper_lock_orig";
- static final String WALLPAPER_INFO = "wallpaper_info.xml";
+ // Names of our local-data stage files
+ @VisibleForTesting
+ static final String SYSTEM_WALLPAPER_STAGE = "wallpaper-stage";
+ @VisibleForTesting
+ static final String LOCK_WALLPAPER_STAGE = "wallpaper-lock-stage";
+ @VisibleForTesting
+ static final String WALLPAPER_INFO_STAGE = "wallpaper-info-stage";
- // Names of our local-data stage files/links
- static final String IMAGE_STAGE = "wallpaper-stage";
- static final String LOCK_IMAGE_STAGE = "wallpaper-lock-stage";
- static final String INFO_STAGE = "wallpaper-info-stage";
static final String EMPTY_SENTINEL = "empty";
static final String QUOTA_SENTINEL = "quota";
- // Not-for-backup bookkeeping
+ // Shared preferences constants.
static final String PREFS_NAME = "wbprefs.xml";
static final String SYSTEM_GENERATION = "system_gen";
static final String LOCK_GENERATION = "lock_gen";
- private File mWallpaperInfo; // wallpaper metadata file
- private File mWallpaperFile; // primary wallpaper image file
- private File mLockWallpaperFile; // lock wallpaper image file
-
// If this file exists, it means we exceeded our quota last time
private File mQuotaFile;
private boolean mQuotaExceeded;
- private WallpaperManager mWm;
+ private WallpaperManager mWallpaperManager;
@Override
public void onCreate() {
@@ -92,11 +110,7 @@ public class WallpaperBackupAgent extends BackupAgent {
Slog.v(TAG, "onCreate()");
}
- File wallpaperDir = getWallpaperDir();
- mWallpaperInfo = new File(wallpaperDir, WALLPAPER_INFO);
- mWallpaperFile = new File(wallpaperDir, WALLPAPER);
- mLockWallpaperFile = new File(wallpaperDir, WALLPAPER_LOCK);
- mWm = (WallpaperManager) getSystemService(Context.WALLPAPER_SERVICE);
+ mWallpaperManager = getSystemService(WallpaperManager.class);
mQuotaFile = new File(getFilesDir(), QUOTA_SENTINEL);
mQuotaExceeded = mQuotaFile.exists();
@@ -105,111 +119,39 @@ public class WallpaperBackupAgent extends BackupAgent {
}
}
- @VisibleForTesting
- protected File getWallpaperDir() {
- return Environment.getUserSystemDirectory(UserHandle.USER_SYSTEM);
- }
-
@Override
public void onFullBackup(FullBackupDataOutput data) throws IOException {
- // To avoid data duplication and disk churn, use links as the stage.
- final File filesDir = getFilesDir();
- final File infoStage = new File(filesDir, INFO_STAGE);
- final File imageStage = new File (filesDir, IMAGE_STAGE);
- final File lockImageStage = new File (filesDir, LOCK_IMAGE_STAGE);
- final File empty = new File (filesDir, EMPTY_SENTINEL);
-
try {
// We always back up this 'empty' file to ensure that the absence of
// storable wallpaper imagery still produces a non-empty backup data
// stream, otherwise it'd simply be ignored in preflight.
+ final File empty = new File(getFilesDir(), EMPTY_SENTINEL);
if (!empty.exists()) {
FileOutputStream touch = new FileOutputStream(empty);
touch.close();
}
backupFile(empty, data);
- SharedPreferences prefs = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
- final int lastSysGeneration = prefs.getInt(SYSTEM_GENERATION, -1);
- final int lastLockGeneration = prefs.getInt(LOCK_GENERATION, -1);
+ SharedPreferences sharedPrefs = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
- final int sysGeneration =
- mWm.getWallpaperIdForUser(FLAG_SYSTEM, UserHandle.USER_SYSTEM);
- final int lockGeneration =
- mWm.getWallpaperIdForUser(FLAG_LOCK, UserHandle.USER_SYSTEM);
+ // Check the IDs of the wallpapers that we backed up last time. If they haven't
+ // changed, we won't re-stage them for backup and use the old staged versions to avoid
+ // disk churn.
+ final int lastSysGeneration = sharedPrefs.getInt(SYSTEM_GENERATION, /* defValue= */ -1);
+ final int lastLockGeneration = sharedPrefs.getInt(LOCK_GENERATION, /* defValue= */ -1);
+ final int sysGeneration = mWallpaperManager.getWallpaperId(FLAG_SYSTEM);
+ final int lockGeneration = mWallpaperManager.getWallpaperId(FLAG_LOCK);
final boolean sysChanged = (sysGeneration != lastSysGeneration);
final boolean lockChanged = (lockGeneration != lastLockGeneration);
- final boolean sysEligible = mWm.isWallpaperBackupEligible(FLAG_SYSTEM);
- final boolean lockEligible = mWm.isWallpaperBackupEligible(FLAG_LOCK);
-
- // There might be a latent lock wallpaper file present but unused: don't
- // include it in the backup if that's the case.
- ParcelFileDescriptor lockFd = mWm.getWallpaperFile(FLAG_LOCK, UserHandle.USER_SYSTEM);
- final boolean hasLockWallpaper = (lockFd != null);
- IoUtils.closeQuietly(lockFd);
-
if (DEBUG) {
Slog.v(TAG, "sysGen=" + sysGeneration + " : sysChanged=" + sysChanged);
Slog.v(TAG, "lockGen=" + lockGeneration + " : lockChanged=" + lockChanged);
- Slog.v(TAG, "sysEligble=" + sysEligible);
- Slog.v(TAG, "lockEligible=" + lockEligible);
- Slog.v(TAG, "hasLockWallpaper=" + hasLockWallpaper);
}
- // only back up the wallpapers if we've been told they're eligible
- if (mWallpaperInfo.exists()) {
- if (sysChanged || lockChanged || !infoStage.exists()) {
- if (DEBUG) Slog.v(TAG, "New wallpaper configuration; copying");
- FileUtils.copyFileOrThrow(mWallpaperInfo, infoStage);
- }
- if (DEBUG) Slog.v(TAG, "Storing wallpaper metadata");
- backupFile(infoStage, data);
- } else {
- Slog.w(TAG, "Wallpaper metadata file doesn't exist: " +
- mWallpaperFile.getPath());
- }
- if (sysEligible && mWallpaperFile.exists()) {
- if (sysChanged || !imageStage.exists()) {
- if (DEBUG) Slog.v(TAG, "New system wallpaper; copying");
- FileUtils.copyFileOrThrow(mWallpaperFile, imageStage);
- }
- if (DEBUG) Slog.v(TAG, "Storing system wallpaper image");
- backupFile(imageStage, data);
- prefs.edit().putInt(SYSTEM_GENERATION, sysGeneration).apply();
- } else {
- Slog.w(TAG, "Not backing up wallpaper as one of conditions is not "
- + "met: sysEligible = " + sysEligible + " wallpaperFileExists = "
- + mWallpaperFile.exists());
- }
-
- // If there's no lock wallpaper, then we have nothing to add to the backup.
- if (lockGeneration == -1) {
- if (lockChanged && lockImageStage.exists()) {
- if (DEBUG) Slog.v(TAG, "Removed lock wallpaper; deleting");
- lockImageStage.delete();
- }
- Slog.d(TAG, "No lockscreen wallpaper set, add nothing to backup");
- prefs.edit().putInt(LOCK_GENERATION, lockGeneration).apply();
- return;
- }
-
- // Don't try to store the lock image if we overran our quota last time
- if (lockEligible && hasLockWallpaper && mLockWallpaperFile.exists() && !mQuotaExceeded) {
- if (lockChanged || !lockImageStage.exists()) {
- if (DEBUG) Slog.v(TAG, "New lock wallpaper; copying");
- FileUtils.copyFileOrThrow(mLockWallpaperFile, lockImageStage);
- }
- if (DEBUG) Slog.v(TAG, "Storing lock wallpaper image");
- backupFile(lockImageStage, data);
- prefs.edit().putInt(LOCK_GENERATION, lockGeneration).apply();
- } else {
- Slog.w(TAG, "Not backing up lockscreen wallpaper as one of conditions is not "
- + "met: lockEligible = " + lockEligible + " hasLockWallpaper = "
- + hasLockWallpaper + " lockWallpaperFileExists = "
- + mLockWallpaperFile.exists() + " quotaExceeded (last time) = "
- + mQuotaExceeded);
- }
+ backupWallpaperInfoFile(/* sysOrLockChanged= */ sysChanged || lockChanged, data);
+ backupSystemWallpaperFile(sharedPrefs, sysChanged, sysGeneration, data);
+ backupLockWallpaperFileIfItExists(sharedPrefs, lockChanged, lockGeneration, data);
} catch (Exception e) {
Slog.e(TAG, "Unable to back up wallpaper", e);
} finally {
@@ -222,6 +164,114 @@ public class WallpaperBackupAgent extends BackupAgent {
}
}
+ private void backupWallpaperInfoFile(boolean sysOrLockChanged, FullBackupDataOutput data)
+ throws IOException {
+ final ParcelFileDescriptor wallpaperInfoFd = mWallpaperManager.getWallpaperInfoFile();
+
+ if (wallpaperInfoFd == null) {
+ Slog.w(TAG, "Wallpaper metadata file doesn't exist");
+ return;
+ }
+
+ final File infoStage = new File(getFilesDir(), WALLPAPER_INFO_STAGE);
+
+ if (sysOrLockChanged || !infoStage.exists()) {
+ if (DEBUG) Slog.v(TAG, "New wallpaper configuration; copying");
+ copyFromPfdToFileAndClosePfd(wallpaperInfoFd, infoStage);
+ }
+
+ if (DEBUG) Slog.v(TAG, "Storing wallpaper metadata");
+ backupFile(infoStage, data);
+ }
+
+ private void backupSystemWallpaperFile(SharedPreferences sharedPrefs,
+ boolean sysChanged, int sysGeneration, FullBackupDataOutput data) throws IOException {
+ if (!mWallpaperManager.isWallpaperBackupEligible(FLAG_SYSTEM)) {
+ Slog.d(TAG, "System wallpaper ineligible for backup");
+ return;
+ }
+
+ final ParcelFileDescriptor systemWallpaperImageFd = mWallpaperManager.getWallpaperFile(
+ FLAG_SYSTEM,
+ /* getCropped= */ false);
+
+ if (systemWallpaperImageFd == null) {
+ Slog.w(TAG, "System wallpaper doesn't exist");
+ return;
+ }
+
+ final File imageStage = new File(getFilesDir(), SYSTEM_WALLPAPER_STAGE);
+
+ if (sysChanged || !imageStage.exists()) {
+ if (DEBUG) Slog.v(TAG, "New system wallpaper; copying");
+ copyFromPfdToFileAndClosePfd(systemWallpaperImageFd, imageStage);
+ }
+
+ if (DEBUG) Slog.v(TAG, "Storing system wallpaper image");
+ backupFile(imageStage, data);
+ sharedPrefs.edit().putInt(SYSTEM_GENERATION, sysGeneration).apply();
+ }
+
+ private void backupLockWallpaperFileIfItExists(SharedPreferences sharedPrefs,
+ boolean lockChanged, int lockGeneration, FullBackupDataOutput data) throws IOException {
+ final File lockImageStage = new File(getFilesDir(), LOCK_WALLPAPER_STAGE);
+
+ // This means there's no lock wallpaper set by the user.
+ if (lockGeneration == -1) {
+ if (lockChanged && lockImageStage.exists()) {
+ if (DEBUG) Slog.v(TAG, "Removed lock wallpaper; deleting");
+ lockImageStage.delete();
+ }
+ Slog.d(TAG, "No lockscreen wallpaper set, add nothing to backup");
+ sharedPrefs.edit().putInt(LOCK_GENERATION, lockGeneration).apply();
+ return;
+ }
+
+ if (!mWallpaperManager.isWallpaperBackupEligible(FLAG_LOCK)) {
+ Slog.d(TAG, "Lock screen wallpaper ineligible for backup");
+ return;
+ }
+
+ final ParcelFileDescriptor lockWallpaperFd = mWallpaperManager.getWallpaperFile(
+ FLAG_LOCK, /* getCropped= */ false);
+
+ // If we get to this point, that means lockGeneration != -1 so there's a lock wallpaper
+ // set, but we can't find it.
+ if (lockWallpaperFd == null) {
+ Slog.w(TAG, "Lock wallpaper doesn't exist");
+ return;
+ }
+
+ if (mQuotaExceeded) {
+ Slog.w(TAG, "Not backing up lock screen wallpaper. Quota was exceeded last time");
+ return;
+ }
+
+ if (lockChanged || !lockImageStage.exists()) {
+ if (DEBUG) Slog.v(TAG, "New lock wallpaper; copying");
+ copyFromPfdToFileAndClosePfd(lockWallpaperFd, lockImageStage);
+ }
+
+ if (DEBUG) Slog.v(TAG, "Storing lock wallpaper image");
+ backupFile(lockImageStage, data);
+ sharedPrefs.edit().putInt(LOCK_GENERATION, lockGeneration).apply();
+ }
+
+ /**
+ * Copies the contents of the given {@code pfd} to the given {@code file}.
+ *
+ * All resources used in the process including the {@code pfd} will be closed.
+ */
+ private static void copyFromPfdToFileAndClosePfd(ParcelFileDescriptor pfd, File file)
+ throws IOException {
+ try (ParcelFileDescriptor.AutoCloseInputStream inputStream =
+ new ParcelFileDescriptor.AutoCloseInputStream(pfd);
+ FileOutputStream outputStream = new FileOutputStream(file)
+ ) {
+ FileUtils.copy(inputStream, outputStream);
+ }
+ }
+
@VisibleForTesting
// fullBackupFile is final, so we intercept backups here in tests.
protected void backupFile(File file, FullBackupDataOutput data) {
@@ -244,9 +294,9 @@ public class WallpaperBackupAgent extends BackupAgent {
public void onRestoreFinished() {
Slog.v(TAG, "onRestoreFinished()");
final File filesDir = getFilesDir();
- final File infoStage = new File(filesDir, INFO_STAGE);
- final File imageStage = new File (filesDir, IMAGE_STAGE);
- final File lockImageStage = new File (filesDir, LOCK_IMAGE_STAGE);
+ final File infoStage = new File(filesDir, WALLPAPER_INFO_STAGE);
+ final File imageStage = new File(filesDir, SYSTEM_WALLPAPER_STAGE);
+ final File lockImageStage = new File(filesDir, LOCK_WALLPAPER_STAGE);
// If we restored separate lock imagery, the system wallpaper should be
// applied as system-only; but if there's no separate lock image, make
@@ -283,11 +333,11 @@ public class WallpaperBackupAgent extends BackupAgent {
void updateWallpaperComponent(ComponentName wpService, boolean applyToLock) throws IOException {
if (servicePackageExists(wpService)) {
Slog.i(TAG, "Using wallpaper service " + wpService);
- mWm.setWallpaperComponent(wpService, UserHandle.USER_SYSTEM);
+ mWallpaperManager.setWallpaperComponent(wpService);
if (applyToLock) {
// We have a live wallpaper and no static lock image,
// allow live wallpaper to show "through" on lock screen.
- mWm.clear(FLAG_LOCK);
+ mWallpaperManager.clear(FLAG_LOCK);
}
} else {
// If we've restored a live wallpaper, but the component doesn't exist,
@@ -311,8 +361,9 @@ public class WallpaperBackupAgent extends BackupAgent {
Slog.i(TAG, "Got restored wallpaper; applying which=" + which
+ "; cropHint = " + cropHint);
try (FileInputStream in = new FileInputStream(stage)) {
- mWm.setStream(in, cropHint.isEmpty() ? null : cropHint, true, which);
- } finally {} // auto-closes 'in'
+ mWallpaperManager.setStream(in, cropHint.isEmpty() ? null : cropHint, true,
+ which);
+ }
}
} else {
Slog.d(TAG, "Restore data doesn't exist for file " + stage.getPath());
@@ -384,7 +435,7 @@ public class WallpaperBackupAgent extends BackupAgent {
if (comp != null) {
final IPackageManager pm = AppGlobals.getPackageManager();
final PackageInfo info = pm.getPackageInfo(comp.getPackageName(),
- 0, UserHandle.USER_SYSTEM);
+ 0, getUserId());
return (info != null);
}
} catch (RemoteException e) {
@@ -393,16 +444,14 @@ public class WallpaperBackupAgent extends BackupAgent {
return false;
}
- //
- // Key/value API: abstract, therefore required; but not used
- //
-
+ /** Unused Key/Value API. */
@Override
public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
ParcelFileDescriptor newState) throws IOException {
// Intentionally blank
}
+ /** Unused Key/Value API. */
@Override
public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState)
throws IOException {
@@ -427,10 +476,10 @@ public class WallpaperBackupAgent extends BackupAgent {
if (componentName.getPackageName().equals(packageName)) {
Slog.d(TAG, "Applying component " + componentName);
- mWm.setWallpaperComponent(componentName);
+ mWallpaperManager.setWallpaperComponent(componentName);
if (applyToLock) {
try {
- mWm.clear(FLAG_LOCK);
+ mWallpaperManager.clear(FLAG_LOCK);
} catch (IOException e) {
Slog.w(TAG, "Failed to apply live wallpaper to lock screen: " + e);
}
diff --git a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
index 4367075abc74..20dd516503b8 100644
--- a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
+++ b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
@@ -18,14 +18,15 @@ package com.android.wallpaperbackup;
import static android.app.WallpaperManager.FLAG_LOCK;
import static android.app.WallpaperManager.FLAG_SYSTEM;
+import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
+
+import static com.android.wallpaperbackup.WallpaperBackupAgent.LOCK_WALLPAPER_STAGE;
+import static com.android.wallpaperbackup.WallpaperBackupAgent.SYSTEM_WALLPAPER_STAGE;
+import static com.android.wallpaperbackup.WallpaperBackupAgent.WALLPAPER_INFO_STAGE;
import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -35,43 +36,42 @@ import android.app.WallpaperManager;
import android.app.backup.FullBackupDataOutput;
import android.content.ComponentName;
import android.content.Context;
-import android.content.SharedPreferences;
-import android.os.UserHandle;
+import android.os.FileUtils;
+import android.os.ParcelFileDescriptor;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.content.PackageMonitor;
-import com.android.wallpaperbackup.WallpaperBackupAgent;
import com.android.wallpaperbackup.utils.ContextWithServiceOverrides;
+import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
-import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
+import java.util.Optional;
@RunWith(AndroidJUnit4.class)
public class WallpaperBackupAgentTest {
- private static final String SYSTEM_GENERATION = "system_gen";
- private static final String LOCK_GENERATION = "lock_gen";
private static final String TEST_WALLPAPER_PACKAGE = "wallpaper_package";
private static final int TEST_SYSTEM_WALLPAPER_ID = 1;
private static final int TEST_LOCK_WALLPAPER_ID = 2;
+ private static final int NO_LOCK_WALLPAPER_ID = -1;
@Mock private FullBackupDataOutput mOutput;
@Mock private WallpaperManager mWallpaperManager;
- @Mock private SharedPreferences mSharedPreferences;
- @Mock private SharedPreferences.Editor mSharedPreferenceEditor;
@Mock private Context mMockContext;
@Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
@@ -84,14 +84,11 @@ public class WallpaperBackupAgentTest {
public void setUp() {
MockitoAnnotations.initMocks(this);
- when(mSharedPreferences.edit()).thenReturn(mSharedPreferenceEditor);
- when(mSharedPreferenceEditor.putInt(anyString(), anyInt()))
- .thenReturn(mSharedPreferenceEditor);
- doNothing().when(mSharedPreferenceEditor).apply();
+ when(mWallpaperManager.isWallpaperBackupEligible(eq(FLAG_SYSTEM))).thenReturn(true);
+ when(mWallpaperManager.isWallpaperBackupEligible(eq(FLAG_LOCK))).thenReturn(true);
mContext = new ContextWithServiceOverrides(ApplicationProvider.getApplicationContext());
mContext.injectSystemService(WallpaperManager.class, mWallpaperManager);
- mContext.setSharedPreferencesOverride(mSharedPreferences);
mWallpaperBackupAgent = new IsolatedWallpaperBackupAgent(mTemporaryFolder.getRoot());
mWallpaperBackupAgent.attach(mContext);
@@ -100,48 +97,236 @@ public class WallpaperBackupAgentTest {
mWallpaperComponent = new ComponentName(TEST_WALLPAPER_PACKAGE, "");
}
+ @After
+ public void tearDown() {
+ FileUtils.deleteContents(mContext.getFilesDir());
+ }
+
@Test
- public void testOnFullBackup_withNoChanges_onlyBacksUpEmptyFile() throws IOException {
- mockBackedUpState();
- mockCurrentWallpapers(TEST_SYSTEM_WALLPAPER_ID, TEST_LOCK_WALLPAPER_ID);
+ public void testOnFullBackup_backsUpEmptyFile() throws IOException {
+ mWallpaperBackupAgent.onFullBackup(mOutput);
+
+ assertThat(getBackedUpFileOptional("empty").isPresent()).isTrue();
+ }
+
+ @Test
+ public void testOnFullBackup_noExistingInfoStage_backsUpInfoFile() throws Exception {
+ mockWallpaperInfoFileWithContents("fake info file");
mWallpaperBackupAgent.onFullBackup(mOutput);
- assertThat(mWallpaperBackupAgent.mBackedUpFiles.size()).isEqualTo(1);
- assertThat(mWallpaperBackupAgent.mBackedUpFiles.get(0).getName()).isEqualTo("empty");
+ assertFileContentEquals(getBackedUpFileOptional(WALLPAPER_INFO_STAGE).get(),
+ "fake info file");
}
@Test
- public void testOnFullBackup_withOnlyChangedSystem_updatesTheSharedPreferences()
- throws IOException {
- mockSystemWallpaperReadyToBackUp();
- mockUnbackedUpState();
- mockCurrentWallpapers(TEST_SYSTEM_WALLPAPER_ID, TEST_LOCK_WALLPAPER_ID);
+ public void testOnFullBackup_existingInfoStage_noChange_backsUpAlreadyStagedInfoFile()
+ throws Exception {
+ // Do a backup first so the info file is staged.
+ mockWallpaperInfoFileWithContents("old info file");
+ // Provide system and lock wallpapers but don't change them in between backups.
+ mockSystemWallpaperFileWithContents("system wallpaper");
+ mockLockWallpaperFileWithContents("lock wallpaper");
+ mWallpaperBackupAgent.onFullBackup(mOutput);
+ mWallpaperBackupAgent.mBackedUpFiles.clear();
+ // This new wallpaper should be ignored since the ID of neither wallpaper changed.
+ mockWallpaperInfoFileWithContents("new info file");
mWallpaperBackupAgent.onFullBackup(mOutput);
- verify(mSharedPreferenceEditor).putInt(eq(SYSTEM_GENERATION), eq(TEST_SYSTEM_WALLPAPER_ID));
+ assertFileContentEquals(getBackedUpFileOptional(WALLPAPER_INFO_STAGE).get(),
+ "old info file");
}
@Test
- public void testOnFullBackup_withLockChangedToMatchSystem_updatesTheSharedPreferences()
- throws IOException {
- mockBackedUpState();
- mockSystemWallpaperReadyToBackUp();
- mockCurrentWallpapers(TEST_SYSTEM_WALLPAPER_ID, -1);
+ public void testOnFullBackup_existingInfoStage_sysChanged_backsUpNewInfoFile()
+ throws Exception {
+ // Do a backup first so the backed up system wallpaper ID is persisted to disk.
+ mockWallpaperInfoFileWithContents("old info file");
+ mockCurrentWallpaperIds(TEST_SYSTEM_WALLPAPER_ID, TEST_LOCK_WALLPAPER_ID);
+ mWallpaperBackupAgent.onFullBackup(mOutput);
+ mWallpaperBackupAgent.mBackedUpFiles.clear();
+ // Mock that the user changed the system wallpaper.
+ mockCurrentWallpaperIds(TEST_SYSTEM_WALLPAPER_ID + 1, TEST_LOCK_WALLPAPER_ID);
+ mockWallpaperInfoFileWithContents("new info file");
+
+ mWallpaperBackupAgent.onFullBackup(mOutput);
+
+ assertFileContentEquals(getBackedUpFileOptional(WALLPAPER_INFO_STAGE).get(),
+ "new info file");
+ }
+
+ @Test
+ public void testOnFullBackup_existingInfoStage_lockChanged_backsUpNewInfoFile()
+ throws Exception {
+ // Do a backup first so the backed up lock wallpaper ID is persisted to disk.
+ mockWallpaperInfoFileWithContents("old info file");
+ mockLockWallpaperFileWithContents("lock wallpaper");
+ mockCurrentWallpaperIds(TEST_SYSTEM_WALLPAPER_ID, TEST_LOCK_WALLPAPER_ID);
+ mWallpaperBackupAgent.onFullBackup(mOutput);
+ mWallpaperBackupAgent.mBackedUpFiles.clear();
+ // Mock that the user changed the system wallpaper.
+ mockCurrentWallpaperIds(TEST_SYSTEM_WALLPAPER_ID, TEST_LOCK_WALLPAPER_ID + 1);
+ mockWallpaperInfoFileWithContents("new info file");
+
+ mWallpaperBackupAgent.onFullBackup(mOutput);
+
+ assertFileContentEquals(getBackedUpFileOptional(WALLPAPER_INFO_STAGE).get(),
+ "new info file");
+ }
+
+ @Test
+ public void testOnFullBackup_systemWallpaperNotEligible_doesNotBackUpSystemWallpaper()
+ throws Exception {
+ when(mWallpaperManager.isWallpaperBackupEligible(eq(FLAG_SYSTEM))).thenReturn(false);
+ mockSystemWallpaperFileWithContents("system wallpaper");
+ mockCurrentWallpaperIds(TEST_SYSTEM_WALLPAPER_ID, NO_LOCK_WALLPAPER_ID);
+
+ mWallpaperBackupAgent.onFullBackup(mOutput);
+
+ assertThat(getBackedUpFileOptional(SYSTEM_WALLPAPER_STAGE).isPresent()).isFalse();
+ }
+
+ @Test
+ public void testOnFullBackup_existingSystemStage_noSysChange_backsUpAlreadyStagedFile()
+ throws Exception {
+ // Do a backup first so that a stage file is created.
+ mockSystemWallpaperFileWithContents("system wallpaper");
+ mockCurrentWallpaperIds(TEST_SYSTEM_WALLPAPER_ID, NO_LOCK_WALLPAPER_ID);
+ mWallpaperBackupAgent.onFullBackup(mOutput);
+ mWallpaperBackupAgent.mBackedUpFiles.clear();
+ // This new file should be ignored since the ID of the wallpaper did not change.
+ mockSystemWallpaperFileWithContents("new system wallpaper");
+
+ mWallpaperBackupAgent.onFullBackup(mOutput);
+
+ assertFileContentEquals(getBackedUpFileOptional(SYSTEM_WALLPAPER_STAGE).get(),
+ "system wallpaper");
+ }
+
+ @Test
+ public void testOnFullBackup_existingSystemStage_sysChanged_backsUpNewSystemWallpaper()
+ throws Exception {
+ // Do a backup first so that a stage file is created.
+ mockSystemWallpaperFileWithContents("system wallpaper");
+ mockCurrentWallpaperIds(TEST_SYSTEM_WALLPAPER_ID, NO_LOCK_WALLPAPER_ID);
+ mWallpaperBackupAgent.onFullBackup(mOutput);
+ mWallpaperBackupAgent.mBackedUpFiles.clear();
+ // Mock that the system wallpaper was changed by the user.
+ mockCurrentWallpaperIds(TEST_SYSTEM_WALLPAPER_ID + 1, NO_LOCK_WALLPAPER_ID);
+ mockSystemWallpaperFileWithContents("new system wallpaper");
mWallpaperBackupAgent.onFullBackup(mOutput);
- InOrder inOrder = inOrder(mSharedPreferenceEditor);
- inOrder.verify(mSharedPreferenceEditor)
- .putInt(eq(SYSTEM_GENERATION), eq(TEST_SYSTEM_WALLPAPER_ID));
- inOrder.verify(mSharedPreferenceEditor).apply();
- inOrder.verify(mSharedPreferenceEditor).putInt(eq(LOCK_GENERATION), eq(-1));
- inOrder.verify(mSharedPreferenceEditor).apply();
+ assertFileContentEquals(getBackedUpFileOptional(SYSTEM_WALLPAPER_STAGE).get(),
+ "new system wallpaper");
}
@Test
- public void updateWallpaperComponent_doesApplyLater() throws IOException {
+ public void testOnFullBackup_noExistingSystemStage_backsUpSystemWallpaper()
+ throws Exception {
+ mockSystemWallpaperFileWithContents("system wallpaper");
+ mockCurrentWallpaperIds(TEST_SYSTEM_WALLPAPER_ID, NO_LOCK_WALLPAPER_ID);
+
+ mWallpaperBackupAgent.onFullBackup(mOutput);
+
+ assertFileContentEquals(getBackedUpFileOptional(SYSTEM_WALLPAPER_STAGE).get(),
+ "system wallpaper");
+ }
+
+ @Test
+ public void testOnFullBackup_lockWallpaperNotEligible_doesNotBackUpLockWallpaper()
+ throws Exception {
+ when(mWallpaperManager.isWallpaperBackupEligible(eq(FLAG_LOCK))).thenReturn(false);
+ mockLockWallpaperFileWithContents("lock wallpaper");
+ mockCurrentWallpaperIds(TEST_SYSTEM_WALLPAPER_ID, TEST_LOCK_WALLPAPER_ID);
+
+ mWallpaperBackupAgent.onFullBackup(mOutput);
+
+ assertThat(getBackedUpFileOptional(LOCK_WALLPAPER_STAGE).isPresent()).isFalse();
+ }
+
+ @Test
+ public void testOnFullBackup_existingLockStage_lockWallpaperRemovedByUser_NotBackUpOldStage()
+ throws Exception {
+ // Do a backup first so that a stage file is created.
+ mockLockWallpaperFileWithContents("lock wallpaper");
+ mockCurrentWallpaperIds(TEST_SYSTEM_WALLPAPER_ID, TEST_LOCK_WALLPAPER_ID);
+ mWallpaperBackupAgent.onFullBackup(mOutput);
+ mWallpaperBackupAgent.mBackedUpFiles.clear();
+ // Mock the ID of the lock wallpaper to indicate it's not set.
+ mockCurrentWallpaperIds(TEST_SYSTEM_WALLPAPER_ID, NO_LOCK_WALLPAPER_ID);
+
+ mWallpaperBackupAgent.onFullBackup(mOutput);
+
+ assertThat(getBackedUpFileOptional(LOCK_WALLPAPER_STAGE).isPresent()).isFalse();
+ }
+
+ @Test
+ public void testOnFullBackup_existingLockStage_lockWallpaperRemovedByUser_deletesExistingStage()
+ throws Exception {
+ // Do a backup first so that a stage file is created.
+ mockLockWallpaperFileWithContents("lock wallpaper");
+ mockCurrentWallpaperIds(TEST_SYSTEM_WALLPAPER_ID, TEST_LOCK_WALLPAPER_ID);
+ mWallpaperBackupAgent.onFullBackup(mOutput);
+ mWallpaperBackupAgent.mBackedUpFiles.clear();
+ // Mock the ID of the lock wallpaper to indicate it's not set.
+ mockCurrentWallpaperIds(TEST_SYSTEM_WALLPAPER_ID, NO_LOCK_WALLPAPER_ID);
+
+ mWallpaperBackupAgent.onFullBackup(mOutput);
+
+ assertThat(new File(mContext.getFilesDir(), LOCK_WALLPAPER_STAGE).exists()).isFalse();
+ }
+
+ @Test
+ public void testOnFullBackup_existingLockStage_noLockChange_backsUpAlreadyStagedFile()
+ throws Exception {
+ // Do a backup first so that a stage file is created.
+ mockLockWallpaperFileWithContents("old lock wallpaper");
+ mockCurrentWallpaperIds(TEST_SYSTEM_WALLPAPER_ID, TEST_LOCK_WALLPAPER_ID);
+ mWallpaperBackupAgent.onFullBackup(mOutput);
+ mWallpaperBackupAgent.mBackedUpFiles.clear();
+ // This new file should be ignored since the ID of the wallpaper did not change.
+ mockLockWallpaperFileWithContents("new lock wallpaper");
+
+ mWallpaperBackupAgent.onFullBackup(mOutput);
+
+ assertFileContentEquals(getBackedUpFileOptional(LOCK_WALLPAPER_STAGE).get(),
+ "old lock wallpaper");
+ }
+
+ @Test
+ public void testOnFullBackup_existingLockStage_lockChanged_backsUpNewLockWallpaper()
+ throws Exception {
+ // Do a backup first so that a stage file is created.
+ mockLockWallpaperFileWithContents("old lock wallpaper");
+ mockCurrentWallpaperIds(TEST_SYSTEM_WALLPAPER_ID, TEST_LOCK_WALLPAPER_ID);
+ mWallpaperBackupAgent.onFullBackup(mOutput);
+ mWallpaperBackupAgent.mBackedUpFiles.clear();
+ // Mock that the lock wallpaper was changed by the user.
+ mockLockWallpaperFileWithContents("new lock wallpaper");
+ mockCurrentWallpaperIds(TEST_SYSTEM_WALLPAPER_ID, TEST_LOCK_WALLPAPER_ID + 1);
+
+ mWallpaperBackupAgent.onFullBackup(mOutput);
+
+ assertFileContentEquals(getBackedUpFileOptional(LOCK_WALLPAPER_STAGE).get(),
+ "new lock wallpaper");
+ }
+
+ @Test
+ public void testOnFullBackup_noExistingLockStage_backsUpLockWallpaper()
+ throws Exception {
+ mockLockWallpaperFileWithContents("lock wallpaper");
+ mockCurrentWallpaperIds(TEST_SYSTEM_WALLPAPER_ID, TEST_LOCK_WALLPAPER_ID);
+
+ mWallpaperBackupAgent.onFullBackup(mOutput);
+
+ assertFileContentEquals(getBackedUpFileOptional(LOCK_WALLPAPER_STAGE).get(),
+ "lock wallpaper");
+ }
+
+ @Test
+ public void testUpdateWallpaperComponent_doesApplyLater() throws IOException {
mWallpaperBackupAgent.mIsDeviceInRestore = true;
mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent,
@@ -156,7 +341,7 @@ public class WallpaperBackupAgentTest {
}
@Test
- public void updateWallpaperComponent_applyToLockFalse_doesApplyLaterOnlyToMainScreen()
+ public void testUpdateWallpaperComponent_applyToLockFalse_doesApplyLaterOnlyToMainScreen()
throws IOException {
mWallpaperBackupAgent.mIsDeviceInRestore = true;
@@ -172,7 +357,7 @@ public class WallpaperBackupAgentTest {
}
@Test
- public void updateWallpaperComponent_deviceNotInRestore_doesNotApply()
+ public void testUpdateWallpaperComponent_deviceNotInRestore_doesNotApply()
throws IOException {
mWallpaperBackupAgent.mIsDeviceInRestore = false;
@@ -188,7 +373,7 @@ public class WallpaperBackupAgentTest {
}
@Test
- public void updateWallpaperComponent_differentPackageInstalled_doesNotApply()
+ public void testUpdateWallpaperComponent_differentPackageInstalled_doesNotApply()
throws IOException {
mWallpaperBackupAgent.mIsDeviceInRestore = false;
@@ -203,33 +388,48 @@ public class WallpaperBackupAgentTest {
verify(mWallpaperManager, never()).clear(eq(FLAG_LOCK));
}
- private void mockUnbackedUpState() {
- mockCurrentWallpapers(TEST_SYSTEM_WALLPAPER_ID, TEST_LOCK_WALLPAPER_ID);
- when(mSharedPreferences.getInt(eq(SYSTEM_GENERATION), eq(-1))).thenReturn(-1);
- when(mSharedPreferences.getInt(eq(LOCK_GENERATION), eq(-1))).thenReturn(-1);
+ private void mockCurrentWallpaperIds(int systemWallpaperId, int lockWallpaperId) {
+ when(mWallpaperManager.getWallpaperId(eq(FLAG_SYSTEM))).thenReturn(systemWallpaperId);
+ when(mWallpaperManager.getWallpaperId(eq(FLAG_LOCK))).thenReturn(lockWallpaperId);
}
- private void mockBackedUpState() {
- when(mSharedPreferences.getInt(eq(SYSTEM_GENERATION), eq(-1)))
- .thenReturn(TEST_SYSTEM_WALLPAPER_ID);
- when(mSharedPreferences.getInt(eq(LOCK_GENERATION), eq(-1)))
- .thenReturn(TEST_LOCK_WALLPAPER_ID);
+ private File createTemporaryFileWithContentString(String contents) throws Exception {
+ File file = mTemporaryFolder.newFile();
+ try (FileOutputStream outputStream = new FileOutputStream(file)) {
+ outputStream.write(contents.getBytes());
+ }
+ return file;
}
- private void mockCurrentWallpapers(int systemWallpaperId, int lockWallpaperId) {
- when(mWallpaperManager.getWallpaperIdForUser(eq(FLAG_SYSTEM), eq(UserHandle.USER_SYSTEM)))
- .thenReturn(systemWallpaperId);
- when(mWallpaperManager.getWallpaperIdForUser(eq(FLAG_LOCK), eq(UserHandle.USER_SYSTEM)))
- .thenReturn(lockWallpaperId);
- when(mWallpaperManager.isWallpaperBackupEligible(eq(FLAG_SYSTEM))).thenReturn(true);
- when(mWallpaperManager.isWallpaperBackupEligible(eq(FLAG_LOCK))).thenReturn(true);
+ private void assertFileContentEquals(File file, String expected) throws Exception {
+ try (FileInputStream inputStream = new FileInputStream(file)) {
+ assertThat(new String(inputStream.readAllBytes())).isEqualTo(expected);
+ }
}
- private void mockSystemWallpaperReadyToBackUp() throws IOException {
- // Create a system wallpaper file
- mTemporaryFolder.newFile("wallpaper_orig");
- // Create staging file to simulate he wallpaper being ready to back up
- new File(mContext.getFilesDir(), "wallpaper-stage").createNewFile();
+ private Optional<File> getBackedUpFileOptional(String fileName) {
+ return mWallpaperBackupAgent.mBackedUpFiles.stream().filter(
+ file -> file.getName().equals(fileName)).findFirst();
+ }
+
+ private void mockWallpaperInfoFileWithContents(String contents) throws Exception {
+ File fakeInfoFile = createTemporaryFileWithContentString(contents);
+ when(mWallpaperManager.getWallpaperInfoFile()).thenReturn(
+ ParcelFileDescriptor.open(fakeInfoFile, MODE_READ_ONLY));
+ }
+
+ private void mockSystemWallpaperFileWithContents(String contents) throws Exception {
+ File fakeSystemWallpaperFile = createTemporaryFileWithContentString(contents);
+ when(mWallpaperManager.getWallpaperFile(eq(FLAG_SYSTEM), /* cropped = */
+ eq(false))).thenReturn(
+ ParcelFileDescriptor.open(fakeSystemWallpaperFile, MODE_READ_ONLY));
+ }
+
+ private void mockLockWallpaperFileWithContents(String contents) throws Exception {
+ File fakeLockWallpaperFile = createTemporaryFileWithContentString(contents);
+ when(mWallpaperManager.getWallpaperFile(eq(FLAG_LOCK), /* cropped = */
+ eq(false))).thenReturn(
+ ParcelFileDescriptor.open(fakeLockWallpaperFile, MODE_READ_ONLY));
}
private class IsolatedWallpaperBackupAgent extends WallpaperBackupAgent {
@@ -243,21 +443,11 @@ public class WallpaperBackupAgentTest {
}
@Override
- protected File getWallpaperDir() {
- return mWallpaperBaseDirectory;
- }
-
- @Override
protected void backupFile(File file, FullBackupDataOutput data) {
mBackedUpFiles.add(file);
}
@Override
- public SharedPreferences getSharedPreferences(File file, int mode) {
- return mSharedPreferences;
- }
-
- @Override
boolean servicePackageExists(ComponentName comp) {
return false;
}
diff --git a/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
index 3fa0ab69d67c..e6abc4c90fac 100644
--- a/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
+++ b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
@@ -391,7 +391,7 @@ public class SystemActionPerformer {
private boolean takeScreenshot() {
ScreenshotHelper screenshotHelper = (mScreenshotHelperSupplier != null)
? mScreenshotHelperSupplier.get() : new ScreenshotHelper(mContext);
- screenshotHelper.takeScreenshot(WindowManager.TAKE_SCREENSHOT_FULLSCREEN,
+ screenshotHelper.takeScreenshot(
WindowManager.ScreenshotSource.SCREENSHOT_ACCESSIBILITY_ACTIONS,
new Handler(Looper.getMainLooper()), null);
return true;
diff --git a/services/api/current.txt b/services/api/current.txt
index 3926b39fb30f..e66bf4d76405 100644
--- a/services/api/current.txt
+++ b/services/api/current.txt
@@ -180,8 +180,8 @@ package com.android.server.pm.pkg {
method @Nullable public String getPrimaryCpuAbi();
method @Nullable public String getSeInfo();
method @Nullable public String getSecondaryCpuAbi();
+ method @NonNull public java.util.List<com.android.server.pm.pkg.SharedLibrary> getSharedLibraryDependencies();
method @NonNull public com.android.server.pm.pkg.PackageUserState getStateForUser(@NonNull android.os.UserHandle);
- method @NonNull public java.util.List<com.android.server.pm.pkg.SharedLibrary> getUsesLibraries();
method public boolean isApex();
method public boolean isPrivileged();
method public boolean isSystem();
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index 2c8bfeb598de..998c9c2278db 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -281,6 +281,8 @@ public class UserBackupManagerService {
// Pseudoname that we use for the Package Manager metadata "package".
public static final String PACKAGE_MANAGER_SENTINEL = "@pm@";
+ public static final String WALLPAPER_PACKAGE = "com.android.wallpaperbackup";
+
// Retry interval for clear/init when the transport is unavailable
private static final long TRANSPORT_RETRY_INTERVAL = 1 * AlarmManager.INTERVAL_HOUR;
@@ -309,7 +311,6 @@ public class UserBackupManagerService {
private static final String SERIAL_ID_FILE = "serial_id";
private static final String SKIP_USER_FACING_PACKAGES = "backup_skip_user_facing_packages";
- private static final String WALLPAPER_PACKAGE = "com.android.wallpaperbackup";
private final @UserIdInt int mUserId;
private final BackupAgentTimeoutParameters mAgentTimeoutParameters;
diff --git a/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java b/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java
index 7f0b56f82fac..b20ff37260a6 100644
--- a/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java
+++ b/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java
@@ -20,6 +20,7 @@ import static com.android.server.backup.BackupManagerService.MORE_DEBUG;
import static com.android.server.backup.BackupManagerService.TAG;
import static com.android.server.backup.UserBackupManagerService.PACKAGE_MANAGER_SENTINEL;
import static com.android.server.backup.UserBackupManagerService.SHARED_BACKUP_AGENT_PACKAGE;
+import static com.android.server.backup.UserBackupManagerService.WALLPAPER_PACKAGE;
import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
import android.annotation.Nullable;
@@ -56,7 +57,7 @@ public class BackupEligibilityRules {
private static final boolean DEBUG = false;
// List of system packages that are eligible for backup in non-system users.
private static final Set<String> systemPackagesAllowedForAllUsers =
- Sets.newArraySet(PACKAGE_MANAGER_SENTINEL, PLATFORM_PACKAGE_NAME);
+ Sets.newArraySet(PACKAGE_MANAGER_SENTINEL, PLATFORM_PACKAGE_NAME, WALLPAPER_PACKAGE);
private final PackageManager mPackageManager;
private final PackageManagerInternal mPackageManagerInternal;
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index db163dcae395..5985ce45e4d5 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -34,6 +34,7 @@ import android.companion.AssociationInfo;
import android.companion.virtual.IVirtualDevice;
import android.companion.virtual.IVirtualDeviceActivityListener;
import android.companion.virtual.IVirtualDeviceIntentInterceptor;
+import android.companion.virtual.IVirtualDeviceSoundEffectListener;
import android.companion.virtual.VirtualDeviceManager;
import android.companion.virtual.VirtualDeviceManager.ActivityListener;
import android.companion.virtual.VirtualDeviceParams;
@@ -119,6 +120,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
private final VirtualDeviceParams mParams;
private final Map<Integer, PowerManager.WakeLock> mPerDisplayWakelocks = new ArrayMap<>();
private final IVirtualDeviceActivityListener mActivityListener;
+ private final IVirtualDeviceSoundEffectListener mSoundEffectListener;
@GuardedBy("mVirtualDeviceLock")
private final Map<IBinder, IntentFilter> mIntentInterceptors = new ArrayMap<>();
@NonNull
@@ -170,6 +172,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
OnDeviceCloseListener onDeviceCloseListener,
PendingTrampolineCallback pendingTrampolineCallback,
IVirtualDeviceActivityListener activityListener,
+ IVirtualDeviceSoundEffectListener soundEffectListener,
Consumer<ArraySet<Integer>> runningAppsChangedCallback,
VirtualDeviceParams params) {
this(
@@ -184,6 +187,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
onDeviceCloseListener,
pendingTrampolineCallback,
activityListener,
+ soundEffectListener,
runningAppsChangedCallback,
params);
}
@@ -201,6 +205,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
OnDeviceCloseListener onDeviceCloseListener,
PendingTrampolineCallback pendingTrampolineCallback,
IVirtualDeviceActivityListener activityListener,
+ IVirtualDeviceSoundEffectListener soundEffectListener,
Consumer<ArraySet<Integer>> runningAppsChangedCallback,
VirtualDeviceParams params) {
super(PermissionEnforcer.fromContext(context));
@@ -209,6 +214,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
mAssociationInfo = associationInfo;
mPendingTrampolineCallback = pendingTrampolineCallback;
mActivityListener = activityListener;
+ mSoundEffectListener = soundEffectListener;
mRunningAppsChangedCallback = runningAppsChangedCallback;
mOwnerUid = ownerUid;
mDeviceId = deviceId;
@@ -937,6 +943,14 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
Toast.LENGTH_LONG, mContext.getMainLooper());
}
+ void playSoundEffect(int effectType) {
+ try {
+ mSoundEffectListener.onPlaySoundEffect(effectType);
+ } catch (RemoteException exception) {
+ Slog.w(TAG, "Unable to invoke sound effect listener", exception);
+ }
+ }
+
/**
* Intercepts intent when matching any of the IntentFilter of any interceptor. Returns true if
* the intent matches any filter notifying the DisplayPolicyController to abort the
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index b0f2464ff52a..47ec80e5de5e 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -30,6 +30,7 @@ import android.companion.CompanionDeviceManager;
import android.companion.virtual.IVirtualDevice;
import android.companion.virtual.IVirtualDeviceActivityListener;
import android.companion.virtual.IVirtualDeviceManager;
+import android.companion.virtual.IVirtualDeviceSoundEffectListener;
import android.companion.virtual.VirtualDevice;
import android.companion.virtual.VirtualDeviceManager;
import android.companion.virtual.VirtualDeviceParams;
@@ -222,7 +223,8 @@ public class VirtualDeviceManagerService extends SystemService {
String packageName,
int associationId,
@NonNull VirtualDeviceParams params,
- @NonNull IVirtualDeviceActivityListener activityListener) {
+ @NonNull IVirtualDeviceActivityListener activityListener,
+ @NonNull IVirtualDeviceSoundEffectListener soundEffectListener) {
getContext().enforceCallingOrSelfPermission(
android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
"createVirtualDevice");
@@ -246,7 +248,7 @@ public class VirtualDeviceManagerService extends SystemService {
VirtualDeviceImpl virtualDevice = new VirtualDeviceImpl(getContext(),
associationInfo, token, callingUid, deviceId, cameraAccessController,
this::onDeviceClosed, mPendingTrampolineCallback, activityListener,
- runningAppsChangedCallback, params);
+ soundEffectListener, runningAppsChangedCallback, params);
mVirtualDevices.put(deviceId, virtualDevice);
return virtualDevice;
}
@@ -364,6 +366,18 @@ public class VirtualDeviceManagerService extends SystemService {
}
}
+ @Override // Binder call
+ public void playSoundEffect(int deviceId, int effectType) {
+ VirtualDeviceImpl virtualDevice;
+ synchronized (mVirtualDeviceManagerLock) {
+ virtualDevice = mVirtualDevices.get(deviceId);
+ }
+
+ if (virtualDevice != null) {
+ virtualDevice.playSoundEffect(effectType);
+ }
+ }
+
@Nullable
private AssociationInfo getAssociationInfo(String packageName, int associationId) {
final UserHandle userHandle = getCallingUserHandle();
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
index 661319f3b764..67f5f1b6ea04 100644
--- a/services/core/java/com/android/server/BinaryTransparencyService.java
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -44,6 +44,13 @@ import android.content.pm.SigningInfo;
import android.content.pm.parsing.result.ParseInput;
import android.content.pm.parsing.result.ParseResult;
import android.content.pm.parsing.result.ParseTypeImpl;
+import android.hardware.biometrics.SensorProperties;
+import android.hardware.biometrics.SensorProperties.ComponentInfo;
+import android.hardware.face.FaceManager;
+import android.hardware.face.FaceSensorProperties;
+import android.hardware.fingerprint.FingerprintManager;
+import android.hardware.fingerprint.FingerprintSensorProperties;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
@@ -1111,6 +1118,15 @@ public class BinaryTransparencyService extends SystemService {
Slog.i(TAG, "Boot completed. Getting VBMeta Digest.");
getVBMetaDigestInformation();
+ // Log to statsd
+ // TODO(b/264061957): For now, biometric system properties are always collected if users
+ // share usage & diagnostics information. In the future, collect biometric system
+ // properties only when transparency log verification of the target partitions fails
+ // (e.g. when the system/vendor partitions have been changed) once the binary
+ // transparency infrastructure is ready.
+ Slog.i(TAG, "Boot completed. Collecting biometric system properties.");
+ collectBiometricProperties();
+
// to avoid the risk of holding up boot time, computations to measure APEX, Module, and
// MBA digests are scheduled here, but only executed when the device is idle and plugged
// in.
@@ -1207,6 +1223,141 @@ public class BinaryTransparencyService extends SystemService {
}
}
+ /**
+ * Convert a {@link FingerprintSensorProperties} sensor type to the corresponding enum to be
+ * logged.
+ *
+ * @param sensorType See {@link FingerprintSensorProperties}
+ * @return The enum to be logged
+ */
+ private int toFingerprintSensorType(@FingerprintSensorProperties.SensorType int sensorType) {
+ switch (sensorType) {
+ case FingerprintSensorProperties.TYPE_REAR:
+ return FrameworkStatsLog
+ .BIOMETRIC_PROPERTIES_COLLECTED__SENSOR_TYPE__SENSOR_FP_REAR;
+ case FingerprintSensorProperties.TYPE_UDFPS_ULTRASONIC:
+ return FrameworkStatsLog
+ .BIOMETRIC_PROPERTIES_COLLECTED__SENSOR_TYPE__SENSOR_FP_UDFPS_ULTRASONIC;
+ case FingerprintSensorProperties.TYPE_UDFPS_OPTICAL:
+ return FrameworkStatsLog
+ .BIOMETRIC_PROPERTIES_COLLECTED__SENSOR_TYPE__SENSOR_FP_UDFPS_OPTICAL;
+ case FingerprintSensorProperties.TYPE_POWER_BUTTON:
+ return FrameworkStatsLog
+ .BIOMETRIC_PROPERTIES_COLLECTED__SENSOR_TYPE__SENSOR_FP_POWER_BUTTON;
+ case FingerprintSensorProperties.TYPE_HOME_BUTTON:
+ return FrameworkStatsLog
+ .BIOMETRIC_PROPERTIES_COLLECTED__SENSOR_TYPE__SENSOR_FP_HOME_BUTTON;
+ default:
+ return FrameworkStatsLog
+ .BIOMETRIC_PROPERTIES_COLLECTED__SENSOR_TYPE__SENSOR_UNKNOWN;
+ }
+ }
+
+ /**
+ * Convert a {@link FaceSensorProperties} sensor type to the corresponding enum to be logged.
+ *
+ * @param sensorType See {@link FaceSensorProperties}
+ * @return The enum to be logged
+ */
+ private int toFaceSensorType(@FaceSensorProperties.SensorType int sensorType) {
+ switch (sensorType) {
+ case FaceSensorProperties.TYPE_RGB:
+ return FrameworkStatsLog
+ .BIOMETRIC_PROPERTIES_COLLECTED__SENSOR_TYPE__SENSOR_FACE_RGB;
+ case FaceSensorProperties.TYPE_IR:
+ return FrameworkStatsLog
+ .BIOMETRIC_PROPERTIES_COLLECTED__SENSOR_TYPE__SENSOR_FACE_IR;
+ default:
+ return FrameworkStatsLog
+ .BIOMETRIC_PROPERTIES_COLLECTED__SENSOR_TYPE__SENSOR_UNKNOWN;
+ }
+ }
+
+ /**
+ * Convert a {@link SensorProperties} sensor strength to the corresponding enum to be logged.
+ *
+ * @param sensorStrength See {@link SensorProperties}
+ * @return The enum to be logged
+ */
+ private int toSensorStrength(@SensorProperties.Strength int sensorStrength) {
+ switch (sensorStrength) {
+ case SensorProperties.STRENGTH_CONVENIENCE:
+ return FrameworkStatsLog
+ .BIOMETRIC_PROPERTIES_COLLECTED__SENSOR_STRENGTH__STRENGTH_CONVENIENCE;
+ case SensorProperties.STRENGTH_WEAK:
+ return FrameworkStatsLog
+ .BIOMETRIC_PROPERTIES_COLLECTED__SENSOR_STRENGTH__STRENGTH_WEAK;
+ case SensorProperties.STRENGTH_STRONG:
+ return FrameworkStatsLog
+ .BIOMETRIC_PROPERTIES_COLLECTED__SENSOR_STRENGTH__STRENGTH_STRONG;
+ default:
+ return FrameworkStatsLog
+ .BIOMETRIC_PROPERTIES_COLLECTED__SENSOR_STRENGTH__STRENGTH_UNKNOWN;
+ }
+ }
+
+ /**
+ * A helper function to log detailed biometric sensor properties to statsd.
+ *
+ * @param prop The biometric sensor properties to be logged
+ * @param modality The modality of the biometric (e.g. fingerprint, face) to be logged
+ * @param sensorType The specific type of the biometric to be logged
+ */
+ private void logBiometricProperties(SensorProperties prop, int modality, int sensorType) {
+ final int sensorId = prop.getSensorId();
+ final int sensorStrength = toSensorStrength(prop.getSensorStrength());
+
+ // Log data for each component
+ // Note: none of the component info is a device identifier since every device of a given
+ // model and build share the same biometric system info (see b/216195167)
+ for (ComponentInfo componentInfo : prop.getComponentInfo()) {
+ FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_PROPERTIES_COLLECTED,
+ sensorId,
+ modality,
+ sensorType,
+ sensorStrength,
+ componentInfo.getComponentId().trim(),
+ componentInfo.getHardwareVersion().trim(),
+ componentInfo.getFirmwareVersion().trim(),
+ componentInfo.getSerialNumber().trim(),
+ componentInfo.getSoftwareVersion().trim());
+ }
+ }
+
+ private void collectBiometricProperties() {
+ PackageManager pm = mContext.getPackageManager();
+ FingerprintManager fpManager = null;
+ FaceManager faceManager = null;
+ if (pm != null && pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
+ fpManager = mContext.getSystemService(FingerprintManager.class);
+ }
+ if (pm != null && pm.hasSystemFeature(PackageManager.FEATURE_FACE)) {
+ faceManager = mContext.getSystemService(FaceManager.class);
+ }
+
+ if (fpManager != null) {
+ // Log data for each fingerprint sensor
+ for (FingerprintSensorPropertiesInternal propInternal :
+ fpManager.getSensorPropertiesInternal()) {
+ final FingerprintSensorProperties prop =
+ FingerprintSensorProperties.from(propInternal);
+ logBiometricProperties(prop,
+ FrameworkStatsLog
+ .BIOMETRIC_PROPERTIES_COLLECTED__MODALITY__MODALITY_FINGERPRINT,
+ toFingerprintSensorType(prop.getSensorType()));
+ }
+ }
+
+ if (faceManager != null) {
+ // Log data for each face sensor
+ for (FaceSensorProperties prop : faceManager.getSensorProperties()) {
+ logBiometricProperties(prop,
+ FrameworkStatsLog.BIOMETRIC_PROPERTIES_COLLECTED__MODALITY__MODALITY_FACE,
+ toFaceSensorType(prop.getSensorType()));
+ }
+ }
+ }
+
private void getVBMetaDigestInformation() {
mVbmetaDigest = SystemProperties.get(SYSPROP_NAME_VBETA_DIGEST, VBMETA_DIGEST_UNAVAILABLE);
Slog.d(TAG, String.format("VBMeta Digest: %s", mVbmetaDigest));
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 4e5ce8804bb1..7b8ca912ecf2 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -3976,6 +3976,66 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
}
}
+ /**
+ * Returns a string representation of the radio technology (network type)
+ * currently in use on the device.
+ * @param type for which network type is returned
+ * @return the name of the radio technology
+ *
+ */
+ private String getNetworkTypeName(@Annotation.NetworkType int type) {
+ switch (type) {
+ case TelephonyManager.NETWORK_TYPE_GPRS:
+ return "GPRS";
+ case TelephonyManager.NETWORK_TYPE_EDGE:
+ return "EDGE";
+ case TelephonyManager.NETWORK_TYPE_UMTS:
+ return "UMTS";
+ case TelephonyManager.NETWORK_TYPE_HSDPA:
+ return "HSDPA";
+ case TelephonyManager.NETWORK_TYPE_HSUPA:
+ return "HSUPA";
+ case TelephonyManager.NETWORK_TYPE_HSPA:
+ return "HSPA";
+ case TelephonyManager.NETWORK_TYPE_CDMA:
+ return "CDMA";
+ case TelephonyManager.NETWORK_TYPE_EVDO_0:
+ return "CDMA - EvDo rev. 0";
+ case TelephonyManager.NETWORK_TYPE_EVDO_A:
+ return "CDMA - EvDo rev. A";
+ case TelephonyManager.NETWORK_TYPE_EVDO_B:
+ return "CDMA - EvDo rev. B";
+ case TelephonyManager.NETWORK_TYPE_1xRTT:
+ return "CDMA - 1xRTT";
+ case TelephonyManager.NETWORK_TYPE_LTE:
+ return "LTE";
+ case TelephonyManager.NETWORK_TYPE_EHRPD:
+ return "CDMA - eHRPD";
+ case TelephonyManager.NETWORK_TYPE_IDEN:
+ return "iDEN";
+ case TelephonyManager.NETWORK_TYPE_HSPAP:
+ return "HSPA+";
+ case TelephonyManager.NETWORK_TYPE_GSM:
+ return "GSM";
+ case TelephonyManager.NETWORK_TYPE_TD_SCDMA:
+ return "TD_SCDMA";
+ case TelephonyManager.NETWORK_TYPE_IWLAN:
+ return "IWLAN";
+
+ //TODO: This network type is marked as hidden because it is not a
+ // true network type and we are looking to remove it completely from the available list
+ // of network types. Since this method is only used for logging, in the event that this
+ // network type is selected, the log will read as "Unknown."
+ //case TelephonyManager.NETWORK_TYPE_LTE_CA:
+ // return "LTE_CA";
+
+ case TelephonyManager.NETWORK_TYPE_NR:
+ return "NR";
+ default:
+ return "UNKNOWN";
+ }
+ }
+
/** Returns a new PreciseCallState object with default values. */
private static PreciseCallState createPreciseCallState() {
return new PreciseCallState(PreciseCallState.PRECISE_CALL_STATE_NOT_VALID,
diff --git a/services/core/java/com/android/server/VpnManagerService.java b/services/core/java/com/android/server/VpnManagerService.java
index ae50b2358139..2b43ef48b922 100644
--- a/services/core/java/com/android/server/VpnManagerService.java
+++ b/services/core/java/com/android/server/VpnManagerService.java
@@ -22,6 +22,7 @@ import static com.android.net.module.util.PermissionUtils.enforceAnyPermissionOf
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -67,6 +68,7 @@ import com.android.internal.util.IndentingPrintWriter;
import com.android.server.connectivity.Vpn;
import com.android.server.connectivity.VpnProfileStore;
import com.android.server.net.LockdownVpnTracker;
+import com.android.server.pm.UserManagerInternal;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -93,6 +95,7 @@ public class VpnManagerService extends IVpnManager.Stub {
private final INetworkManagementService mNMS;
private final INetd mNetd;
private final UserManager mUserManager;
+ private final int mMainUserId;
@VisibleForTesting
@GuardedBy("mVpns")
@@ -145,6 +148,12 @@ public class VpnManagerService extends IVpnManager.Stub {
Vpn vpn, VpnProfile profile) {
return new LockdownVpnTracker(context, handler, vpn, profile);
}
+
+ /** Get the main user on the device. */
+ public @UserIdInt int getMainUserId() {
+ // TODO(b/265785220): Change to use UserManager method instead.
+ return LocalServices.getService(UserManagerInternal.class).getMainUserId();
+ }
}
public VpnManagerService(Context context, Dependencies deps) {
@@ -159,6 +168,7 @@ public class VpnManagerService extends IVpnManager.Stub {
mNMS = mDeps.getINetworkManagementService();
mNetd = mDeps.getNetd();
mUserManager = mContext.getSystemService(UserManager.class);
+ mMainUserId = mDeps.getMainUserId();
registerReceivers();
log("VpnManagerService starting up");
}
@@ -478,11 +488,12 @@ public class VpnManagerService extends IVpnManager.Stub {
@Override
public boolean updateLockdownVpn() {
- // Allow the system UID for the system server and for Settings.
+ // Allow the system UID for the system server and for Settings (from user 0 or main user).
// Also, for unit tests, allow the process that ConnectivityService is running in.
if (mDeps.getCallingUid() != Process.SYSTEM_UID
+ && mDeps.getCallingUid() != UserHandle.getUid(mMainUserId, Process.SYSTEM_UID)
&& Binder.getCallingPid() != Process.myPid()) {
- logw("Lockdown VPN only available to system process or AID_SYSTEM");
+ logw("Lockdown VPN only available to system process or AID_SYSTEM on main user");
return false;
}
@@ -697,7 +708,7 @@ public class VpnManagerService extends IVpnManager.Stub {
intentFilter,
null /* broadcastPermission */,
mHandler);
- mContext.createContextAsUser(UserHandle.SYSTEM, 0 /* flags */).registerReceiver(
+ mContext.createContextAsUser(UserHandle.of(mMainUserId), 0 /* flags */).registerReceiver(
mUserPresentReceiver,
new IntentFilter(Intent.ACTION_USER_PRESENT),
null /* broadcastPermission */,
@@ -735,6 +746,7 @@ public class VpnManagerService extends IVpnManager.Stub {
if (LockdownVpnTracker.ACTION_LOCKDOWN_RESET.equals(action)) {
onVpnLockdownReset();
+ return;
}
// UserId should be filled for below intents, check the existence.
@@ -795,7 +807,7 @@ public class VpnManagerService extends IVpnManager.Stub {
userVpn = mDeps.createVpn(mHandler.getLooper(), mContext, mNMS, mNetd, userId);
mVpns.put(userId, userVpn);
- if (user.isPrimary() && isLockdownVpnEnabled()) {
+ if (userId == mMainUserId && isLockdownVpnEnabled()) {
updateLockdownVpn();
}
}
@@ -910,15 +922,9 @@ public class VpnManagerService extends IVpnManager.Stub {
}
private void onUserUnlocked(int userId) {
- UserInfo user = mUserManager.getUserInfo(userId);
- if (user == null) {
- logw("Unlocked user doesn't exist. UserId: " + userId);
- return;
- }
-
synchronized (mVpns) {
// User present may be sent because of an unlock, which might mean an unlocked keystore.
- if (user.isPrimary() && isLockdownVpnEnabled()) {
+ if (userId == mMainUserId && isLockdownVpnEnabled()) {
updateLockdownVpn();
} else {
startAlwaysOnVpn(userId);
@@ -984,7 +990,7 @@ public class VpnManagerService extends IVpnManager.Stub {
}
// Turn Always-on VPN off
- if (mLockdownEnabled && userId == UserHandle.USER_SYSTEM) {
+ if (mLockdownEnabled && userId == mMainUserId) {
final long ident = Binder.clearCallingIdentity();
try {
mVpnProfileStore.remove(Credentials.LOCKDOWN_VPN);
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 35b5f1b05788..c16314b6a117 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -1491,13 +1491,7 @@ public class AccountManagerService
Account[] sharedAccounts = getSharedAccountsAsUser(userId);
if (sharedAccounts == null || sharedAccounts.length == 0) return;
Account[] accounts = getAccountsAsUser(null, userId, mContext.getOpPackageName());
- int parentUserId = UserManager.isSplitSystemUser()
- ? getUserManager().getUserInfo(userId).restrictedProfileParentId
- : UserHandle.USER_SYSTEM;
- if (parentUserId < 0) {
- Log.w(TAG, "User " + userId + " has shared accounts, but no parent user");
- return;
- }
+ int parentUserId = UserHandle.USER_SYSTEM;
for (Account sa : sharedAccounts) {
if (ArrayUtils.contains(accounts, sa)) continue;
// Account doesn't exist. Copy it now.
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 6719fdbbeb8b..41437d112d0b 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -1940,6 +1940,12 @@ public final class ActiveServices {
ignoreForeground = true;
}
+ // Whether FGS-BG-start restriction is enabled for this service.
+ final boolean isBgFgsRestrictionEnabledForService = isBgFgsRestrictionEnabled(r);
+
+ // Whether to extend the SHORT_SERVICE time out.
+ boolean extendShortServiceTimeout = false;
+
int fgsTypeCheckCode = FGS_TYPE_POLICY_CHECK_UNKNOWN;
if (!ignoreForeground) {
if (foregroundServiceType == FOREGROUND_SERVICE_TYPE_SHORT_SERVICE
@@ -1955,41 +1961,77 @@ public final class ActiveServices {
"startForeground(SHORT_SERVICE) called on a service that's not"
+ " started.");
}
- // If the service is already an FGS, and the type is changing, then we
- // may need to do some extra work here.
- if (r.isForeground && (r.foregroundServiceType != foregroundServiceType)) {
- // TODO(short-service): Consider transitions:
- // A. Short -> other types:
- // Apply the BG restriction again. Don't just allow it.
- // i.e. unless the app is in a situation where it's allowed to start
- // a FGS, this transition shouldn't be allowed.
- // ... But think about it more, there may be a case this should be
- // allowed.
- //
- // If the transition is allowed, stop the timeout.
- // If the transition is _not_ allowed... keep the timeout?
- //
- // B. Short -> Short:
- // Allowed, but the timeout won't reset. The original timeout is used.
- // C. Other -> short:
- // This should always be allowed.
- // A timeout should start.
-
- // For now, let's just disallow transition from / to SHORT_SERVICE.
- final boolean isNewTypeShortFgs =
- foregroundServiceType == FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
- if (r.isShortFgs() != isNewTypeShortFgs) {
- // TODO(short-service): We should (probably) allow it.
- throw new IllegalArgumentException(
- "setForeground(): Changing foreground service type from / to "
- + " SHORT_SERVICE is now allowed");
- }
- }
- // If a valid short-service (which has to be "started"), happens to
+ // Side note: If a valid short-service (which has to be "started"), happens to
// also be bound, then we still _will_ apply a timeout, because it still has
// to be stopped.
- if (r.mStartForegroundCount == 0) {
+
+ // Calling startForeground on a SHORT_SERVICE will require some additional
+ // checks.
+ // A) SHORT_SERVICE -> another type.
+ // - This should be allowed only when the app could start another FGS.
+ // - When succeed, the timeout should stop.
+ // B) SHORT_SERVICE -> SHORT_SERVICE
+ // - If the app could start an FGS, then this would extend the timeout.
+ // - Otherwise, it's basically a no-op.
+ // - If it's already timed out, we also throw.
+ // Also,
+ // C) another type -> SHORT_SERVICE
+ // - This will always be allowed.
+ // - Timeout will start.
+
+ final boolean isOldTypeShortFgs = r.isShortFgs();
+ final boolean isNewTypeShortFgs =
+ foregroundServiceType == FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
+ final boolean isOldTypeShortFgsAndTimedOut = r.shouldTriggerShortFgsTimeout();
+
+ if (isOldTypeShortFgs || isNewTypeShortFgs) {
+ if (DEBUG_SHORT_SERVICE) {
+ Slog.i(TAG_SERVICE, String.format(
+ "FGS type changing from %x%s to %x: %s",
+ r.foregroundServiceType,
+ (isOldTypeShortFgsAndTimedOut ? "(timed out short FGS)" : ""),
+ foregroundServiceStartType,
+ r.toString()));
+ }
+ }
+
+ if (r.isForeground && isOldTypeShortFgs) {
+ // If we get here, that means startForeground(SHORT_SERVICE) is called again
+ // on a SHORT_SERVICE FGS.
+
+ // See if the app could start an FGS or not.
+ r.mAllowStartForeground = REASON_DENIED;
+ setFgsRestrictionLocked(r.serviceInfo.packageName, r.app.getPid(),
+ r.appInfo.uid, r.intent.getIntent(), r, r.userId,
+ BackgroundStartPrivileges.NONE,
+ false /* isBindService */);
+
+ final boolean fgsStartAllowed =
+ !isBgFgsRestrictionEnabledForService
+ || (r.mAllowStartForeground != REASON_DENIED);
+
+ if (fgsStartAllowed) {
+ if (isNewTypeShortFgs) {
+ // Only in this case, we extend the SHORT_SERVICE time out.
+ extendShortServiceTimeout = true;
+ if (DEBUG_SHORT_SERVICE) {
+ Slog.i(TAG_SERVICE, "Extending SHORT_SERVICE time out: " + r);
+ }
+ } else {
+ // FGS type is changing from SHORT_SERVICE to another type when
+ // an app is allowed to start FGS, so this will succeed.
+ // The timeout will stop -- we actually don't cancel the handler
+ // events, but they'll be ignored if the service type is not
+ // SHORT_SERVICE.
+ // TODO(short-service) Let's actaully cancel the handler events.
+ }
+ } else {
+ // We catch this case later, in the
+ // "if (r.mAllowStartForeground == REASON_DENIED...)" block below.
+ }
+
+ } else if (r.mStartForegroundCount == 0) {
/*
If the service was started with startService(), not
startForegroundService(), and if startForeground() isn't called within
@@ -2032,6 +2074,7 @@ public final class ActiveServices {
BackgroundStartPrivileges.NONE,
false /* isBindService */);
}
+
// If the foreground service is not started from TOP process, do not allow it to
// have while-in-use location/camera/microphone access.
if (!r.mAllowWhileInUsePermissionInFgs) {
@@ -2041,10 +2084,12 @@ public final class ActiveServices {
+ r.shortInstanceName);
}
logFgsBackgroundStart(r);
- if (r.mAllowStartForeground == REASON_DENIED && isBgFgsRestrictionEnabled(r)) {
+ if (r.mAllowStartForeground == REASON_DENIED
+ && isBgFgsRestrictionEnabledForService) {
final String msg = "Service.startForeground() not allowed due to "
+ "mAllowStartForeground false: service "
- + r.shortInstanceName;
+ + r.shortInstanceName
+ + (isOldTypeShortFgs ? " (Called on SHORT_SERVICE)" : "");
Slog.w(TAG, msg);
showFgsBgRestrictedNotificationLocked(r);
updateServiceForegroundLocked(psr, true);
@@ -2181,11 +2226,8 @@ public final class ActiveServices {
mAm.notifyPackageUse(r.serviceInfo.packageName,
PackageManager.NOTIFY_PACKAGE_USE_FOREGROUND_SERVICE);
- // Note, we'll get here if setForeground(SHORT_SERVICE) is called on a
- // already short-fgs.
- // In that case, because ShortFgsInfo is already set, this method
- // will be noop.
- maybeStartShortFgsTimeoutAndUpdateShortFgsInfoLocked(r);
+ maybeStartShortFgsTimeoutAndUpdateShortFgsInfoLocked(r,
+ extendShortServiceTimeout);
} else {
if (DEBUG_FOREGROUND_SERVICE) {
Slog.d(TAG, "Suppressing startForeground() for FAS " + r);
@@ -2982,17 +3024,21 @@ public final class ActiveServices {
/**
* If {@code sr} is of a short-fgs, start a short-FGS timeout.
*/
- private void maybeStartShortFgsTimeoutAndUpdateShortFgsInfoLocked(ServiceRecord sr) {
+ private void maybeStartShortFgsTimeoutAndUpdateShortFgsInfoLocked(ServiceRecord sr,
+ boolean extendTimeout) {
if (!sr.isShortFgs()) {
return;
}
if (DEBUG_SHORT_SERVICE) {
Slog.i(TAG_SERVICE, "Short FGS started: " + sr);
}
- if (sr.hasShortFgsInfo()) {
- sr.getShortFgsInfo().update();
- } else {
+
+ if (extendTimeout || !sr.hasShortFgsInfo()) {
sr.setShortFgsInfo(SystemClock.uptimeMillis());
+ } else {
+ // We only (potentially) update the start command, start count, but not the timeout
+ // time.
+ sr.getShortFgsInfo().update();
}
unscheduleShortFgsTimeoutLocked(sr); // Do it just in case
@@ -7562,7 +7608,8 @@ public final class ActiveServices {
if (!r.mLoggedInfoAllowStartForeground) {
final String msg = "Background started FGS: "
+ ((r.mAllowStartForeground != REASON_DENIED) ? "Allowed " : "Disallowed ")
- + r.mInfoAllowStartForeground;
+ + r.mInfoAllowStartForeground
+ + (r.isShortFgs() ? " (Called on SHORT_SERVICE)" : "");
if (r.mAllowStartForeground != REASON_DENIED) {
if (ActivityManagerUtils.shouldSamplePackageForAtom(r.packageName,
mAm.mConstants.mFgsStartAllowedLogSampleRate)) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index e962fb021cbb..de87a0cf7f98 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1606,8 +1606,6 @@ public class ActivityManagerService extends IActivityManager.Stub
// Encapsulates the global setting "hidden_api_blacklist_exemptions"
final HiddenApiSettings mHiddenApiBlacklist;
- final SdkSandboxSettings mSdkSandboxSettings;
-
private final PlatformCompat mPlatformCompat;
PackageManagerInternal mPackageManagerInt;
@@ -2324,53 +2322,6 @@ public class ActivityManagerService extends IActivityManager.Stub
}
}
- /**
- * Handles settings related to the enforcement of SDK sandbox restrictions.
- */
- static class SdkSandboxSettings implements DeviceConfig.OnPropertiesChangedListener {
-
- private final Context mContext;
- private final Object mLock = new Object();
-
- @GuardedBy("mLock")
- private boolean mEnforceBroadcastReceiverRestrictions;
-
- /**
- * Property to enforce broadcast receiver restrictions for SDK sandbox processes. If the
- * value of this property is {@code true}, the restrictions will be enforced.
- */
- public static final String ENFORCE_BROADCAST_RECEIVER_RESTRICTIONS =
- "enforce_broadcast_receiver_restrictions";
-
- SdkSandboxSettings(Context context) {
- mContext = context;
- }
-
- void registerObserver() {
- synchronized (mLock) {
- mEnforceBroadcastReceiverRestrictions = DeviceConfig.getBoolean(
- DeviceConfig.NAMESPACE_SDK_SANDBOX,
- ENFORCE_BROADCAST_RECEIVER_RESTRICTIONS, false);
- DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SDK_SANDBOX,
- mContext.getMainExecutor(), this);
- }
- }
-
- @Override
- public void onPropertiesChanged(DeviceConfig.Properties properties) {
- synchronized (mLock) {
- mEnforceBroadcastReceiverRestrictions = properties.getBoolean(
- ENFORCE_BROADCAST_RECEIVER_RESTRICTIONS, false);
- }
- }
-
- boolean isBroadcastReceiverRestrictionsEnforced() {
- synchronized (mLock) {
- return mEnforceBroadcastReceiverRestrictions;
- }
- }
- }
-
AppOpsManager getAppOpsManager() {
if (mAppOpsManager == null) {
mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
@@ -2414,7 +2365,6 @@ public class ActivityManagerService extends IActivityManager.Stub
mProcStartHandlerThread = null;
mProcStartHandler = null;
mHiddenApiBlacklist = null;
- mSdkSandboxSettings = null;
mFactoryTest = FACTORY_TEST_OFF;
mUgmInternal = LocalServices.getService(UriGrantsManagerInternal.class);
mInternal = new LocalService();
@@ -2539,7 +2489,6 @@ public class ActivityManagerService extends IActivityManager.Stub
mAtmInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
mHiddenApiBlacklist = new HiddenApiSettings(mHandler, mContext);
- mSdkSandboxSettings = new SdkSandboxSettings(mContext);
Watchdog.getInstance().addMonitor(this);
Watchdog.getInstance().addThread(mHandler);
@@ -7438,11 +7387,12 @@ public class ActivityManagerService extends IActivityManager.Stub
if (shareDescription != null) {
triggerShellBugreport.putExtra(EXTRA_DESCRIPTION, shareDescription);
}
- UserHandle callingUser = Binder.getCallingUserHandle();
final long identity = Binder.clearCallingIdentity();
try {
// Send broadcast to shell to trigger bugreport using Bugreport API
- mContext.sendBroadcastAsUser(triggerShellBugreport, callingUser);
+ // Always start the shell process on the current user to ensure that
+ // the foreground user can see all bugreport notifications.
+ mContext.sendBroadcastAsUser(triggerShellBugreport, getCurrentUser().getUserHandle());
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -7514,7 +7464,10 @@ public class ActivityManagerService extends IActivityManager.Stub
*/
@Override
public boolean launchBugReportHandlerApp() {
- if (!BugReportHandlerUtil.isBugReportHandlerEnabled(mContext)) {
+
+ Context currentUserContext = mContext.createContextAsUser(getCurrentUser().getUserHandle(),
+ /* flags= */ 0);
+ if (!BugReportHandlerUtil.isBugReportHandlerEnabled(currentUserContext)) {
return false;
}
@@ -7523,7 +7476,7 @@ public class ActivityManagerService extends IActivityManager.Stub
enforceCallingPermission(android.Manifest.permission.DUMP,
"launchBugReportHandlerApp");
- return BugReportHandlerUtil.launchBugReportHandlerApp(mContext);
+ return BugReportHandlerUtil.launchBugReportHandlerApp(currentUserContext);
}
/**
@@ -8296,7 +8249,6 @@ public class ActivityManagerService extends IActivityManager.Stub
final boolean alwaysFinishActivities =
Settings.Global.getInt(resolver, ALWAYS_FINISH_ACTIVITIES, 0) != 0;
mHiddenApiBlacklist.registerObserver();
- mSdkSandboxSettings.registerObserver();
mPlatformCompat.registerContentObserver();
mAppProfiler.retrieveSettings();
@@ -8467,10 +8419,7 @@ public class ActivityManagerService extends IActivityManager.Stub
// Enable home activity for system user, so that the system can always boot. We don't
// do this when the system user is not setup since the setup wizard should be the one
// to handle home activity in this case.
- if (UserManager.isSplitSystemUser() &&
- Settings.Secure.getIntForUser(mContext.getContentResolver(),
- Settings.Secure.USER_SETUP_COMPLETE, 0, currentUserId) != 0
- || SystemProperties.getBoolean(SYSTEM_USER_HOME_NEEDED, false)) {
+ if (SystemProperties.getBoolean(SYSTEM_USER_HOME_NEEDED, false)) {
t.traceBegin("enableHomeActivity");
ComponentName cName = new ComponentName(mContext, SystemUserHomeActivity.class);
try {
@@ -13541,16 +13490,6 @@ public class ActivityManagerService extends IActivityManager.Stub
String callerFeatureId, String receiverId, IIntentReceiver receiver,
IntentFilter filter, String permission, int userId, int flags) {
enforceNotIsolatedCaller("registerReceiver");
-
- // Allow Sandbox process to register only unexported receivers.
- boolean unexported = (flags & Context.RECEIVER_NOT_EXPORTED) != 0;
- if (mSdkSandboxSettings.isBroadcastReceiverRestrictionsEnforced()
- && Process.isSdkSandboxUid(Binder.getCallingUid())
- && !unexported) {
- throw new SecurityException("SDK sandbox process not allowed to call "
- + "registerReceiver");
- }
-
ArrayList<Intent> stickyIntents = null;
ProcessRecord callerApp = null;
final boolean visibleToInstantApps
@@ -13614,6 +13553,20 @@ public class ActivityManagerService extends IActivityManager.Stub
}
}
+ if (Process.isSdkSandboxUid(Binder.getCallingUid())) {
+ SdkSandboxManagerLocal sdkSandboxManagerLocal =
+ LocalManagerRegistry.getManager(SdkSandboxManagerLocal.class);
+ if (sdkSandboxManagerLocal == null) {
+ throw new IllegalStateException("SdkSandboxManagerLocal not found when checking"
+ + " whether SDK sandbox uid can register to broadcast receivers.");
+ }
+ if (!sdkSandboxManagerLocal.canRegisterBroadcastReceiver(
+ /*IntentFilter=*/ filter, flags, onlyProtectedBroadcasts)) {
+ throw new SecurityException("SDK sandbox not allowed to register receiver"
+ + " with the given IntentFilter");
+ }
+ }
+
// If the change is enabled, but neither exported or not exported is set, we need to log
// an error so the consumer can know to explicitly set the value for their flag.
// If the caller is registering for a sticky broadcast with a null receiver, we won't
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 5b453b268e79..0327d161928e 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -380,7 +380,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub
}
mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(context, mStats,
mBatteryUsageStatsStore);
- mCpuWakeupStats = new CpuWakeupStats(context, R.xml.irq_device_map);
+ mCpuWakeupStats = new CpuWakeupStats(context, R.xml.irq_device_map, mHandler);
}
public void publish() {
diff --git a/services/core/java/com/android/server/am/BugReportHandlerUtil.java b/services/core/java/com/android/server/am/BugReportHandlerUtil.java
index 2142ebc7ee8e..63b14b8de989 100644
--- a/services/core/java/com/android/server/am/BugReportHandlerUtil.java
+++ b/services/core/java/com/android/server/am/BugReportHandlerUtil.java
@@ -17,6 +17,7 @@
package com.android.server.am;
import static android.app.AppOpsManager.OP_NONE;
+
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -66,29 +67,29 @@ public final class BugReportHandlerUtil {
* Launches a bugreport-allowlisted app to handle a bugreport.
*
* <p>Allows a bug report handler app to take bugreports on the user's behalf. The handler can
- * be predefined in the config, meant to be launched with the primary user. The user can
- * override this with a different (or same) handler app on possibly a different user. This is
- * useful for capturing bug reports from work profile, for instance.
- *
- * @param context Context
- * @return true if there is a bugreport-allowlisted app to handle a bugreport, or false
+ * be predefined in the config, meant to be launched with the current foreground user. The user
+ * can override this with a different (or same) handler app on possibly a different
+ * user profile. This is useful for capturing bug reports from work profile, for instance.
+ * @param userContext Context of the current foreground user
+ * @return true if there is a bugreport-allow-listed app to handle a bugreport, or false
* otherwise
*/
- static boolean launchBugReportHandlerApp(Context context) {
- if (!isBugReportHandlerEnabled(context)) {
+ static boolean launchBugReportHandlerApp(Context userContext) {
+ if (!isBugReportHandlerEnabled(userContext)) {
return false;
}
- String handlerApp = getCustomBugReportHandlerApp(context);
+ String handlerApp = getCustomBugReportHandlerApp(userContext);
if (isShellApp(handlerApp)) {
return false;
}
- int handlerUser = getCustomBugReportHandlerUser(context);
+ int handlerUser = getCustomBugReportHandlerUser(userContext);
if (!isValidBugReportHandlerApp(handlerApp)) {
- handlerApp = getDefaultBugReportHandlerApp(context);
- handlerUser = UserHandle.USER_SYSTEM;
- } else if (getBugReportHandlerAppReceivers(context, handlerApp, handlerUser).isEmpty()) {
+ handlerApp = getDefaultBugReportHandlerApp(userContext);
+ handlerUser = userContext.getUserId();
+ } else if (getBugReportHandlerAppReceivers(userContext, handlerApp, handlerUser)
+ .isEmpty()) {
// It looks like the settings are outdated, reset outdated settings.
//
// i.e.
@@ -97,21 +98,23 @@ public final class BugReportHandlerUtil {
// === RESULT ===
// The chosen bugreport handler app is outdated because the profile is removed,
// so reset the chosen app and profile
- handlerApp = getDefaultBugReportHandlerApp(context);
- handlerUser = UserHandle.USER_SYSTEM;
- resetCustomBugreportHandlerAppAndUser(context);
+ handlerApp = getDefaultBugReportHandlerApp(userContext);
+ handlerUser = userContext.getUserId();
+ resetCustomBugreportHandlerAppAndUser(userContext);
}
if (isShellApp(handlerApp) || !isValidBugReportHandlerApp(handlerApp)
- || getBugReportHandlerAppReceivers(context, handlerApp, handlerUser).isEmpty()) {
+ || getBugReportHandlerAppReceivers(userContext, handlerApp, handlerUser)
+ .isEmpty()) {
return false;
}
- if (getBugReportHandlerAppResponseReceivers(context, handlerApp, handlerUser).isEmpty()) {
+ if (getBugReportHandlerAppResponseReceivers(userContext, handlerApp, handlerUser)
+ .isEmpty()) {
// Just try to launch bugreport handler app to handle bugreport request
// because the bugreport handler app is old and not support to provide response to
// let BugReportHandlerUtil know it is available or not.
- launchBugReportHandlerApp(context, handlerApp, handlerUser);
+ launchBugReportHandlerApp(userContext, handlerApp, handlerUser);
return true;
}
@@ -124,7 +127,7 @@ public final class BugReportHandlerUtil {
try {
// Handler app's BroadcastReceiver should call setResultCode(Activity.RESULT_OK) to
// let BugreportHandlerResponseBroadcastReceiver know the handler app is available.
- context.sendOrderedBroadcastAsUser(intent,
+ userContext.sendOrderedBroadcastAsUser(intent,
UserHandle.of(handlerUser),
android.Manifest.permission.DUMP,
OP_NONE, /* options= */ null,
@@ -166,13 +169,14 @@ public final class BugReportHandlerUtil {
private static String getCustomBugReportHandlerApp(Context context) {
// Get the package of custom bugreport handler app
- return Settings.Global.getString(context.getContentResolver(),
- Settings.Global.CUSTOM_BUGREPORT_HANDLER_APP);
+ return Settings.Secure.getStringForUser(context.getContentResolver(),
+ Settings.Secure.CUSTOM_BUGREPORT_HANDLER_APP, context.getUserId());
}
private static int getCustomBugReportHandlerUser(Context context) {
- return Settings.Global.getInt(context.getContentResolver(),
- Settings.Global.CUSTOM_BUGREPORT_HANDLER_USER, UserHandle.USER_NULL);
+ return Settings.Secure.getIntForUser(context.getContentResolver(),
+ Settings.Secure.CUSTOM_BUGREPORT_HANDLER_USER, UserHandle.USER_NULL,
+ context.getUserId());
}
private static boolean isShellApp(String app) {
@@ -219,11 +223,11 @@ public final class BugReportHandlerUtil {
private static void resetCustomBugreportHandlerAppAndUser(Context context) {
final long identity = Binder.clearCallingIdentity();
try {
- Settings.Global.putString(context.getContentResolver(),
- Settings.Global.CUSTOM_BUGREPORT_HANDLER_APP,
+ Settings.Secure.putString(context.getContentResolver(),
+ Settings.Secure.CUSTOM_BUGREPORT_HANDLER_APP,
getDefaultBugReportHandlerApp(context));
- Settings.Global.putInt(context.getContentResolver(),
- Settings.Global.CUSTOM_BUGREPORT_HANDLER_USER, UserHandle.USER_SYSTEM);
+ Settings.Secure.putInt(context.getContentResolver(),
+ Settings.Secure.CUSTOM_BUGREPORT_HANDLER_USER, context.getUserId());
} finally {
Binder.restoreCallingIdentity(identity);
}
diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java
index d2fb7b55952d..85de637bf1ac 100644
--- a/services/core/java/com/android/server/am/ContentProviderHelper.java
+++ b/services/core/java/com/android/server/am/ContentProviderHelper.java
@@ -83,11 +83,13 @@ import com.android.internal.os.BackgroundThread;
import com.android.internal.os.TimeoutRecord;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.LocalManagerRegistry;
import com.android.server.LocalServices;
import com.android.server.RescueParty;
import com.android.server.pm.UserManagerInternal;
import com.android.server.pm.UserManagerService;
import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.sdksandbox.SdkSandboxManagerLocal;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -124,13 +126,6 @@ public class ContentProviderHelper {
ContentProviderHolder getContentProvider(IApplicationThread caller, String callingPackage,
String name, int userId, boolean stable) {
mService.enforceNotIsolatedCaller("getContentProvider");
- if (Process.isSdkSandboxUid(Binder.getCallingUid())) {
- // TODO(b/226318628): for sdk sandbox processes only allow accessing CPs registered by
- // the WebView apk.
- Slog.w(TAG, "Sdk sandbox process " + Binder.getCallingUid()
- + " is accessing content provider " + name
- + ". This access will most likely be blocked in the future");
- }
if (caller == null) {
String msg = "null IApplicationThread when getting content provider " + name;
Slog.w(TAG, msg);
@@ -255,6 +250,7 @@ public class ContentProviderHelper {
if (r != null && cpr.canRunHere(r)) {
checkAssociationAndPermissionLocked(r, cpi, callingUid, userId, checkCrossUser,
cpr.name.flattenToShortString(), startTime);
+ enforceContentProviderRestrictionsForSdkSandbox(cpi);
// This provider has been published or is in the process
// of being published... but it is also allowed to run
@@ -447,6 +443,7 @@ public class ContentProviderHelper {
// info and allow the caller to instantiate it. Only do
// this if the provider is the same user as the caller's
// process, or can run as root (so can be in any process).
+ enforceContentProviderRestrictionsForSdkSandbox(cpi);
return cpr.newHolder(null, true);
}
@@ -589,6 +586,8 @@ public class ContentProviderHelper {
// Return a holder instance even if we are waiting for the publishing of the
// provider, client will check for the holder.provider to see if it needs to wait
// for it.
+ //todo(b/265965249) Need to perform cleanup before calling enforce method here
+ enforceContentProviderRestrictionsForSdkSandbox(cpi);
return cpr.newHolder(conn, false);
}
}
@@ -650,6 +649,7 @@ public class ContentProviderHelper {
+ " caller=" + callerName + "/" + Binder.getCallingUid());
return null;
}
+ enforceContentProviderRestrictionsForSdkSandbox(cpi);
return cpr.newHolder(conn, false);
}
@@ -1230,6 +1230,7 @@ public class ContentProviderHelper {
appName = r.toString();
}
+ enforceContentProviderRestrictionsForSdkSandbox(cpi);
return checkContentProviderPermission(cpi, callingPid, Binder.getCallingUid(),
userId, checkUser, appName);
}
@@ -1998,6 +1999,26 @@ public class ContentProviderHelper {
}
}
+ // Binder.clearCallingIdentity() shouldn't be called before this method
+ // as Binder should have its original callingUid for the check
+ private void enforceContentProviderRestrictionsForSdkSandbox(ProviderInfo cpi) {
+ if (!Process.isSdkSandboxUid(Binder.getCallingUid())) {
+ return;
+ }
+ final SdkSandboxManagerLocal sdkSandboxManagerLocal =
+ LocalManagerRegistry.getManager(SdkSandboxManagerLocal.class);
+ if (sdkSandboxManagerLocal == null) {
+ throw new IllegalStateException("SdkSandboxManagerLocal not found "
+ + "when checking whether SDK sandbox uid may "
+ + "access the contentprovider.");
+ }
+ if (!sdkSandboxManagerLocal
+ .canAccessContentProviderFromSdkSandbox(cpi)) {
+ throw new SecurityException(
+ "SDK sandbox uid may not access contentprovider " + cpi.name);
+ }
+ }
+
/**
* There are three ways to call this:
* - no provider specified: dump all the providers
diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java
index 874fda32d784..43075bcabeab 100644
--- a/services/core/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/core/java/com/android/server/am/PendingIntentRecord.java
@@ -22,16 +22,21 @@ import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.BackgroundStartPrivileges;
import android.app.BroadcastOptions;
import android.app.IApplicationThread;
import android.app.PendingIntent;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
import android.content.IIntentReceiver;
import android.content.IIntentSender;
import android.content.Intent;
import android.os.Binder;
+import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.PowerWhitelistManager;
@@ -40,6 +45,7 @@ import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.TransactionTooLargeException;
import android.os.UserHandle;
+import android.provider.DeviceConfig;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;
@@ -56,6 +62,13 @@ import java.util.Objects;
public final class PendingIntentRecord extends IIntentSender.Stub {
private static final String TAG = TAG_WITH_CLASS_NAME ? "PendingIntentRecord" : TAG_AM;
+ /** If enabled BAL are prevented by default in applications targeting U and later. */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+ private static final long DEFAULT_RESCIND_BAL_PRIVILEGES_FROM_PENDING_INTENT_SENDER = 244637991;
+ private static final String ENABLE_DEFAULT_RESCIND_BAL_PRIVILEGES_FROM_PENDING_INTENT_SENDER =
+ "enable_default_rescind_bal_privileges_from_pending_intent_sender";
+
public static final int FLAG_ACTIVITY_SENDER = 1 << 0;
public static final int FLAG_BROADCAST_SENDER = 1 << 1;
public static final int FLAG_SERVICE_SENDER = 1 << 2;
@@ -357,16 +370,34 @@ public final class PendingIntentRecord extends IIntentSender.Stub {
: BackgroundStartPrivileges.NONE;
}
+ private static boolean isDefaultRescindBalPrivilegesFromPendingIntentSenderEnabled() {
+ return DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_WINDOW_MANAGER,
+ ENABLE_DEFAULT_RESCIND_BAL_PRIVILEGES_FROM_PENDING_INTENT_SENDER,
+ false); // assume false if the property is unknown
+ }
+
/**
* Default {@link BackgroundStartPrivileges} to be used if the intent sender has not made an
* explicit choice.
*
* @hide
*/
- public static BackgroundStartPrivileges getDefaultBackgroundStartPrivileges(int callingUid) {
- // TODO: In the next step this will return ALLOW_FGS instead, if the app that sent the
- // PendingIntent is targeting Android U
- return BackgroundStartPrivileges.ALLOW_BAL;
+ @RequiresPermission(
+ allOf = {
+ android.Manifest.permission.READ_COMPAT_CHANGE_CONFIG,
+ android.Manifest.permission.LOG_COMPAT_CHANGE
+ })
+ public static BackgroundStartPrivileges getDefaultBackgroundStartPrivileges(
+ int callingUid) {
+ boolean isFlagEnabled = isDefaultRescindBalPrivilegesFromPendingIntentSenderEnabled();
+ boolean isChangeEnabledForApp = CompatChanges.isChangeEnabled(
+ DEFAULT_RESCIND_BAL_PRIVILEGES_FROM_PENDING_INTENT_SENDER, callingUid);
+ if (isFlagEnabled && isChangeEnabledForApp) {
+ return BackgroundStartPrivileges.ALLOW_FGS;
+ } else {
+ return BackgroundStartPrivileges.ALLOW_BAL;
+ }
}
@Deprecated
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index f61737e3f549..3df060b2b47d 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -555,12 +555,6 @@ class UserController implements Handler.Callback {
// This user is already stopping, doesn't count.
continue;
}
- if (userId == UserHandle.USER_SYSTEM) {
- // We only count system user as running when it is not a pure system user.
- if (UserInfo.isSystemOnly(userId)) {
- continue;
- }
- }
runningUsers.add(userId);
}
return runningUsers;
diff --git a/services/core/java/com/android/server/am/UserSwitchingDialog.java b/services/core/java/com/android/server/am/UserSwitchingDialog.java
index 7a6603d1f243..a5651bfa3dde 100644
--- a/services/core/java/com/android/server/am/UserSwitchingDialog.java
+++ b/services/core/java/com/android/server/am/UserSwitchingDialog.java
@@ -95,9 +95,7 @@ class UserSwitchingDialog extends AlertDialog
R.layout.user_switching_dialog, null);
String viewMessage = null;
- if (UserManager.isSplitSystemUser() && mNewUser.id == UserHandle.USER_SYSTEM) {
- viewMessage = res.getString(R.string.user_logging_out_message, mOldUser.name);
- } else if (UserManager.isDeviceInDemoMode(mContext)) {
+ if (UserManager.isDeviceInDemoMode(mContext)) {
if (mOldUser.isDemo()) {
viewMessage = res.getString(R.string.demo_restarting_message);
} else {
diff --git a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
index 908cb3f0c720..684d6a0fc596 100644
--- a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
+++ b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
@@ -17,6 +17,8 @@
package com.android.server.app;
import static android.Manifest.permission.MANAGE_GAME_ACTIVITY;
+import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_OTHER;
+import static android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE;
import android.annotation.EnforcePermission;
import android.annotation.NonNull;
@@ -34,7 +36,6 @@ import android.graphics.Bitmap;
import android.graphics.Insets;
import android.graphics.Rect;
import android.net.Uri;
-import android.os.Bundle;
import android.os.RemoteException;
import android.os.UserHandle;
import android.service.games.CreateGameSessionRequest;
@@ -51,7 +52,6 @@ import android.text.TextUtils;
import android.util.Slog;
import android.view.SurfaceControl;
import android.view.SurfaceControlViewHost.SurfacePackage;
-import android.view.WindowManager;
import android.window.ScreenCapture;
import com.android.internal.annotations.GuardedBy;
@@ -61,6 +61,7 @@ import com.android.internal.infra.ServiceConnector;
import com.android.internal.infra.ServiceConnector.ServiceLifecycleCallbacks;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.ScreenshotHelper;
+import com.android.internal.util.ScreenshotRequest;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.WindowManagerInternal;
import com.android.server.wm.WindowManagerInternal.TaskSystemBarsListener;
@@ -863,8 +864,6 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan
Slog.w(TAG, "Could not get bitmap for id: " + taskId);
callback.complete(GameScreenshotResult.createInternalErrorResult());
} else {
- final Bundle bundle = ScreenshotHelper.HardwareBitmapBundler.hardwareBitmapToBundle(
- bitmap);
final RunningTaskInfo runningTaskInfo =
mGameTaskInfoProvider.getRunningTaskInfo(taskId);
if (runningTaskInfo == null) {
@@ -879,11 +878,17 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan
callback.complete(GameScreenshotResult.createSuccessResult());
}
};
- mScreenshotHelper.provideScreenshot(bundle, crop, Insets.NONE, taskId,
- mUserHandle.getIdentifier(), gameSessionRecord.getComponentName(),
- WindowManager.ScreenshotSource.SCREENSHOT_OTHER,
- BackgroundThread.getHandler(),
- completionConsumer);
+ ScreenshotRequest request = new ScreenshotRequest.Builder(
+ TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_OTHER)
+ .setTopComponent(gameSessionRecord.getComponentName())
+ .setTaskId(taskId)
+ .setUserId(mUserHandle.getIdentifier())
+ .setBitmap(bitmap)
+ .setBoundsOnScreen(crop)
+ .setInsets(Insets.NONE)
+ .build();
+ mScreenshotHelper.takeScreenshot(
+ request, BackgroundThread.getHandler(), completionConsumer);
}
});
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index c794b0429fcc..cb98c6653067 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -3782,7 +3782,7 @@ public class AudioService extends IAudioService.Stub
VolumeGroupState vgs = sVolumeGroupStates.get(volumeGroup);
sVolumeLogger.enqueue(new VolumeEvent(VolumeEvent.VOL_SET_GROUP_VOL, attr, vgs.name(),
- index/*val1*/, flags/*val2*/, callingPackage));
+ index, flags, callingPackage + ", user " + ActivityManager.getCurrentUser()));
vgs.setVolumeIndex(index, flags);
@@ -3803,7 +3803,7 @@ public class AudioService extends IAudioService.Stub
@Nullable
private AudioVolumeGroup getAudioVolumeGroupById(int volumeGroupId) {
- for (final AudioVolumeGroup avg : AudioVolumeGroup.getAudioVolumeGroups()) {
+ for (AudioVolumeGroup avg : AudioVolumeGroup.getAudioVolumeGroups()) {
if (avg.getId() == volumeGroupId) {
return avg;
}
@@ -3819,14 +3819,15 @@ public class AudioService extends IAudioService.Stub
super.getVolumeIndexForAttributes_enforcePermission();
Objects.requireNonNull(attr, "attr must not be null");
- final int volumeGroup =
- AudioProductStrategy.getVolumeGroupIdForAudioAttributes(
- attr, /* fallbackOnDefault= */false);
- if (sVolumeGroupStates.indexOfKey(volumeGroup) < 0) {
- throw new IllegalArgumentException("No volume group for attributes " + attr);
+ synchronized (VolumeStreamState.class) {
+ int volumeGroup = AudioProductStrategy.getVolumeGroupIdForAudioAttributes(
+ attr, /* fallbackOnDefault= */false);
+ if (sVolumeGroupStates.indexOfKey(volumeGroup) < 0) {
+ throw new IllegalArgumentException("No volume group for attributes " + attr);
+ }
+ VolumeGroupState vgs = sVolumeGroupStates.get(volumeGroup);
+ return vgs.isMuted() ? vgs.getMinIndex() : vgs.getVolumeIndex();
}
- final VolumeGroupState vgs = sVolumeGroupStates.get(volumeGroup);
- return vgs.getVolumeIndex();
}
@android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
@@ -5922,7 +5923,7 @@ public class AudioService extends IAudioService.Stub
mSoundDoseHelper.restoreMusicActiveMs();
mSoundDoseHelper.enforceSafeMediaVolumeIfActive(TAG);
- readVolumeGroupsSettings();
+ readVolumeGroupsSettings(userSwitch);
if (DEBUG_VOL) {
Log.d(TAG, "Restoring device volume behavior");
@@ -7311,6 +7312,7 @@ public class AudioService extends IAudioService.Stub
try {
// if no valid attributes, this volume group is not controllable, throw exception
ensureValidAttributes(avg);
+ sVolumeGroupStates.append(avg.getId(), new VolumeGroupState(avg));
} catch (IllegalArgumentException e) {
// Volume Groups without attributes are not controllable through set/get volume
// using attributes. Do not append them.
@@ -7319,11 +7321,10 @@ public class AudioService extends IAudioService.Stub
}
continue;
}
- sVolumeGroupStates.append(avg.getId(), new VolumeGroupState(avg));
}
for (int i = 0; i < sVolumeGroupStates.size(); i++) {
final VolumeGroupState vgs = sVolumeGroupStates.valueAt(i);
- vgs.applyAllVolumes();
+ vgs.applyAllVolumes(/* userSwitch= */ false);
}
}
@@ -7336,14 +7337,22 @@ public class AudioService extends IAudioService.Stub
}
}
- private void readVolumeGroupsSettings() {
- if (DEBUG_VOL) {
- Log.v(TAG, "readVolumeGroupsSettings");
- }
- for (int i = 0; i < sVolumeGroupStates.size(); i++) {
- final VolumeGroupState vgs = sVolumeGroupStates.valueAt(i);
- vgs.readSettings();
- vgs.applyAllVolumes();
+ private void readVolumeGroupsSettings(boolean userSwitch) {
+ synchronized (mSettingsLock) {
+ synchronized (VolumeStreamState.class) {
+ if (DEBUG_VOL) {
+ Log.d(TAG, "readVolumeGroupsSettings userSwitch=" + userSwitch);
+ }
+ for (int i = 0; i < sVolumeGroupStates.size(); i++) {
+ VolumeGroupState vgs = sVolumeGroupStates.valueAt(i);
+ // as for STREAM_MUSIC, preserve volume from one user to the next.
+ if (!(userSwitch && vgs.isMusic())) {
+ vgs.clearIndexCache();
+ vgs.readSettings();
+ }
+ vgs.applyAllVolumes(userSwitch);
+ }
+ }
}
}
@@ -7354,7 +7363,7 @@ public class AudioService extends IAudioService.Stub
}
for (int i = 0; i < sVolumeGroupStates.size(); i++) {
final VolumeGroupState vgs = sVolumeGroupStates.valueAt(i);
- vgs.applyAllVolumes();
+ vgs.applyAllVolumes(false/*userSwitch*/);
}
}
@@ -7367,17 +7376,24 @@ public class AudioService extends IAudioService.Stub
}
}
+ private static boolean isCallStream(int stream) {
+ return stream == AudioSystem.STREAM_VOICE_CALL
+ || stream == AudioSystem.STREAM_BLUETOOTH_SCO;
+ }
+
// NOTE: Locking order for synchronized objects related to volume management:
// 1 mSettingsLock
- // 2 VolumeGroupState.class
+ // 2 VolumeStreamState.class
private class VolumeGroupState {
private final AudioVolumeGroup mAudioVolumeGroup;
private final SparseIntArray mIndexMap = new SparseIntArray(8);
private int mIndexMin;
private int mIndexMax;
- private int mLegacyStreamType = AudioSystem.STREAM_DEFAULT;
+ private boolean mHasValidStreamType = false;
private int mPublicStreamType = AudioSystem.STREAM_MUSIC;
private AudioAttributes mAudioAttributes = AudioProductStrategy.getDefaultAttributes();
+ private boolean mIsMuted = false;
+ private final String mSettingName;
// No API in AudioSystem to get a device from strategy or from attributes.
// Need a valid public stream type to use current API getDeviceForStream
@@ -7391,20 +7407,22 @@ public class AudioService extends IAudioService.Stub
Log.v(TAG, "VolumeGroupState for " + avg.toString());
}
// mAudioAttributes is the default at this point
- for (final AudioAttributes aa : avg.getAudioAttributes()) {
+ for (AudioAttributes aa : avg.getAudioAttributes()) {
if (!aa.equals(mAudioAttributes)) {
mAudioAttributes = aa;
break;
}
}
- final int[] streamTypes = mAudioVolumeGroup.getLegacyStreamTypes();
+ int[] streamTypes = mAudioVolumeGroup.getLegacyStreamTypes();
+ String streamSettingName = "";
if (streamTypes.length != 0) {
// Uses already initialized MIN / MAX if a stream type is attached to group
- mLegacyStreamType = streamTypes[0];
- for (final int streamType : streamTypes) {
+ for (int streamType : streamTypes) {
if (streamType != AudioSystem.STREAM_DEFAULT
&& streamType < AudioSystem.getNumStreamTypes()) {
mPublicStreamType = streamType;
+ mHasValidStreamType = true;
+ streamSettingName = System.VOLUME_SETTINGS_INT[mPublicStreamType];
break;
}
}
@@ -7414,10 +7432,10 @@ public class AudioService extends IAudioService.Stub
mIndexMin = AudioSystem.getMinVolumeIndexForAttributes(mAudioAttributes);
mIndexMax = AudioSystem.getMaxVolumeIndexForAttributes(mAudioAttributes);
} else {
- Log.e(TAG, "volume group: " + mAudioVolumeGroup.name()
+ throw new IllegalArgumentException("volume group: " + mAudioVolumeGroup.name()
+ " has neither valid attributes nor valid stream types assigned");
- return;
}
+ mSettingName = !streamSettingName.isEmpty() ? streamSettingName : ("volume_" + name());
// Load volume indexes from data base
readSettings();
}
@@ -7430,40 +7448,101 @@ public class AudioService extends IAudioService.Stub
return mAudioVolumeGroup.name();
}
+ /**
+ * Volume group with non null minimum index are considered as non mutable, thus
+ * bijectivity is broken with potential associated stream type.
+ * VOICE_CALL stream has minVolumeIndex > 0 but can be muted directly by an
+ * app that has MODIFY_PHONE_STATE permission.
+ */
+ private boolean isVssMuteBijective(int stream) {
+ return isStreamAffectedByMute(stream)
+ && (getMinIndex() == (mStreamStates[stream].mIndexMin + 5) / 10)
+ && (getMinIndex() == 0 || isCallStream(stream));
+ }
+
+ private boolean isMutable() {
+ return mIndexMin == 0 || (mHasValidStreamType && isVssMuteBijective(mPublicStreamType));
+ }
+ /**
+ * Mute/unmute the volume group
+ * @param muted the new mute state
+ */
+ @GuardedBy("AudioService.VolumeStreamState.class")
+ public boolean mute(boolean muted) {
+ if (!isMutable()) {
+ // Non mutable volume group
+ if (DEBUG_VOL) {
+ Log.d(TAG, "invalid mute on unmutable volume group " + name());
+ }
+ return false;
+ }
+ boolean changed = (mIsMuted != muted);
+ // As for VSS, mute shall apply minIndex to all devices found in IndexMap and default.
+ if (changed) {
+ mIsMuted = muted;
+ applyAllVolumes(false /*userSwitch*/);
+ }
+ return changed;
+ }
+
+ public boolean isMuted() {
+ return mIsMuted;
+ }
+
public int getVolumeIndex() {
- return getIndex(getDeviceForVolume());
+ synchronized (VolumeStreamState.class) {
+ return getIndex(getDeviceForVolume());
+ }
}
public void setVolumeIndex(int index, int flags) {
- if (mUseFixedVolume) {
- return;
+ synchronized (VolumeStreamState.class) {
+ if (mUseFixedVolume) {
+ return;
+ }
+ setVolumeIndex(index, getDeviceForVolume(), flags);
}
- setVolumeIndex(index, getDeviceForVolume(), flags);
}
+ @GuardedBy("AudioService.VolumeStreamState.class")
private void setVolumeIndex(int index, int device, int flags) {
- // Set the volume index
- setVolumeIndexInt(index, device, flags);
+ // Update cache & persist (muted by volume 0 shall be persisted)
+ updateVolumeIndex(index, device);
+ // setting non-zero volume for a muted stream unmutes the stream and vice versa,
+ boolean changed = mute(index == 0);
+ if (!changed) {
+ // Set the volume index only if mute operation is a no-op
+ index = getValidIndex(index);
+ setVolumeIndexInt(index, device, flags);
+ }
+ }
- // Update local cache
- mIndexMap.put(device, index);
+ @GuardedBy("AudioService.VolumeStreamState.class")
+ public void updateVolumeIndex(int index, int device) {
+ // Filter persistency if already exist and the index has not changed
+ if (mIndexMap.indexOfKey(device) < 0 || mIndexMap.get(device) != index) {
+ // Update local cache
+ mIndexMap.put(device, getValidIndex(index));
- // update data base - post a persist volume group msg
- sendMsg(mAudioHandler,
- MSG_PERSIST_VOLUME_GROUP,
- SENDMSG_QUEUE,
- device,
- 0,
- this,
- PERSIST_DELAY);
+ // update data base - post a persist volume group msg
+ sendMsg(mAudioHandler,
+ MSG_PERSIST_VOLUME_GROUP,
+ SENDMSG_QUEUE,
+ device,
+ 0,
+ this,
+ PERSIST_DELAY);
+ }
}
+ @GuardedBy("AudioService.VolumeStreamState.class")
private void setVolumeIndexInt(int index, int device, int flags) {
// Reflect mute state of corresponding stream by forcing index to 0 if muted
// Only set audio policy BT SCO stream volume to 0 when the stream is actually muted.
// This allows RX path muting by the audio HAL only when explicitly muted but not when
// index is just set to 0 to repect BT requirements
- if (mStreamStates[mPublicStreamType].isFullyMuted()) {
+ if (mHasValidStreamType && isVssMuteBijective(mPublicStreamType)
+ && mStreamStates[mPublicStreamType].isFullyMuted()) {
index = 0;
} else if (mPublicStreamType == AudioSystem.STREAM_BLUETOOTH_SCO && index == 0) {
index = 1;
@@ -7472,18 +7551,16 @@ public class AudioService extends IAudioService.Stub
AudioSystem.setVolumeIndexForAttributes(mAudioAttributes, index, device);
}
- public int getIndex(int device) {
- synchronized (VolumeGroupState.class) {
- int index = mIndexMap.get(device, -1);
- // there is always an entry for AudioSystem.DEVICE_OUT_DEFAULT
- return (index != -1) ? index : mIndexMap.get(AudioSystem.DEVICE_OUT_DEFAULT);
- }
+ @GuardedBy("AudioService.VolumeStreamState.class")
+ private int getIndex(int device) {
+ int index = mIndexMap.get(device, -1);
+ // there is always an entry for AudioSystem.DEVICE_OUT_DEFAULT
+ return (index != -1) ? index : mIndexMap.get(AudioSystem.DEVICE_OUT_DEFAULT);
}
- public boolean hasIndexForDevice(int device) {
- synchronized (VolumeGroupState.class) {
- return (mIndexMap.get(device, -1) != -1);
- }
+ @GuardedBy("AudioService.VolumeStreamState.class")
+ private boolean hasIndexForDevice(int device) {
+ return (mIndexMap.get(device, -1) != -1);
}
public int getMaxIndex() {
@@ -7494,55 +7571,108 @@ public class AudioService extends IAudioService.Stub
return mIndexMin;
}
- private boolean isValidLegacyStreamType() {
- return (mLegacyStreamType != AudioSystem.STREAM_DEFAULT)
- && (mLegacyStreamType < mStreamStates.length);
+ private boolean isValidStream(int stream) {
+ return (stream != AudioSystem.STREAM_DEFAULT) && (stream < mStreamStates.length);
}
- public void applyAllVolumes() {
- synchronized (VolumeGroupState.class) {
- int deviceForStream = AudioSystem.DEVICE_NONE;
- int volumeIndexForStream = 0;
- if (isValidLegacyStreamType()) {
- // Prevent to apply settings twice when group is associated to public stream
- deviceForStream = getDeviceForStream(mLegacyStreamType);
- volumeIndexForStream = getStreamVolume(mLegacyStreamType);
- }
+ public boolean isMusic() {
+ return mHasValidStreamType && mPublicStreamType == AudioSystem.STREAM_MUSIC;
+ }
+
+ public void applyAllVolumes(boolean userSwitch) {
+ String caller = "from vgs";
+ synchronized (VolumeStreamState.class) {
// apply device specific volumes first
- int index;
for (int i = 0; i < mIndexMap.size(); i++) {
- final int device = mIndexMap.keyAt(i);
+ int device = mIndexMap.keyAt(i);
+ int index = mIndexMap.valueAt(i);
+ boolean synced = false;
if (device != AudioSystem.DEVICE_OUT_DEFAULT) {
- index = mIndexMap.valueAt(i);
- if (device == deviceForStream && volumeIndexForStream == index) {
- continue;
+ for (int stream : getLegacyStreamTypes()) {
+ if (isValidStream(stream)) {
+ boolean streamMuted = mStreamStates[stream].mIsMuted;
+ int deviceForStream = getDeviceForStream(stream);
+ int indexForStream =
+ (mStreamStates[stream].getIndex(deviceForStream) + 5) / 10;
+ if (device == deviceForStream) {
+ if (indexForStream == index && (isMuted() == streamMuted)
+ && isVssMuteBijective(stream)) {
+ synced = true;
+ continue;
+ }
+ if (indexForStream != index) {
+ mStreamStates[stream].setIndex(index * 10, device, caller,
+ true /*hasModifyAudioSettings*/);
+ }
+ if ((isMuted() != streamMuted) && isVssMuteBijective(stream)) {
+ mStreamStates[stream].mute(isMuted());
+ }
+ }
+ }
}
- if (DEBUG_VOL) {
- Log.v(TAG, "applyAllVolumes: restore index " + index + " for group "
- + mAudioVolumeGroup.name() + " and device "
- + AudioSystem.getOutputDeviceName(device));
+ if (!synced) {
+ if (DEBUG_VOL) {
+ Log.d(TAG, "applyAllVolumes: apply index " + index + ", group "
+ + mAudioVolumeGroup.name() + " and device "
+ + AudioSystem.getOutputDeviceName(device));
+ }
+ setVolumeIndexInt(isMuted() ? 0 : index, device, 0 /*flags*/);
}
- setVolumeIndexInt(index, device, 0 /*flags*/);
}
}
// apply default volume last: by convention , default device volume will be used
// by audio policy manager if no explicit volume is present for a given device type
- index = getIndex(AudioSystem.DEVICE_OUT_DEFAULT);
- if (DEBUG_VOL) {
- Log.v(TAG, "applyAllVolumes: restore default device index " + index
- + " for group " + mAudioVolumeGroup.name());
+ int index = getIndex(AudioSystem.DEVICE_OUT_DEFAULT);
+ boolean synced = false;
+ int deviceForVolume = getDeviceForVolume();
+ boolean forceDeviceSync = userSwitch && (mIndexMap.indexOfKey(deviceForVolume) < 0);
+ for (int stream : getLegacyStreamTypes()) {
+ if (isValidStream(stream)) {
+ boolean streamMuted = mStreamStates[stream].mIsMuted;
+ int defaultStreamIndex = (mStreamStates[stream].getIndex(
+ AudioSystem.DEVICE_OUT_DEFAULT) + 5) / 10;
+ if (forceDeviceSync) {
+ mStreamStates[stream].setIndex(index * 10, deviceForVolume, caller,
+ true /*hasModifyAudioSettings*/);
+ }
+ if (defaultStreamIndex == index && (isMuted() == streamMuted)
+ && isVssMuteBijective(stream)) {
+ synced = true;
+ continue;
+ }
+ if (defaultStreamIndex != index) {
+ mStreamStates[stream].setIndex(
+ index * 10, AudioSystem.DEVICE_OUT_DEFAULT, caller,
+ true /*hasModifyAudioSettings*/);
+ }
+ if ((isMuted() != streamMuted) && isVssMuteBijective(stream)) {
+ mStreamStates[stream].mute(isMuted());
+ }
+ }
}
- if (isValidLegacyStreamType()) {
- int defaultStreamIndex = (mStreamStates[mLegacyStreamType]
- .getIndex(AudioSystem.DEVICE_OUT_DEFAULT) + 5) / 10;
- if (defaultStreamIndex == index) {
- return;
+ if (!synced) {
+ if (DEBUG_VOL) {
+ Log.d(TAG, "applyAllVolumes: apply default device index " + index
+ + ", group " + mAudioVolumeGroup.name());
+ }
+ setVolumeIndexInt(
+ isMuted() ? 0 : index, AudioSystem.DEVICE_OUT_DEFAULT, 0 /*flags*/);
+ }
+ if (forceDeviceSync) {
+ if (DEBUG_VOL) {
+ Log.d(TAG, "applyAllVolumes: forceDeviceSync index " + index
+ + ", device " + AudioSystem.getOutputDeviceName(deviceForVolume)
+ + ", group " + mAudioVolumeGroup.name());
}
+ setVolumeIndexInt(isMuted() ? 0 : index, deviceForVolume, 0);
}
- setVolumeIndexInt(index, AudioSystem.DEVICE_OUT_DEFAULT, 0 /*flags*/);
}
}
+ public void clearIndexCache() {
+ mIndexMap.clear();
+ }
+
private void persistVolumeGroup(int device) {
if (mUseFixedVolume) {
return;
@@ -7556,16 +7686,14 @@ public class AudioService extends IAudioService.Stub
boolean success = mSettings.putSystemIntForUser(mContentResolver,
getSettingNameForDevice(device),
getIndex(device),
- UserHandle.USER_CURRENT);
+ isMusic() ? UserHandle.USER_SYSTEM : UserHandle.USER_CURRENT);
if (!success) {
Log.e(TAG, "persistVolumeGroup failed for group " + mAudioVolumeGroup.name());
}
}
public void readSettings() {
- synchronized (VolumeGroupState.class) {
- // First clear previously loaded (previous user?) settings
- mIndexMap.clear();
+ synchronized (VolumeStreamState.class) {
// force maximum volume on all streams if fixed volume property is set
if (mUseFixedVolume) {
mIndexMap.put(AudioSystem.DEVICE_OUT_DEFAULT, mIndexMax);
@@ -7580,7 +7708,8 @@ public class AudioService extends IAudioService.Stub
int index;
String name = getSettingNameForDevice(device);
index = mSettings.getSystemIntForUser(
- mContentResolver, name, defaultIndex, UserHandle.USER_CURRENT);
+ mContentResolver, name, defaultIndex,
+ isMusic() ? UserHandle.USER_SYSTEM : UserHandle.USER_CURRENT);
if (index == -1) {
continue;
}
@@ -7598,6 +7727,7 @@ public class AudioService extends IAudioService.Stub
}
}
+ @GuardedBy("AudioService.VolumeStreamState.class")
private int getValidIndex(int index) {
if (index < mIndexMin) {
return mIndexMin;
@@ -7608,15 +7738,17 @@ public class AudioService extends IAudioService.Stub
}
public @NonNull String getSettingNameForDevice(int device) {
- final String suffix = AudioSystem.getOutputDeviceName(device);
+ String suffix = AudioSystem.getOutputDeviceName(device);
if (suffix.isEmpty()) {
- return mAudioVolumeGroup.name();
+ return mSettingName;
}
- return mAudioVolumeGroup.name() + "_" + AudioSystem.getOutputDeviceName(device);
+ return mSettingName + "_" + AudioSystem.getOutputDeviceName(device);
}
private void dump(PrintWriter pw) {
pw.println("- VOLUME GROUP " + mAudioVolumeGroup.name() + ":");
+ pw.print(" Muted: ");
+ pw.println(mIsMuted);
pw.print(" Min: ");
pw.println(mIndexMin);
pw.print(" Max: ");
@@ -7626,9 +7758,9 @@ public class AudioService extends IAudioService.Stub
if (i > 0) {
pw.print(", ");
}
- final int device = mIndexMap.keyAt(i);
+ int device = mIndexMap.keyAt(i);
pw.print(Integer.toHexString(device));
- final String deviceName = device == AudioSystem.DEVICE_OUT_DEFAULT ? "default"
+ String deviceName = device == AudioSystem.DEVICE_OUT_DEFAULT ? "default"
: AudioSystem.getOutputDeviceName(device);
if (!deviceName.isEmpty()) {
pw.print(" (");
@@ -7641,7 +7773,7 @@ public class AudioService extends IAudioService.Stub
pw.println();
pw.print(" Devices: ");
int n = 0;
- final int devices = getDeviceForVolume();
+ int devices = getDeviceForVolume();
for (int device : AudioSystem.DEVICE_OUT_ALL_SET) {
if ((devices & device) == device) {
if (n++ > 0) {
@@ -7650,6 +7782,10 @@ public class AudioService extends IAudioService.Stub
pw.print(AudioSystem.getOutputDeviceName(device));
}
}
+ pw.println();
+ pw.print(" Streams: ");
+ Arrays.stream(getLegacyStreamTypes())
+ .forEach(stream -> pw.print(AudioSystem.streamToString(stream) + " "));
}
}
diff --git a/services/core/java/com/android/server/devicestate/DeviceState.java b/services/core/java/com/android/server/devicestate/DeviceState.java
index 42fe9d88825b..a589313a1e92 100644
--- a/services/core/java/com/android/server/devicestate/DeviceState.java
+++ b/services/core/java/com/android/server/devicestate/DeviceState.java
@@ -64,10 +64,27 @@ public final class DeviceState {
*/
public static final int FLAG_EMULATED_ONLY = 1 << 2;
+ /**
+ * This flag indicates that the corresponding state should be automatically canceled when the
+ * requesting app is no longer on top. The app is considered not on top when (1) the top
+ * activity in the system is from a different app, (2) the device is in sleep mode, or
+ * (3) the keyguard shows up.
+ */
+ public static final int FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP = 1 << 3;
+
+ /**
+ * This flag indicates that the corresponding state should be disabled when the device is
+ * overheating and reaching the critical status.
+ */
+ public static final int FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL = 1 << 4;
+
/** @hide */
@IntDef(prefix = {"FLAG_"}, flag = true, value = {
FLAG_CANCEL_OVERRIDE_REQUESTS,
- FLAG_APP_INACCESSIBLE
+ FLAG_APP_INACCESSIBLE,
+ FLAG_EMULATED_ONLY,
+ FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP,
+ FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL
})
@Retention(RetentionPolicy.SOURCE)
public @interface DeviceStateFlags {}
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index c856cabdc967..064cd2d50161 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -33,6 +33,7 @@ import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.TaskStackListener;
import android.content.Context;
import android.hardware.devicestate.DeviceStateInfo;
import android.hardware.devicestate.DeviceStateManager;
@@ -176,6 +177,12 @@ public final class DeviceStateManagerService extends SystemService {
@NonNull
private final SystemPropertySetter mSystemPropertySetter;
+ @VisibleForTesting
+ TaskStackListener mOverrideRequestTaskStackListener = new OverrideRequestTaskStackListener();
+ @VisibleForTesting
+ ActivityTaskManagerInternal.ScreenObserver mOverrideRequestScreenObserver =
+ new OverrideRequestScreenObserver();
+
public DeviceStateManagerService(@NonNull Context context) {
this(context, DeviceStatePolicy.Provider
.fromResources(context.getResources())
@@ -215,6 +222,9 @@ public final class DeviceStateManagerService extends SystemService {
readStatesAvailableForRequestFromApps();
mFoldedDeviceStates = readFoldedStates();
}
+
+ mActivityTaskManagerInternal.registerTaskStackListener(mOverrideRequestTaskStackListener);
+ mActivityTaskManagerInternal.registerScreenObserver(mOverrideRequestScreenObserver);
}
@VisibleForTesting
@@ -842,9 +852,7 @@ public final class DeviceStateManagerService extends SystemService {
* @param state state that is being requested.
*/
private void assertCanRequestDeviceState(int callingPid, int state) {
- final WindowProcessController topApp = mActivityTaskManagerInternal.getTopApp();
- if (topApp == null || topApp.getPid() != callingPid
- || !isStateAvailableForAppRequests(state)) {
+ if (!isTopApp(callingPid) || !isStateAvailableForAppRequests(state)) {
getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE,
"Permission required to request device state, "
+ "or the call must come from the top app "
@@ -859,14 +867,18 @@ public final class DeviceStateManagerService extends SystemService {
* @param callingPid Process ID that is requesting this state change
*/
private void assertCanControlDeviceState(int callingPid) {
- final WindowProcessController topApp = mActivityTaskManagerInternal.getTopApp();
- if (topApp == null || topApp.getPid() != callingPid) {
+ if (!isTopApp(callingPid)) {
getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE,
"Permission required to request device state, "
+ "or the call must come from the top app.");
}
}
+ private boolean isTopApp(int callingPid) {
+ final WindowProcessController topApp = mActivityTaskManagerInternal.getTopApp();
+ return topApp != null && topApp.getPid() == callingPid;
+ }
+
private boolean isStateAvailableForAppRequests(int state) {
synchronized (mLock) {
return mDeviceStatesAvailableForAppRequests.contains(state);
@@ -1185,4 +1197,52 @@ public final class DeviceStateManagerService extends SystemService {
}
}
}
+
+ @GuardedBy("mLock")
+ private boolean shouldCancelOverrideRequestWhenRequesterNotOnTop() {
+ if (mActiveOverride.isEmpty()) {
+ return false;
+ }
+ int identifier = mActiveOverride.get().getRequestedState();
+ DeviceState deviceState = mDeviceStates.get(identifier);
+ return deviceState.hasFlag(DeviceState.FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP);
+ }
+
+ private class OverrideRequestTaskStackListener extends TaskStackListener {
+ @Override
+ public void onTaskStackChanged() throws RemoteException {
+ synchronized (mLock) {
+ if (!shouldCancelOverrideRequestWhenRequesterNotOnTop()) {
+ return;
+ }
+
+ OverrideRequest request = mActiveOverride.get();
+ if (!isTopApp(request.getPid())) {
+ mOverrideRequestController.cancelRequest(request);
+ }
+ }
+ }
+ }
+
+ private class OverrideRequestScreenObserver implements
+ ActivityTaskManagerInternal.ScreenObserver {
+
+ @Override
+ public void onAwakeStateChanged(boolean isAwake) {
+ synchronized (mLock) {
+ if (!isAwake && shouldCancelOverrideRequestWhenRequesterNotOnTop()) {
+ mOverrideRequestController.cancelRequest(mActiveOverride.get());
+ }
+ }
+ }
+
+ @Override
+ public void onKeyguardStateChanged(boolean isShowing) {
+ synchronized (mLock) {
+ if (isShowing && shouldCancelOverrideRequestWhenRequesterNotOnTop()) {
+ mOverrideRequestController.cancelRequest(mActiveOverride.get());
+ }
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index bb342ffac3bd..5087caa9adc3 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -177,6 +177,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
private Layout mCurrentLayout = null;
private int mDeviceState = DeviceStateManager.INVALID_DEVICE_STATE;
private int mPendingDeviceState = DeviceStateManager.INVALID_DEVICE_STATE;
+ private int mDeviceStateToBeAppliedAfterBoot = DeviceStateManager.INVALID_DEVICE_STATE;
private boolean mBootCompleted = false;
private boolean mInteractive;
@@ -373,6 +374,12 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
ipw.println("mDeviceStatesOnWhichToWakeUp=" + mDeviceStatesOnWhichToWakeUp);
ipw.println("mDeviceStatesOnWhichToSleep=" + mDeviceStatesOnWhichToSleep);
ipw.println("mInteractive=" + mInteractive);
+ ipw.println("mBootCompleted=" + mBootCompleted);
+
+ ipw.println();
+ ipw.println("mDeviceState=" + mDeviceState);
+ ipw.println("mPendingDeviceState=" + mPendingDeviceState);
+ ipw.println("mDeviceStateToBeAppliedAfterBoot=" + mDeviceStateToBeAppliedAfterBoot);
final int logicalDisplayCount = mLogicalDisplays.size();
ipw.println();
@@ -403,10 +410,6 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
}
void setDeviceStateLocked(int state, boolean isOverrideActive) {
- Slog.i(TAG, "Requesting Transition to state: " + state + ", from state=" + mDeviceState
- + ", interactive=" + mInteractive + ", mBootCompleted=" + mBootCompleted);
- mPendingDeviceState = state;
-
if (!mBootCompleted) {
// The boot animation might still be in progress, we do not want to switch states now
// as the boot animation would end up with an incorrect size.
@@ -414,14 +417,19 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
Slog.d(TAG, "Postponing transition to state: " + mPendingDeviceState
+ " until boot is completed");
}
+ mDeviceStateToBeAppliedAfterBoot = state;
return;
}
+ Slog.i(TAG, "Requesting Transition to state: " + state + ", from state=" + mDeviceState
+ + ", interactive=" + mInteractive + ", mBootCompleted=" + mBootCompleted);
// As part of a state transition, we may need to turn off some displays temporarily so that
// the transition is smooth. Plus, on some devices, only one internal displays can be
// on at a time. We use LogicalDisplay.setIsInTransition to mark a display that needs to be
// temporarily turned off.
resetLayoutLocked(mDeviceState, state, /* transitionValue= */ true);
+ mPendingDeviceState = state;
+ mDeviceStateToBeAppliedAfterBoot = DeviceStateManager.INVALID_DEVICE_STATE;
final boolean wakeDevice = shouldDeviceBeWoken(mPendingDeviceState, mDeviceState,
mInteractive, mBootCompleted);
final boolean sleepDevice = shouldDeviceBePutToSleep(mPendingDeviceState, mDeviceState,
@@ -468,8 +476,9 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
void onBootCompleted() {
synchronized (mSyncRoot) {
mBootCompleted = true;
- if (mPendingDeviceState != DeviceStateManager.INVALID_DEVICE_STATE) {
- setDeviceStateLocked(mPendingDeviceState, /* isOverrideActive= */ false);
+ if (mDeviceStateToBeAppliedAfterBoot != DeviceStateManager.INVALID_DEVICE_STATE) {
+ setDeviceStateLocked(mDeviceStateToBeAppliedAfterBoot,
+ /* isOverrideActive= */ false);
}
}
}
@@ -524,7 +533,8 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
@VisibleForTesting
boolean shouldDeviceBePutToSleep(int pendingState, int currentState, boolean isOverrideActive,
boolean isInteractive, boolean isBootCompleted) {
- return mDeviceStatesOnWhichToSleep.get(pendingState)
+ return currentState != DeviceStateManager.INVALID_DEVICE_STATE
+ && mDeviceStatesOnWhichToSleep.get(pendingState)
&& !mDeviceStatesOnWhichToSleep.get(currentState)
&& !isOverrideActive
&& isInteractive && isBootCompleted;
diff --git a/services/core/java/com/android/server/dreams/DreamController.java b/services/core/java/com/android/server/dreams/DreamController.java
index e5357f61d076..c1780a3c9744 100644
--- a/services/core/java/com/android/server/dreams/DreamController.java
+++ b/services/core/java/com/android/server/dreams/DreamController.java
@@ -16,9 +16,6 @@
package com.android.server.dreams;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
-
-import android.app.ActivityTaskManager;
import android.app.BroadcastOptions;
import android.content.ComponentName;
import android.content.Context;
@@ -65,7 +62,6 @@ final class DreamController {
private final Context mContext;
private final Handler mHandler;
private final Listener mListener;
- private final ActivityTaskManager mActivityTaskManager;
private final Intent mDreamingStartedIntent = new Intent(Intent.ACTION_DREAMING_STARTED)
.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
@@ -93,7 +89,6 @@ final class DreamController {
mContext = context;
mHandler = handler;
mListener = listener;
- mActivityTaskManager = mContext.getSystemService(ActivityTaskManager.class);
mCloseNotificationShadeIntent = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
mCloseNotificationShadeIntent.putExtra("reason", "dream");
}
@@ -277,9 +272,6 @@ final class DreamController {
mSentStartBroadcast = false;
}
- mActivityTaskManager.removeRootTasksWithActivityTypes(
- new int[] {ACTIVITY_TYPE_DREAM});
-
mListener.onDreamStopped(dream.mToken);
}
} finally {
diff --git a/services/core/java/com/android/server/incident/PendingReports.java b/services/core/java/com/android/server/incident/PendingReports.java
index 684b5f16e999..adcda0a63152 100644
--- a/services/core/java/com/android/server/incident/PendingReports.java
+++ b/services/core/java/com/android/server/incident/PendingReports.java
@@ -16,15 +16,20 @@
package com.android.server.incident;
+import static android.permission.PermissionManager.PERMISSION_GRANTED;
+
+import android.Manifest;
import android.annotation.UserIdInt;
import android.app.AppOpsManager;
import android.app.BroadcastOptions;
+import android.content.AttributionSource;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
+import android.os.Build;
import android.os.Handler;
import android.os.IIncidentAuthListener;
import android.os.IncidentManager;
@@ -32,6 +37,7 @@ import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
+import android.permission.PermissionManager;
import android.util.Log;
import java.io.FileDescriptor;
@@ -55,6 +61,7 @@ class PendingReports {
private final Context mContext;
private final PackageManager mPackageManager;
private final AppOpsManager mAppOpsManager;
+ private final PermissionManager mPermissionManager;
//
// All fields below must be protected by mLock
@@ -126,6 +133,7 @@ class PendingReports {
mContext = context;
mPackageManager = context.getPackageManager();
mAppOpsManager = context.getSystemService(AppOpsManager.class);
+ mPermissionManager = context.getSystemService(PermissionManager.class);
}
/**
@@ -297,6 +305,35 @@ class PendingReports {
return;
}
+ // Only with userdebug/eng build: it could check capture consentless bugreport permission
+ // and approve the report when it's granted.
+ boolean captureConsentlessBugreportOnUserdebugBuildGranted = false;
+ if ((Build.IS_USERDEBUG || Build.IS_ENG)
+ && (flags & IncidentManager.FLAG_ALLOW_CONSENTLESS_BUGREPORT) != 0) {
+ AttributionSource attributionSource =
+ new AttributionSource.Builder(callingUid)
+ .setPackageName(callingPackage)
+ .build();
+ captureConsentlessBugreportOnUserdebugBuildGranted =
+ mPermissionManager.checkPermissionForDataDelivery(
+ Manifest.permission.CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD,
+ attributionSource,
+ /* message= */ null)
+ == PERMISSION_GRANTED;
+ }
+ if (captureConsentlessBugreportOnUserdebugBuildGranted) {
+ try {
+ PendingReportRec rec =
+ new PendingReportRec(
+ callingPackage, receiverClass, reportId, flags, listener);
+ Log.d(TAG, "approving consentless report: " + rec.getUri());
+ listener.onReportApproved();
+ return;
+ } catch (RemoteException e) {
+ Log.e(TAG, "authorizeReportImpl listener.onReportApproved RemoteException: ", e);
+ }
+ }
+
// Save the record for when the PermissionController comes back to authorize it.
PendingReportRec rec = null;
synchronized (mLock) {
diff --git a/services/core/java/com/android/server/input/BatteryController.java b/services/core/java/com/android/server/input/BatteryController.java
index 993b4fdb2498..ff9ce6f16075 100644
--- a/services/core/java/com/android/server/input/BatteryController.java
+++ b/services/core/java/com/android/server/input/BatteryController.java
@@ -234,7 +234,8 @@ final class BatteryController {
}
private boolean isUsiDevice(int deviceId) {
- return processInputDevice(deviceId, false /*defaultValue*/, InputDevice::supportsUsi);
+ return processInputDevice(deviceId, false /*defaultValue*/,
+ (device) -> device.getHostUsiVersion() != null);
}
@Nullable
diff --git a/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java b/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
index 35434b76e12c..e21895aced1f 100644
--- a/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
+++ b/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
@@ -136,14 +136,14 @@ final class DefaultImeVisibilityApplier implements ImeVisibilityApplier {
@ImeVisibilityStateComputer.VisibilityState int state, int reason) {
switch (state) {
case STATE_SHOW_IME:
- ImeTracker.forLogging().onProgress(statsToken,
+ ImeTracker.get().onProgress(statsToken,
ImeTracker.PHASE_SERVER_APPLY_IME_VISIBILITY);
// Send to window manager to show IME after IME layout finishes.
mWindowManagerInternal.showImePostLayout(windowToken, statsToken);
break;
case STATE_HIDE_IME:
if (mService.mCurFocusedWindowClient != null) {
- ImeTracker.forLogging().onProgress(statsToken,
+ ImeTracker.get().onProgress(statsToken,
ImeTracker.PHASE_SERVER_APPLY_IME_VISIBILITY);
// IMMS only knows of focused window, not the actual IME target.
// e.g. it isn't aware of any window that has both
@@ -154,7 +154,7 @@ final class DefaultImeVisibilityApplier implements ImeVisibilityApplier {
mWindowManagerInternal.hideIme(windowToken,
mService.mCurFocusedWindowClient.mSelfReportedDisplayId, statsToken);
} else {
- ImeTracker.forLogging().onFailed(statsToken,
+ ImeTracker.get().onFailed(statsToken,
ImeTracker.PHASE_SERVER_APPLY_IME_VISIBILITY);
}
break;
diff --git a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
index 10c16b60e044..db61e954893e 100644
--- a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
+++ b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
@@ -182,10 +182,10 @@ public final class ImeVisibilityStateComputer {
*/
boolean onImeShowFlags(@NonNull ImeTracker.Token statsToken, int showFlags) {
if (mPolicy.mA11yRequestingNoSoftKeyboard || mPolicy.mImeHiddenByDisplayPolicy) {
- ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_SERVER_ACCESSIBILITY);
+ ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_ACCESSIBILITY);
return false;
}
- ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_ACCESSIBILITY);
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_ACCESSIBILITY);
if ((showFlags & InputMethodManager.SHOW_FORCED) != 0) {
mRequestedShowExplicitly = true;
mShowForced = true;
@@ -206,15 +206,15 @@ public final class ImeVisibilityStateComputer {
if ((hideFlags & InputMethodManager.HIDE_IMPLICIT_ONLY) != 0
&& (mRequestedShowExplicitly || mShowForced)) {
if (DEBUG) Slog.v(TAG, "Not hiding: explicit show not cancelled by non-explicit hide");
- ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_SERVER_HIDE_IMPLICIT);
+ ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_HIDE_IMPLICIT);
return false;
}
if (mShowForced && (hideFlags & InputMethodManager.HIDE_NOT_ALWAYS) != 0) {
if (DEBUG) Slog.v(TAG, "Not hiding: forced show not cancelled by not-always hide");
- ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_SERVER_HIDE_NOT_ALWAYS);
+ ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_HIDE_NOT_ALWAYS);
return false;
}
- ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_HIDE_NOT_ALWAYS);
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_HIDE_NOT_ALWAYS);
return true;
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index ce3abfd1586b..a63b95c204b4 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -2290,7 +2290,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
mCurClient.mSessionRequestedForAccessibility = false;
mCurClient = null;
mCurVirtualDisplayToScreenMatrix = null;
- ImeTracker.forLogging().onFailed(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
+ ImeTracker.get().onFailed(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
mCurStatsToken = null;
mMenuController.hideInputMethodMenuLocked();
@@ -3276,8 +3276,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
"InputMethodManagerService#showSoftInput");
synchronized (ImfLock.class) {
if (!canInteractWithImeLocked(uid, client, "showSoftInput", statsToken)) {
- ImeTracker.forLogging().onFailed(
- statsToken, ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
+ ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
return false;
}
final long ident = Binder.clearCallingIdentity();
@@ -3378,7 +3377,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
// TODO(b/261565259): to avoid using null, add package name in ClientState
final String packageName = (mCurEditorInfo != null) ? mCurEditorInfo.packageName : null;
final int uid = mCurClient != null ? mCurClient.mUid : -1;
- statsToken = ImeTracker.forLogging().onRequestShow(packageName, uid,
+ statsToken = ImeTracker.get().onRequestShow(packageName, uid,
ImeTracker.ORIGIN_SERVER_START_INPUT, reason);
}
@@ -3387,19 +3386,19 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
}
if (!mSystemReady) {
- ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_SERVER_SYSTEM_READY);
+ ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_SYSTEM_READY);
return false;
}
- ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_SYSTEM_READY);
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_SYSTEM_READY);
mVisibilityStateComputer.requestImeVisibility(windowToken, true);
// Ensure binding the connection when IME is going to show.
mBindingController.setCurrentMethodVisible();
final IInputMethodInvoker curMethod = getCurMethodLocked();
- ImeTracker.forLogging().onCancelled(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
+ ImeTracker.get().onCancelled(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
if (curMethod != null) {
- ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_HAS_IME);
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_HAS_IME);
mCurStatsToken = null;
if (lastClickToolType != MotionEvent.TOOL_TYPE_UNKNOWN) {
@@ -3410,7 +3409,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
mVisibilityStateComputer.setInputShown(true);
return true;
} else {
- ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
mCurStatsToken = statsToken;
}
return false;
@@ -3426,10 +3425,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
synchronized (ImfLock.class) {
if (!canInteractWithImeLocked(uid, client, "hideSoftInput", statsToken)) {
if (isInputShown()) {
- ImeTracker.forLogging().onFailed(
- statsToken, ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
+ ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
} else {
- ImeTracker.forLogging().onCancelled(statsToken,
+ ImeTracker.get().onCancelled(statsToken,
ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
}
return false;
@@ -3462,7 +3460,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
} else {
uid = -1;
}
- statsToken = ImeTracker.forLogging().onRequestHide(packageName, uid,
+ statsToken = ImeTracker.get().onRequestHide(packageName, uid,
ImeTracker.ORIGIN_SERVER_HIDE_INPUT, reason);
}
@@ -3488,15 +3486,15 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
// delivered to the IME process as an IPC. Hence the inconsistency between
// IMMS#mInputShown and IMMS#mImeWindowVis should be resolved spontaneously in
// the final state.
- ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_SHOULD_HIDE);
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_SHOULD_HIDE);
mVisibilityApplier.performHideIme(windowToken, statsToken, resultReceiver, reason);
} else {
- ImeTracker.forLogging().onCancelled(statsToken, ImeTracker.PHASE_SERVER_SHOULD_HIDE);
+ ImeTracker.get().onCancelled(statsToken, ImeTracker.PHASE_SERVER_SHOULD_HIDE);
}
mBindingController.setCurrentMethodNotVisible();
mVisibilityStateComputer.clearImeShowFlags();
// Cancel existing statsToken for show IME as we got a hide request.
- ImeTracker.forLogging().onCancelled(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
+ ImeTracker.get().onCancelled(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
mCurStatsToken = null;
return shouldHideSoftInput;
}
@@ -3768,16 +3766,16 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
// be made before input is started in it.
final ClientState cs = mClients.get(client.asBinder());
if (cs == null) {
- ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_SERVER_CLIENT_KNOWN);
+ ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_CLIENT_KNOWN);
throw new IllegalArgumentException("unknown client " + client.asBinder());
}
- ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_CLIENT_KNOWN);
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_CLIENT_KNOWN);
if (!isImeClientFocused(mCurFocusedWindow, cs)) {
Slog.w(TAG, String.format("Ignoring %s of uid %d : %s", methodName, uid, client));
return false;
}
}
- ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
+ ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
return true;
}
@@ -4554,8 +4552,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.applyImeVisibility");
synchronized (ImfLock.class) {
if (!calledWithValidTokenLocked(token)) {
- ImeTracker.forLogging().onFailed(statsToken,
- ImeTracker.PHASE_SERVER_APPLY_IME_VISIBILITY);
+ ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_APPLY_IME_VISIBILITY);
return;
}
final IBinder requestToken = mVisibilityStateComputer.getWindowTokenFrom(windowToken);
diff --git a/services/core/java/com/android/server/pm/AppDataHelper.java b/services/core/java/com/android/server/pm/AppDataHelper.java
index d252d409732a..b1c6f8c8499b 100644
--- a/services/core/java/com/android/server/pm/AppDataHelper.java
+++ b/services/core/java/com/android/server/pm/AppDataHelper.java
@@ -20,6 +20,7 @@ import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
import static com.android.server.pm.DexOptHelper.useArtService;
import static com.android.server.pm.PackageManagerService.TAG;
+import static com.android.server.pm.PackageManagerServiceUtils.getPackageManagerLocal;
import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo;
import android.annotation.NonNull;
@@ -586,11 +587,14 @@ public class AppDataHelper {
Slog.wtf(TAG, "Package was null!", new Throwable());
return;
}
- // TODO(b/251903639): Call into ART Service.
- try {
- mArtManagerService.clearAppProfiles(pkg);
- } catch (LegacyDexoptDisabledException e) {
- throw new RuntimeException(e);
+ if (useArtService()) {
+ destroyAppProfilesWithArtService(pkg);
+ } else {
+ try {
+ mArtManagerService.clearAppProfiles(pkg);
+ } catch (LegacyDexoptDisabledException e) {
+ throw new RuntimeException(e);
+ }
}
}
@@ -629,13 +633,28 @@ public class AppDataHelper {
}
private void destroyAppProfilesLeafLIF(AndroidPackage pkg) {
- // TODO(b/251903639): Call into ART Service.
- try {
- mInstaller.destroyAppProfiles(pkg.getPackageName());
- } catch (LegacyDexoptDisabledException e) {
- throw new RuntimeException(e);
- } catch (Installer.InstallerException e) {
- Slog.w(TAG, String.valueOf(e));
+ if (useArtService()) {
+ destroyAppProfilesWithArtService(pkg);
+ } else {
+ try {
+ mInstaller.destroyAppProfiles(pkg.getPackageName());
+ } catch (LegacyDexoptDisabledException e) {
+ throw new RuntimeException(e);
+ } catch (Installer.InstallerException e) {
+ Slog.w(TAG, String.valueOf(e));
+ }
+ }
+ }
+
+ private void destroyAppProfilesWithArtService(AndroidPackage pkg) {
+ try (PackageManagerLocal.FilteredSnapshot snapshot =
+ getPackageManagerLocal().withFilteredSnapshot()) {
+ try {
+ DexOptHelper.getArtManagerLocal().clearAppProfiles(snapshot, pkg.getPackageName());
+ } catch (IllegalArgumentException e) {
+ // Package isn't found, but that should only happen due to race.
+ Slog.w(TAG, e);
+ }
}
}
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index c2f0f5262305..094b18212580 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -2998,35 +2998,40 @@ public class ComputerEngine implements Computer {
}
ipw.println("Dexopt state:");
ipw.increaseIndent();
- Collection<? extends PackageStateInternal> pkgSettings;
- if (setting != null) {
- pkgSettings = Collections.singletonList(setting);
+ if (DexOptHelper.useArtService()) {
+ DexOptHelper.dumpDexoptState(ipw, packageName);
} else {
- pkgSettings = mSettings.getPackages().values();
- }
-
- for (PackageStateInternal pkgSetting : pkgSettings) {
- final AndroidPackage pkg = pkgSetting.getPkg();
- if (pkg == null || pkg.isApex()) {
- // Skip APEX which is not dex-optimized
- continue;
+ Collection<? extends PackageStateInternal> pkgSettings;
+ if (setting != null) {
+ pkgSettings = Collections.singletonList(setting);
+ } else {
+ pkgSettings = mSettings.getPackages().values();
}
- final String pkgName = pkg.getPackageName();
- ipw.println("[" + pkgName + "]");
- ipw.increaseIndent();
- // TODO(b/251903639): Call into ART Service.
- try {
- mPackageDexOptimizer.dumpDexoptState(ipw, pkg, pkgSetting,
- mDexManager.getPackageUseInfoOrDefault(pkgName));
- } catch (LegacyDexoptDisabledException e) {
- throw new RuntimeException(e);
+ for (PackageStateInternal pkgSetting : pkgSettings) {
+ final AndroidPackage pkg = pkgSetting.getPkg();
+ if (pkg == null || pkg.isApex()) {
+ // Skip APEX which is not dex-optimized
+ continue;
+ }
+ final String pkgName = pkg.getPackageName();
+ ipw.println("[" + pkgName + "]");
+ ipw.increaseIndent();
+
+ // TODO(b/251903639): Call into ART Service.
+ try {
+ mPackageDexOptimizer.dumpDexoptState(ipw, pkg, pkgSetting,
+ mDexManager.getPackageUseInfoOrDefault(pkgName));
+ } catch (LegacyDexoptDisabledException e) {
+ throw new RuntimeException(e);
+ }
+ ipw.decreaseIndent();
}
+ ipw.println("BgDexopt state:");
+ ipw.increaseIndent();
+ mBackgroundDexOptService.dump(ipw);
ipw.decreaseIndent();
}
- ipw.println("BgDexopt state:");
- ipw.increaseIndent();
- mBackgroundDexOptService.dump(ipw);
ipw.decreaseIndent();
break;
}
diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java
index 53e23e056698..382ae26e59ec 100644
--- a/services/core/java/com/android/server/pm/DexOptHelper.java
+++ b/services/core/java/com/android/server/pm/DexOptHelper.java
@@ -23,6 +23,7 @@ import static com.android.server.pm.ApexManager.ActiveApexInfo;
import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
import static com.android.server.pm.PackageManagerService.DEBUG_DEXOPT;
import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
+import static com.android.server.pm.PackageManagerService.REASON_BOOT_AFTER_MAINLINE_UPDATE;
import static com.android.server.pm.PackageManagerService.REASON_BOOT_AFTER_OTA;
import static com.android.server.pm.PackageManagerService.REASON_CMDLINE;
import static com.android.server.pm.PackageManagerService.REASON_FIRST_BOOT;
@@ -32,14 +33,12 @@ import static com.android.server.pm.PackageManagerServiceCompilerMapping.getComp
import static com.android.server.pm.PackageManagerServiceCompilerMapping.getDefaultCompilerFilter;
import static com.android.server.pm.PackageManagerServiceUtils.REMOVE_IF_APEX_PKG;
import static com.android.server.pm.PackageManagerServiceUtils.REMOVE_IF_NULL_PKG;
+import static com.android.server.pm.PackageManagerServiceUtils.getPackageManagerLocal;
import static dalvik.system.DexFile.isProfileGuidedCompilerFilter;
-import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.RequiresPermission;
-import android.app.ActivityManager;
import android.app.AppGlobals;
import android.content.Context;
import android.content.Intent;
@@ -52,18 +51,17 @@ import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
-import android.provider.DeviceConfig;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
import android.util.Slog;
-import com.android.internal.R;
-import com.android.internal.annotations.GuardedBy;
import com.android.internal.logging.MetricsLogger;
+import com.android.internal.util.IndentingPrintWriter;
import com.android.server.LocalManagerRegistry;
import com.android.server.art.ArtManagerLocal;
import com.android.server.art.DexUseManagerLocal;
+import com.android.server.art.ReasonMapping;
import com.android.server.art.model.ArtFlags;
import com.android.server.art.model.DexoptParams;
import com.android.server.art.model.DexoptResult;
@@ -85,10 +83,7 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
-import java.util.Objects;
-import java.util.Optional;
import java.util.Set;
-import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
@@ -100,17 +95,9 @@ public final class DexOptHelper {
private final PackageManagerService mPm;
- public boolean isDexOptDialogShown() {
- synchronized (mLock) {
- return mDexOptDialogShown;
- }
- }
-
- // TODO: Is this lock really necessary?
- private final Object mLock = new Object();
-
- @GuardedBy("mLock")
- private boolean mDexOptDialogShown;
+ // Start time for the boot dexopt in performPackageDexOptUpgradeIfNeeded when ART Service is
+ // used, to make it available to the onDexoptDone callback.
+ private volatile long mBootDexoptStartTime;
DexOptHelper(PackageManagerService pm) {
mPm = pm;
@@ -129,7 +116,7 @@ public final class DexOptHelper {
* which are (in order) {@code numberOfPackagesOptimized}, {@code numberOfPackagesSkipped}
* and {@code numberOfPackagesFailed}.
*/
- public int[] performDexOptUpgrade(List<PackageStateInternal> packageStates, boolean showDialog,
+ public int[] performDexOptUpgrade(List<PackageStateInternal> packageStates,
final int compilationReason, boolean bootComplete)
throws LegacyDexoptDisabledException {
Installer.checkLegacyDexoptDisabled();
@@ -221,18 +208,6 @@ public final class DexOptHelper {
+ numberOfPackagesToDexopt + ": " + pkg.getPackageName());
}
- if (showDialog) {
- try {
- ActivityManager.getService().showBootMessage(
- mPm.mContext.getResources().getString(R.string.android_upgrading_apk,
- numberOfPackagesVisited, numberOfPackagesToDexopt), true);
- } catch (RemoteException e) {
- }
- synchronized (mLock) {
- mDexOptDialogShown = true;
- }
- }
-
int pkgCompilationReason = compilationReason;
if (useProfileForDexopt) {
// Use background dexopt mode to try and use the profile. Note that this does not
@@ -240,6 +215,7 @@ public final class DexOptHelper {
pkgCompilationReason = PackageManagerService.REASON_BACKGROUND_DEXOPT;
}
+ // TODO(b/251903639): Do this when ART Service is used, or remove it from here.
if (SystemProperties.getBoolean(mPm.PRECOMPILE_LAYOUTS, false)) {
mPm.mArtManagerService.compileLayouts(packageState, pkg);
}
@@ -290,7 +266,7 @@ public final class DexOptHelper {
* Checks if system UI package (typically "com.android.systemui") needs to be re-compiled, and
* compiles it if needed.
*/
- private void checkAndDexOptSystemUi() throws LegacyDexoptDisabledException {
+ private void checkAndDexOptSystemUi(int reason) throws LegacyDexoptDisabledException {
Installer.checkLegacyDexoptDisabled();
Computer snapshot = mPm.snapshotComputer();
String sysUiPackageName =
@@ -301,10 +277,6 @@ public final class DexOptHelper {
return;
}
- // It could also be after mainline update, but we're not introducing a new reason just for
- // this special case.
- int reason = REASON_BOOT_AFTER_OTA;
-
String defaultCompilerFilter = getCompilerFilterForReason(reason);
String targetCompilerFilter =
SystemProperties.get("dalvik.vm.systemuicompilerfilter", defaultCompilerFilter);
@@ -347,49 +319,68 @@ public final class DexOptHelper {
compilerFilter, null /* splitName */, dexoptFlags));
}
- @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
- public void performPackageDexOptUpgradeIfNeeded() throws LegacyDexoptDisabledException {
+ /**
+ * Called during startup to do any boot time dexopting. This can occasionally be time consuming
+ * (30+ seconds) and the function will block until it is complete.
+ */
+ public void performPackageDexOptUpgradeIfNeeded() {
PackageManagerServiceUtils.enforceSystemOrRoot(
"Only the system can request package update");
- // The default is "true".
- if (!"false".equals(DeviceConfig.getProperty("runtime", "dexopt_system_ui_on_boot"))) {
- // System UI is important to user experience, so we check it after a mainline update or
- // an OTA. It may need to be re-compiled in these cases.
- if (hasBcpApexesChanged() || mPm.isDeviceUpgrading()) {
- checkAndDexOptSystemUi();
- }
+ int reason;
+ if (mPm.isFirstBoot()) {
+ reason = REASON_FIRST_BOOT; // First boot or factory reset.
+ } else if (mPm.isDeviceUpgrading()) {
+ reason = REASON_BOOT_AFTER_OTA;
+ } else if (hasBcpApexesChanged()) {
+ reason = REASON_BOOT_AFTER_MAINLINE_UPDATE;
+ } else {
+ return;
}
- // We need to re-extract after an OTA.
- boolean causeUpgrade = mPm.isDeviceUpgrading();
+ final long startTime = System.nanoTime();
- // First boot or factory reset.
- // Note: we also handle devices that are upgrading to N right now as if it is their
- // first boot, as they do not have profile data.
- boolean causeFirstBoot = mPm.isFirstBoot() || mPm.isPreNUpgrade();
+ if (useArtService()) {
+ mBootDexoptStartTime = startTime;
+ getArtManagerLocal().onBoot(DexoptOptions.convertToArtServiceDexoptReason(reason),
+ null /* progressCallbackExecutor */, null /* progressCallback */);
+ } else {
+ try {
+ // System UI is important to user experience, so we check it after a mainline update
+ // or an OTA. It may need to be re-compiled in these cases.
+ checkAndDexOptSystemUi(reason);
- if (!causeUpgrade && !causeFirstBoot) {
- return;
- }
+ if (reason != REASON_BOOT_AFTER_OTA && reason != REASON_FIRST_BOOT) {
+ return;
+ }
- final Computer snapshot = mPm.snapshotComputer();
- List<PackageStateInternal> pkgSettings =
- getPackagesForDexopt(snapshot.getPackageStates().values(), mPm);
+ final Computer snapshot = mPm.snapshotComputer();
- final long startTime = System.nanoTime();
- final int[] stats = performDexOptUpgrade(pkgSettings, mPm.isPreNUpgrade() /* showDialog */,
- causeFirstBoot ? REASON_FIRST_BOOT : REASON_BOOT_AFTER_OTA,
- false /* bootComplete */);
+ // TODO(b/251903639): Align this with how ART Service selects packages for boot
+ // compilation.
+ List<PackageStateInternal> pkgSettings =
+ getPackagesForDexopt(snapshot.getPackageStates().values(), mPm);
+
+ final int[] stats =
+ performDexOptUpgrade(pkgSettings, reason, false /* bootComplete */);
+ reportBootDexopt(startTime, stats[0], stats[1], stats[2]);
+ } catch (LegacyDexoptDisabledException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ private void reportBootDexopt(long startTime, int numDexopted, int numSkipped, int numFailed) {
final int elapsedTimeSeconds =
(int) TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - startTime);
final Computer newSnapshot = mPm.snapshotComputer();
- MetricsLogger.histogram(mPm.mContext, "opt_dialog_num_dexopted", stats[0]);
- MetricsLogger.histogram(mPm.mContext, "opt_dialog_num_skipped", stats[1]);
- MetricsLogger.histogram(mPm.mContext, "opt_dialog_num_failed", stats[2]);
+ MetricsLogger.histogram(mPm.mContext, "opt_dialog_num_dexopted", numDexopted);
+ MetricsLogger.histogram(mPm.mContext, "opt_dialog_num_skipped", numSkipped);
+ MetricsLogger.histogram(mPm.mContext, "opt_dialog_num_failed", numFailed);
+ // TODO(b/251903639): getOptimizablePackages calls PackageDexOptimizer.canOptimizePackage
+ // which duplicates logic in ART Service (com.android.server.art.Utils.canDexoptPackage).
MetricsLogger.histogram(mPm.mContext, "opt_dialog_num_total",
getOptimizablePackages(newSnapshot).size());
MetricsLogger.histogram(mPm.mContext, "opt_dialog_time_s", elapsedTimeSeconds);
@@ -421,9 +412,8 @@ public final class DexOptHelper {
@DexOptResult int dexoptStatus;
if (options.isDexoptOnlySecondaryDex()) {
- Optional<Integer> artSrvRes = performDexOptWithArtService(options, 0 /* extraFlags */);
- if (artSrvRes.isPresent()) {
- dexoptStatus = artSrvRes.get();
+ if (useArtService()) {
+ dexoptStatus = performDexOptWithArtService(options, 0 /* extraFlags */);
} else {
try {
return mPm.getDexManager().dexoptSecondaryDex(options);
@@ -463,10 +453,8 @@ public final class DexOptHelper {
// if the package can now be considered up to date for the given filter.
@DexOptResult
private int performDexOptInternal(DexoptOptions options) {
- Optional<Integer> artSrvRes =
- performDexOptWithArtService(options, ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES);
- if (artSrvRes.isPresent()) {
- return artSrvRes.get();
+ if (useArtService()) {
+ return performDexOptWithArtService(options, ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES);
}
AndroidPackage p;
@@ -496,46 +484,26 @@ public final class DexOptHelper {
}
/**
- * Performs dexopt on the given package using ART Service.
- *
- * @return a {@link DexOptResult}, or empty if the request isn't supported so that it is
- * necessary to fall back to the legacy code paths.
+ * Performs dexopt on the given package using ART Service. May only be called when ART Service
+ * is enabled, i.e. when {@link useArtService} returns true.
*/
- private Optional<Integer> performDexOptWithArtService(DexoptOptions options,
+ @DexOptResult
+ private int performDexOptWithArtService(DexoptOptions options,
/*@DexoptFlags*/ int extraFlags) {
- ArtManagerLocal artManager = getArtManagerLocal();
- if (artManager == null) {
- return Optional.empty();
- }
-
try (PackageManagerLocal.FilteredSnapshot snapshot =
getPackageManagerLocal().withFilteredSnapshot()) {
PackageState ops = snapshot.getPackageState(options.getPackageName());
if (ops == null) {
- return Optional.of(PackageDexOptimizer.DEX_OPT_FAILED);
+ return PackageDexOptimizer.DEX_OPT_FAILED;
}
AndroidPackage oap = ops.getAndroidPackage();
if (oap == null) {
- return Optional.of(PackageDexOptimizer.DEX_OPT_FAILED);
- }
- if (oap.isApex()) {
- return Optional.of(PackageDexOptimizer.DEX_OPT_SKIPPED);
+ return PackageDexOptimizer.DEX_OPT_FAILED;
}
-
DexoptParams params = options.convertToDexoptParams(extraFlags);
- if (params == null) {
- return Optional.empty();
- }
-
- DexoptResult result;
- try {
- result = artManager.dexoptPackage(snapshot, options.getPackageName(), params);
- } catch (UnsupportedOperationException e) {
- reportArtManagerFallback(options.getPackageName(), e.toString());
- return Optional.empty();
- }
-
- return Optional.of(convertToDexOptResult(result));
+ DexoptResult result =
+ getArtManagerLocal().dexoptPackage(snapshot, options.getPackageName(), params);
+ return convertToDexOptResult(result);
}
}
@@ -613,14 +581,12 @@ public final class DexOptHelper {
getDefaultCompilerFilter(), null /* splitName */,
DexoptOptions.DEXOPT_FORCE | DexoptOptions.DEXOPT_BOOT_COMPLETE);
- // performDexOptWithArtService ignores the snapshot and takes its own, so it can race with
- // the package checks above, but at worst the effect is only a bit less friendly error
- // below.
- Optional<Integer> artSrvRes =
- performDexOptWithArtService(options, ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES);
- int res;
- if (artSrvRes.isPresent()) {
- res = artSrvRes.get();
+ @DexOptResult int res;
+ if (useArtService()) {
+ // performDexOptWithArtService ignores the snapshot and takes its own, so it can race
+ // with the package checks above, but at worst the effect is only a bit less friendly
+ // error below.
+ res = performDexOptWithArtService(options, ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES);
} else {
try {
res = performDexOptInternalWithDependenciesLI(pkg, packageState, options);
@@ -879,6 +845,26 @@ public final class DexOptHelper {
}
/**
+ * Dumps the dexopt state for the given package, or all packages if it is null.
+ */
+ public static void dumpDexoptState(
+ @NonNull IndentingPrintWriter ipw, @Nullable String packageName) {
+ try (PackageManagerLocal.FilteredSnapshot snapshot =
+ getPackageManagerLocal().withFilteredSnapshot()) {
+ if (packageName != null) {
+ try {
+ DexOptHelper.getArtManagerLocal().dumpPackage(ipw, snapshot, packageName);
+ } catch (IllegalArgumentException e) {
+ // Package isn't found, but that should only happen due to race.
+ ipw.println(e);
+ }
+ } else {
+ DexOptHelper.getArtManagerLocal().dump(ipw, snapshot);
+ }
+ }
+ }
+
+ /**
* Returns the module names of the APEXes that contribute to bootclasspath.
*/
private static List<String> getBcpApexes() {
@@ -916,23 +902,6 @@ public final class DexOptHelper {
return false;
}
- private @NonNull PackageManagerLocal getPackageManagerLocal() {
- try {
- return LocalManagerRegistry.getManagerOrThrow(PackageManagerLocal.class);
- } catch (ManagerNotFoundException e) {
- throw new RuntimeException(e);
- }
- }
-
- /**
- * Called whenever we need to fall back from ART Service to the legacy dexopt code.
- */
- public static void reportArtManagerFallback(String packageName, String reason) {
- // STOPSHIP(b/251903639): Minimize these calls to avoid platform getting shipped with code
- // paths that will always bypass ART Service.
- Slog.i(TAG, "Falling back to old PackageManager dexopt for " + packageName + ": " + reason);
- }
-
/**
* Returns true if ART Service should be used for package optimization.
*/
@@ -954,16 +923,39 @@ public final class DexOptHelper {
}
}
- private static class DexoptDoneHandler implements ArtManagerLocal.DexoptDoneCallback {
- @NonNull private final PackageManagerService mPm;
-
- DexoptDoneHandler(@NonNull PackageManagerService pm) { mPm = pm; }
-
+ private class DexoptDoneHandler implements ArtManagerLocal.DexoptDoneCallback {
/**
- * Called after every package dexopt operation done by {@link ArtManagerLocal}.
+ * Called after every package dexopt operation done by {@link ArtManagerLocal} (when ART
+ * Service is in use).
*/
@Override
public void onDexoptDone(@NonNull DexoptResult result) {
+ switch (result.getReason()) {
+ case ReasonMapping.REASON_FIRST_BOOT:
+ case ReasonMapping.REASON_BOOT_AFTER_OTA:
+ case ReasonMapping.REASON_BOOT_AFTER_MAINLINE_UPDATE:
+ int numDexopted = 0;
+ int numSkipped = 0;
+ int numFailed = 0;
+ for (DexoptResult.PackageDexoptResult pkgRes :
+ result.getPackageDexoptResults()) {
+ switch (pkgRes.getStatus()) {
+ case DexoptResult.DEXOPT_PERFORMED:
+ numDexopted += 1;
+ break;
+ case DexoptResult.DEXOPT_SKIPPED:
+ numSkipped += 1;
+ break;
+ case DexoptResult.DEXOPT_FAILED:
+ numFailed += 1;
+ break;
+ }
+ }
+
+ reportBootDexopt(mBootDexoptStartTime, numDexopted, numSkipped, numFailed);
+ break;
+ }
+
for (DexoptResult.PackageDexoptResult pkgRes : result.getPackageDexoptResults()) {
CompilerStats.PackageStats stats =
mPm.getOrCreateCompilerPackageStats(pkgRes.getPackageName());
@@ -991,22 +983,17 @@ public final class DexOptHelper {
}
ArtManagerLocal artManager = new ArtManagerLocal(systemContext);
- // There doesn't appear to be any checks that @NonNull is heeded, so use requireNonNull
- // below to ensure we don't store away a null that we'll fail on later.
- artManager.addDexoptDoneCallback(false /* onlyIncludeUpdates */,
- Runnable::run, new DexoptDoneHandler(Objects.requireNonNull(pm)));
+ artManager.addDexoptDoneCallback(false /* onlyIncludeUpdates */, Runnable::run,
+ pm.getDexOptHelper().new DexoptDoneHandler());
LocalManagerRegistry.addManager(ArtManagerLocal.class, artManager);
artManager.scheduleBackgroundDexoptJob();
}
/**
- * Returns {@link ArtManagerLocal} if ART Service should be used for package dexopt.
+ * Returns the registered {@link ArtManagerLocal} instance, or else throws an unchecked error.
*/
- private static @Nullable ArtManagerLocal getArtManagerLocal() {
- if (!useArtService()) {
- return null;
- }
+ public static @NonNull ArtManagerLocal getArtManagerLocal() {
try {
return LocalManagerRegistry.getManagerOrThrow(ArtManagerLocal.class);
} catch (ManagerNotFoundException e) {
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index f0f23cd4b932..a4c9baa2f2c1 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -158,6 +158,8 @@ import com.android.internal.util.ArrayUtils;
import com.android.internal.util.CollectionUtils;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.EventLogTags;
+import com.android.server.LocalManagerRegistry;
+import com.android.server.art.model.DexoptParams;
import com.android.server.pm.Installer.LegacyDexoptDisabledException;
import com.android.server.pm.dex.ArtManagerService;
import com.android.server.pm.dex.DexManager;
@@ -388,7 +390,8 @@ final class InstallPackageHelper {
}
if (reconciledPkg.mCollectedSharedLibraryInfos != null
- || (oldPkgSetting != null && oldPkgSetting.getUsesLibraries() != null)) {
+ || (oldPkgSetting != null
+ && !oldPkgSetting.getSharedLibraryDependencies().isEmpty())) {
// Reconcile if the new package or the old package uses shared libraries.
// It is possible that the old package uses shared libraries but the new one doesn't.
mSharedLibraries.executeSharedLibrariesUpdate(pkg, pkgSetting, null, null,
@@ -1080,19 +1083,32 @@ final class InstallPackageHelper {
"MinInstallableTargetSdk__min_installable_target_sdk",
0);
+ // Determine if enforcement is in strict mode
+ boolean strictMode = false;
+ if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE,
+ "MinInstallableTargetSdk__install_block_strict_mode_enabled",
+ false)) {
+ if (parsedPackage.getTargetSdkVersion()
+ < DeviceConfig.getInt(DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE,
+ "MinInstallableTargetSdk__strict_mode_target_sdk",
+ 0)) {
+ strictMode = true;
+ }
+ }
+
// Skip enforcement when the bypass flag is set
boolean bypassLowTargetSdkBlock =
((installFlags & PackageManager.INSTALL_BYPASS_LOW_TARGET_SDK_BLOCK) != 0);
// Skip enforcement for tests that were installed from adb
- if (!bypassLowTargetSdkBlock
+ if (!strictMode && !bypassLowTargetSdkBlock
&& ((installFlags & PackageManager.INSTALL_FROM_ADB) != 0)) {
bypassLowTargetSdkBlock = true;
}
// Skip enforcement if the installer package name is not set
// (e.g. "pm install" from shell)
- if (!bypassLowTargetSdkBlock) {
+ if (!strictMode && !bypassLowTargetSdkBlock) {
if (request.getInstallerPackageName() == null) {
bypassLowTargetSdkBlock = true;
} else {
@@ -2325,7 +2341,6 @@ final class InstallPackageHelper {
@GuardedBy("mPm.mInstallLock")
private void executePostCommitStepsLIF(List<ReconciledPackage> reconciledPackages) {
final ArraySet<IncrementalStorage> incrementalStorages = new ArraySet<>();
- final ArrayList<String> apkPaths = new ArrayList<>();
for (ReconciledPackage reconciledPkg : reconciledPackages) {
final InstallRequest installRequest = reconciledPkg.mInstallRequest;
final boolean instantApp = ((installRequest.getScanFlags() & SCAN_AS_INSTANT_APP) != 0);
@@ -2344,13 +2359,6 @@ final class InstallPackageHelper {
incrementalStorages.add(storage);
}
- // Enabling fs-verity is a blocking operation. To reduce the impact to the install time,
- // collect the files to later enable in a background thread.
- apkPaths.add(pkg.getBaseApkPath());
- if (pkg.getSplitCodePaths() != null) {
- Collections.addAll(apkPaths, pkg.getSplitCodePaths());
- }
-
// Hardcode previousAppId to 0 to disable any data migration (http://b/221088088)
mAppDataHelper.prepareAppDataPostCommitLIF(pkg, 0);
if (installRequest.isClearCodeCache()) {
@@ -2392,6 +2400,7 @@ final class InstallPackageHelper {
|| installRequest.getInstallReason() == INSTALL_REASON_DEVICE_SETUP;
final int dexoptFlags = DexoptOptions.DEXOPT_BOOT_COMPLETE
+ | DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES
| DexoptOptions.DEXOPT_INSTALL_WITH_DEX_METADATA_FILE
| (isBackupOrRestore ? DexoptOptions.DEXOPT_FOR_RESTORE : 0);
DexoptOptions dexoptOptions =
@@ -2452,13 +2461,25 @@ final class InstallPackageHelper {
realPkgSetting.getPkgState().setUpdatedSystemApp(isUpdatedSystemApp);
- // TODO(b/251903639): Call into ART Service.
- try {
- mPackageDexOptimizer.performDexOpt(pkg, realPkgSetting,
- null /* instructionSets */, mPm.getOrCreateCompilerPackageStats(pkg),
- mDexManager.getPackageUseInfoOrDefault(packageName), dexoptOptions);
- } catch (LegacyDexoptDisabledException e) {
- throw new RuntimeException(e);
+ if (useArtService()) {
+ PackageManagerLocal packageManagerLocal =
+ LocalManagerRegistry.getManager(PackageManagerLocal.class);
+ try (PackageManagerLocal.FilteredSnapshot snapshot =
+ packageManagerLocal.withFilteredSnapshot()) {
+ DexoptParams params =
+ dexoptOptions.convertToDexoptParams(0 /* extraFlags */);
+ DexOptHelper.getArtManagerLocal().dexoptPackage(
+ snapshot, packageName, params);
+ }
+ } else {
+ try {
+ mPackageDexOptimizer.performDexOpt(pkg, realPkgSetting,
+ null /* instructionSets */,
+ mPm.getOrCreateCompilerPackageStats(pkg),
+ mDexManager.getPackageUseInfoOrDefault(packageName), dexoptOptions);
+ } catch (LegacyDexoptDisabledException e) {
+ throw new RuntimeException(e);
+ }
}
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
@@ -2479,20 +2500,6 @@ final class InstallPackageHelper {
}
PackageManagerServiceUtils.waitForNativeBinariesExtractionForIncremental(
incrementalStorages);
-
- mInjector.getBackgroundHandler().post(() -> {
- for (String path : apkPaths) {
- if (!VerityUtils.hasFsverity(path)) {
- try {
- VerityUtils.setUpFsverity(path);
- } catch (IOException e) {
- // There's nothing we can do if the setup failed. Since fs-verity is
- // optional, just ignore the error for now.
- Slog.e(TAG, "Failed to fully enable fs-verity to " + path);
- }
- }
- }
- });
}
Pair<Integer, String> verifyReplacingVersionCode(PackageInfoLite pkgLite,
diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java
index 490b2a954418..767c0a73bc54 100644
--- a/services/core/java/com/android/server/pm/OtaDexoptService.java
+++ b/services/core/java/com/android/server/pm/OtaDexoptService.java
@@ -168,13 +168,7 @@ public class OtaDexoptService extends IOtaDexopt.Stub {
Log.i(TAG, "Low on space, deleting oat files in an attempt to free up space: "
+ DexOptHelper.packagesToString(others));
for (PackageStateInternal pkg : others) {
- // TODO(b/251903639): Call into ART Service.
- try {
- mPackageManagerService.deleteOatArtifactsOfPackage(
- snapshot, pkg.getPackageName());
- } catch (LegacyDexoptDisabledException e) {
- throw new RuntimeException(e);
- }
+ mPackageManagerService.deleteOatArtifactsOfPackage(snapshot, pkg.getPackageName());
}
}
long spaceAvailableNow = getAvailableSpace();
diff --git a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
index 99fff720221e..4e7521070132 100644
--- a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
+++ b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
@@ -47,7 +47,6 @@ import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.SparseArray;
-import com.android.server.pm.Installer.LegacyDexoptDisabledException;
import com.android.server.pm.dex.DexManager;
import com.android.server.pm.permission.PermissionManagerServiceInternal;
import com.android.server.pm.pkg.AndroidPackage;
@@ -708,12 +707,7 @@ abstract class PackageManagerInternalBase extends PackageManagerInternal {
@Override
@Deprecated
public final long deleteOatArtifactsOfPackage(String packageName) {
- // TODO(b/251903639): Call into ART Service.
- try {
- return mService.deleteOatArtifactsOfPackage(snapshot(), packageName);
- } catch (LegacyDexoptDisabledException e) {
- throw new RuntimeException(e);
- }
+ return mService.deleteOatArtifactsOfPackage(snapshot(), packageName);
}
@Override
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 8f95093ad826..9a15e1108b52 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -196,6 +196,7 @@ import com.android.server.SystemConfig;
import com.android.server.Watchdog;
import com.android.server.apphibernation.AppHibernationManagerInternal;
import com.android.server.art.DexUseManagerLocal;
+import com.android.server.art.model.DeleteResult;
import com.android.server.compat.CompatChange;
import com.android.server.compat.PlatformCompat;
import com.android.server.pm.Installer.InstallerException;
@@ -552,6 +553,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService
private static final int REQUIRED_VERIFIERS_MAX_COUNT = 2;
// Compilation reasons.
+ // TODO(b/260124949): Clean this up with the legacy dexopt code.
public static final int REASON_FIRST_BOOT = 0;
public static final int REASON_BOOT_AFTER_OTA = 1;
public static final int REASON_POST_BOOT = 2;
@@ -565,7 +567,8 @@ public class PackageManagerService implements PackageSender, TestUtilityService
public static final int REASON_AB_OTA = 10;
public static final int REASON_INACTIVE_PACKAGE_DOWNGRADE = 11;
public static final int REASON_CMDLINE = 12;
- public static final int REASON_SHARED = 13;
+ public static final int REASON_BOOT_AFTER_MAINLINE_UPDATE = 13;
+ public static final int REASON_SHARED = 14;
public static final int REASON_LAST = REASON_SHARED;
@@ -588,7 +591,6 @@ public class PackageManagerService implements PackageSender, TestUtilityService
private final int mDefParseFlags;
private final String[] mSeparateProcesses;
private final boolean mIsUpgrade;
- private final boolean mIsPreNUpgrade;
private final boolean mIsPreNMR1Upgrade;
private final boolean mIsPreQUpgrade;
@@ -1734,7 +1736,6 @@ public class PackageManagerService implements PackageSender, TestUtilityService
mInstantAppResolverConnection = testParams.instantAppResolverConnection;
mInstantAppResolverSettingsComponent = testParams.instantAppResolverSettingsComponent;
mIsPreNMR1Upgrade = testParams.isPreNmr1Upgrade;
- mIsPreNUpgrade = testParams.isPreNupgrade;
mIsPreQUpgrade = testParams.isPreQupgrade;
mIsUpgrade = testParams.isUpgrade;
mMetrics = testParams.Metrics;
@@ -2069,10 +2070,6 @@ public class PackageManagerService implements PackageSender, TestUtilityService
mPromoteSystemApps =
mIsUpgrade && ver.sdkVersion <= Build.VERSION_CODES.LOLLIPOP_MR1;
- // When upgrading from pre-N, we need to handle package extraction like first boot,
- // as there is no profiling data available.
- mIsPreNUpgrade = mIsUpgrade && ver.sdkVersion < Build.VERSION_CODES.N;
-
mIsPreNMR1Upgrade = mIsUpgrade && ver.sdkVersion < Build.VERSION_CODES.N_MR1;
mIsPreQUpgrade = mIsUpgrade && ver.sdkVersion < Build.VERSION_CODES.Q;
@@ -2954,13 +2951,12 @@ public class PackageManagerService implements PackageSender, TestUtilityService
}
if (doTrim) {
if (!isFirstBoot()) {
- if (mDexOptHelper.isDexOptDialogShown()) {
- try {
- ActivityManager.getService().showBootMessage(
- mContext.getResources().getString(
- R.string.android_upgrading_fstrim), true);
- } catch (RemoteException e) {
- }
+ try {
+ ActivityManager.getService().showBootMessage(
+ mContext.getResources().getString(
+ R.string.android_upgrading_fstrim),
+ true);
+ } catch (RemoteException e) {
}
}
sm.runMaintenance();
@@ -2974,12 +2970,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService
}
public void updatePackagesIfNeeded() {
- // TODO(b/251903639): Call into ART Service.
- try {
- mDexOptHelper.performPackageDexOptUpgradeIfNeeded();
- } catch (LegacyDexoptDisabledException e) {
- throw new RuntimeException(e);
- }
+ mDexOptHelper.performPackageDexOptUpgradeIfNeeded();
}
private void notifyPackageUseInternal(String packageName, int reason) {
@@ -2997,6 +2988,10 @@ public class PackageManagerService implements PackageSender, TestUtilityService
return mDexManager;
}
+ /*package*/ DexOptHelper getDexOptHelper() {
+ return mDexOptHelper;
+ }
+
/*package*/ DynamicCodeLogger getDynamicCodeLogger() {
return mDynamicCodeLogger;
}
@@ -5435,9 +5430,11 @@ public class PackageManagerService implements PackageSender, TestUtilityService
String loadingPkgDexCodeIsa = InstructionSets.getDexCodeInstructionSet(
VMRuntime.getInstructionSet(loadingPkgAbi));
if (!loaderIsa.equals(loadingPkgDexCodeIsa)) {
- // TODO(b/251903639): Make this crash to surface this problem
- // better.
- Slog.w(PackageManagerService.TAG,
+ // TODO(b/251903639): We make this a wtf to surface any situations
+ // where this argument doesn't correspond to our expectations. Later
+ // it should be turned into an IllegalArgumentException, when we can
+ // assume it's the caller that's wrong rather than us.
+ Log.wtf(TAG,
"Invalid loaderIsa in notifyDexLoad call from "
+ loadingPackageName + ", uid " + callingUid
+ ": expected " + loadingPkgDexCodeIsa + ", got "
@@ -7097,17 +7094,39 @@ public class PackageManagerService implements PackageSender, TestUtilityService
return AndroidPackageUtils.canHaveOatDir(packageState, packageState.getPkg());
}
- long deleteOatArtifactsOfPackage(@NonNull Computer snapshot, String packageName)
- throws LegacyDexoptDisabledException {
+ long deleteOatArtifactsOfPackage(@NonNull Computer snapshot, String packageName) {
PackageManagerServiceUtils.enforceSystemOrRootOrShell(
"Only the system or shell can delete oat artifacts");
- PackageStateInternal packageState = snapshot.getPackageStateInternal(packageName);
- if (packageState == null || packageState.getPkg() == null) {
- return -1; // error code of deleteOptimizedFiles
+ if (DexOptHelper.useArtService()) {
+ // TODO(chiuwinson): Retrieve filtered snapshot from Computer instance instead.
+ try (PackageManagerLocal.FilteredSnapshot filteredSnapshot =
+ PackageManagerServiceUtils.getPackageManagerLocal()
+ .withFilteredSnapshot()) {
+ try {
+ DeleteResult res = DexOptHelper.getArtManagerLocal().deleteDexoptArtifacts(
+ filteredSnapshot, packageName);
+ return res.getFreedBytes();
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, e.toString());
+ return -1;
+ } catch (IllegalStateException e) {
+ Slog.wtfStack(TAG, e.toString());
+ return -1;
+ }
+ }
+ } else {
+ PackageStateInternal packageState = snapshot.getPackageStateInternal(packageName);
+ if (packageState == null || packageState.getPkg() == null) {
+ return -1; // error code of deleteOptimizedFiles
+ }
+ try {
+ return mDexManager.deleteOptimizedFiles(
+ ArtUtils.createArtPackageInfo(packageState.getPkg(), packageState));
+ } catch (LegacyDexoptDisabledException e) {
+ throw new RuntimeException(e);
+ }
}
- return mDexManager.deleteOptimizedFiles(
- ArtUtils.createArtPackageInfo(packageState.getPkg(), packageState));
}
List<String> getMimeGroupInternal(@NonNull Computer snapshot, String packageName,
@@ -7476,10 +7495,6 @@ public class PackageManagerService implements PackageSender, TestUtilityService
return mPlatformPackage;
}
- boolean isPreNUpgrade() {
- return mIsPreNUpgrade;
- }
-
boolean isPreNMR1Upgrade() {
return mIsPreNMR1Upgrade;
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java b/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java
index 7c1f054a8fec..e2ddba5188fa 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java
@@ -41,6 +41,7 @@ public class PackageManagerServiceCompilerMapping {
"ab-ota",
"inactive",
"cmdline",
+ "boot-after-mainline-update",
// "shared" must be the last entry
"shared"
};
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
index 08ff51d0f1ab..e5cfa671a930 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
@@ -63,7 +63,6 @@ public final class PackageManagerServiceTestParams {
public InstantAppResolverConnection instantAppResolverConnection;
public ComponentName instantAppResolverSettingsComponent;
public boolean isPreNmr1Upgrade;
- public boolean isPreNupgrade;
public boolean isPreQupgrade;
public boolean isUpgrade;
public LegacyPermissionManagerInternal legacyPermissionManagerInternal;
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 0de1a4e0bc7c..ad8e35db2eaa 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -24,6 +24,7 @@ import static android.system.OsConstants.O_CREAT;
import static android.system.OsConstants.O_RDWR;
import static com.android.internal.content.NativeLibraryHelper.LIB_DIR_NAME;
+import static com.android.server.LocalManagerRegistry.ManagerNotFoundException;
import static com.android.server.pm.PackageManagerService.COMPRESSED_EXTENSION;
import static com.android.server.pm.PackageManagerService.DEBUG_COMPRESSION;
import static com.android.server.pm.PackageManagerService.DEBUG_INTENT_MATCHING;
@@ -91,6 +92,7 @@ import com.android.internal.util.FastPrintWriter;
import com.android.internal.util.HexDump;
import com.android.server.EventLogTags;
import com.android.server.IntentResolver;
+import com.android.server.LocalManagerRegistry;
import com.android.server.Watchdog;
import com.android.server.compat.PlatformCompat;
import com.android.server.pm.dex.PackageDexUsage;
@@ -201,6 +203,17 @@ public class PackageManagerServiceUtils {
private static final boolean FORCE_PACKAGE_PARSED_CACHE_ENABLED = false;
/**
+ * Returns the registered PackageManagerLocal instance, or else throws an unchecked error.
+ */
+ public static @NonNull PackageManagerLocal getPackageManagerLocal() {
+ try {
+ return LocalManagerRegistry.getManagerOrThrow(PackageManagerLocal.class);
+ } catch (ManagerNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
* Checks if the package was inactive during since <code>thresholdTimeinMillis</code>.
* Package is considered active, if:
* 1) It was active in foreground.
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 53fdfaad38ac..2a1172c93d74 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -1219,7 +1219,7 @@ public class PackageSetting extends SettingBase implements PackageStateInternal
@NonNull
@Override
- public List<SharedLibrary> getUsesLibraries() {
+ public List<SharedLibrary> getSharedLibraryDependencies() {
return (List<SharedLibrary>) (List<?>) pkgState.getUsesLibraryInfos();
}
diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java
index e5aaddbf57f8..10673c6f2dd2 100644
--- a/services/core/java/com/android/server/pm/RemovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java
@@ -422,15 +422,18 @@ final class RemovePackageHelper {
if (instructionSets == null) {
throw new IllegalStateException("instructionSet == null");
}
- String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
- for (String codePath : allCodePaths) {
- for (String dexCodeInstructionSet : dexCodeInstructionSets) {
- // TODO(b/251903639): Call into ART Service.
- try {
- mPm.mInstaller.rmdex(codePath, dexCodeInstructionSet);
- } catch (LegacyDexoptDisabledException e) {
- throw new RuntimeException(e);
- } catch (Installer.InstallerException ignored) {
+ // TODO(b/265813358): ART Service currently doesn't support deleting optimized artifacts
+ // relative to an arbitrary APK path. Skip this and rely on its file GC instead.
+ if (!DexOptHelper.useArtService()) {
+ String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
+ for (String codePath : allCodePaths) {
+ for (String dexCodeInstructionSet : dexCodeInstructionSets) {
+ try {
+ mPm.mInstaller.rmdex(codePath, dexCodeInstructionSet);
+ } catch (LegacyDexoptDisabledException e) {
+ throw new RuntimeException(e);
+ } catch (Installer.InstallerException ignored) {
+ }
}
}
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 53a5648c8403..328fc8b692bf 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -35,6 +35,7 @@ import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.ActivityManagerNative;
+import android.app.BroadcastOptions;
import android.app.IActivityManager;
import android.app.IStopUserCallback;
import android.app.KeyguardManager;
@@ -2924,7 +2925,13 @@ public class UserManagerService extends IUserManager.Stub {
final Intent broadcast = new Intent(UserManager.ACTION_USER_RESTRICTIONS_CHANGED)
.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
- mContext.sendBroadcastAsUser(broadcast, UserHandle.of(userId));
+ // Setting the MOST_RECENT policy allows us to discard older broadcasts
+ // still waiting to be delivered.
+ final Bundle options = BroadcastOptions.makeBasic()
+ .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
+ .toBundle();
+ mContext.sendBroadcastAsUser(broadcast, UserHandle.of(userId),
+ null /* receiverPermission */, options);
}
});
}
@@ -3718,14 +3725,12 @@ public class UserManagerService extends IUserManager.Stub {
}
if (userVersion < 6) {
- final boolean splitSystemUser = UserManager.isSplitSystemUser();
synchronized (mUsersLock) {
for (int i = 0; i < mUsers.size(); i++) {
UserData userData = mUsers.valueAt(i);
- // In non-split mode, only user 0 can have restricted profiles
- if (!splitSystemUser && userData.info.isRestricted()
- && (userData.info.restrictedProfileParentId
- == UserInfo.NO_PROFILE_GROUP_ID)) {
+ // Only system user can have restricted profiles
+ if (userData.info.isRestricted() && (userData.info.restrictedProfileParentId
+ == UserInfo.NO_PROFILE_GROUP_ID)) {
userData.info.restrictedProfileParentId = UserHandle.USER_SYSTEM;
userIdsToWrite.add(userData.info.id);
}
@@ -6496,7 +6501,6 @@ public class UserManagerService extends IUserManager.Stub {
pw.println(" All guests ephemeral: " + Resources.getSystem().getBoolean(
com.android.internal.R.bool.config_guestUserEphemeral));
pw.println(" Force ephemeral users: " + mForceEphemeralUsers);
- pw.println(" Is split-system user: " + UserManager.isSplitSystemUser());
final boolean isHeadlessSystemUserMode = isHeadlessSystemUserMode();
pw.println(" Is headless-system mode: " + isHeadlessSystemUserMode);
if (isHeadlessSystemUserMode != RoSystemProperties.MULTIUSER_HEADLESS_SYSTEM_USER) {
diff --git a/services/core/java/com/android/server/pm/dex/ArtManagerService.java b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
index b5b6347b79c8..d8b6cd5bb7e3 100644
--- a/services/core/java/com/android/server/pm/dex/ArtManagerService.java
+++ b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
@@ -50,11 +50,15 @@ import com.android.internal.os.RoSystemProperties;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.Preconditions;
import com.android.server.LocalServices;
+import com.android.server.art.ArtManagerLocal;
+import com.android.server.pm.DexOptHelper;
import com.android.server.pm.Installer;
import com.android.server.pm.Installer.InstallerException;
import com.android.server.pm.Installer.LegacyDexoptDisabledException;
+import com.android.server.pm.PackageManagerLocal;
import com.android.server.pm.PackageManagerService;
import com.android.server.pm.PackageManagerServiceCompilerMapping;
+import com.android.server.pm.PackageManagerServiceUtils;
import com.android.server.pm.parsing.PackageInfoUtils;
import com.android.server.pm.pkg.AndroidPackage;
import com.android.server.pm.pkg.PackageState;
@@ -210,20 +214,15 @@ public class ArtManagerService extends android.content.pm.dex.IArtManager.Stub {
Slog.d(TAG, "Requested snapshot for " + packageName + ":" + codePath);
}
- // TODO(b/251903639): Call into ART Service.
- try {
- if (bootImageProfile) {
- snapshotBootImageProfile(callback);
- } else {
- snapshotAppProfile(packageName, codePath, callback);
- }
- } catch (LegacyDexoptDisabledException e) {
- throw new RuntimeException(e);
+ if (bootImageProfile) {
+ snapshotBootImageProfile(callback);
+ } else {
+ snapshotAppProfile(packageName, codePath, callback);
}
}
- private void snapshotAppProfile(String packageName, String codePath,
- ISnapshotRuntimeProfileCallback callback) throws LegacyDexoptDisabledException {
+ private void snapshotAppProfile(
+ String packageName, String codePath, ISnapshotRuntimeProfileCallback callback) {
PackageInfo info = null;
try {
// Note that we use the default user 0 to retrieve the package info.
@@ -260,17 +259,45 @@ public class ArtManagerService extends android.content.pm.dex.IArtManager.Stub {
}
// All good, create the profile snapshot.
- int appId = UserHandle.getAppId(info.applicationInfo.uid);
- if (appId < 0) {
- postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
- Slog.wtf(TAG, "AppId is -1 for package: " + packageName);
- return;
- }
+ if (DexOptHelper.useArtService()) {
+ ParcelFileDescriptor fd;
+
+ try (PackageManagerLocal.FilteredSnapshot snapshot =
+ PackageManagerServiceUtils.getPackageManagerLocal()
+ .withFilteredSnapshot()) {
+ fd = DexOptHelper.getArtManagerLocal().snapshotAppProfile(
+ snapshot, packageName, splitName);
+ } catch (IllegalArgumentException e) {
+ // ArtManagerLocal.snapshotAppProfile couldn't find the package or split. Since
+ // we've checked them above this can only happen due to race, i.e. the package got
+ // removed. So let's report it as SNAPSHOT_FAILED_PACKAGE_NOT_FOUND even if it was
+ // for the split.
+ // TODO(mast): Reuse the same snapshot to avoid this race.
+ postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_PACKAGE_NOT_FOUND);
+ return;
+ } catch (IllegalStateException | ArtManagerLocal.SnapshotProfileException e) {
+ postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
+ return;
+ }
+
+ postSuccess(packageName, fd, callback);
+ } else {
+ int appId = UserHandle.getAppId(info.applicationInfo.uid);
+ if (appId < 0) {
+ postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
+ Slog.wtf(TAG, "AppId is -1 for package: " + packageName);
+ return;
+ }
- createProfileSnapshot(packageName, ArtManager.getProfileName(splitName), codePath,
- appId, callback);
- // Destroy the snapshot, we no longer need it.
- destroyProfileSnapshot(packageName, ArtManager.getProfileName(splitName));
+ try {
+ createProfileSnapshot(packageName, ArtManager.getProfileName(splitName), codePath,
+ appId, callback);
+ // Destroy the snapshot, we no longer need it.
+ destroyProfileSnapshot(packageName, ArtManager.getProfileName(splitName));
+ } catch (LegacyDexoptDisabledException e) {
+ throw new RuntimeException(e);
+ }
+ }
}
private void createProfileSnapshot(String packageName, String profileName, String classpath,
@@ -340,23 +367,43 @@ public class ArtManagerService extends android.content.pm.dex.IArtManager.Stub {
}
}
- private void snapshotBootImageProfile(ISnapshotRuntimeProfileCallback callback)
- throws LegacyDexoptDisabledException {
- // Combine the profiles for boot classpath and system server classpath.
- // This avoids having yet another type of profiles and simplifies the processing.
- String classpath = String.join(":", Os.getenv("BOOTCLASSPATH"),
- Os.getenv("SYSTEMSERVERCLASSPATH"));
+ private void snapshotBootImageProfile(ISnapshotRuntimeProfileCallback callback) {
+ if (DexOptHelper.useArtService()) {
+ ParcelFileDescriptor fd;
+
+ try (PackageManagerLocal.FilteredSnapshot snapshot =
+ PackageManagerServiceUtils.getPackageManagerLocal()
+ .withFilteredSnapshot()) {
+ fd = DexOptHelper.getArtManagerLocal().snapshotBootImageProfile(snapshot);
+ } catch (IllegalStateException | ArtManagerLocal.SnapshotProfileException e) {
+ postError(callback, BOOT_IMAGE_ANDROID_PACKAGE,
+ ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
+ return;
+ }
- final String standaloneSystemServerJars = Os.getenv("STANDALONE_SYSTEMSERVER_JARS");
- if (standaloneSystemServerJars != null) {
- classpath = String.join(":", classpath, standaloneSystemServerJars);
- }
+ postSuccess(BOOT_IMAGE_ANDROID_PACKAGE, fd, callback);
+ } else {
+ // Combine the profiles for boot classpath and system server classpath.
+ // This avoids having yet another type of profiles and simplifies the processing.
+ String classpath = String.join(
+ ":", Os.getenv("BOOTCLASSPATH"), Os.getenv("SYSTEMSERVERCLASSPATH"));
+
+ final String standaloneSystemServerJars = Os.getenv("STANDALONE_SYSTEMSERVER_JARS");
+ if (standaloneSystemServerJars != null) {
+ classpath = String.join(":", classpath, standaloneSystemServerJars);
+ }
- // Create the snapshot.
- createProfileSnapshot(BOOT_IMAGE_ANDROID_PACKAGE, BOOT_IMAGE_PROFILE_NAME, classpath,
- /*appId*/ -1, callback);
- // Destroy the snapshot, we no longer need it.
- destroyProfileSnapshot(BOOT_IMAGE_ANDROID_PACKAGE, BOOT_IMAGE_PROFILE_NAME);
+ try {
+ // Create the snapshot.
+ createProfileSnapshot(BOOT_IMAGE_ANDROID_PACKAGE, BOOT_IMAGE_PROFILE_NAME,
+ classpath,
+ /*appId*/ -1, callback);
+ // Destroy the snapshot, we no longer need it.
+ destroyProfileSnapshot(BOOT_IMAGE_ANDROID_PACKAGE, BOOT_IMAGE_PROFILE_NAME);
+ } catch (LegacyDexoptDisabledException e) {
+ throw new RuntimeException(e);
+ }
+ }
}
/**
@@ -620,6 +667,7 @@ public class ArtManagerService extends android.content.pm.dex.IArtManager.Stub {
private static final int TRON_COMPILATION_REASON_CMDLINE = 22;
private static final int TRON_COMPILATION_REASON_PREBUILT = 23;
private static final int TRON_COMPILATION_REASON_VDEX = 24;
+ private static final int TRON_COMPILATION_REASON_BOOT_AFTER_MAINLINE_UPDATE = 25;
// The annotation to add as a suffix to the compilation reason when dexopt was
// performed with dex metadata.
@@ -634,6 +682,8 @@ public class ArtManagerService extends android.content.pm.dex.IArtManager.Stub {
case "error" : return TRON_COMPILATION_REASON_ERROR;
case "first-boot" : return TRON_COMPILATION_REASON_FIRST_BOOT;
case "boot-after-ota": return TRON_COMPILATION_REASON_BOOT_AFTER_OTA;
+ case "boot-after-mainline-update":
+ return TRON_COMPILATION_REASON_BOOT_AFTER_MAINLINE_UPDATE;
case "post-boot" : return TRON_COMPILATION_REASON_POST_BOOT;
case "install" : return TRON_COMPILATION_REASON_INSTALL;
case "bg-dexopt" : return TRON_COMPILATION_REASON_BG_DEXOPT;
diff --git a/services/core/java/com/android/server/pm/dex/DexoptOptions.java b/services/core/java/com/android/server/pm/dex/DexoptOptions.java
index d3fba7c3308d..310c0e8c68fe 100644
--- a/services/core/java/com/android/server/pm/dex/DexoptOptions.java
+++ b/services/core/java/com/android/server/pm/dex/DexoptOptions.java
@@ -20,18 +20,20 @@ import static com.android.server.pm.PackageManagerServiceCompilerMapping.getComp
import static dalvik.system.DexFile.isProfileGuidedCompilerFilter;
-import android.annotation.Nullable;
+import android.annotation.NonNull;
+import android.util.Log;
import com.android.server.art.ReasonMapping;
import com.android.server.art.model.ArtFlags;
import com.android.server.art.model.DexoptParams;
-import com.android.server.pm.DexOptHelper;
import com.android.server.pm.PackageManagerService;
/**
* Options used for dexopt invocations.
*/
public final class DexoptOptions {
+ private static final String TAG = "DexoptOptions";
+
// When set, the profiles will be checked for updates before calling dexopt. If
// the apps profiles didn't update in a meaningful way (decided by the compiler), dexopt
// will be skipped.
@@ -87,8 +89,9 @@ public final class DexoptOptions {
// The set of flags for the dexopt options. It's a mix of the DEXOPT_* flags.
private final int mFlags;
- // When not null, dexopt will optimize only the split identified by this name.
- // It only applies for primary apk and it's always null if mOnlySecondaryDex is true.
+ // When not null, dexopt will optimize only the split identified by this APK file name (not
+ // split name). It only applies for primary apk and it's always null if mOnlySecondaryDex is
+ // true.
private final String mSplitName;
// The reason for invoking dexopt (see PackageManagerService.REASON_* constants).
@@ -201,19 +204,68 @@ public final class DexoptOptions {
}
/**
+ * Returns the ART Service reason for the given PackageManagerService reason. Throws unchecked
+ * exceptions for reasons that aren't supported.
+ */
+ public static @NonNull String convertToArtServiceDexoptReason(int pmDexoptReason) {
+ switch (pmDexoptReason) {
+ case PackageManagerService.REASON_FIRST_BOOT:
+ return ReasonMapping.REASON_FIRST_BOOT;
+ case PackageManagerService.REASON_BOOT_AFTER_OTA:
+ return ReasonMapping.REASON_BOOT_AFTER_OTA;
+ case PackageManagerService.REASON_INSTALL:
+ return ReasonMapping.REASON_INSTALL;
+ case PackageManagerService.REASON_INSTALL_FAST:
+ return ReasonMapping.REASON_INSTALL_FAST;
+ case PackageManagerService.REASON_INSTALL_BULK:
+ return ReasonMapping.REASON_INSTALL_BULK;
+ case PackageManagerService.REASON_INSTALL_BULK_SECONDARY:
+ return ReasonMapping.REASON_INSTALL_BULK_SECONDARY;
+ case PackageManagerService.REASON_INSTALL_BULK_DOWNGRADED:
+ return ReasonMapping.REASON_INSTALL_BULK_DOWNGRADED;
+ case PackageManagerService.REASON_INSTALL_BULK_SECONDARY_DOWNGRADED:
+ return ReasonMapping.REASON_INSTALL_BULK_SECONDARY_DOWNGRADED;
+ case PackageManagerService.REASON_BACKGROUND_DEXOPT:
+ return ReasonMapping.REASON_BG_DEXOPT;
+ case PackageManagerService.REASON_INACTIVE_PACKAGE_DOWNGRADE:
+ return ReasonMapping.REASON_INACTIVE;
+ case PackageManagerService.REASON_CMDLINE:
+ return ReasonMapping.REASON_CMDLINE;
+ case PackageManagerService.REASON_POST_BOOT:
+ case PackageManagerService.REASON_SHARED:
+ case PackageManagerService.REASON_AB_OTA:
+ // REASON_POST_BOOT isn't supported - that dexopt stage is getting removed.
+ // REASON_SHARED shouldn't go to ART Service - it's only used at lower levels
+ // in PackageDexOptimizer.
+ // TODO(b/251921228): OTA isn't supported, so REASON_AB_OTA shouldn't come this way
+ // either.
+ throw new UnsupportedOperationException(
+ "ART Service unsupported compilation reason " + pmDexoptReason);
+ default:
+ throw new IllegalArgumentException("Invalid compilation reason " + pmDexoptReason);
+ }
+ }
+
+ /**
* Returns an {@link DexoptParams} instance corresponding to this object, for use with
* {@link com.android.server.art.ArtManagerLocal}.
*
* @param extraFlags extra {@link ArtFlags#DexoptFlags} to set in the returned
* {@code DexoptParams} beyond those converted from this object
- * @return null if the settings cannot be accurately represented, and hence the old
- * PackageManager/installd code paths need to be used.
+ * @throws UnsupportedOperationException if the settings cannot be accurately represented.
*/
- public @Nullable DexoptParams convertToDexoptParams(/*@DexoptFlags*/ int extraFlags) {
+ public @NonNull DexoptParams convertToDexoptParams(/*@DexoptFlags*/ int extraFlags) {
if (mSplitName != null) {
- DexOptHelper.reportArtManagerFallback(
- mPackageName, "Request to optimize only split " + mSplitName);
- return null;
+ // ART Service supports dexopting a single split - see ArtFlags.FLAG_FOR_SINGLE_SPLIT.
+ // However using it here requires searching through the splits to find the one matching
+ // the APK file name in mSplitName, and we don't have the AndroidPackage available for
+ // that.
+ //
+ // Hence we throw here instead, under the assumption that no code paths that dexopt
+ // splits need this conversion (e.g. shell commands with the --split argument are
+ // handled by ART Service directly).
+ throw new UnsupportedOperationException(
+ "Request to optimize only split " + mSplitName + " for " + mPackageName);
}
/*@DexoptFlags*/ int flags = extraFlags;
@@ -236,11 +288,11 @@ public final class DexoptOptions {
flags |= ArtFlags.FLAG_SHOULD_DOWNGRADE;
}
if ((mFlags & DEXOPT_INSTALL_WITH_DEX_METADATA_FILE) == 0) {
- // ART Service cannot be instructed to ignore a DM file if present, so not setting this
- // flag is not supported.
- DexOptHelper.reportArtManagerFallback(
- mPackageName, "DEXOPT_INSTALL_WITH_DEX_METADATA_FILE not set");
- return null;
+ // ART Service cannot be instructed to ignore a DM file if present.
+ Log.w(TAG,
+ "DEXOPT_INSTALL_WITH_DEX_METADATA_FILE not set in request to optimise "
+ + mPackageName
+ + " - ART Service will unconditionally use a DM file if present.");
}
/*@PriorityClassApi*/ int priority;
@@ -269,60 +321,7 @@ public final class DexoptOptions {
// - DEXOPT_IDLE_BACKGROUND_JOB: Its only effect is to allow the debug variant dex2oatd to
// be used, but ART Service never uses that (cf. Artd::GetDex2Oat in artd.cc).
- String reason;
- switch (mCompilationReason) {
- case PackageManagerService.REASON_FIRST_BOOT:
- reason = ReasonMapping.REASON_FIRST_BOOT;
- break;
- case PackageManagerService.REASON_BOOT_AFTER_OTA:
- reason = ReasonMapping.REASON_BOOT_AFTER_OTA;
- break;
- case PackageManagerService.REASON_POST_BOOT:
- // This reason will go away with the legacy dexopt code.
- DexOptHelper.reportArtManagerFallback(
- mPackageName, "Unsupported compilation reason REASON_POST_BOOT");
- return null;
- case PackageManagerService.REASON_INSTALL:
- reason = ReasonMapping.REASON_INSTALL;
- break;
- case PackageManagerService.REASON_INSTALL_FAST:
- reason = ReasonMapping.REASON_INSTALL_FAST;
- break;
- case PackageManagerService.REASON_INSTALL_BULK:
- reason = ReasonMapping.REASON_INSTALL_BULK;
- break;
- case PackageManagerService.REASON_INSTALL_BULK_SECONDARY:
- reason = ReasonMapping.REASON_INSTALL_BULK_SECONDARY;
- break;
- case PackageManagerService.REASON_INSTALL_BULK_DOWNGRADED:
- reason = ReasonMapping.REASON_INSTALL_BULK_DOWNGRADED;
- break;
- case PackageManagerService.REASON_INSTALL_BULK_SECONDARY_DOWNGRADED:
- reason = ReasonMapping.REASON_INSTALL_BULK_SECONDARY_DOWNGRADED;
- break;
- case PackageManagerService.REASON_BACKGROUND_DEXOPT:
- reason = ReasonMapping.REASON_BG_DEXOPT;
- break;
- case PackageManagerService.REASON_INACTIVE_PACKAGE_DOWNGRADE:
- reason = ReasonMapping.REASON_INACTIVE;
- break;
- case PackageManagerService.REASON_CMDLINE:
- reason = ReasonMapping.REASON_CMDLINE;
- break;
- case PackageManagerService.REASON_SHARED:
- case PackageManagerService.REASON_AB_OTA:
- // REASON_SHARED shouldn't go into this code path - it's only used at lower levels
- // in PackageDexOptimizer.
- // TODO(b/251921228): OTA isn't supported, so REASON_AB_OTA shouldn't come this way
- // either.
- throw new UnsupportedOperationException(
- "ART Service unsupported compilation reason " + mCompilationReason);
- default:
- throw new IllegalArgumentException(
- "Invalid compilation reason " + mCompilationReason);
- }
-
- return new DexoptParams.Builder(reason, flags)
+ return new DexoptParams.Builder(convertToArtServiceDexoptReason(mCompilationReason), flags)
.setCompilerFilter(mCompilerFilter)
.setPriorityClass(priority)
.build();
diff --git a/services/core/java/com/android/server/pm/pkg/PackageState.java b/services/core/java/com/android/server/pm/pkg/PackageState.java
index a12c9d0498a1..106b149997a7 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageState.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageState.java
@@ -158,10 +158,11 @@ public interface PackageState {
PackageUserState getStateForUser(@NonNull UserHandle user);
/**
- * @see R.styleable#AndroidManifestUsesLibrary
+ * List of shared libraries that this package declares a dependency on. This includes all
+ * types of libraries, system or app provided and Java or native.
*/
@NonNull
- List<SharedLibrary> getUsesLibraries();
+ List<SharedLibrary> getSharedLibraryDependencies();
/** Whether this represents an APEX module. This is different from an APK inside an APEX. */
boolean isApex();
diff --git a/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java b/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
index bc6dab41fc7e..91a25db3710e 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
@@ -190,7 +190,7 @@ public class PackageStateImpl implements PackageState {
mUsesSdkLibrariesVersionsMajor = pkgState.getUsesSdkLibrariesVersionsMajor();
mUsesStaticLibraries = pkgState.getUsesStaticLibraries();
mUsesStaticLibrariesVersions = pkgState.getUsesStaticLibrariesVersions();
- mUsesLibraries = Collections.unmodifiableList(pkgState.getUsesLibraries());
+ mUsesLibraries = Collections.unmodifiableList(pkgState.getSharedLibraryDependencies());
mUsesLibraryFiles = Collections.unmodifiableList(pkgState.getUsesLibraryFiles());
setBoolean(Booleans.FORCE_QUERYABLE_OVERRIDE, pkgState.isForceQueryableOverride());
setBoolean(Booleans.HIDDEN_UNTIL_INSTALLED, pkgState.isHiddenUntilInstalled());
@@ -693,7 +693,7 @@ public class PackageStateImpl implements PackageState {
}
@DataClass.Generated.Member
- public @NonNull List<SharedLibrary> getUsesLibraries() {
+ public @NonNull List<SharedLibrary> getSharedLibraryDependencies() {
return mUsesLibraries;
}
diff --git a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
index 91bb677524cf..7f733efef8a2 100644
--- a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
+++ b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
@@ -27,6 +27,7 @@ import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Environment;
+import android.os.PowerManager;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;
@@ -80,7 +81,8 @@ import javax.xml.datatype.DatatypeConfigurationException;
* provided.
*/
public final class DeviceStateProviderImpl implements DeviceStateProvider,
- InputManagerInternal.LidSwitchCallback, SensorEventListener {
+ InputManagerInternal.LidSwitchCallback, SensorEventListener,
+ PowerManager.OnThermalStatusChangedListener {
private static final String TAG = "DeviceStateProviderImpl";
private static final boolean DEBUG = false;
@@ -97,6 +99,10 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider,
private static final String FLAG_CANCEL_OVERRIDE_REQUESTS = "FLAG_CANCEL_OVERRIDE_REQUESTS";
private static final String FLAG_APP_INACCESSIBLE = "FLAG_APP_INACCESSIBLE";
private static final String FLAG_EMULATED_ONLY = "FLAG_EMULATED_ONLY";
+ private static final String FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP =
+ "FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP";
+ private static final String FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL =
+ "FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL";
/** Interface that allows reading the device state configuration. */
interface ReadableConfig {
@@ -152,6 +158,13 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider,
break;
case FLAG_EMULATED_ONLY:
flags |= DeviceState.FLAG_EMULATED_ONLY;
+ break;
+ case FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP:
+ flags |= DeviceState.FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP;
+ break;
+ case FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL:
+ flags |= DeviceState.FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL;
+ break;
default:
Slog.w(TAG, "Parsed unknown flag with name: "
+ configFlagString);
@@ -194,6 +207,8 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider,
private Boolean mIsLidOpen;
@GuardedBy("mLock")
private final Map<Sensor, SensorEvent> mLatestSensorEvent = new ArrayMap<>();
+ @GuardedBy("mLock")
+ private @PowerManager.ThermalStatus int mThermalStatus = PowerManager.THERMAL_STATUS_NONE;
private DeviceStateProviderImpl(@NonNull Context context,
@NonNull List<DeviceState> deviceStates,
@@ -208,6 +223,16 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider,
mOrderedStates = orderedStates;
setStateConditions(deviceStates, stateConditions);
+
+ // If any of the device states are thermal sensitive, i.e. it should be disabled when the
+ // device is overheating, then we will update the list of supported states when thermal
+ // status changes.
+ if (hasThermalSensitiveState(deviceStates)) {
+ PowerManager powerManager = context.getSystemService(PowerManager.class);
+ if (powerManager != null) {
+ powerManager.addThermalStatusListener(this);
+ }
+ }
}
private void setStateConditions(@NonNull List<DeviceState> deviceStates,
@@ -347,16 +372,25 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider,
/** Notifies the listener that the set of supported device states has changed. */
private void notifySupportedStatesChanged() {
- DeviceState[] supportedStates;
+ List<DeviceState> supportedStates = new ArrayList<>();
+ Listener listener;
synchronized (mLock) {
if (mListener == null) {
return;
}
-
- supportedStates = Arrays.copyOf(mOrderedStates, mOrderedStates.length);
+ listener = mListener;
+ for (DeviceState deviceState : mOrderedStates) {
+ if (isThermalStatusCriticalOrAbove(mThermalStatus)
+ && deviceState.hasFlag(
+ DeviceState.FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL)) {
+ continue;
+ }
+ supportedStates.add(deviceState);
+ }
}
- mListener.onSupportedDeviceStatesChanged(supportedStates);
+ listener.onSupportedDeviceStatesChanged(
+ supportedStates.toArray(new DeviceState[supportedStates.size()]));
}
/** Computes the current device state and notifies the listener of a change, if needed. */
@@ -639,4 +673,43 @@ public final class DeviceStateProviderImpl implements DeviceStateProvider,
return new FileInputStream(mFile);
}
}
+
+ @Override
+ public void onThermalStatusChanged(@PowerManager.ThermalStatus int thermalStatus) {
+ int previousThermalStatus;
+ synchronized (mLock) {
+ previousThermalStatus = mThermalStatus;
+ mThermalStatus = thermalStatus;
+ }
+
+ boolean isThermalStatusCriticalOrAbove = isThermalStatusCriticalOrAbove(thermalStatus);
+ boolean isPreviousThermalStatusCriticalOrAbove =
+ isThermalStatusCriticalOrAbove(previousThermalStatus);
+ if (isThermalStatusCriticalOrAbove != isPreviousThermalStatusCriticalOrAbove) {
+ Slog.i(TAG, "Updating supported device states due to thermal status change."
+ + " isThermalStatusCriticalOrAbove: " + isThermalStatusCriticalOrAbove);
+ notifySupportedStatesChanged();
+ }
+ }
+
+ private static boolean isThermalStatusCriticalOrAbove(
+ @PowerManager.ThermalStatus int thermalStatus) {
+ switch (thermalStatus) {
+ case PowerManager.THERMAL_STATUS_CRITICAL:
+ case PowerManager.THERMAL_STATUS_EMERGENCY:
+ case PowerManager.THERMAL_STATUS_SHUTDOWN:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ private static boolean hasThermalSensitiveState(List<DeviceState> deviceStates) {
+ for (DeviceState state : deviceStates) {
+ if (state.hasFlag(DeviceState.FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL)) {
+ return true;
+ }
+ }
+ return false;
+ }
}
diff --git a/services/core/java/com/android/server/policy/LegacyGlobalActions.java b/services/core/java/com/android/server/policy/LegacyGlobalActions.java
index 983b7f463a1c..fa815406d25e 100644
--- a/services/core/java/com/android/server/policy/LegacyGlobalActions.java
+++ b/services/core/java/com/android/server/policy/LegacyGlobalActions.java
@@ -282,8 +282,9 @@ class LegacyGlobalActions implements DialogInterface.OnDismissListener, DialogIn
} else if (GLOBAL_ACTION_KEY_AIRPLANE.equals(actionKey)) {
mItems.add(mAirplaneModeOn);
} else if (GLOBAL_ACTION_KEY_BUGREPORT.equals(actionKey)) {
- if (Settings.Global.getInt(mContext.getContentResolver(),
- Settings.Global.BUGREPORT_IN_POWER_MENU, 0) != 0 && isCurrentUserAdmin()) {
+ if (Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ Settings.Secure.BUGREPORT_IN_POWER_MENU, 0, mContext.getUserId()) != 0
+ && isCurrentUserAdmin()) {
mItems.add(new BugReportAction());
}
} else if (GLOBAL_ACTION_KEY_SILENT.equals(actionKey)) {
@@ -537,7 +538,7 @@ class LegacyGlobalActions implements DialogInterface.OnDismissListener, DialogIn
private boolean isCurrentUserAdmin() {
UserInfo currentUser = getCurrentUser();
- return currentUser == null || currentUser.isAdmin();
+ return currentUser != null && currentUser.isAdmin();
}
private void addUsersToMenu(ArrayList<Action> items) {
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index b7a801a8fc86..302f4c447a90 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -717,7 +717,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
handleRingerChordGesture();
break;
case MSG_SCREENSHOT_CHORD:
- handleScreenShot(msg.arg1, msg.arg2);
+ handleScreenShot(msg.arg1);
break;
}
}
@@ -1518,9 +1518,9 @@ public class PhoneWindowManager implements WindowManagerPolicy {
|| mShortPressOnStemPrimaryBehavior != SHORT_PRESS_PRIMARY_NOTHING;
}
- private void interceptScreenshotChord(int type, int source, long pressDelay) {
+ private void interceptScreenshotChord(int source, long pressDelay) {
mHandler.removeMessages(MSG_SCREENSHOT_CHORD);
- mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SCREENSHOT_CHORD, type, source),
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SCREENSHOT_CHORD, source),
pressDelay);
}
@@ -1590,9 +1590,8 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
};
- private void handleScreenShot(@WindowManager.ScreenshotType int type,
- @WindowManager.ScreenshotSource int source) {
- mDefaultDisplayPolicy.takeScreenshot(type, source);
+ private void handleScreenShot(@WindowManager.ScreenshotSource int source) {
+ mDefaultDisplayPolicy.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN, source);
}
@Override
@@ -2228,7 +2227,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
@Override
void execute() {
mPowerKeyHandled = true;
- interceptScreenshotChord(TAKE_SCREENSHOT_FULLSCREEN,
+ interceptScreenshotChord(
SCREENSHOT_KEY_CHORD, getScreenshotChordLongPressDelay());
}
@Override
@@ -2956,8 +2955,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
break;
case KeyEvent.KEYCODE_S:
if (down && event.isMetaPressed() && event.isCtrlPressed() && repeatCount == 0) {
- interceptScreenshotChord(
- TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER, 0 /*pressDelay*/);
+ interceptScreenshotChord(SCREENSHOT_KEY_OTHER, 0 /*pressDelay*/);
return key_consumed;
}
break;
@@ -3402,8 +3400,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
break;
case KeyEvent.KEYCODE_SYSRQ:
if (down && repeatCount == 0) {
- interceptScreenshotChord(
- TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER, 0 /*pressDelay*/);
+ interceptScreenshotChord(SCREENSHOT_KEY_OTHER, 0 /*pressDelay*/);
}
return true;
}
@@ -4344,7 +4341,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
// from windowmanager. Currently, we need to ensure the setInputWindows completes,
// which would force the focus event to be queued before the current key event.
// TODO(b/70668286): post call to 'moveDisplayToTop' to mHandler instead
- Log.i(TAG, "Moving non-focused display " + displayId + " to top "
+ Log.i(TAG, "Attempting to move non-focused display " + displayId + " to top "
+ "because a key is targeting it");
mWindowManagerFuncs.moveDisplayToTopIfAllowed(displayId);
}
diff --git a/services/core/java/com/android/server/power/stats/CpuWakeupStats.java b/services/core/java/com/android/server/power/stats/CpuWakeupStats.java
index 79e35c2daf6f..54f3476d3b86 100644
--- a/services/core/java/com/android/server/power/stats/CpuWakeupStats.java
+++ b/services/core/java/com/android/server/power/stats/CpuWakeupStats.java
@@ -20,8 +20,10 @@ import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_ALARM;
import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_UNKNOWN;
import android.content.Context;
+import android.os.Handler;
import android.os.UserHandle;
import android.util.IndentingPrintWriter;
+import android.util.IntArray;
import android.util.LongSparseArray;
import android.util.Slog;
import android.util.SparseArray;
@@ -32,6 +34,7 @@ import android.util.TimeUtils;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.IntPair;
import java.util.Arrays;
@@ -43,12 +46,16 @@ import java.util.regex.Pattern;
* Stores stats about CPU wakeups and tries to attribute them to subsystems and uids.
*/
public class CpuWakeupStats {
+ private static final String TAG = "CpuWakeupStats";
+
private static final String SUBSYSTEM_ALARM_STRING = "Alarm";
@VisibleForTesting
static final long WAKEUP_RETENTION_MS = 3 * 24 * 60 * 60_000; // 3 days.
@VisibleForTesting
static final long WAKEUP_REASON_HALF_WINDOW_MS = 500;
+ private static final long WAKEUP_WRITE_DELAY_MS = 2 * 60 * 1000; // 2 minutes.
+ private final Handler mHandler;
private final IrqDeviceMap mIrqDeviceMap;
private final WakingActivityHistory mRecentWakingActivity = new WakingActivityHistory();
@@ -58,8 +65,58 @@ public class CpuWakeupStats {
final TimeSparseArray<SparseArray<SparseBooleanArray>> mWakeupAttribution =
new TimeSparseArray<>();
- public CpuWakeupStats(Context context, int mapRes) {
+ public CpuWakeupStats(Context context, int mapRes, Handler handler) {
mIrqDeviceMap = IrqDeviceMap.getInstance(context, mapRes);
+ mHandler = handler;
+ }
+
+ private static int subsystemToStatsReason(int subsystem) {
+ switch (subsystem) {
+ case CPU_WAKEUP_SUBSYSTEM_ALARM:
+ return FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED__REASON__ALARM;
+ }
+ return FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED__REASON__UNKNOWN;
+ }
+
+ private synchronized void logWakeupToStatsLog(Wakeup wakeupToLog) {
+ if (ArrayUtils.isEmpty(wakeupToLog.mDevices)) {
+ FrameworkStatsLog.write(FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED,
+ FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED__TYPE__TYPE_UNKNOWN,
+ FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED__REASON__UNKNOWN,
+ null,
+ wakeupToLog.mElapsedMillis);
+ return;
+ }
+
+ final SparseArray<SparseBooleanArray> wakeupAttribution = mWakeupAttribution.get(
+ wakeupToLog.mElapsedMillis);
+ if (wakeupAttribution == null) {
+ // This is not expected but can theoretically happen in extreme situations, e.g. if we
+ // remove the wakeup before the handler gets to process this message.
+ Slog.wtf(TAG, "Unexpected null attribution found for " + wakeupToLog);
+ return;
+ }
+ for (int i = 0; i < wakeupAttribution.size(); i++) {
+ final int subsystem = wakeupAttribution.keyAt(i);
+ final SparseBooleanArray uidMap = wakeupAttribution.valueAt(i);
+ final int[] uids;
+ if (uidMap == null || uidMap.size() == 0) {
+ uids = new int[0];
+ } else {
+ final IntArray tmp = new IntArray(uidMap.size());
+ for (int j = 0; j < uidMap.size(); j++) {
+ if (uidMap.valueAt(j)) {
+ tmp.add(uidMap.keyAt(j));
+ }
+ }
+ uids = tmp.toArray();
+ }
+ FrameworkStatsLog.write(FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED,
+ FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED__TYPE__TYPE_IRQ,
+ subsystemToStatsReason(subsystem),
+ uids,
+ wakeupToLog.mElapsedMillis);
+ }
}
/** Notes a wakeup reason as reported by SuspendControlService to battery stats. */
@@ -83,6 +140,7 @@ public class CpuWakeupStats {
for (int i = lastIdx; i >= 0; i--) {
mWakeupAttribution.removeAt(i);
}
+ mHandler.postDelayed(() -> logWakeupToStatsLog(parsedWakeup), WAKEUP_WRITE_DELAY_MS);
}
/** Notes a waking activity that could have potentially woken up the CPU. */
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 075bac12635a..b14676707069 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -87,7 +87,6 @@ import android.os.IBinder;
import android.os.IInterface;
import android.os.IRemoteCallback;
import android.os.ParcelFileDescriptor;
-import android.os.Process;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.ResultReceiver;
@@ -2169,12 +2168,14 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
@Override
public ParcelFileDescriptor getWallpaper(String callingPkg, IWallpaperManagerCallback cb,
final int which, Bundle outParams, int wallpaperUserId) {
- return getWallpaperWithFeature(callingPkg, null, cb, which, outParams, wallpaperUserId);
+ return getWallpaperWithFeature(callingPkg, null, cb, which, outParams,
+ wallpaperUserId, /* getCropped= */ true);
}
@Override
public ParcelFileDescriptor getWallpaperWithFeature(String callingPkg, String callingFeatureId,
- IWallpaperManagerCallback cb, final int which, Bundle outParams, int wallpaperUserId) {
+ IWallpaperManagerCallback cb, final int which, Bundle outParams, int wallpaperUserId,
+ boolean getCropped) {
final boolean hasPrivilege = hasPermission(READ_WALLPAPER_INTERNAL);
if (!hasPrivilege) {
mContext.getSystemService(StorageManager.class).checkPermissionReadImages(true,
@@ -2209,10 +2210,14 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
if (cb != null) {
wallpaper.callbacks.register(cb);
}
- if (!wallpaper.cropFile.exists()) {
+
+ File fileToReturn = getCropped ? wallpaper.cropFile : wallpaper.wallpaperFile;
+
+ if (!fileToReturn.exists()) {
return null;
}
- return ParcelFileDescriptor.open(wallpaper.cropFile, MODE_READ_ONLY);
+
+ return ParcelFileDescriptor.open(fileToReturn, MODE_READ_ONLY);
} catch (FileNotFoundException e) {
/* Shouldn't happen as we check to see if the file exists */
Slog.w(TAG, "Error getting wallpaper", e);
@@ -2250,6 +2255,25 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
}
@Override
+ public ParcelFileDescriptor getWallpaperInfoFile(int userId) {
+ synchronized (mLock) {
+ try {
+ File file = new File(getWallpaperDir(userId), WALLPAPER_INFO);
+
+ if (!file.exists()) {
+ return null;
+ }
+
+ return ParcelFileDescriptor.open(file, MODE_READ_ONLY);
+ } catch (FileNotFoundException e) {
+ /* Shouldn't happen as we check to see if the file exists */
+ Slog.w(TAG, "Error getting wallpaper info file", e);
+ }
+ return null;
+ }
+ }
+
+ @Override
public int getWallpaperIdForUser(int which, int userId) {
userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
Binder.getCallingUid(), userId, false, true, "getWallpaperIdForUser", null);
@@ -3201,10 +3225,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
@Override
public boolean isWallpaperBackupEligible(int which, int userId) {
- if (Binder.getCallingUid() != Process.SYSTEM_UID) {
- throw new SecurityException("Only the system may call isWallpaperBackupEligible");
- }
-
WallpaperData wallpaper = (which == FLAG_LOCK)
? mLockWallpaperMap.get(userId)
: mWallpaperMap.get(userId);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 90d25eec5bf3..4f87b1a22f02 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -5257,9 +5257,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
transferStartingWindowFromHiddenAboveTokenIfNeeded();
}
- // If in a transition, defer commits for activities that are going invisible
- if (!visible && inTransition()) {
- if (mTransitionController.inPlayingTransition(this)
+ // Defer committing visibility until transition starts.
+ if (inTransition()) {
+ if (!visible && mTransitionController.inPlayingTransition(this)
&& mTransitionController.isCollecting(this)) {
mTransitionChangeFlags |= FLAG_IS_OCCLUDED;
}
@@ -5509,6 +5509,17 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
}
+ /** Updates draw state and shows drawn windows. */
+ void commitFinishDrawing(SurfaceControl.Transaction t) {
+ boolean committed = false;
+ for (int i = mChildren.size() - 1; i >= 0; i--) {
+ committed |= mChildren.get(i).commitFinishDrawing(t);
+ }
+ if (committed) {
+ requestUpdateWallpaperIfNeeded();
+ }
+ }
+
/**
* Check if visibility of this {@link ActivityRecord} should be updated as part of an app
* transition.
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index bd4f1a6894ec..2bd905230b69 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -24,6 +24,7 @@ import android.app.AppProtoEnums;
import android.app.BackgroundStartPrivileges;
import android.app.IActivityManager;
import android.app.IApplicationThread;
+import android.app.ITaskStackListener;
import android.app.ProfilerInfo;
import android.content.ComponentName;
import android.content.IIntentSender;
@@ -740,4 +741,10 @@ public abstract class ActivityTaskManagerInternal {
*/
public abstract void restartTaskActivityProcessIfVisible(
int taskId, @NonNull String packageName);
+
+ /** Sets the task stack listener that gets callbacks when a task stack changes. */
+ public abstract void registerTaskStackListener(ITaskStackListener listener);
+
+ /** Unregister a task stack listener so that it stops receiving callbacks. */;
+ public abstract void unregisterTaskStackListener(ITaskStackListener listener);
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 6fe77b7614dc..a927ed3e9e23 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -6916,5 +6916,17 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
activity.restartProcessIfVisible();
}
}
+
+ /** Sets the task stack listener that gets callbacks when a task stack changes. */
+ @Override
+ public void registerTaskStackListener(ITaskStackListener listener) {
+ ActivityTaskManagerService.this.registerTaskStackListener(listener);
+ }
+
+ /** Unregister a task stack listener so that it stops receiving callbacks. */
+ @Override
+ public void unregisterTaskStackListener(ITaskStackListener listener) {
+ ActivityTaskManagerService.this.unregisterTaskStackListener(listener);
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 63d6509dffae..0bd59a8509f8 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -6841,12 +6841,12 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
public void showInsets(@WindowInsets.Type.InsetsType int types, boolean fromIme,
@Nullable ImeTracker.Token statsToken) {
try {
- ImeTracker.forLogging().onProgress(statsToken,
+ ImeTracker.get().onProgress(statsToken,
ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_SHOW_INSETS);
mRemoteInsetsController.showInsets(types, fromIme, statsToken);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to deliver showInsets", e);
- ImeTracker.forLogging().onFailed(statsToken,
+ ImeTracker.get().onFailed(statsToken,
ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_SHOW_INSETS);
}
}
@@ -6855,12 +6855,12 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
public void hideInsets(@InsetsType int types, boolean fromIme,
@Nullable ImeTracker.Token statsToken) {
try {
- ImeTracker.forLogging().onProgress(statsToken,
+ ImeTracker.get().onProgress(statsToken,
ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_HIDE_INSETS);
mRemoteInsetsController.hideInsets(types, fromIme, statsToken);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to deliver hideInsets", e);
- ImeTracker.forLogging().onFailed(statsToken,
+ ImeTracker.get().onFailed(statsToken,
ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_HIDE_INSETS);
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index a68d7af08118..cfcf459fc72e 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -129,6 +129,7 @@ import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.statusbar.LetterboxDetails;
import com.android.internal.util.ScreenshotHelper;
+import com.android.internal.util.ScreenshotRequest;
import com.android.internal.util.function.TriConsumer;
import com.android.internal.view.AppearanceRegion;
import com.android.internal.widget.PointerLocationView;
@@ -2451,8 +2452,9 @@ public class DisplayPolicy {
*/
public void takeScreenshot(int screenshotType, int source) {
if (mScreenshotHelper != null) {
- mScreenshotHelper.takeScreenshot(screenshotType,
- source, mHandler, null /* completionConsumer */);
+ ScreenshotRequest request =
+ new ScreenshotRequest.Builder(screenshotType, source).build();
+ mScreenshotHelper.takeScreenshot(request, mHandler, null /* completionConsumer */);
}
}
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index 60e2e95d14a7..85938e3bfd71 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -182,8 +182,7 @@ final class ImeInsetsSourceProvider extends WindowContainerInsetsSourceProvider
boolean targetChanged = isTargetChangedWithinActivity(imeTarget);
mImeRequester = imeTarget;
// There was still a stats token, so that request presumably failed.
- ImeTracker.forLogging().onFailed(
- mImeRequesterStatsToken, ImeTracker.PHASE_WM_SHOW_IME_RUNNER);
+ ImeTracker.get().onFailed(mImeRequesterStatsToken, ImeTracker.PHASE_WM_SHOW_IME_RUNNER);
mImeRequesterStatsToken = statsToken;
if (targetChanged) {
// target changed, check if new target can show IME.
@@ -198,12 +197,12 @@ final class ImeInsetsSourceProvider extends WindowContainerInsetsSourceProvider
ProtoLog.d(WM_DEBUG_IME, "Schedule IME show for %s", mImeRequester.getWindow() == null
? mImeRequester : mImeRequester.getWindow().getName());
mShowImeRunner = () -> {
- ImeTracker.forLogging().onProgress(mImeRequesterStatsToken,
+ ImeTracker.get().onProgress(mImeRequesterStatsToken,
ImeTracker.PHASE_WM_SHOW_IME_RUNNER);
ProtoLog.d(WM_DEBUG_IME, "Run showImeRunner");
// Target should still be the same.
if (isReadyToShowIme()) {
- ImeTracker.forLogging().onProgress(mImeRequesterStatsToken,
+ ImeTracker.get().onProgress(mImeRequesterStatsToken,
ImeTracker.PHASE_WM_SHOW_IME_READY);
final InsetsControlTarget target = mDisplayContent.getImeTarget(IME_TARGET_CONTROL);
@@ -220,7 +219,7 @@ final class ImeInsetsSourceProvider extends WindowContainerInsetsSourceProvider
? mImeRequester.getWindow().getName() : ""));
}
} else {
- ImeTracker.forLogging().onFailed(mImeRequesterStatsToken,
+ ImeTracker.get().onFailed(mImeRequesterStatsToken,
ImeTracker.PHASE_WM_SHOW_IME_READY);
}
// Clear token here so we don't report an error in abortShowImePostLayout().
@@ -259,8 +258,7 @@ final class ImeInsetsSourceProvider extends WindowContainerInsetsSourceProvider
mImeRequester = null;
mIsImeLayoutDrawn = false;
mShowImeRunner = null;
- ImeTracker.forLogging().onCancelled(
- mImeRequesterStatsToken, ImeTracker.PHASE_WM_SHOW_IME_RUNNER);
+ ImeTracker.get().onCancelled(mImeRequesterStatsToken, ImeTracker.PHASE_WM_SHOW_IME_RUNNER);
mImeRequesterStatsToken = null;
}
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index 874f942b1bfd..1df534f21c18 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -707,8 +707,7 @@ class InsetsPolicy {
InsetsPolicyAnimationControlListener(boolean show, Runnable finishCallback, int types) {
super(show, false /* hasCallbacks */, types, BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE,
- false /* disable */, 0 /* floatingImeBottomInsets */,
- null /* loggingListener */, null /* jankContext */);
+ false /* disable */, 0 /* floatingImeBottomInsets */, null);
mFinishCallback = finishCallback;
mControlCallbacks = new InsetsPolicyAnimationControlCallbacks(this);
}
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index cd23959a265e..8b1fb51cb6b7 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -70,6 +70,7 @@ import android.os.Binder;
import android.os.IBinder;
import android.os.IRemoteCallback;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.os.Trace;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -211,6 +212,8 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
private IContainerFreezer mContainerFreezer = null;
private final SurfaceControl.Transaction mTmpTransaction = new SurfaceControl.Transaction();
+ final TransitionController.Logger mLogger = new TransitionController.Logger();
+
Transition(@TransitionType int type, @TransitionFlags int flags,
TransitionController controller, BLASTSyncEngine syncEngine) {
mType = type;
@@ -219,6 +222,8 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
mSyncEngine = syncEngine;
mToken = new Token(this);
+ mLogger.mCreateWallTimeMs = System.currentTimeMillis();
+ mLogger.mCreateTimeNs = SystemClock.uptimeNanos();
controller.mTransitionTracer.logState(this);
}
@@ -380,6 +385,8 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
mState = STATE_COLLECTING;
mSyncId = mSyncEngine.startSyncSet(this, timeoutMs, TAG, method);
+ mLogger.mSyncId = mSyncId;
+ mLogger.mCollectTimeNs = SystemClock.uptimeNanos();
mController.mTransitionTracer.logState(this);
}
@@ -399,6 +406,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
mSyncId);
applyReady();
+ mLogger.mStartTimeNs = SystemClock.uptimeNanos();
mController.mTransitionTracer.logState(this);
mController.updateAnimatingState(mTmpTransaction);
@@ -608,6 +616,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
"Set transition ready=%b %d", ready, mSyncId);
mSyncEngine.setReady(mSyncId, ready);
+ if (ready) mLogger.mReadyTimeNs = SystemClock.uptimeNanos();
}
/**
@@ -759,6 +768,8 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER, TRACE_NAME_PLAY_TRANSITION,
System.identityHashCode(this));
}
+ mLogger.mFinishTimeNs = SystemClock.uptimeNanos();
+ mController.mLoggerHandler.post(mLogger::logOnFinish);
// Close the transactions now. They were originally copied to Shell in case we needed to
// apply them due to a remote failure. Since we don't need to apply them anymore, free them
// immediately.
@@ -845,7 +856,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord();
if (ar == null || !ar.isVisible() || ar.getParent() == null) continue;
if (inputSinkTransaction == null) {
- inputSinkTransaction = new SurfaceControl.Transaction();
+ inputSinkTransaction = ar.mWmService.mTransactionFactory.get();
}
ar.mActivityRecordInputSink.applyChangesToSurfaceIfChanged(inputSinkTransaction);
}
@@ -961,6 +972,12 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
// time being, we don't have full cross-display transitions so it isn't a problem.
final DisplayContent dc = mTargetDisplays.get(0);
+ // Commit the visibility of visible activities before calculateTransitionInfo(), so the
+ // TaskInfo can be visible. Also it needs to be done before moveToPlaying(), otherwise
+ // ActivityRecord#canShowWindows() may reject to show its window. The visibility also
+ // needs to be updated for STATE_ABORT.
+ commitVisibleActivities(transaction);
+
if (mState == STATE_ABORT) {
mController.abort(this);
dc.getPendingTransaction().merge(transaction);
@@ -1082,6 +1099,8 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
try {
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
"Calling onTransitionReady: %s", info);
+ mLogger.mSendTimeNs = SystemClock.uptimeNanos();
+ mLogger.mInfo = info;
mController.getTransitionPlayer().onTransitionReady(
mToken, info, transaction, mFinishTransaction);
if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
@@ -1097,6 +1116,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
// No player registered, so just finish/apply immediately
cleanUpOnFailure();
}
+ mController.mLoggerHandler.post(mLogger::logOnSend);
mOverrideOptions = null;
reportStartReasonsToLogger();
@@ -1132,6 +1152,19 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
}
}
+ /** The transition is ready to play. Make the start transaction show the surfaces. */
+ private void commitVisibleActivities(SurfaceControl.Transaction transaction) {
+ for (int i = mParticipants.size() - 1; i >= 0; --i) {
+ final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord();
+ if (ar == null || !ar.isVisibleRequested()) {
+ continue;
+ }
+ ar.commitVisibility(true /* visible */, false /* performLayout */,
+ true /* fromTransition */);
+ ar.commitFinishDrawing(transaction);
+ }
+ }
+
/** @see RecentsAnimationController#attachNavigationBarToApp */
private void handleLegacyRecentsStartBehavior(DisplayContent dc, TransitionInfo info) {
if ((mFlags & TRANSIT_FLAG_IS_RECENTS) == 0) {
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 73cd25134bbc..5e116baa3fae 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -30,6 +30,7 @@ import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.IApplicationThread;
import android.app.WindowConfiguration;
+import android.os.Handler;
import android.os.IBinder;
import android.os.IRemoteCallback;
import android.os.RemoteException;
@@ -38,6 +39,7 @@ import android.os.SystemProperties;
import android.os.Trace;
import android.util.ArrayMap;
import android.util.Slog;
+import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
import android.view.SurfaceControl;
import android.view.WindowManager;
@@ -46,11 +48,13 @@ import android.window.ITransitionPlayer;
import android.window.RemoteTransition;
import android.window.TransitionInfo;
import android.window.TransitionRequestInfo;
+import android.window.WindowContainerTransaction;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.ProtoLogGroup;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.server.FgThread;
import com.android.server.LocalServices;
import com.android.server.statusbar.StatusBarManagerInternal;
@@ -120,6 +124,8 @@ class TransitionController {
private boolean mAnimatingState = false;
+ final Handler mLoggerHandler = FgThread.getHandler();
+
TransitionController(ActivityTaskManagerService atm,
TaskSnapshotController taskSnapshotController,
TransitionTracer transitionTracer) {
@@ -462,9 +468,11 @@ class TransitionController {
info = new ActivityManager.RunningTaskInfo();
startTask.fillTaskInfo(info);
}
- mTransitionPlayer.requestStartTransition(transition.getToken(),
- new TransitionRequestInfo(transition.mType, info, remoteTransition,
- displayChange));
+ final TransitionRequestInfo request = new TransitionRequestInfo(
+ transition.mType, info, remoteTransition, displayChange);
+ transition.mLogger.mRequestTimeNs = SystemClock.uptimeNanos();
+ transition.mLogger.mRequest = request;
+ mTransitionPlayer.requestStartTransition(transition.getToken(), request);
transition.setRemoteTransition(remoteTransition);
} catch (RemoteException e) {
Slog.e(TAG, "Error requesting transition", e);
@@ -847,6 +855,66 @@ class TransitionController {
}
}
+ /**
+ * Data-class to store recorded events/info for a transition. This allows us to defer the
+ * actual logging until the system isn't busy. This also records some common metrics to see
+ * delays at-a-glance.
+ *
+ * Beside `mCreateWallTimeMs`, all times are elapsed times and will all be reported relative
+ * to when the transition was created.
+ */
+ static class Logger {
+ long mCreateWallTimeMs;
+ long mCreateTimeNs;
+ long mRequestTimeNs;
+ long mCollectTimeNs;
+ long mStartTimeNs;
+ long mReadyTimeNs;
+ long mSendTimeNs;
+ long mFinishTimeNs;
+ TransitionRequestInfo mRequest;
+ WindowContainerTransaction mStartWCT;
+ int mSyncId;
+ TransitionInfo mInfo;
+
+ private String buildOnSendLog() {
+ StringBuilder sb = new StringBuilder("Sent Transition #").append(mSyncId)
+ .append(" createdAt=").append(TimeUtils.logTimeOfDay(mCreateWallTimeMs));
+ if (mRequest != null) {
+ sb.append(" via request=").append(mRequest);
+ }
+ return sb.toString();
+ }
+
+ void logOnSend() {
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, "%s", buildOnSendLog());
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, " startWCT=%s", mStartWCT);
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, " info=%s", mInfo);
+ }
+
+ private static String toMsString(long nanos) {
+ return ((double) Math.round((double) nanos / 1000) / 1000) + "ms";
+ }
+
+ private String buildOnFinishLog() {
+ StringBuilder sb = new StringBuilder("Finish Transition #").append(mSyncId)
+ .append(": created at ").append(TimeUtils.logTimeOfDay(mCreateWallTimeMs));
+ sb.append(" collect-started=").append(toMsString(mCollectTimeNs - mCreateTimeNs));
+ if (mRequestTimeNs != 0) {
+ sb.append(" request-sent=").append(toMsString(mRequestTimeNs - mCreateTimeNs));
+ }
+ sb.append(" started=").append(toMsString(mStartTimeNs - mCreateTimeNs));
+ sb.append(" ready=").append(toMsString(mReadyTimeNs - mCreateTimeNs));
+ sb.append(" sent=").append(toMsString(mSendTimeNs - mCreateTimeNs));
+ sb.append(" finished=").append(toMsString(mFinishTimeNs - mCreateTimeNs));
+ return sb.toString();
+ }
+
+ void logOnFinish() {
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, "%s", buildOnFinishLog());
+ }
+ }
+
static class TransitionMetricsReporter extends ITransitionMetricsReporter.Stub {
private final ArrayMap<IBinder, LongConsumer> mMetricConsumers = new ArrayMap<>();
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 18ad43c11a4d..87670969aae6 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -8087,14 +8087,14 @@ public class WindowManagerService extends IWindowManager.Stub
dc.getInsetsStateController().getImeSourceProvider().abortShowImePostLayout();
}
if (dc != null && dc.getImeTarget(IME_TARGET_CONTROL) != null) {
- ImeTracker.forLogging().onProgress(statsToken,
+ ImeTracker.get().onProgress(statsToken,
ImeTracker.PHASE_WM_HAS_IME_INSETS_CONTROL_TARGET);
ProtoLog.d(WM_DEBUG_IME, "hideIme Control target: %s ",
dc.getImeTarget(IME_TARGET_CONTROL));
dc.getImeTarget(IME_TARGET_CONTROL).hideInsets(WindowInsets.Type.ime(),
true /* fromIme */, statsToken);
} else {
- ImeTracker.forLogging().onFailed(statsToken,
+ ImeTracker.get().onFailed(statsToken,
ImeTracker.PHASE_WM_HAS_IME_INSETS_CONTROL_TARGET);
}
if (dc != null) {
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 6a1adb45ea7c..5c68b127c45b 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -318,6 +318,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
transition = mTransitionController.createTransition(type);
}
transition.start();
+ transition.mLogger.mStartWCT = wct;
applyTransaction(wct, -1 /*syncId*/, transition, caller);
if (needsSetReady) {
transition.setAllReady();
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 7a0070bf2b56..223352e32f9f 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -3993,12 +3993,12 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
public void showInsets(@InsetsType int types, boolean fromIme,
@Nullable ImeTracker.Token statsToken) {
try {
- ImeTracker.forLogging().onProgress(statsToken,
+ ImeTracker.get().onProgress(statsToken,
ImeTracker.PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_SHOW_INSETS);
mClient.showInsets(types, fromIme, statsToken);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to deliver showInsets", e);
- ImeTracker.forLogging().onFailed(statsToken,
+ ImeTracker.get().onFailed(statsToken,
ImeTracker.PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_SHOW_INSETS);
}
}
@@ -4007,12 +4007,12 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
public void hideInsets(@InsetsType int types, boolean fromIme,
@Nullable ImeTracker.Token statsToken) {
try {
- ImeTracker.forLogging().onProgress(statsToken,
+ ImeTracker.get().onProgress(statsToken,
ImeTracker.PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_HIDE_INSETS);
mClient.hideInsets(types, fromIme, statsToken);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to deliver hideInsets", e);
- ImeTracker.forLogging().onFailed(statsToken,
+ ImeTracker.get().onFailed(statsToken,
ImeTracker.PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_HIDE_INSETS);
}
}
@@ -4616,6 +4616,19 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
}
}
+ /** Makes the surface of drawn window (COMMIT_DRAW_PENDING) to be visible. */
+ boolean commitFinishDrawing(SurfaceControl.Transaction t) {
+ boolean committed = mWinAnimator.commitFinishDrawingLocked();
+ if (committed) {
+ // Ensure that the visibility of buffer layer is set.
+ mWinAnimator.prepareSurfaceLocked(t);
+ }
+ for (int i = mChildren.size() - 1; i >= 0; i--) {
+ committed |= mChildren.get(i).commitFinishDrawing(t);
+ }
+ return committed;
+ }
+
// This must be called while inside a transaction.
boolean performShowLocked() {
if (!showToCurrentUser()) {
diff --git a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
index acfa491d72e1..668f8ed692a5 100644
--- a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
@@ -35,7 +35,7 @@ import android.util.Log;
import java.util.ArrayList;
/**
- * Central session for a single {@link CredentialManager#executeCreateCredential} request.
+ * Central session for a single {@link CredentialManager#createCredential} request.
* This class listens to the responses from providers, and the UX app, and updates the
* provider(s) state maintained in {@link ProviderCreateSession}.
*/
diff --git a/services/credentials/java/com/android/server/credentials/CredentialDescriptionRegistry.java b/services/credentials/java/com/android/server/credentials/CredentialDescriptionRegistry.java
new file mode 100644
index 000000000000..b7c5fc2de4f3
--- /dev/null
+++ b/services/credentials/java/com/android/server/credentials/CredentialDescriptionRegistry.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.credentials;
+
+import android.credentials.CredentialDescription;
+import android.credentials.IRegisterCredentialDescriptionCallback;
+import android.credentials.IUnregisterCredentialDescriptionCallback;
+import android.credentials.RegisterCredentialDescriptionRequest;
+import android.credentials.UnregisterCredentialDescriptionRequest;
+import android.os.RemoteException;
+import android.util.SparseArray;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/** Contains information on what CredentialProvider has what provisioned Credential. */
+public class CredentialDescriptionRegistry {
+
+ private static final int MAX_ALLOWED_CREDENTIAL_DESCRIPTIONS = 128;
+ private static SparseArray<CredentialDescriptionRegistry> sCredentialDescriptionSessionPerUser;
+
+ static {
+ sCredentialDescriptionSessionPerUser = new SparseArray<>();
+ }
+
+ // TODO(b/265992655): add a way to update CredentialRegistry when a user is removed.
+ /** Get and/or create a {@link CredentialDescription} for the given user id. */
+ public static CredentialDescriptionRegistry forUser(int userId) {
+ CredentialDescriptionRegistry session =
+ sCredentialDescriptionSessionPerUser.get(userId, null);
+
+ if (session == null) {
+ session = new CredentialDescriptionRegistry();
+ sCredentialDescriptionSessionPerUser.put(userId, session);
+ }
+ return session;
+ }
+
+ private Map<String, Set<CredentialDescription>> mCredentialDescriptions;
+
+ private CredentialDescriptionRegistry() {
+ this.mCredentialDescriptions = new HashMap<>();
+ }
+
+ /** Handle the given {@link RegisterCredentialDescriptionRequest} by creating
+ * the appropriate package name mapping. */
+ public void executeRegisterRequest(RegisterCredentialDescriptionRequest request,
+ String callingPackageName,
+ IRegisterCredentialDescriptionCallback callback) {
+
+ if (!mCredentialDescriptions.containsKey(callingPackageName)
+ && mCredentialDescriptions.size() <= MAX_ALLOWED_CREDENTIAL_DESCRIPTIONS) {
+ mCredentialDescriptions.put(callingPackageName, new HashSet<>());
+ }
+
+ mCredentialDescriptions.get(callingPackageName)
+ .addAll(request.getCredentialDescriptions());
+
+ try {
+ callback.onResponse();
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /** Handle the given {@link UnregisterCredentialDescriptionRequest} by creating
+ * the appropriate package name mapping. */
+ public void executeUnregisterRequest(
+ UnregisterCredentialDescriptionRequest request,
+ String callingPackageName,
+ IUnregisterCredentialDescriptionCallback callback) {
+
+ if (mCredentialDescriptions.containsKey(callingPackageName)) {
+ mCredentialDescriptions.get(callingPackageName)
+ .remove(request.getCredentialDescription());
+ }
+
+ try {
+ callback.onResponse();
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /** Returns package names of CredentialProviders that can satisfy a given
+ * {@link CredentialDescription}. */
+ public Set<String> filterCredentials(String flatRequestString) {
+
+ Set<String> result = new HashSet<>();
+
+ for (String componentName: mCredentialDescriptions.keySet()) {
+ Set<CredentialDescription> currentSet = mCredentialDescriptions.get(componentName);
+ for (CredentialDescription containedDescription: currentSet) {
+ if (flatRequestString.equals(containedDescription.getFlattenedRequestString())) {
+ result.add(componentName);
+ }
+ }
+ }
+
+ return result;
+ }
+
+ void evictProviderWithPackageName(String packageName) {
+ if (mCredentialDescriptions.containsKey(packageName)) {
+ mCredentialDescriptions.remove(packageName);
+ }
+ }
+
+}
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index f76cf4993ebc..620b81bd0cd9 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -36,14 +36,19 @@ import android.credentials.ICreateCredentialCallback;
import android.credentials.ICredentialManager;
import android.credentials.IGetCredentialCallback;
import android.credentials.IListEnabledProvidersCallback;
+import android.credentials.IRegisterCredentialDescriptionCallback;
import android.credentials.ISetEnabledProvidersCallback;
+import android.credentials.IUnregisterCredentialDescriptionCallback;
import android.credentials.ListEnabledProvidersResponse;
+import android.credentials.RegisterCredentialDescriptionRequest;
+import android.credentials.UnregisterCredentialDescriptionRequest;
import android.credentials.ui.IntentFactory;
import android.os.Binder;
import android.os.CancellationSignal;
import android.os.ICancellationSignal;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.provider.DeviceConfig;
import android.provider.Settings;
import android.service.credentials.BeginCreateCredentialRequest;
import android.service.credentials.BeginGetCredentialRequest;
@@ -59,9 +64,13 @@ import com.android.server.infra.AbstractMasterSystemService;
import com.android.server.infra.SecureSettingsServiceNameResolver;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.List;
+import java.util.Set;
import java.util.function.Consumer;
+import java.util.function.Function;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
/**
* Entry point service for credential management.
@@ -75,6 +84,8 @@ public final class CredentialManagerService
CredentialManagerService, CredentialManagerServiceImpl> {
private static final String TAG = "CredManSysService";
+ private static final String DEVICE_CONFIG_ENABLE_CREDENTIAL_DESC_API =
+ "enable_credential_description_api";
private final Context mContext;
@@ -164,6 +175,7 @@ public final class CredentialManagerService
if (services == null) {
return;
}
+
CredentialManagerServiceImpl serviceToBeRemoved = null;
for (CredentialManagerServiceImpl service : services) {
if (service != null) {
@@ -180,10 +192,14 @@ public final class CredentialManagerService
}
if (serviceToBeRemoved != null) {
removeServiceFromCache(serviceToBeRemoved, userId);
+ CredentialDescriptionRegistry.forUser(userId)
+ .evictProviderWithPackageName(serviceToBeRemoved.getServicePackageName());
}
// TODO("Iterate over system services and remove if needed")
}
+
+
@GuardedBy("mLock")
private List<CredentialManagerServiceImpl> getOrConstructSystemServiceListLock(
int resolvedUserId) {
@@ -223,6 +239,53 @@ public final class CredentialManagerService
concatenatedServices.addAll(getOrConstructSystemServiceListLock(userId));
return concatenatedServices;
}
+ public static boolean isCredentialDescriptionApiEnabled() {
+ return DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_CREDENTIAL, DEVICE_CONFIG_ENABLE_CREDENTIAL_DESC_API, false);
+ }
+
+ @SuppressWarnings("GuardedBy") // ErrorProne requires initiateProviderSessionForRequestLocked
+ // to be guarded by 'service.mLock', which is the same as mLock.
+ private List<ProviderSession> initiateProviderSessionsWithActiveContainers(
+ RequestSession session,
+ List<String> requestOptions, Set<ComponentName> activeCredentialContainers) {
+ List<ProviderSession> providerSessions = new ArrayList<>();
+ // Invoke all services of a user to initiate a provider session
+ runForUser((service) -> {
+ if (activeCredentialContainers.contains(service.getComponentName())) {
+ ProviderSession providerSession = service
+ .initiateProviderSessionForRequestLocked(session, requestOptions);
+ if (providerSession != null) {
+ providerSessions.add(providerSession);
+ }
+ }
+ });
+ return providerSessions;
+ }
+
+ @NonNull
+ private Set<String> getMatchingProviders(GetCredentialRequest request) {
+ // Session for active/provisioned credential descriptions;
+ CredentialDescriptionRegistry registry = CredentialDescriptionRegistry
+ .forUser(UserHandle.getCallingUserId());
+
+ // All requested credential descriptions based on the given request.
+ Set<String> requestedCredentialDescriptions =
+ request.getGetCredentialOptions().stream().map(
+ getCredentialOption -> getCredentialOption
+ .getCredentialRetrievalData()
+ .getString(RegisterCredentialDescriptionRequest
+ .FLATTENED_REQUEST_STRING_KEY))
+ .collect(Collectors.toSet());
+
+ // All requested credential descriptions based on the given request.
+ return requestedCredentialDescriptions.stream()
+ .map(registry::filterCredentials)
+ .flatMap(
+ (Function<Set<String>, Stream<String>>)
+ Collection::stream)
+ .collect(Collectors.toSet());
+ }
@SuppressWarnings("GuardedBy") // ErrorProne requires initiateProviderSessionForRequestLocked
// to be guarded by 'service.mLock', which is the same as mLock.
@@ -282,11 +345,11 @@ public final class CredentialManagerService
// Initiate all provider sessions
List<ProviderSession> providerSessions =
- initiateProviderSessions(
- session,
- request.getGetCredentialOptions().stream()
- .map(GetCredentialOption::getType)
- .collect(Collectors.toList()));
+ initiateProviderSessions(
+ session,
+ request.getGetCredentialOptions().stream()
+ .map(GetCredentialOption::getType)
+ .collect(Collectors.toList()));
if (providerSessions.isEmpty()) {
try {
@@ -316,7 +379,7 @@ public final class CredentialManagerService
ICreateCredentialCallback callback,
String callingPackage) {
Log.i(TAG, "starting executeCreateCredential with callingPackage: " + callingPackage);
- // TODO : Implement cancellation
+
ICancellationSignal cancelTransport = CancellationSignal.createTransport();
// New request session, scoped for this request only.
@@ -478,5 +541,77 @@ public final class CredentialManagerService
});
return cancelTransport;
}
+
+ @Override
+ public ICancellationSignal registerCredentialDescription(
+ RegisterCredentialDescriptionRequest request,
+ IRegisterCredentialDescriptionCallback callback, String callingPackage) {
+ Log.i(TAG, "registerCredentialDescription");
+ ICancellationSignal cancelTransport = CancellationSignal.createTransport();
+
+
+ List<CredentialProviderInfo> services =
+ CredentialProviderInfo.getAvailableServices(mContext,
+ UserHandle.getCallingUserId());
+
+ List<String> providers = services.stream()
+ .map(credentialProviderInfo
+ -> credentialProviderInfo.getServiceInfo().packageName).toList();
+ if (!providers.contains(callingPackage)) {
+ try {
+ callback.onError("UNKNOWN",
+ "Not an existing provider.");
+ } catch (RemoteException e) {
+ Log.i(
+ TAG,
+ "Issue invoking onError on IRegisterCredentialDescriptionCallback "
+ + "callback: "
+ + e.getMessage());
+ }
+ }
+
+ CredentialDescriptionRegistry session = CredentialDescriptionRegistry
+ .forUser(UserHandle.getCallingUserId());
+
+ session.executeRegisterRequest(request, callingPackage, callback);
+
+ return cancelTransport;
+ }
+
+ @Override
+ public ICancellationSignal unRegisterCredentialDescription(
+ UnregisterCredentialDescriptionRequest request,
+ IUnregisterCredentialDescriptionCallback callback, String callingPackage) {
+ Log.i(TAG, "registerCredentialDescription");
+ ICancellationSignal cancelTransport = CancellationSignal.createTransport();
+
+ List<CredentialProviderInfo> services =
+ CredentialProviderInfo.getAvailableServices(mContext,
+ UserHandle.getCallingUserId());
+
+ List<String> providers = services.stream()
+ .map(credentialProviderInfo
+ -> credentialProviderInfo.getServiceInfo().packageName).toList();
+
+ if (!providers.contains(callingPackage)) {
+ try {
+ callback.onError("UNKNOWN",
+ "Not an existing provider.");
+ } catch (RemoteException e) {
+ Log.i(
+ TAG,
+ "Issue invoking onError on IRegisterCredentialDescriptionCallback "
+ + "callback: "
+ + e.getMessage());
+ }
+ }
+
+ CredentialDescriptionRegistry session = CredentialDescriptionRegistry
+ .forUser(UserHandle.getCallingUserId());
+
+ session.executeUnregisterRequest(request, callingPackage, callback);
+
+ return cancelTransport;
+ }
}
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 5c5442d0b9c3..3725756d1d44 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -2736,6 +2736,10 @@ public final class SystemServer implements Dumpable {
mSystemServiceManager.startService(PermissionPolicyService.class);
t.traceEnd();
+ t.traceBegin("ArtManagerLocal");
+ DexOptHelper.initializeArtManagerLocal(context, mPackageManagerService);
+ t.traceEnd();
+
t.traceBegin("MakePackageManagerServiceReady");
mPackageManagerService.systemReady();
t.traceEnd();
@@ -2770,10 +2774,6 @@ public final class SystemServer implements Dumpable {
mSystemServiceManager.startService(GAME_MANAGER_SERVICE_CLASS);
t.traceEnd();
- t.traceBegin("ArtManagerLocal");
- DexOptHelper.initializeArtManagerLocal(context, mPackageManagerService);
- t.traceEnd();
-
if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_UWB)) {
t.traceBegin("UwbService");
mSystemServiceManager.startServiceFromJar(UWB_SERVICE_CLASS, UWB_APEX_SERVICE_JAR_PATH);
diff --git a/services/people/java/com/android/server/people/data/DataManager.java b/services/people/java/com/android/server/people/data/DataManager.java
index ae8dd4180dc8..0ca4dfced1b6 100644
--- a/services/people/java/com/android/server/people/data/DataManager.java
+++ b/services/people/java/com/android/server/people/data/DataManager.java
@@ -1162,9 +1162,9 @@ public class DataManager {
private final int mUserId;
- // Conversation package name + shortcut ID -> Number of active notifications
+ // Conversation package name + shortcut ID -> Keys of active notifications
@GuardedBy("this")
- private final Map<Pair<String, String>, Integer> mActiveNotifCounts = new ArrayMap<>();
+ private final Map<Pair<String, String>, Set<String>> mActiveNotifKeys = new ArrayMap<>();
private NotificationListener(int userId) {
mUserId = userId;
@@ -1178,8 +1178,10 @@ public class DataManager {
String shortcutId = sbn.getNotification().getShortcutId();
PackageData packageData = getPackageIfConversationExists(sbn, conversationInfo -> {
synchronized (this) {
- mActiveNotifCounts.merge(
- Pair.create(sbn.getPackageName(), shortcutId), 1, Integer::sum);
+ Set<String> notificationKeys = mActiveNotifKeys.computeIfAbsent(
+ Pair.create(sbn.getPackageName(), shortcutId),
+ (unusedKey) -> new HashSet<>());
+ notificationKeys.add(sbn.getKey());
}
});
@@ -1218,12 +1220,12 @@ public class DataManager {
Pair<String, String> conversationKey =
Pair.create(sbn.getPackageName(), shortcutId);
synchronized (this) {
- int count = mActiveNotifCounts.getOrDefault(conversationKey, 0) - 1;
- if (count <= 0) {
- mActiveNotifCounts.remove(conversationKey);
+ Set<String> notificationKeys = mActiveNotifKeys.computeIfAbsent(
+ conversationKey, (unusedKey) -> new HashSet<>());
+ notificationKeys.remove(sbn.getKey());
+ if (notificationKeys.isEmpty()) {
+ mActiveNotifKeys.remove(conversationKey);
cleanupCachedShortcuts(mUserId, MAX_CACHED_RECENT_SHORTCUTS);
- } else {
- mActiveNotifCounts.put(conversationKey, count);
}
}
});
@@ -1289,7 +1291,7 @@ public class DataManager {
}
synchronized boolean hasActiveNotifications(String packageName, String shortcutId) {
- return mActiveNotifCounts.containsKey(Pair.create(packageName, shortcutId));
+ return mActiveNotifKeys.containsKey(Pair.create(packageName, shortcutId));
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index 5f4ff1a742ef..45fe795ede53 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -1191,31 +1191,6 @@ public class BroadcastQueueTest {
}
/**
- * Verify that we detect and ANR a wedged process when delivering a
- * broadcast with more than one priority tranche.
- */
- @Test
- public void testWedged_Registered_Prioritized() throws Exception {
- // Legacy stack doesn't detect these ANRs; likely an oversight
- Assume.assumeTrue(mImpl == Impl.MODERN);
-
- final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
- final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN,
- ProcessBehavior.WEDGE);
- final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE,
- ProcessBehavior.NORMAL);
-
- final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
- enqueueBroadcast(makeBroadcastRecord(airplane, callerApp,
- List.of(makeRegisteredReceiver(receiverGreenApp, 10),
- makeRegisteredReceiver(receiverBlueApp, 5))));
-
- waitForIdle();
- verify(mAms).appNotResponding(eq(receiverGreenApp), any());
- verifyScheduleRegisteredReceiver(receiverBlueApp, airplane);
- }
-
- /**
* Verify that we handle registered receivers in a process that always
* responds with {@link DeadObjectException}, recovering to restart the
* process and deliver their next broadcast.
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java
index 3f16a98c81c9..3deb903ca0b5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java
@@ -1048,8 +1048,7 @@ public final class GameServiceProviderInstanceImplTest {
Consumer<Uri> consumer = invocation.getArgument(invocation.getArguments().length - 1);
consumer.accept(Uri.parse("a/b.png"));
return null;
- }).when(mMockScreenshotHelper).provideScreenshot(
- any(), any(), any(), anyInt(), anyInt(), any(), anyInt(), any(), any());
+ }).when(mMockScreenshotHelper).takeScreenshot(any(), any(), any());
mGameServiceProviderInstance.start();
startTask(taskId, GAME_A_MAIN_ACTIVITY);
mFakeGameService.requestCreateGameSession(taskId);
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java
index 7e1a42b67922..ddb6f239a2c3 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java
@@ -30,6 +30,7 @@ import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_EJ;
import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_FGS;
import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_NONE;
import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_TOP;
+import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_UI;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
@@ -855,6 +856,9 @@ public final class JobConcurrencyManagerTest {
case WORK_TYPE_FGS:
workTypeString = "fgs";
break;
+ case WORK_TYPE_UI:
+ workTypeString = "ui";
+ break;
case WORK_TYPE_EJ:
workTypeString = "ej";
break;
diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
index a92420781ce8..19b5ad6c7572 100644
--- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
@@ -29,6 +29,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.server.wallpaper.WallpaperUtils.WALLPAPER;
+import static com.android.server.wallpaper.WallpaperUtils.WALLPAPER_CROP;
import static org.hamcrest.core.IsNot.not;
import static org.junit.Assert.assertEquals;
@@ -59,6 +60,7 @@ import android.content.pm.ParceledListSlice;
import android.content.pm.ServiceInfo;
import android.graphics.Color;
import android.hardware.display.DisplayManager;
+import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.SystemClock;
import android.platform.test.annotations.Presubmit;
@@ -100,6 +102,8 @@ import org.xmlpull.v1.XmlPullParserException;
import java.io.ByteArrayOutputStream;
import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
@@ -158,6 +162,9 @@ public class WallpaperManagerServiceTests {
sContext.getTestablePermissions().setPermission(
android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT,
PackageManager.PERMISSION_GRANTED);
+ sContext.getTestablePermissions().setPermission(
+ android.Manifest.permission.READ_WALLPAPER_INTERNAL,
+ PackageManager.PERMISSION_GRANTED);
doNothing().when(sContext).sendBroadcastAsUser(any(), any());
//Wallpaper components
@@ -494,6 +501,50 @@ public class WallpaperManagerServiceTests {
colorHints & WallpaperColors.HINT_SUPPORTS_DARK_THEME);
}
+ @Test
+ public void getWallpaperWithFeature_getCropped_returnsCropFile() throws Exception {
+ File cropSystemWallpaperFile =
+ new File(WallpaperUtils.getWallpaperDir(USER_SYSTEM), WALLPAPER_CROP);
+ cropSystemWallpaperFile.createNewFile();
+ try (FileOutputStream outputStream = new FileOutputStream(cropSystemWallpaperFile)) {
+ outputStream.write("Crop system wallpaper".getBytes());
+ }
+
+ ParcelFileDescriptor pfd =
+ mService.getWallpaperWithFeature(
+ sContext.getPackageName(),
+ sContext.getAttributionTag(),
+ /* cb= */ null,
+ FLAG_SYSTEM,
+ /* outParams= */ null,
+ USER_SYSTEM,
+ /* getCropped= */ true);
+
+ assertPfdAndFileContentsEqual(pfd, cropSystemWallpaperFile);
+ }
+
+ @Test
+ public void getWallpaperWithFeature_notGetCropped_returnsOriginalFile() throws Exception {
+ File originalSystemWallpaperFile =
+ new File(WallpaperUtils.getWallpaperDir(USER_SYSTEM), WALLPAPER);
+ originalSystemWallpaperFile.createNewFile();
+ try (FileOutputStream outputStream = new FileOutputStream(originalSystemWallpaperFile)) {
+ outputStream.write("Original system wallpaper".getBytes());
+ }
+
+ ParcelFileDescriptor pfd =
+ mService.getWallpaperWithFeature(
+ sContext.getPackageName(),
+ sContext.getAttributionTag(),
+ /* cb= */ null,
+ FLAG_SYSTEM,
+ /* outParams= */ null,
+ USER_SYSTEM,
+ /* getCropped= */ false);
+
+ assertPfdAndFileContentsEqual(pfd, originalSystemWallpaperFile);
+ }
+
// Verify that after continue switch user from userId 0 to lastUserId, the wallpaper data for
// non-current user must not bind to wallpaper service.
private void verifyNoConnectionBeforeLastUser(int lastUserId) {
@@ -529,4 +580,22 @@ public class WallpaperManagerServiceTests {
data.mHeight >= DISPLAY_SIZE_DIMENSION);
});
}
+
+ /**
+ * Asserts that the contents of the given {@link ParcelFileDescriptor} and {@link File} contain
+ * exactly the same bytes.
+ *
+ * Both the PFD and File contents will be loaded to memory. The PFD will be closed at the end.
+ */
+ private static void assertPfdAndFileContentsEqual(ParcelFileDescriptor pfd, File file)
+ throws IOException {
+ try (ParcelFileDescriptor.AutoCloseInputStream pfdInputStream =
+ new ParcelFileDescriptor.AutoCloseInputStream(pfd);
+ FileInputStream fileInputStream = new FileInputStream(file)
+ ) {
+ String pfdContents = new String(pfdInputStream.readAllBytes());
+ String fileContents = new String(fileInputStream.readAllBytes());
+ assertEquals(pfdContents, fileContents);
+ }
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java
index 13d93cbbfde4..d9461aada4d3 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java
@@ -17,7 +17,6 @@
package com.android.server.accessibility;
import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_ACCESSIBILITY_ACTIONS;
-import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.is;
@@ -305,9 +304,7 @@ public class SystemActionPerformerTest {
mSystemActionPerformer.performSystemAction(
AccessibilityService.GLOBAL_ACTION_TAKE_SCREENSHOT);
verify(mMockScreenshotHelper).takeScreenshot(
- eq(TAKE_SCREENSHOT_FULLSCREEN),
- eq(SCREENSHOT_ACCESSIBILITY_ACTIONS),
- any(Handler.class), any());
+ eq(SCREENSHOT_ACCESSIBILITY_ACTIONS), any(Handler.class), any());
}
// PendingIntent is a final class and cannot be mocked. So we are using this
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 00d4a6dc9a8c..6a4435f480f5 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
@@ -51,6 +51,7 @@ import android.app.admin.DevicePolicyManager;
import android.companion.AssociationInfo;
import android.companion.virtual.IVirtualDeviceActivityListener;
import android.companion.virtual.IVirtualDeviceIntentInterceptor;
+import android.companion.virtual.IVirtualDeviceSoundEffectListener;
import android.companion.virtual.VirtualDeviceParams;
import android.companion.virtual.audio.IAudioConfigChangedCallback;
import android.companion.virtual.audio.IAudioRoutingCallback;
@@ -75,6 +76,7 @@ import android.hardware.input.VirtualMouseScrollEvent;
import android.hardware.input.VirtualNavigationTouchpadConfig;
import android.hardware.input.VirtualTouchEvent;
import android.hardware.input.VirtualTouchscreenConfig;
+import android.media.AudioManager;
import android.net.MacAddress;
import android.net.Uri;
import android.os.Binder;
@@ -223,6 +225,8 @@ public class VirtualDeviceManagerServiceTest {
@Mock
private IVirtualDeviceActivityListener mActivityListener;
@Mock
+ private IVirtualDeviceSoundEffectListener mSoundEffectListener;
+ @Mock
private Consumer<ArraySet<Integer>> mRunningAppsChangedCallback;
@Mock
private VirtualDeviceManagerInternal.VirtualDisplayListener mDisplayListener;
@@ -1572,6 +1576,13 @@ public class VirtualDeviceManagerServiceTest {
intent.filterEquals(blockedAppIntent)), any(), any());
}
+ @Test
+ public void playSoundEffect_callsSoundEffectListener() throws Exception {
+ mVdm.playSoundEffect(mDeviceImpl.getDeviceId(), AudioManager.FX_KEY_CLICK);
+
+ verify(mSoundEffectListener).onPlaySoundEffect(AudioManager.FX_KEY_CLICK);
+ }
+
private VirtualDeviceImpl createVirtualDevice(int virtualDeviceId, int ownerUid) {
VirtualDeviceParams params = new VirtualDeviceParams.Builder()
.setBlockedActivities(getBlockedActivities())
@@ -1585,7 +1596,8 @@ public class VirtualDeviceManagerServiceTest {
mAssociationInfo, new Binder(), ownerUid, virtualDeviceId,
mInputController, mSensorController, mCameraAccessController,
/* onDeviceCloseListener= */ deviceId -> mVdms.removeVirtualDevice(deviceId),
- mPendingTrampolineCallback, mActivityListener, mRunningAppsChangedCallback, params);
+ mPendingTrampolineCallback, mActivityListener, mSoundEffectListener,
+ mRunningAppsChangedCallback, params);
mVdms.addVirtualDevice(virtualDeviceImpl);
return virtualDeviceImpl;
}
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
index a45144e23c17..82f64933c282 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
@@ -67,6 +67,9 @@ public final class DeviceStateManagerServiceTest {
new DeviceState(0, "DEFAULT", 0 /* flags */);
private static final DeviceState OTHER_DEVICE_STATE =
new DeviceState(1, "OTHER", 0 /* flags */);
+ private static final DeviceState DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP =
+ new DeviceState(2, "DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP",
+ DeviceState.FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP /* flags */);
// A device state that is not reported as being supported for the default test provider.
private static final DeviceState UNSUPPORTED_DEVICE_STATE =
new DeviceState(255, "UNSUPPORTED", 0 /* flags */);
@@ -77,6 +80,7 @@ public final class DeviceStateManagerServiceTest {
private TestDeviceStateProvider mProvider;
private DeviceStateManagerService mService;
private TestSystemPropertySetter mSysPropSetter;
+ private WindowProcessController mWindowProcessController;
@Before
public void setup() {
@@ -88,10 +92,10 @@ public final class DeviceStateManagerServiceTest {
// Necessary to allow us to check for top app process id in tests
mService.mActivityTaskManagerInternal = mock(ActivityTaskManagerInternal.class);
- WindowProcessController windowProcessController = mock(WindowProcessController.class);
+ mWindowProcessController = mock(WindowProcessController.class);
when(mService.mActivityTaskManagerInternal.getTopApp())
- .thenReturn(windowProcessController);
- when(windowProcessController.getPid()).thenReturn(FAKE_PROCESS_ID);
+ .thenReturn(mWindowProcessController);
+ when(mWindowProcessController.getPid()).thenReturn(FAKE_PROCESS_ID);
flushHandler(); // Flush the handler to ensure the initial values are committed.
}
@@ -201,7 +205,7 @@ public final class DeviceStateManagerServiceTest {
DEFAULT_DEVICE_STATE.getIdentifier() + ":" + DEFAULT_DEVICE_STATE.getName());
assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
assertThat(mService.getSupportedStates()).asList().containsExactly(DEFAULT_DEVICE_STATE,
- OTHER_DEVICE_STATE);
+ OTHER_DEVICE_STATE, DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP);
mProvider.notifySupportedDeviceStates(new DeviceState[]{DEFAULT_DEVICE_STATE});
flushHandler();
@@ -234,10 +238,10 @@ public final class DeviceStateManagerServiceTest {
DEFAULT_DEVICE_STATE.getIdentifier() + ":" + DEFAULT_DEVICE_STATE.getName());
assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
assertThat(mService.getSupportedStates()).asList().containsExactly(DEFAULT_DEVICE_STATE,
- OTHER_DEVICE_STATE);
+ OTHER_DEVICE_STATE, DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP);
mProvider.notifySupportedDeviceStates(new DeviceState[]{DEFAULT_DEVICE_STATE,
- OTHER_DEVICE_STATE});
+ OTHER_DEVICE_STATE, DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP});
flushHandler();
// The current committed and requests states do not change because the current state remains
@@ -248,7 +252,7 @@ public final class DeviceStateManagerServiceTest {
DEFAULT_DEVICE_STATE.getIdentifier() + ":" + DEFAULT_DEVICE_STATE.getName());
assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
assertThat(mService.getSupportedStates()).asList().containsExactly(DEFAULT_DEVICE_STATE,
- OTHER_DEVICE_STATE);
+ OTHER_DEVICE_STATE, DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP);
// The callback wasn't notified about a change in supported states as the states have not
// changed.
@@ -261,7 +265,8 @@ public final class DeviceStateManagerServiceTest {
assertNotNull(info);
assertArrayEquals(info.supportedStates,
new int[] { DEFAULT_DEVICE_STATE.getIdentifier(),
- OTHER_DEVICE_STATE.getIdentifier() });
+ OTHER_DEVICE_STATE.getIdentifier(),
+ DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP.getIdentifier()});
assertEquals(info.baseState, DEFAULT_DEVICE_STATE.getIdentifier());
assertEquals(info.currentState, DEFAULT_DEVICE_STATE.getIdentifier());
}
@@ -513,6 +518,54 @@ public final class DeviceStateManagerServiceTest {
OTHER_DEVICE_STATE.getIdentifier());
}
+ @Test
+ public void requestState_flagCancelWhenRequesterNotOnTop_onDeviceSleep()
+ throws RemoteException {
+ requestState_flagCancelWhenRequesterNotOnTop_common(
+ // When the device is awake, the state should not change
+ () -> mService.mOverrideRequestScreenObserver.onAwakeStateChanged(true),
+ // When the device is in sleep mode, the state should be canceled
+ () -> mService.mOverrideRequestScreenObserver.onAwakeStateChanged(false)
+ );
+ }
+
+ @Test
+ public void requestState_flagCancelWhenRequesterNotOnTop_onKeyguardShow()
+ throws RemoteException {
+ requestState_flagCancelWhenRequesterNotOnTop_common(
+ // When the keyguard is not showing, the state should not change
+ () -> mService.mOverrideRequestScreenObserver.onKeyguardStateChanged(false),
+ // When the keyguard is showing, the state should be canceled
+ () -> mService.mOverrideRequestScreenObserver.onKeyguardStateChanged(true)
+ );
+ }
+
+ @Test
+ public void requestState_flagCancelWhenRequesterNotOnTop_onTaskStackChanged()
+ throws RemoteException {
+ requestState_flagCancelWhenRequesterNotOnTop_common(
+ // When the app is foreground, the state should not change
+ () -> {
+ int pid = Binder.getCallingPid();
+ when(mWindowProcessController.getPid()).thenReturn(pid);
+ try {
+ mService.mOverrideRequestTaskStackListener.onTaskStackChanged();
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ },
+ // When the app is not foreground, the state should change
+ () -> {
+ when(mWindowProcessController.getPid()).thenReturn(FAKE_PROCESS_ID);
+ try {
+ mService.mOverrideRequestTaskStackListener.onTaskStackChanged();
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ );
+ }
+
@FlakyTest(bugId = 200332057)
@Test
public void requestState_becomesUnsupported() throws RemoteException {
@@ -743,6 +796,84 @@ public final class DeviceStateManagerServiceTest {
Assert.assertTrue(Arrays.equals(expected, actual));
}
+ /**
+ * Common code to verify the handling of FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP flag.
+ *
+ * The device state with FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP should be automatically canceled
+ * when certain events happen, e.g. when the top activity belongs to another app or when the
+ * device goes into the sleep mode.
+ *
+ * @param noChangeEvent an event that should not trigger auto cancellation of the state.
+ * @param autoCancelEvent an event that should trigger auto cancellation of the state.
+ * @throws RemoteException when the service throws exceptions.
+ */
+ private void requestState_flagCancelWhenRequesterNotOnTop_common(
+ Runnable noChangeEvent,
+ Runnable autoCancelEvent
+ ) throws RemoteException {
+ TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
+ mService.getBinderService().registerCallback(callback);
+ flushHandler();
+
+ final IBinder token = new Binder();
+ assertEquals(callback.getLastNotifiedStatus(token),
+ TestDeviceStateManagerCallback.STATUS_UNKNOWN);
+
+ mService.getBinderService().requestState(token,
+ DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP.getIdentifier(),
+ 0 /* flags */);
+ flushHandler(2 /* count */);
+
+ assertEquals(callback.getLastNotifiedStatus(token),
+ TestDeviceStateManagerCallback.STATUS_ACTIVE);
+
+ // Committed state changes as there is a requested override.
+ assertDeviceStateConditions(
+ DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP,
+ DEFAULT_DEVICE_STATE, /* base state */
+ true /* isOverrideState */);
+
+ noChangeEvent.run();
+ flushHandler();
+ assertEquals(callback.getLastNotifiedStatus(token),
+ TestDeviceStateManagerCallback.STATUS_ACTIVE);
+ assertDeviceStateConditions(
+ DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP,
+ DEFAULT_DEVICE_STATE, /* base state */
+ true /* isOverrideState */);
+
+ autoCancelEvent.run();
+ flushHandler();
+ assertEquals(callback.getLastNotifiedStatus(token),
+ TestDeviceStateManagerCallback.STATUS_CANCELED);
+ assertDeviceStateConditions(DEFAULT_DEVICE_STATE, DEFAULT_DEVICE_STATE,
+ false /* isOverrideState */);
+ }
+
+ /**
+ * Verify that the current device state and base state match the expected values.
+ *
+ * @param state the expected committed state.
+ * @param baseState the expected base state.
+ * @param isOverrideState whether a state override is active.
+ */
+ private void assertDeviceStateConditions(
+ DeviceState state, DeviceState baseState, boolean isOverrideState) {
+ assertEquals(mService.getCommittedState(), Optional.of(state));
+ assertEquals(mService.getBaseState(), Optional.of(baseState));
+ assertEquals(mSysPropSetter.getValue(),
+ state.getIdentifier() + ":" + state.getName());
+ assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
+ state.getIdentifier());
+ if (isOverrideState) {
+ // When a state override is active, the committed state should batch the override state.
+ assertEquals(mService.getOverrideState().get(), state);
+ } else {
+ // When there is no state override, the override state should be empty.
+ assertFalse(mService.getOverrideState().isPresent());
+ }
+ }
+
private static final class TestDeviceStatePolicy extends DeviceStatePolicy {
private final DeviceStateProvider mProvider;
private int mLastDeviceStateRequestedToConfigure = INVALID_DEVICE_STATE;
@@ -801,8 +932,10 @@ public final class DeviceStateManagerServiceTest {
}
private static final class TestDeviceStateProvider implements DeviceStateProvider {
- private DeviceState[] mSupportedDeviceStates = new DeviceState[]{ DEFAULT_DEVICE_STATE,
- OTHER_DEVICE_STATE };
+ private DeviceState[] mSupportedDeviceStates = new DeviceState[]{
+ DEFAULT_DEVICE_STATE,
+ OTHER_DEVICE_STATE,
+ DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP};
private Listener mListener;
@Override
diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
index a9b7143b1810..5ef762bc9a4d 100644
--- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -691,6 +691,7 @@ public class LogicalDisplayMapperTest {
// 2) Mark the displays as STATE_OFF so that it can continue with transition
// 3) Send DISPLAY_DEVICE_EVENT_CHANGE to inform the mapper of the new display state
// 4) Dispatch handler events.
+ mLogicalDisplayMapper.onBootCompleted();
mLogicalDisplayMapper.setDeviceStateLocked(0, false);
mDisplayDeviceRepo.onDisplayDeviceEvent(device3, DISPLAY_DEVICE_EVENT_CHANGED);
advanceTime(1000);
diff --git a/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt b/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt
index 3ce747f145dc..e1a04ad5ab7d 100644
--- a/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt
+++ b/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt
@@ -25,6 +25,7 @@ import android.hardware.BatteryState.STATUS_CHARGING
import android.hardware.BatteryState.STATUS_DISCHARGING
import android.hardware.BatteryState.STATUS_FULL
import android.hardware.BatteryState.STATUS_UNKNOWN
+import android.hardware.input.HostUsiVersion
import android.hardware.input.IInputDeviceBatteryListener
import android.hardware.input.IInputDeviceBatteryState
import android.hardware.input.IInputDevicesChangedListener
@@ -86,7 +87,7 @@ private fun createInputDevice(
.setDescriptor("descriptor $deviceId")
.setExternal(true)
.setHasBattery(hasBattery)
- .setSupportsUsi(supportsUsi)
+ .setUsiVersion(if (supportsUsi) HostUsiVersion(1, 0) else null)
.setGeneration(generation)
.build()
diff --git a/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java b/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java
index 0fd6a9e9248a..a0f03bbcb150 100644
--- a/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/WorkCountTrackerTest.java
@@ -22,7 +22,6 @@ import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BG;
import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BGUSER;
import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BGUSER_IMPORTANT;
import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_EJ;
-import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_FGS;
import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_NONE;
import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_TOP;
import static com.android.server.job.JobConcurrencyManager.workTypeToString;
@@ -59,7 +58,8 @@ public class WorkCountTrackerTest {
private static final double[] EQUAL_PROBABILITY_CDF =
buildWorkTypeCdf(1.0 / NUM_WORK_TYPES, 1.0 / NUM_WORK_TYPES, 1.0 / NUM_WORK_TYPES,
- 1.0 / NUM_WORK_TYPES, 1.0 / NUM_WORK_TYPES, 1.0 / NUM_WORK_TYPES);
+ 1.0 / NUM_WORK_TYPES, 1.0 / NUM_WORK_TYPES, 1.0 / NUM_WORK_TYPES,
+ 1.0 / NUM_WORK_TYPES);
private Random mRandom;
private WorkCountTracker mWorkCountTracker;
@@ -72,8 +72,9 @@ public class WorkCountTrackerTest {
@NonNull
private static double[] buildWorkTypeCdf(
- double pTop, double pFgs, double pEj, double pBg, double pBgUserImp, double pBgUser) {
- return buildCdf(pTop, pFgs, pEj, pBg, pBgUserImp, pBgUser);
+ double pTop, double pFgs, double pUi, double pEj, double pBg,
+ double pBgUserImp, double pBgUser) {
+ return buildCdf(pTop, pFgs, pUi, pEj, pBg, pBgUserImp, pBgUser);
}
@NonNull
@@ -108,23 +109,9 @@ public class WorkCountTrackerTest {
@JobConcurrencyManager.WorkType
static int getRandomWorkType(double[] cdf, double rand) {
+ assertThat(cdf.length).isEqualTo(NUM_WORK_TYPES);
final int index = getRandomIndex(cdf, rand);
- switch (index) {
- case 0:
- return WORK_TYPE_TOP;
- case 1:
- return WORK_TYPE_FGS;
- case 2:
- return WORK_TYPE_EJ;
- case 3:
- return WORK_TYPE_BG;
- case 4:
- return WORK_TYPE_BGUSER_IMPORTANT;
- case 5:
- return WORK_TYPE_BGUSER;
- default:
- throw new IllegalStateException("Unknown work type");
- }
+ return 1 << index;
}
/**
@@ -326,7 +313,7 @@ public class WorkCountTrackerTest {
final List<Pair<Integer, Float>> maxLimitRatios =
List.of(Pair.create(WORK_TYPE_BG, 1f), Pair.create(WORK_TYPE_BGUSER, .5f));
final double probStop = 0.5;
- final double[] cdf = buildWorkTypeCdf(0.5, 0, 0, 0.5, 0, 0);
+ final double[] cdf = buildWorkTypeCdf(0.5, 0, 0, 0, 0.5, 0, 0);
final double[] numTypesCdf = buildCdf(.5, .3, .15, .05);
final double probStart = 0.5;
@@ -344,7 +331,7 @@ public class WorkCountTrackerTest {
final List<Pair<Integer, Float>> maxLimitRatios =
List.of(Pair.create(WORK_TYPE_BG, 1f), Pair.create(WORK_TYPE_BGUSER, .5f));
final double probStop = 0.5;
- final double[] cdf = buildWorkTypeCdf(1.0 / 3, 0, 0, 1.0 / 3, 0, 1.0 / 3);
+ final double[] cdf = buildWorkTypeCdf(1.0 / 3, 0, 0, 0, 1.0 / 3, 0, 1.0 / 3);
final double[] numTypesCdf = buildCdf(.75, .2, .05);
final double probStart = 0.5;
@@ -362,7 +349,7 @@ public class WorkCountTrackerTest {
final List<Pair<Integer, Float>> maxLimitRatios =
List.of(Pair.create(WORK_TYPE_BG, .2f), Pair.create(WORK_TYPE_BGUSER, .1f));
final double probStop = 0.5;
- final double[] cdf = buildWorkTypeCdf(1.0 / 3, 0, 0, 1.0 / 3, 0, 1.0 / 3);
+ final double[] cdf = buildWorkTypeCdf(1.0 / 3, 0, 0, 0, 1.0 / 3, 0, 1.0 / 3);
final double[] numTypesCdf = buildCdf(.05, .95);
final double probStart = 0.5;
@@ -382,7 +369,7 @@ public class WorkCountTrackerTest {
List.of(Pair.create(WORK_TYPE_BG, 2.0f / 3),
Pair.create(WORK_TYPE_BGUSER, 2.0f / 3));
final double probStop = 0.5;
- final double[] cdf = buildWorkTypeCdf(0.1, 0, 0, 0.8, 0.02, .08);
+ final double[] cdf = buildWorkTypeCdf(0.1, 0, 0, 0, 0.8, 0.02, .08);
final double[] numTypesCdf = buildCdf(.5, .3, .15, .05);
final double probStart = 0.5;
@@ -402,7 +389,7 @@ public class WorkCountTrackerTest {
List.of(Pair.create(WORK_TYPE_BG, 2.0f / 3),
Pair.create(WORK_TYPE_BGUSER, 1.0f / 3));
final double probStop = 0.5;
- final double[] cdf = buildWorkTypeCdf(0.85, 0.05, 0, 0.1, 0, 0);
+ final double[] cdf = buildWorkTypeCdf(0.8, 0.05, 0.05, 0, 0.1, 0, 0);
final double[] numTypesCdf = buildCdf(1);
final double probStart = 0.5;
@@ -422,7 +409,7 @@ public class WorkCountTrackerTest {
List.of(Pair.create(WORK_TYPE_BG, 2.0f / 3),
Pair.create(WORK_TYPE_BGUSER, 1.0f / 3));
final double probStop = 0.4;
- final double[] cdf = buildWorkTypeCdf(0.1, 0, 0, 0.1, 0.05, .75);
+ final double[] cdf = buildWorkTypeCdf(0.1, 0, 0, 0, 0.1, 0.05, .75);
final double[] numTypesCdf = buildCdf(0.5, 0.5);
final double probStart = 0.5;
@@ -443,7 +430,7 @@ public class WorkCountTrackerTest {
List.of(Pair.create(WORK_TYPE_BG, 2.0f / 3),
Pair.create(WORK_TYPE_BGUSER, 1.0f / 3));
final double probStop = 0.4;
- final double[] cdf = buildWorkTypeCdf(0.8, 0.1, 0, 0.05, 0, 0.05);
+ final double[] cdf = buildWorkTypeCdf(0.8, 0.1, 0, 0, 0.05, 0, 0.05);
final double[] numTypesCdf = buildCdf(1);
final double probStart = 0.5;
@@ -464,7 +451,7 @@ public class WorkCountTrackerTest {
List.of(Pair.create(WORK_TYPE_BG, 2.0f / 3),
Pair.create(WORK_TYPE_BGUSER, 1.0f / 3));
final double probStop = 0.5;
- final double[] cdf = buildWorkTypeCdf(0, 0, 0, 0.5, 0, 0.5);
+ final double[] cdf = buildWorkTypeCdf(0, 0, 0, 0, 0.5, 0, 0.5);
final double[] numTypesCdf = buildCdf(1);
final double probStart = 0.5;
@@ -485,7 +472,7 @@ public class WorkCountTrackerTest {
List.of(Pair.create(WORK_TYPE_BG, 2.0f / 3),
Pair.create(WORK_TYPE_BGUSER, 1.0f / 3));
final double probStop = 0.5;
- final double[] cdf = buildWorkTypeCdf(0, 0, 0, 0.1, 0, 0.9);
+ final double[] cdf = buildWorkTypeCdf(0, 0, 0, 0, 0.1, 0, 0.9);
final double[] numTypesCdf = buildCdf(0.9, 0.1);
final double probStart = 0.5;
@@ -506,7 +493,7 @@ public class WorkCountTrackerTest {
List.of(Pair.create(WORK_TYPE_BG, 2.0f / 3),
Pair.create(WORK_TYPE_BGUSER, 1.0f / 3));
final double probStop = 0.5;
- final double[] cdf = buildWorkTypeCdf(0, 0, 0, 0.9, 0, 0.1);
+ final double[] cdf = buildWorkTypeCdf(0, 0, .4, 0, 0.5, 0, 0.1);
final double[] numTypesCdf = buildCdf(1);
final double probStart = 0.5;
@@ -525,7 +512,7 @@ public class WorkCountTrackerTest {
final List<Pair<Integer, Float>> maxLimitRatios =
List.of(Pair.create(WORK_TYPE_BG, 2.0f / 3));
final double probStop = 0.4;
- final double[] cdf = buildWorkTypeCdf(0.5, 0, 0.5, 0, 0, 0);
+ final double[] cdf = buildWorkTypeCdf(0.5, 0, 0.25, 0.25, 0, 0, 0);
final double[] numTypesCdf = buildCdf(0.1, 0.7, 0.2);
final double probStart = 0.5;
@@ -566,7 +553,7 @@ public class WorkCountTrackerTest {
final List<Pair<Integer, Float>> maxLimitRatios =
List.of(Pair.create(WORK_TYPE_EJ, 5.0f / 6), Pair.create(WORK_TYPE_BG, 2.0f / 3));
final double probStop = 0.4;
- final double[] cdf = buildWorkTypeCdf(.1, 0, 0.5, 0.35, 0, 0.05);
+ final double[] cdf = buildWorkTypeCdf(.1, 0, 0.05, 0.45, 0.35, 0, 0.05);
final double[] numTypesCdf = buildCdf(1);
final double probStart = 0.5;
@@ -586,7 +573,7 @@ public class WorkCountTrackerTest {
List.of(Pair.create(WORK_TYPE_EJ, 5.0f / 6), Pair.create(WORK_TYPE_BG, 2.0f / 3),
Pair.create(WORK_TYPE_BGUSER, 1.0f / 6));
final double probStop = 0.4;
- final double[] cdf = buildWorkTypeCdf(0.01, 0.09, 0.4, 0.1, 0, 0.4);
+ final double[] cdf = buildWorkTypeCdf(0.01, 0.09, 0.2, 0.2, 0.1, 0, 0.4);
final double[] numTypesCdf = buildCdf(0.7, 0.3);
final double probStart = 0.5;
@@ -607,7 +594,7 @@ public class WorkCountTrackerTest {
Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 1.0f / 7),
Pair.create(WORK_TYPE_BGUSER, 1.0f / 7));
final double probStop = 0.4;
- final double[] cdf = buildWorkTypeCdf(0.01, 0.09, 0.25, 0.05, 0.3, 0.3);
+ final double[] cdf = buildWorkTypeCdf(0.01, 0.02, 0.09, 0.25, 0.05, 0.3, 0.3);
final double[] numTypesCdf = buildCdf(0.7, 0.3);
final double probStart = 0.5;
diff --git a/services/tests/servicestests/src/com/android/server/job/WorkTypeConfigTest.java b/services/tests/servicestests/src/com/android/server/job/WorkTypeConfigTest.java
index bd5a063b1484..94dfae30a7c0 100644
--- a/services/tests/servicestests/src/com/android/server/job/WorkTypeConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/WorkTypeConfigTest.java
@@ -21,6 +21,7 @@ import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BGUSER_IMPO
import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_EJ;
import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_FGS;
import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_TOP;
+import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_UI;
import static com.android.server.job.JobConcurrencyManager.workTypeToString;
import static org.junit.Assert.assertEquals;
@@ -47,6 +48,7 @@ public class WorkTypeConfigTest {
private static final String KEY_MAX_TOTAL = "concurrency_max_total_test";
private static final String KEY_MAX_RATIO_TOP = "concurrency_max_ratio_top_test";
private static final String KEY_MAX_RATIO_FGS = "concurrency_max_ratio_fgs_test";
+ private static final String KEY_MAX_RATIO_UI = "concurrency_max_ratio_ui_test";
private static final String KEY_MAX_RATIO_EJ = "concurrency_max_ratio_ej_test";
private static final String KEY_MAX_RATIO_BG = "concurrency_max_ratio_bg_test";
private static final String KEY_MAX_RATIO_BGUSER_IMPORTANT =
@@ -54,6 +56,7 @@ public class WorkTypeConfigTest {
private static final String KEY_MAX_RATIO_BGUSER = "concurrency_max_ratio_bguser_test";
private static final String KEY_MIN_RATIO_TOP = "concurrency_min_ratio_top_test";
private static final String KEY_MIN_RATIO_FGS = "concurrency_min_ratio_fgs_test";
+ private static final String KEY_MIN_RATIO_UI = "concurrency_min_ratio_ui_test";
private static final String KEY_MIN_RATIO_EJ = "concurrency_min_ratio_ej_test";
private static final String KEY_MIN_RATIO_BG = "concurrency_min_ratio_bg_test";
private static final String KEY_MIN_RATIO_BGUSER_IMPORTANT =
@@ -326,31 +329,35 @@ public class WorkTypeConfigTest {
/* max */ List.of(Pair.create(WORK_TYPE_TOP, 16), Pair.create(WORK_TYPE_BG, 16)));
check(new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER)
- .setInt(KEY_MAX_TOTAL, 16)
+ .setInt(KEY_MAX_TOTAL, 32)
.setFloat(KEY_MAX_RATIO_TOP, 1f)
- .setFloat(KEY_MIN_RATIO_TOP, 1.0f / 16)
- .setFloat(KEY_MAX_RATIO_FGS, 15.0f / 16)
- .setFloat(KEY_MIN_RATIO_FGS, 2.0f / 16)
- .setFloat(KEY_MAX_RATIO_EJ, 14.0f / 16)
- .setFloat(KEY_MIN_RATIO_EJ, 3.0f / 16)
- .setFloat(KEY_MAX_RATIO_BG, 13.0f / 16)
- .setFloat(KEY_MIN_RATIO_BG, 3.0f / 16)
- .setFloat(KEY_MAX_RATIO_BGUSER_IMPORTANT, 12.0f / 16)
- .setFloat(KEY_MIN_RATIO_BGUSER_IMPORTANT, 2.0f / 16)
- .setFloat(KEY_MAX_RATIO_BGUSER, 11.0f / 16)
- .setFloat(KEY_MIN_RATIO_BGUSER, 2.0f / 16)
+ .setFloat(KEY_MIN_RATIO_TOP, 1.0f / 32)
+ .setFloat(KEY_MAX_RATIO_FGS, 15.0f / 32)
+ .setFloat(KEY_MIN_RATIO_FGS, 2.0f / 32)
+ .setFloat(KEY_MAX_RATIO_UI, 10.0f / 32)
+ .setFloat(KEY_MIN_RATIO_UI, 4.0f / 32)
+ .setFloat(KEY_MAX_RATIO_EJ, 14.0f / 32)
+ .setFloat(KEY_MIN_RATIO_EJ, 3.0f / 32)
+ .setFloat(KEY_MAX_RATIO_BG, 13.0f / 32)
+ .setFloat(KEY_MIN_RATIO_BG, 3.0f / 32)
+ .setFloat(KEY_MAX_RATIO_BGUSER_IMPORTANT, 12.0f / 32)
+ .setFloat(KEY_MIN_RATIO_BGUSER_IMPORTANT, 2.0f / 32)
+ .setFloat(KEY_MAX_RATIO_BGUSER, 11.0f / 32)
+ .setFloat(KEY_MIN_RATIO_BGUSER, 2.0f / 32)
.build(),
- /* limit */ 16,
+ /* limit */ 32,
/*default*/ 9,
/* min */ List.of(Pair.create(WORK_TYPE_BG, .99f)),
/* max */ List.of(Pair.create(WORK_TYPE_BG, 1f)),
- /*expected*/ true, 16,
+ /*expected*/ true, 32,
/* min */ List.of(Pair.create(WORK_TYPE_TOP, 1), Pair.create(WORK_TYPE_FGS, 2),
+ Pair.create(WORK_TYPE_UI, 4),
Pair.create(WORK_TYPE_EJ, 3), Pair.create(WORK_TYPE_BG, 3),
Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 2),
Pair.create(WORK_TYPE_BGUSER, 2)),
/* max */
- List.of(Pair.create(WORK_TYPE_TOP, 16), Pair.create(WORK_TYPE_FGS, 15),
+ List.of(Pair.create(WORK_TYPE_TOP, 32), Pair.create(WORK_TYPE_FGS, 15),
+ Pair.create(WORK_TYPE_UI, 10),
Pair.create(WORK_TYPE_EJ, 14), Pair.create(WORK_TYPE_BG, 13),
Pair.create(WORK_TYPE_BGUSER_IMPORTANT, 12),
Pair.create(WORK_TYPE_BGUSER, 11)));
diff --git a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
index fa8d866089b4..93f6db7e9386 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
@@ -500,6 +500,7 @@ public final class DataManagerTest {
// The cached conversations are above the limit because every conversation has active
// notifications. To uncache one of them, the notifications for that conversation need to
// be dismissed.
+ String notificationKey = "";
for (int i = 0; i < DataManager.MAX_CACHED_RECENT_SHORTCUTS + 1; i++) {
String shortcutId = TEST_SHORTCUT_ID + i;
ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, shortcutId,
@@ -507,11 +508,13 @@ public final class DataManagerTest {
shortcut.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
mDataManager.addOrUpdateConversationInfo(shortcut);
when(mNotification.getShortcutId()).thenReturn(shortcutId);
- sendGenericNotification();
+ notificationKey = String.format("notification-key-%d", i);
+ sendGenericNotificationWithKey(notificationKey);
}
// Post another notification for the last conversation.
- sendGenericNotification();
+ String otherNotificationKey = "other-notification-key";
+ sendGenericNotificationWithKey(otherNotificationKey);
// Removing one of the two notifications does not un-cache the shortcut.
listenerService.onNotificationRemoved(mGenericSbn, null,
@@ -520,6 +523,7 @@ public final class DataManagerTest {
anyInt(), any(), anyString(), any(), anyInt(), anyInt());
// Removing the second notification un-caches the shortcut.
+ when(mGenericSbn.getKey()).thenReturn(notificationKey);
listenerService.onNotificationRemoved(mGenericSbn, null,
NotificationListenerService.REASON_CANCEL_ALL);
verify(mShortcutServiceInternal).uncacheShortcuts(
@@ -687,6 +691,35 @@ public final class DataManagerTest {
}
@Test
+ public void testGetConversation_trackActiveConversations() {
+ mDataManager.onUserUnlocked(USER_ID_PRIMARY);
+ assertThat(mDataManager.getConversation(TEST_PKG_NAME, USER_ID_PRIMARY,
+ TEST_SHORTCUT_ID)).isNull();
+ ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
+ buildPerson());
+ shortcut.setCached(ShortcutInfo.FLAG_PINNED);
+ mDataManager.addOrUpdateConversationInfo(shortcut);
+ assertThat(mDataManager.getConversation(TEST_PKG_NAME, USER_ID_PRIMARY,
+ TEST_SHORTCUT_ID)).isNotNull();
+
+ sendGenericNotification();
+ sendGenericNotification();
+ ConversationChannel result = mDataManager.getConversation(TEST_PKG_NAME, USER_ID_PRIMARY,
+ TEST_SHORTCUT_ID);
+ assertTrue(result.hasActiveNotifications());
+
+ // Both generic notifications have the same notification key, so a single dismiss will
+ // remove both of them.
+ NotificationListenerService listenerService =
+ mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
+ listenerService.onNotificationRemoved(mGenericSbn, null,
+ NotificationListenerService.REASON_CANCEL);
+ ConversationChannel resultTwo = mDataManager.getConversation(TEST_PKG_NAME, USER_ID_PRIMARY,
+ TEST_SHORTCUT_ID);
+ assertFalse(resultTwo.hasActiveNotifications());
+ }
+
+ @Test
public void testGetConversation_unsyncedShortcut() {
mDataManager.onUserUnlocked(USER_ID_PRIMARY);
ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
@@ -1322,7 +1355,7 @@ public final class DataManagerTest {
sendGenericNotification();
- mDataManager.getRecentConversations(USER_ID_PRIMARY);
+ mDataManager.getRecentConversations(USER_ID_PRIMARY);
verify(mShortcutServiceInternal).getShortcuts(
anyInt(), anyString(), anyLong(), anyString(), anyList(), any(), any(),
@@ -1693,6 +1726,12 @@ public final class DataManagerTest {
// "Sends" a notification to a non-customized notification channel - the notification channel
// is something generic like "messages" and the notification has a shortcut id
private void sendGenericNotification() {
+ sendGenericNotificationWithKey(GENERIC_KEY);
+ }
+
+ // "Sends" a notification to a non-customized notification channel with the specified key.
+ private void sendGenericNotificationWithKey(String key) {
+ when(mGenericSbn.getKey()).thenReturn(key);
when(mNotification.getChannelId()).thenReturn(PARENT_NOTIFICATION_CHANNEL_ID);
doAnswer(invocationOnMock -> {
NotificationListenerService.Ranking ranking = (NotificationListenerService.Ranking)
@@ -1708,7 +1747,7 @@ public final class DataManagerTest {
mParentNotificationChannel, null, null, true, 0, false, -1, false, null, null,
false, false, false, null, 0, false, 0);
return true;
- }).when(mRankingMap).getRanking(eq(GENERIC_KEY),
+ }).when(mRankingMap).getRanking(eq(key),
any(NotificationListenerService.Ranking.class));
NotificationListenerService listenerService =
mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
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 76a13f1db8d4..1305e07666ab 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -26,10 +26,8 @@ import static org.testng.Assert.assertThrows;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
-import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
-import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.content.pm.UserProperties;
@@ -41,6 +39,7 @@ import android.platform.test.annotations.Postsubmit;
import android.provider.Settings;
import android.test.suitebuilder.annotation.LargeTest;
import android.test.suitebuilder.annotation.MediumTest;
+import android.util.ArraySet;
import android.util.Slog;
import androidx.annotation.Nullable;
@@ -48,6 +47,8 @@ import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.compatibility.common.util.BlockingBroadcastReceiver;
+
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Range;
@@ -56,16 +57,14 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Function;
import java.util.stream.Collectors;
-import javax.annotation.concurrent.GuardedBy;
-
/** Test {@link UserManager} functionality. */
@Postsubmit
@RunWith(AndroidJUnit4.class)
@@ -73,8 +72,6 @@ public final class UserManagerTest {
// Taken from UserManagerService
private static final long EPOCH_PLUS_30_YEARS = 30L * 365 * 24 * 60 * 60 * 1000L; // 30 years
- private static final int REMOVE_CHECK_INTERVAL_MILLIS = 500; // 0.5 seconds
- private static final int REMOVE_TIMEOUT_MILLIS = 60 * 1000; // 60 seconds
private static final int SWITCH_USER_TIMEOUT_SECONDS = 40; // 40 seconds
// Packages which are used during tests.
@@ -86,12 +83,10 @@ public final class UserManagerTest {
private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
- private final Object mUserRemoveLock = new Object();
-
private UserManager mUserManager = null;
private ActivityManager mActivityManager;
private PackageManager mPackageManager;
- private List<Integer> usersToRemove;
+ private ArraySet<Integer> mUsersToRemove;
private UserSwitchWaiter mUserSwitchWaiter;
@Before
@@ -101,30 +96,16 @@ public final class UserManagerTest {
mPackageManager = mContext.getPackageManager();
mUserSwitchWaiter = new UserSwitchWaiter(TAG, SWITCH_USER_TIMEOUT_SECONDS);
- IntentFilter filter = new IntentFilter(Intent.ACTION_USER_REMOVED);
- mContext.registerReceiver(new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- switch (intent.getAction()) {
- case Intent.ACTION_USER_REMOVED:
- synchronized (mUserRemoveLock) {
- mUserRemoveLock.notifyAll();
- }
- break;
- }
- }
- }, filter);
-
+ mUsersToRemove = new ArraySet<>();
removeExistingUsers();
- usersToRemove = new ArrayList<>();
}
@After
public void tearDown() throws Exception {
mUserSwitchWaiter.close();
- for (Integer userId : usersToRemove) {
- removeUser(userId);
- }
+
+ // Making a copy of mUsersToRemove to avoid ConcurrentModificationException
+ mUsersToRemove.stream().toList().forEach(this::removeUser);
}
private void removeExistingUsers() {
@@ -406,13 +387,11 @@ public final class UserManagerTest {
mUserManager.setUserRestriction(UserManager.DISALLOW_REMOVE_USER, /* value= */ true,
asHandle(currentUser));
try {
- synchronized (mUserRemoveLock) {
+ runThenWaitForUserRemoval(() -> {
assertThat(mUserManager.removeUserWhenPossible(user1.getUserHandle(),
/* overrideDevicePolicy= */ true))
- .isEqualTo(UserManager.REMOVE_RESULT_REMOVED);
- waitForUserRemovalLocked(user1.id);
- }
-
+ .isEqualTo(UserManager.REMOVE_RESULT_REMOVED);
+ }, user1.id); // wait for user removal
} finally {
mUserManager.setUserRestriction(UserManager.DISALLOW_REMOVE_USER, /* value= */ false,
asHandle(currentUser));
@@ -477,13 +456,12 @@ public final class UserManagerTest {
assertThat(hasUser(user1.id)).isTrue();
assertThat(getUser(user1.id).isEphemeral()).isTrue();
- // Switch back to the starting user.
- switchUser(startUser);
+ runThenWaitForUserRemoval(() -> {
+ // Switch back to the starting user.
+ switchUser(startUser);
+ // User will be removed once switch is complete
+ }, user1.id); // wait for user removal
- // User is removed once switch is complete
- synchronized (mUserRemoveLock) {
- waitForUserRemovalLocked(user1.id);
- }
assertThat(hasUser(user1.id)).isFalse();
}
@@ -495,19 +473,19 @@ public final class UserManagerTest {
// Switch to the user just created.
switchUser(testUser.id);
- switchUserThenRun(startUser, () -> {
- // While the user switch is happening, call removeUserWhenPossible for the current user.
- assertThat(mUserManager.removeUserWhenPossible(testUser.getUserHandle(), false))
- .isEqualTo(UserManager.REMOVE_RESULT_DEFERRED);
+ runThenWaitForUserRemoval(() -> {
+ switchUserThenRun(startUser, () -> {
+ // While the switch is happening, call removeUserWhenPossible for the current user.
+ assertThat(mUserManager.removeUserWhenPossible(testUser.getUserHandle(),
+ /* overrideDevicePolicy= */ false))
+ .isEqualTo(UserManager.REMOVE_RESULT_DEFERRED);
- assertThat(hasUser(testUser.id)).isTrue();
- assertThat(getUser(testUser.id).isEphemeral()).isTrue();
- });
+ assertThat(hasUser(testUser.id)).isTrue();
+ assertThat(getUser(testUser.id).isEphemeral()).isTrue();
+ }); // wait for user switch - startUser
+ // User will be removed once switch is complete
+ }, testUser.id); // wait for user removal
- // User is removed once switch is complete
- synchronized (mUserRemoveLock) {
- waitForUserRemovalLocked(testUser.id);
- }
assertThat(hasUser(testUser.id)).isFalse();
}
@@ -519,20 +497,20 @@ public final class UserManagerTest {
switchUserThenRun(testUser.id, () -> {
// While the user switch is happening, call removeUserWhenPossible for the target user.
- assertThat(mUserManager.removeUserWhenPossible(testUser.getUserHandle(), false))
+ assertThat(mUserManager.removeUserWhenPossible(testUser.getUserHandle(),
+ /* overrideDevicePolicy= */ false))
.isEqualTo(UserManager.REMOVE_RESULT_DEFERRED);
assertThat(hasUser(testUser.id)).isTrue();
assertThat(getUser(testUser.id).isEphemeral()).isTrue();
- });
+ }); // wait for user switch - testUser
- // Switch back to the starting user.
- switchUser(startUser);
+ runThenWaitForUserRemoval(() -> {
+ // Switch back to the starting user.
+ switchUser(startUser);
+ // User will be removed once switch is complete
+ }, testUser.id); // wait for user removal
- // User is removed once switch is complete
- synchronized (mUserRemoveLock) {
- waitForUserRemovalLocked(testUser.id);
- }
assertThat(hasUser(testUser.id)).isFalse();
}
@@ -540,12 +518,12 @@ public final class UserManagerTest {
@Test
public void testRemoveUserWhenPossible_nonCurrentUserRemoved() throws Exception {
final UserInfo user1 = createUser("User 1", /* flags= */ 0);
- synchronized (mUserRemoveLock) {
+
+ runThenWaitForUserRemoval(() -> {
assertThat(mUserManager.removeUserWhenPossible(user1.getUserHandle(),
/* overrideDevicePolicy= */ false))
- .isEqualTo(UserManager.REMOVE_RESULT_REMOVED);
- waitForUserRemovalLocked(user1.id);
- }
+ .isEqualTo(UserManager.REMOVE_RESULT_REMOVED);
+ }, user1.id); // wait for user removal
assertThat(hasUser(user1.id)).isFalse();
}
@@ -562,12 +540,12 @@ public final class UserManagerTest {
final UserInfo workProfileUser = createProfileForUser("Work Profile user",
UserManager.USER_TYPE_PROFILE_MANAGED,
parentUser.id);
- synchronized (mUserRemoveLock) {
+
+ runThenWaitForUserRemoval(() -> {
assertThat(mUserManager.removeUserWhenPossible(parentUser.getUserHandle(),
/* overrideDevicePolicy= */ false))
.isEqualTo(UserManager.REMOVE_RESULT_REMOVED);
- waitForUserRemovalLocked(parentUser.id);
- }
+ }, parentUser.id); // wait for user removal
assertThat(hasUser(parentUser.id)).isFalse();
assertThat(hasUser(cloneProfileUser.id)).isFalse();
@@ -1298,9 +1276,7 @@ public final class UserManagerTest {
UserInfo user = mUserManager.createUser(userName, 0);
if (user != null) {
created.incrementAndGet();
- synchronized (mUserRemoveLock) {
- usersToRemove.add(user.id);
- }
+ mUsersToRemove.add(user.id);
}
});
}
@@ -1462,40 +1438,41 @@ public final class UserManagerTest {
}, () -> fail("Could not complete switching to user " + userId));
}
- private void removeUser(UserHandle user) {
- synchronized (mUserRemoveLock) {
- mUserManager.removeUser(user);
- waitForUserRemovalLocked(user.getIdentifier());
- }
+ private void removeUser(UserHandle userHandle) {
+ runThenWaitForUserRemoval(
+ () -> mUserManager.removeUser(userHandle),
+ userHandle == null ? UserHandle.USER_NULL : userHandle.getIdentifier()
+ );
}
private void removeUser(int userId) {
- synchronized (mUserRemoveLock) {
- mUserManager.removeUser(userId);
- waitForUserRemovalLocked(userId);
- }
+ runThenWaitForUserRemoval(
+ () -> mUserManager.removeUser(userId),
+ userId
+ );
}
- @GuardedBy("mUserRemoveLock")
- private void waitForUserRemovalLocked(int userId) {
- long time = System.currentTimeMillis();
- while (mUserManager.getUserInfo(userId) != null) {
- try {
- mUserRemoveLock.wait(REMOVE_CHECK_INTERVAL_MILLIS);
- } catch (InterruptedException ie) {
- Thread.currentThread().interrupt();
- return;
- }
- if (System.currentTimeMillis() - time > REMOVE_TIMEOUT_MILLIS) {
- fail("Timeout waiting for removeUser. userId = " + userId);
- }
+ private void runThenWaitForUserRemoval(Runnable runnable, int userIdToWaitUntilDeleted) {
+ Function<Intent, Boolean> checker = intent -> {
+ UserHandle userHandle = intent.getParcelableExtra(Intent.EXTRA_USER, UserHandle.class);
+ return userHandle != null && userHandle.getIdentifier() == userIdToWaitUntilDeleted;
+ };
+
+ BlockingBroadcastReceiver blockingBroadcastReceiver = BlockingBroadcastReceiver.create(
+ mContext, Intent.ACTION_USER_REMOVED, checker);
+
+ blockingBroadcastReceiver.register();
+
+ try (blockingBroadcastReceiver) {
+ runnable.run();
}
+ mUsersToRemove.remove(userIdToWaitUntilDeleted);
}
private UserInfo createUser(String name, int flags) {
UserInfo user = mUserManager.createUser(name, flags);
if (user != null) {
- usersToRemove.add(user.id);
+ mUsersToRemove.add(user.id);
}
return user;
}
@@ -1503,7 +1480,7 @@ public final class UserManagerTest {
private UserInfo createUser(String name, String userType, int flags) {
UserInfo user = mUserManager.createUser(name, userType, flags);
if (user != null) {
- usersToRemove.add(user.id);
+ mUsersToRemove.add(user.id);
}
return user;
}
@@ -1517,7 +1494,7 @@ public final class UserManagerTest {
UserInfo profile = mUserManager.createProfileForUser(
name, userType, 0, userHandle, disallowedPackages);
if (profile != null) {
- usersToRemove.add(profile.id);
+ mUsersToRemove.add(profile.id);
}
return profile;
}
@@ -1527,7 +1504,7 @@ public final class UserManagerTest {
UserInfo profile = mUserManager.createProfileForUserEvenWhenDisallowed(
name, userType, 0, userHandle, null);
if (profile != null) {
- usersToRemove.add(profile.id);
+ mUsersToRemove.add(profile.id);
}
return profile;
}
@@ -1535,7 +1512,7 @@ public final class UserManagerTest {
private UserInfo createRestrictedProfile(String name) {
UserInfo profile = mUserManager.createRestrictedProfile(name);
if (profile != null) {
- usersToRemove.add(profile.id);
+ mUsersToRemove.add(profile.id);
}
return profile;
}
diff --git a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
index 261156611a06..7fac9b608114 100644
--- a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
@@ -35,6 +35,7 @@ import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorManager;
+import android.os.PowerManager;
import androidx.annotation.NonNull;
@@ -312,6 +313,14 @@ public final class DeviceStateProviderImplTest {
+ " </sensor>\n"
+ " </conditions>\n"
+ " </device-state>\n"
+ + " <device-state>\n"
+ + " <identifier>4</identifier>\n"
+ + " <name>THERMAL_TEST</name>\n"
+ + " <flags>\n"
+ + " <flag>FLAG_EMULATED_ONLY</flag>\n"
+ + " <flag>FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL</flag>\n"
+ + " </flags>\n"
+ + " </device-state>\n"
+ "</device-state-config>\n";
DeviceStateProviderImpl.ReadableConfig config = new TestReadableConfig(configString);
return DeviceStateProviderImpl.createFromConfig(mContext,
@@ -332,7 +341,10 @@ public final class DeviceStateProviderImplTest {
new DeviceState[]{
new DeviceState(1, "CLOSED", 0 /* flags */),
new DeviceState(2, "HALF_OPENED", 0 /* flags */),
- new DeviceState(3, "OPENED", 0 /* flags */) },
+ new DeviceState(3, "OPENED", 0 /* flags */),
+ new DeviceState(4, "THERMAL_TEST",
+ DeviceState.FLAG_EMULATED_ONLY
+ | DeviceState.FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL) },
mDeviceStateArrayCaptor.getValue());
// onStateChanged() should not be called because the provider has not yet been notified of
// the initial sensor state.
@@ -376,6 +388,57 @@ public final class DeviceStateProviderImplTest {
}
@Test
+ public void test_flagDisableWhenThermalStatusCritical() throws Exception {
+ Sensor sensor = newSensor("sensor", Sensor.STRING_TYPE_HINGE_ANGLE);
+ when(mSensorManager.getSensorList(anyInt())).thenReturn(List.of(sensor));
+ DeviceStateProviderImpl provider = create_sensorBasedProvider(sensor);
+
+ provider.onThermalStatusChanged(PowerManager.THERMAL_STATUS_LIGHT);
+ DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
+ provider.setListener(listener);
+
+ verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+ assertArrayEquals(
+ new DeviceState[]{
+ new DeviceState(1, "CLOSED", 0 /* flags */),
+ new DeviceState(2, "HALF_OPENED", 0 /* flags */),
+ new DeviceState(3, "OPENED", 0 /* flags */),
+ new DeviceState(4, "THERMAL_TEST",
+ DeviceState.FLAG_EMULATED_ONLY
+ | DeviceState.FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL) },
+ mDeviceStateArrayCaptor.getValue());
+ Mockito.clearInvocations(listener);
+
+ provider.onThermalStatusChanged(PowerManager.THERMAL_STATUS_MODERATE);
+ verify(listener, never()).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+ Mockito.clearInvocations(listener);
+
+ // The THERMAL_TEST state should be disabled.
+ provider.onThermalStatusChanged(PowerManager.THERMAL_STATUS_CRITICAL);
+ verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+ assertArrayEquals(
+ new DeviceState[]{
+ new DeviceState(1, "CLOSED", 0 /* flags */),
+ new DeviceState(2, "HALF_OPENED", 0 /* flags */),
+ new DeviceState(3, "OPENED", 0 /* flags */) },
+ mDeviceStateArrayCaptor.getValue());
+ Mockito.clearInvocations(listener);
+
+ // The THERMAL_TEST state should be re-enabled.
+ provider.onThermalStatusChanged(PowerManager.THERMAL_STATUS_LIGHT);
+ verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture());
+ assertArrayEquals(
+ new DeviceState[]{
+ new DeviceState(1, "CLOSED", 0 /* flags */),
+ new DeviceState(2, "HALF_OPENED", 0 /* flags */),
+ new DeviceState(3, "OPENED", 0 /* flags */),
+ new DeviceState(4, "THERMAL_TEST",
+ DeviceState.FLAG_EMULATED_ONLY
+ | DeviceState.FLAG_DISABLE_WHEN_THERMAL_STATUS_CRITICAL) },
+ mDeviceStateArrayCaptor.getValue());
+ }
+
+ @Test
public void test_invalidSensorValues() throws Exception {
// onStateChanged() should not be triggered by invalid sensor values.
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/CpuWakeupStatsTest.java b/services/tests/servicestests/src/com/android/server/power/stats/CpuWakeupStatsTest.java
index c2556e9267d5..34e45c2096ea 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/CpuWakeupStatsTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/CpuWakeupStatsTest.java
@@ -25,6 +25,7 @@ import static com.android.server.power.stats.CpuWakeupStats.WAKEUP_RETENTION_MS;
import static com.google.common.truth.Truth.assertThat;
import android.content.Context;
+import android.os.Handler;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
@@ -35,6 +36,7 @@ import com.android.frameworks.servicestests.R;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mockito;
import java.util.HashSet;
import java.util.Set;
@@ -55,12 +57,13 @@ public class CpuWakeupStatsTest {
private static final int TEST_UID_5 = 76421423;
private static final Context sContext = InstrumentationRegistry.getTargetContext();
+ private final Handler mHandler = Mockito.mock(Handler.class);
private final ThreadLocalRandom mRandom = ThreadLocalRandom.current();
@Test
public void removesOldWakeups() {
// The xml resource doesn't matter for this test.
- final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_1);
+ final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_1, mHandler);
final Set<Long> timestamps = new HashSet<>();
final long firstWakeup = 453192;
@@ -88,7 +91,7 @@ public class CpuWakeupStatsTest {
@Test
public void alarmIrqAttributionSolo() {
- final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3);
+ final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3, mHandler);
final long wakeupTime = 12423121;
obj.noteWakeupTimeAndReason(wakeupTime, 1, KERNEL_REASON_ALARM_IRQ);
@@ -113,7 +116,7 @@ public class CpuWakeupStatsTest {
@Test
public void alarmIrqAttributionCombined() {
- final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3);
+ final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3, mHandler);
final long wakeupTime = 92123210;
obj.noteWakeupTimeAndReason(wakeupTime, 4,
@@ -143,7 +146,7 @@ public class CpuWakeupStatsTest {
@Test
public void unknownIrqAttribution() {
- final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3);
+ final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3, mHandler);
final long wakeupTime = 92123410;
obj.noteWakeupTimeAndReason(wakeupTime, 24, KERNEL_REASON_UNKNOWN_IRQ);
@@ -163,7 +166,7 @@ public class CpuWakeupStatsTest {
@Test
public void unknownAttribution() {
- final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3);
+ final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3, mHandler);
final long wakeupTime = 72123210;
obj.noteWakeupTimeAndReason(wakeupTime, 34, KERNEL_REASON_UNKNOWN);
@@ -178,7 +181,7 @@ public class CpuWakeupStatsTest {
@Test
public void unsupportedAttribution() {
- final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3);
+ final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3, mHandler);
long wakeupTime = 970934;
obj.noteWakeupTimeAndReason(wakeupTime, 34, KERNEL_REASON_UNSUPPORTED);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index caaf9349c937..1deb58e9cefb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -71,6 +71,7 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -749,16 +750,15 @@ public class ActivityStarterTests extends WindowTestsBase {
}
/**
- * This test ensures that supported usecases aren't aborted when background starts are
- * disallowed. Each scenarios tests one condition that makes them supported in isolation. In
- * this case the real calling process (pending intent) has a visible window.
+ * The sending app has a visible window, but does not (by default) allow the pending intent to
+ * start the background activity.
*/
@Test
- public void
- testBackgroundActivityStartsDisallowed_realCallingUidHasVisibleWindowNotAborted() {
+ public void testBackgroundActivityStartsDisallowed_realCallingUidHasVisibleWindowAborted() {
doReturn(false).when(mAtm).isBackgroundActivityStartsEnabled();
+
runAndVerifyBackgroundActivityStartsSubtest(
- "disallowed_realCallingUidHasVisibleWindow_notAborted", false,
+ "disallowed_realCallingUidHasVisibleWindow_abortedInU", true,
UNIMPORTANT_UID, false, PROCESS_STATE_BOUND_TOP,
UNIMPORTANT_UID2, true, PROCESS_STATE_BOUND_TOP,
false, false, false, false, false, false);
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 0231b4644953..21135e0a80cc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -564,20 +564,23 @@ public class TransitionTests extends WindowTestsBase {
final WindowProcessController delegateProc = mSystemServicesTestRule.addProcess(
"pkg.delegate", "proc.delegate", 6000 /* pid */, 6000 /* uid */);
doReturn(mock(IBinder.class)).when(delegateProc.getThread()).asBinder();
- final ActivityRecord app = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ final ActivityRecord app = new ActivityBuilder(mAtm).setCreateTask(true)
+ .setVisible(false).build();
+ app.setVisibleRequested(true);
final TransitionController controller = app.mTransitionController;
final Transition transition = controller.createTransition(TRANSIT_OPEN);
final RemoteTransition remoteTransition = new RemoteTransition(
mock(IRemoteTransition.class));
remoteTransition.setAppThread(delegateProc.getThread());
- transition.collectExistenceChange(app.getTask());
- controller.requestStartTransition(transition, app.getTask(), remoteTransition,
+ transition.collect(app);
+ controller.requestStartTransition(transition, null /* startTask */, remoteTransition,
null /* displayChange */);
testPlayer.startTransition();
testPlayer.onTransactionReady(app.getSyncTransaction());
assertTrue(playerProc.isRunningRemoteTransition());
assertTrue(delegateProc.isRunningRemoteTransition());
assertTrue(controller.mRemotePlayer.reportRunning(delegateProc.getThread()));
+ assertTrue(app.isVisible());
testPlayer.finish();
assertFalse(playerProc.isRunningRemoteTransition());
@@ -1114,6 +1117,14 @@ public class TransitionTests extends WindowTestsBase {
assertFalse(activity1.isVisible());
assertTrue(activity2.isVisible());
+
+ // The abort should still commit visible-requested to visible.
+ final Transition abortTransition = controller.createTransition(TRANSIT_OPEN);
+ abortTransition.collect(activity1);
+ activity1.setVisibleRequested(true);
+ activity1.setVisible(false);
+ abortTransition.abort();
+ assertTrue(activity1.isVisible());
}
@Test
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index 04c8f8f31bc2..127195c4f9d3 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -21,7 +21,7 @@ import static android.app.ActivityManager.START_ASSISTANT_NOT_ACTIVE_SESSION;
import static android.app.ActivityManager.START_VOICE_HIDDEN_SESSION;
import static android.app.ActivityManager.START_VOICE_NOT_ACTIVE_SESSION;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
-import static android.service.voice.VoiceInteractionService.KEY_SHOW_SESSION_ID;
+import static android.service.voice.VoiceInteractionSession.KEY_SHOW_SESSION_ID;
import static com.android.server.policy.PhoneWindowManager.SYSTEM_DIALOG_REASON_ASSIST;
diff --git a/telecomm/java/android/telecom/CallControl.java b/telecomm/java/android/telecom/CallControl.java
index 770a3741b867..315ac6741c51 100644
--- a/telecomm/java/android/telecom/CallControl.java
+++ b/telecomm/java/android/telecom/CallControl.java
@@ -21,6 +21,7 @@ import static android.telecom.CallException.TRANSACTION_EXCEPTION_KEY;
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.os.Binder;
import android.os.Bundle;
import android.os.OutcomeReceiver;
@@ -45,7 +46,8 @@ import java.util.concurrent.Executor;
* {@link OutcomeReceiver#onError} is called and provides a {@link CallException} that details why
* the operation failed.
*/
-public final class CallControl implements AutoCloseable {
+@SuppressLint("NotCloseable")
+public final class CallControl {
private static final String TAG = CallControl.class.getSimpleName();
private static final String INTERFACE_ERROR_MSG = "Call Control is not available";
private final String mCallId;
@@ -261,17 +263,6 @@ public final class CallControl implements AutoCloseable {
}
/**
- * This method should be called after
- * {@link CallControl#disconnect(DisconnectCause, Executor, OutcomeReceiver)} or
- * {@link CallControl#rejectCall(Executor, OutcomeReceiver)}
- * to destroy all references of this object and avoid memory leaks.
- */
- @Override
- public void close() {
- mRepository.removeCallFromServiceWrapper(mPhoneAccountHandle, mCallId);
- }
-
- /**
* Since {@link OutcomeReceiver}s cannot be passed via AIDL, a ResultReceiver (which can) must
* wrap the Clients {@link OutcomeReceiver} passed in and await for the Telecom Server side
* response in {@link ResultReceiver#onReceiveResult(int, Bundle)}.
diff --git a/telecomm/java/android/telecom/Phone.java b/telecomm/java/android/telecom/Phone.java
index 95a8e16ace3d..fd2907c69d7a 100644
--- a/telecomm/java/android/telecom/Phone.java
+++ b/telecomm/java/android/telecom/Phone.java
@@ -32,8 +32,10 @@ import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
/**
* A unified virtual device providing a means of voice (and other) communication on a device.
@@ -148,6 +150,14 @@ public final class Phone {
private final Object mLock = new Object();
+ // Future used to delay terminating the InCallService before the call disconnect tone
+ // finishes playing.
+ private static Map<String, CompletableFuture<Void>> sDisconnectedToneFutures = new ArrayMap<>();
+
+ // Timeout value to be used to ensure future completion for sDisconnectedToneFutures. This is
+ // set to 4 seconds to account for the exceptional case (TONE_CONGESTION).
+ private static final int DISCONNECTED_TONE_TIMEOUT = 4000;
+
Phone(InCallAdapter adapter, String callingPackage, int targetSdkVersion) {
mInCallAdapter = adapter;
mCallingPackage = callingPackage;
@@ -456,9 +466,45 @@ public final class Phone {
}
private void fireCallRemoved(Call call) {
- for (Listener listener : mListeners) {
- listener.onCallRemoved(this, call);
+ String callId = call.internalGetCallId();
+ CompletableFuture<Void> disconnectedToneFuture = initializeDisconnectedToneFuture(callId);
+ // delay the InCallService termination until after the disconnect tone finishes playing
+ disconnectedToneFuture.thenRunAsync(() -> {
+ for (Listener listener : mListeners) {
+ listener.onCallRemoved(this, call);
+ }
+ // clean up the future after
+ sDisconnectedToneFutures.remove(callId);
+ });
+ }
+
+ /**
+ * Initialize disconnect tone future to be used in delaying ICS termination.
+ *
+ * @return CompletableFuture to delay InCallService termination until after the disconnect tone
+ * finishes playing. A timeout of 4s is used to handle the use case when we play
+ * TONE_CONGESTION and to ensure completion so that we don't block the removal of the service.
+ */
+ private CompletableFuture<Void> initializeDisconnectedToneFuture(String callId) {
+ // create the future and map (sDisconnectedToneFutures) it to the corresponding call id
+ CompletableFuture<Void> disconnectedToneFuture = new CompletableFuture<Void>()
+ .completeOnTimeout(null, DISCONNECTED_TONE_TIMEOUT, TimeUnit.MILLISECONDS);
+ // we should not encounter duplicate insertions since call ids are unique
+ sDisconnectedToneFutures.put(callId, disconnectedToneFuture);
+ return disconnectedToneFuture;
+ }
+
+ /**
+ * Completes disconnected tone future with passed in result.
+ * @hide
+ * @return true if future was completed, false otherwise
+ */
+ public static boolean completeDisconnectedToneFuture(String callId) {
+ if (sDisconnectedToneFutures.containsKey(callId)) {
+ sDisconnectedToneFutures.get(callId).complete(null);
+ return true;
}
+ return false;
}
private void fireCallAudioStateChanged(CallAudioState audioState) {
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index b55a29cc1f77..79503514c530 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -7510,6 +7510,48 @@ public class CarrierConfigManager {
public static final String KEY_PREFER_IMS_EMERGENCY_WHEN_VOICE_CALLS_ON_CS_BOOL =
KEY_PREFIX + "prefer_ims_emergency_when_voice_calls_on_cs_bool";
+ /** @hide */
+ @IntDef({
+ VOWIFI_REQUIRES_NONE,
+ VOWIFI_REQUIRES_SETTING_ENABLED,
+ VOWIFI_REQUIRES_VALID_EID,
+ })
+ public @interface VoWiFiRequires {}
+
+ /**
+ * Default value.
+ * If {@link ImsWfc#KEY_EMERGENCY_CALL_OVER_EMERGENCY_PDN_BOOL} is {@code true},
+ * VoWi-Fi emergency call shall be attempted if Wi-Fi network is connected.
+ * Otherwise, it shall be attempted if IMS is registered over Wi-Fi.
+ * @hide
+ */
+ public static final int VOWIFI_REQUIRES_NONE = 0;
+
+ /**
+ * VoWi-Fi emergency call shall be attempted on IMS over Wi-Fi if Wi-Fi network is connected
+ * and Wi-Fi calling setting is enabled. This value is applicable if the value of
+ * {@link ImsWfc#KEY_EMERGENCY_CALL_OVER_EMERGENCY_PDN_BOOL} is {@code true}.
+ * @hide
+ */
+ public static final int VOWIFI_REQUIRES_SETTING_ENABLED = 1;
+
+ /**
+ * VoWi-Fi emergency call shall be attempted on IMS over Wi-Fi if Wi-Fi network is connected
+ * and Wi-Fi calling is activated successfully. This value is applicable if the value of
+ * {@link ImsWfc#KEY_EMERGENCY_CALL_OVER_EMERGENCY_PDN_BOOL} is {@code true}.
+ * @hide
+ */
+ public static final int VOWIFI_REQUIRES_VALID_EID = 2;
+
+ /**
+ * Specifies the condition when emergency call shall be attempted on IMS over Wi-Fi.
+ *
+ * The default value for this key is {@code #VOWIFI_REQUIRES_NONE}.
+ * @hide
+ */
+ public static final String KEY_EMERGENCY_VOWIFI_REQUIRES_CONDITION_INT =
+ KEY_PREFIX + "emergency_vowifi_requires_condition_int";
+
/**
* Specifies maximum number of emergency call retries over Wi-Fi.
* This is valid only when {@link #DOMAIN_PS_NON_3GPP} is included in
@@ -7621,7 +7663,8 @@ public class CarrierConfigManager {
KEY_PREFIX + "emergency_cdma_preferred_numbers_string_array";
/**
- * Specifies if emergency call shall be attempted on IMS only when VoLTE is enabled.
+ * Specifies if emergency call shall be attempted on IMS over cellular network
+ * only when VoLTE is enabled.
*
* The default value for this key is {@code false}.
* @hide
@@ -7685,6 +7728,7 @@ public class CarrierConfigManager {
});
defaults.putBoolean(KEY_PREFER_IMS_EMERGENCY_WHEN_VOICE_CALLS_ON_CS_BOOL, false);
+ defaults.putInt(KEY_EMERGENCY_VOWIFI_REQUIRES_CONDITION_INT, VOWIFI_REQUIRES_NONE);
defaults.putInt(KEY_MAXIMUM_NUMBER_OF_EMERGENCY_TRIES_OVER_VOWIFI_INT, 1);
defaults.putInt(KEY_EMERGENCY_SCAN_TIMER_SEC_INT, 10);
defaults.putInt(KEY_EMERGENCY_NETWORK_SCAN_TYPE_INT, SCAN_TYPE_NO_PREFERENCE);
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 2a965ba83577..c47c67e6e649 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -3242,17 +3242,17 @@ public class TelephonyManager {
case NETWORK_TYPE_CDMA:
return "CDMA";
case NETWORK_TYPE_EVDO_0:
- return "EVDO-0";
+ return "CDMA - EvDo rev. 0";
case NETWORK_TYPE_EVDO_A:
- return "EVDO-A";
+ return "CDMA - EvDo rev. A";
case NETWORK_TYPE_EVDO_B:
- return "EVDO-B";
+ return "CDMA - EvDo rev. B";
case NETWORK_TYPE_1xRTT:
- return "1xRTT";
+ return "CDMA - 1xRTT";
case NETWORK_TYPE_LTE:
return "LTE";
case NETWORK_TYPE_EHRPD:
- return "eHRPD";
+ return "CDMA - eHRPD";
case NETWORK_TYPE_IDEN:
return "iDEN";
case NETWORK_TYPE_HSPAP:
@@ -3260,7 +3260,7 @@ public class TelephonyManager {
case NETWORK_TYPE_GSM:
return "GSM";
case NETWORK_TYPE_TD_SCDMA:
- return "TD-SCDMA";
+ return "TD_SCDMA";
case NETWORK_TYPE_IWLAN:
return "IWLAN";
case NETWORK_TYPE_LTE_CA:
@@ -9574,10 +9574,10 @@ public class TelephonyManager {
*/
public static boolean isValidAllowedNetworkTypesReason(@AllowedNetworkTypesReason int reason) {
switch (reason) {
- case ALLOWED_NETWORK_TYPES_REASON_USER:
- case ALLOWED_NETWORK_TYPES_REASON_POWER:
- case ALLOWED_NETWORK_TYPES_REASON_CARRIER:
- case ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G:
+ case TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER:
+ case TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_POWER:
+ case TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_CARRIER:
+ case TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G:
case ALLOWED_NETWORK_TYPES_REASON_USER_RESTRICTIONS:
return true;
}
@@ -15002,6 +15002,14 @@ public class TelephonyManager {
@TestApi
public static final int HAL_SERVICE_IMS = 7;
+ /**
+ * HAL service type that supports the HAL APIs implementation of IRadioSatellite
+ * {@link RadioSatelliteProxy}
+ * @hide
+ */
+ @TestApi
+ public static final int HAL_SERVICE_SATELLITE = 8;
+
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = {"HAL_SERVICE_"},
@@ -15014,6 +15022,7 @@ public class TelephonyManager {
HAL_SERVICE_SIM,
HAL_SERVICE_VOICE,
HAL_SERVICE_IMS,
+ HAL_SERVICE_SATELLITE
})
public @interface HalService {}
@@ -18034,45 +18043,4 @@ public class TelephonyManager {
return "UNKNOWN(" + state + ")";
}
}
-
- /**
- * Convert the allowed network types reason to string.
- *
- * @param reason The allowed network types reason.
- * @return The converted string.
- *
- * @hide
- */
- @NonNull
- public static String allowedNetworkTypesReasonToString(@AllowedNetworkTypesReason int reason) {
- switch (reason) {
- case ALLOWED_NETWORK_TYPES_REASON_USER: return "user";
- case ALLOWED_NETWORK_TYPES_REASON_POWER: return "power";
- case ALLOWED_NETWORK_TYPES_REASON_CARRIER: return "carrier";
- case ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G: return "enable_2g";
- case ALLOWED_NETWORK_TYPES_REASON_USER_RESTRICTIONS: return "user_restrictions";
- default: return "unknown(" + reason + ")";
- }
- }
-
- /**
- * Convert the allowed network types reason from string.
- *
- * @param reason The reason in string format.
- * @return The allowed network types reason.
- *
- * @hide
- */
- @AllowedNetworkTypesReason
- public static int allowedNetworkTypesReasonFromString(@NonNull String reason) {
- switch (reason) {
- case "user": return ALLOWED_NETWORK_TYPES_REASON_USER;
- case "power": return ALLOWED_NETWORK_TYPES_REASON_POWER;
- case "carrier": return ALLOWED_NETWORK_TYPES_REASON_CARRIER;
- case "enable_2g": return ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G;
- case "user_restrictions": return ALLOWED_NETWORK_TYPES_REASON_USER_RESTRICTIONS;
- default: throw new IllegalArgumentException("allowedNetworkTypesReasonFromString: "
- + "invalid reason " + reason);
- }
- }
}
diff --git a/telephony/java/android/telephony/satellite/PointingInfo.java b/telephony/java/android/telephony/satellite/PointingInfo.java
new file mode 100644
index 000000000000..d6dd57aac63b
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/PointingInfo.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.satellite;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * @hide
+ */
+public final class PointingInfo implements Parcelable {
+ /** Satellite azimuth in degrees */
+ private float mSatelliteAzimuthDegrees;
+
+ /** Satellite elevation in degrees */
+ private float mSatelliteElevationDegrees;
+
+ /** Antenna azimuth in degrees */
+ private float mAntennaAzimuthDegrees;
+
+ /**
+ * Angle of rotation about the x axis. This value represents the angle between a plane
+ * parallel to the device's screen and a plane parallel to the ground.
+ */
+ private float mAntennaPitchDegrees;
+
+ /**
+ * Angle of rotation about the y axis. This value represents the angle between a plane
+ * perpendicular to the device's screen and a plane parallel to the ground.
+ */
+ private float mAntennaRollDegrees;
+
+ /**
+ * @hide
+ */
+ public PointingInfo(float satelliteAzimuthDegress, float satelliteElevationDegress,
+ float antennaAzimuthDegrees, float antennaPitchDegrees, float antennaRollDegrees) {
+ mSatelliteAzimuthDegrees = satelliteAzimuthDegress;
+ mSatelliteElevationDegrees = satelliteElevationDegress;
+ mAntennaAzimuthDegrees = antennaAzimuthDegrees;
+ mAntennaPitchDegrees = antennaPitchDegrees;
+ mAntennaRollDegrees = antennaRollDegrees;
+ }
+
+ private PointingInfo(Parcel in) {
+ readFromParcel(in);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeFloat(mSatelliteAzimuthDegrees);
+ out.writeFloat(mSatelliteElevationDegrees);
+ out.writeFloat(mAntennaAzimuthDegrees);
+ out.writeFloat(mAntennaPitchDegrees);
+ out.writeFloat(mAntennaRollDegrees);
+ }
+
+ public static final @android.annotation.NonNull Creator<PointingInfo> CREATOR =
+ new Creator<PointingInfo>() {
+ @Override
+ public PointingInfo createFromParcel(Parcel in) {
+ return new PointingInfo(in);
+ }
+
+ @Override
+ public PointingInfo[] newArray(int size) {
+ return new PointingInfo[size];
+ }
+ };
+
+ @NonNull
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+
+ sb.append("SatelliteAzimuthDegrees:");
+ sb.append(mSatelliteAzimuthDegrees);
+ sb.append(",");
+
+ sb.append("SatelliteElevationDegrees:");
+ sb.append(mSatelliteElevationDegrees);
+ sb.append(",");
+
+ sb.append("AntennaAzimuthDegrees:");
+ sb.append(mAntennaAzimuthDegrees);
+ sb.append(",");
+
+ sb.append("AntennaPitchDegrees:");
+ sb.append(mAntennaPitchDegrees);
+ sb.append(",");
+
+ sb.append("AntennaRollDegrees:");
+ sb.append(mAntennaRollDegrees);
+ return sb.toString();
+ }
+
+ public float getSatelliteAzimuthDegrees() {
+ return mSatelliteAzimuthDegrees;
+ }
+
+ public float getSatelliteElevationDegrees() {
+ return mSatelliteElevationDegrees;
+ }
+
+ public float getAntennaAzimuthDegrees() {
+ return mAntennaAzimuthDegrees;
+ }
+
+ public float getAntennaPitchDegrees() {
+ return mAntennaPitchDegrees;
+ }
+
+ public float getAntennaRollDegrees() {
+ return mAntennaRollDegrees;
+ }
+
+ private void readFromParcel(Parcel in) {
+ mSatelliteAzimuthDegrees = in.readFloat();
+ mSatelliteElevationDegrees = in.readFloat();
+ mAntennaAzimuthDegrees = in.readFloat();
+ mAntennaPitchDegrees = in.readFloat();
+ mAntennaRollDegrees = in.readFloat();
+ }
+}
diff --git a/telephony/java/android/telephony/satellite/SatelliteCapabilities.java b/telephony/java/android/telephony/satellite/SatelliteCapabilities.java
new file mode 100644
index 000000000000..c5ae4dbcb8b0
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/SatelliteCapabilities.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.satellite;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @hide
+ */
+public final class SatelliteCapabilities implements Parcelable {
+ /**
+ * List of technologies supported by the satellite modem.
+ */
+ private Set<Integer> mSupportedRadioTechnologies;
+
+ /**
+ * Whether satellite mode is always on (this to indicate power impact of keeping it on is
+ * very minimal).
+ */
+ private boolean mIsAlwaysOn;
+
+ /**
+ * Whether UE needs to point to a satellite to send and receive data.
+ */
+ private boolean mNeedsPointingToSatellite;
+
+ /**
+ * List of features supported by the Satellite modem.
+ */
+ private Set<Integer> mSupportedFeatures;
+
+ /**
+ * Whether UE needs a separate SIM profile to communicate with the Satellite network.
+ */
+ private boolean mNeedsSeparateSimProfile;
+
+ /**
+ * @hide
+ */
+ public SatelliteCapabilities(Set<Integer> supportedRadioTechnologies, boolean isAlwaysOn,
+ boolean needsPointingToSatellite, Set<Integer> supportedFeatures,
+ boolean needsSeparateSimProfile) {
+ mSupportedRadioTechnologies = supportedRadioTechnologies;
+ mIsAlwaysOn = isAlwaysOn;
+ mNeedsPointingToSatellite = needsPointingToSatellite;
+ mSupportedFeatures = supportedFeatures;
+ mNeedsSeparateSimProfile = needsSeparateSimProfile;
+ }
+
+ private SatelliteCapabilities(Parcel in) {
+ readFromParcel(in);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ if (mSupportedRadioTechnologies != null && !mSupportedRadioTechnologies.isEmpty()) {
+ out.writeInt(mSupportedRadioTechnologies.size());
+ for (int technology : mSupportedRadioTechnologies) {
+ out.writeInt(technology);
+ }
+ } else {
+ out.writeInt(0);
+ }
+
+ out.writeBoolean(mIsAlwaysOn);
+ out.writeBoolean(mNeedsPointingToSatellite);
+
+ if (mSupportedFeatures != null && !mSupportedFeatures.isEmpty()) {
+ out.writeInt(mSupportedFeatures.size());
+ for (int feature : mSupportedFeatures) {
+ out.writeInt(feature);
+ }
+ } else {
+ out.writeInt(0);
+ }
+
+ out.writeBoolean(mNeedsSeparateSimProfile);
+ }
+
+ public static final @android.annotation.NonNull Creator<SatelliteCapabilities> CREATOR =
+ new Creator<SatelliteCapabilities>() {
+ @Override
+ public SatelliteCapabilities createFromParcel(Parcel in) {
+ return new SatelliteCapabilities(in);
+ }
+
+ @Override
+ public SatelliteCapabilities[] newArray(int size) {
+ return new SatelliteCapabilities[size];
+ }
+ };
+
+ @NonNull
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+
+ sb.append("SupportedRadioTechnology:");
+ if (mSupportedRadioTechnologies != null && !mSupportedRadioTechnologies.isEmpty()) {
+ for (int technology : mSupportedRadioTechnologies) {
+ sb.append(technology);
+ sb.append(",");
+ }
+ } else {
+ sb.append("none,");
+ }
+
+ sb.append("SupportedFeatures:");
+ if (mSupportedFeatures != null && !mSupportedFeatures.isEmpty()) {
+ for (int feature : mSupportedFeatures) {
+ sb.append(feature);
+ sb.append(",");
+ }
+ } else {
+ sb.append("none,");
+ }
+
+ sb.append("isAlwaysOn:");
+ sb.append(mIsAlwaysOn);
+ sb.append(",");
+
+ sb.append("needsPointingToSatellite:");
+ sb.append(mNeedsPointingToSatellite);
+ sb.append(",");
+
+ sb.append("needsSeparateSimProfile:");
+ sb.append(mNeedsSeparateSimProfile);
+ return sb.toString();
+ }
+
+ @NonNull
+ public Set<Integer> getSupportedRadioTechnologies() {
+ return mSupportedRadioTechnologies;
+ }
+
+ public boolean isAlwaysOn() {
+ return mIsAlwaysOn;
+ }
+
+ /** Get function for mNeedsPointingToSatellite */
+ public boolean needsPointingToSatellite() {
+ return mNeedsPointingToSatellite;
+ }
+
+ @NonNull
+ public Set<Integer> getSupportedFeatures() {
+ return mSupportedFeatures;
+ }
+
+ /** Get function for mNeedsSeparateSimProfile */
+ public boolean needsSeparateSimProfile() {
+ return mNeedsSeparateSimProfile;
+ }
+
+ private void readFromParcel(Parcel in) {
+ mSupportedRadioTechnologies = new HashSet<>();
+ int numSupportedRadioTechnologies = in.readInt();
+ if (numSupportedRadioTechnologies > 0) {
+ for (int i = 0; i < numSupportedRadioTechnologies; i++) {
+ mSupportedRadioTechnologies.add(in.readInt());
+ }
+ }
+
+ mIsAlwaysOn = in.readBoolean();
+ mNeedsPointingToSatellite = in.readBoolean();
+
+ mSupportedFeatures = new HashSet<>();
+ int numSupportedFeatures = in.readInt();
+ if (numSupportedFeatures > 0) {
+ for (int i = 0; i < numSupportedFeatures; i++) {
+ mSupportedFeatures.add(in.readInt());
+ }
+ }
+
+ mNeedsSeparateSimProfile = in.readBoolean();
+ }
+}
diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
new file mode 100644
index 000000000000..d3964a888d9e
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.satellite.stub;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+
+/**
+ * @hide
+ */
+public class SatelliteImplBase {
+ private static final String TAG = "SatelliteImplBase";
+
+ /**@hide*/
+ @IntDef(
+ prefix = "TECHNOLOGY_",
+ value = {
+ TECHNOLOGY_NB_IOT_NTN,
+ TECHNOLOGY_NR_NTN,
+ TECHNOLOGY_EMTC_NTN,
+ TECHNOLOGY_PROPRIETARY
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface NTRadioTechnology {}
+
+ /** 3GPP NB-IoT (Narrowband Internet of Things) over Non-Terrestrial-Networks technology */
+ public static final int TECHNOLOGY_NB_IOT_NTN =
+ android.hardware.radio.satellite.NTRadioTechnology.NB_IOT_NTN;
+ /** 3GPP 5G NR over Non-Terrestrial-Networks technology */
+ public static final int TECHNOLOGY_NR_NTN =
+ android.hardware.radio.satellite.NTRadioTechnology.NR_NTN;
+ /** 3GPP eMTC (enhanced Machine-Type Communication) over Non-Terrestrial-Networks technology */
+ public static final int TECHNOLOGY_EMTC_NTN =
+ android.hardware.radio.satellite.NTRadioTechnology.EMTC_NTN;
+ /** Proprietary technology like Iridium or Bullitt */
+ public static final int TECHNOLOGY_PROPRIETARY =
+ android.hardware.radio.satellite.NTRadioTechnology.PROPRIETARY;
+
+ /**@hide*/
+ @IntDef(
+ prefix = "FEATURE_",
+ value = {
+ FEATURE_SOS_SMS,
+ FEATURE_EMERGENCY_SMS,
+ FEATURE_SMS,
+ FEATURE_LOCATION_SHARING,
+ FEATURE_UNKNOWN
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Feature {}
+
+ /** Able to send and receive SMS messages to/from SOS numbers like call/service centers */
+ public static final int FEATURE_SOS_SMS =
+ android.hardware.radio.satellite.SatelliteFeature.SOS_SMS;
+ /** Able to send and receive SMS messages to/from emergency numbers like 911 */
+ public static final int FEATURE_EMERGENCY_SMS =
+ android.hardware.radio.satellite.SatelliteFeature.EMERGENCY_SMS;
+ /** Able to send and receive SMS messages to/from any allowed contacts */
+ public static final int FEATURE_SMS = android.hardware.radio.satellite.SatelliteFeature.SMS;
+ /** Able to send device location to allowed contacts */
+ public static final int FEATURE_LOCATION_SHARING =
+ android.hardware.radio.satellite.SatelliteFeature.LOCATION_SHARING;
+ /** This feature is not defined in satellite HAL APIs */
+ public static final int FEATURE_UNKNOWN = 0xFFFF;
+
+ /**@hide*/
+ @IntDef(
+ prefix = "MODE_",
+ value = {
+ MODE_POWERED_OFF,
+ MODE_OUT_OF_SERVICE_NOT_SEARCHING,
+ MODE_OUT_OF_SERVICE_SEARCHING,
+ MODE_ACQUIRED,
+ MODE_MESSAGE_TRANSFERRING
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Mode {}
+
+ /** Satellite modem is powered off */
+ public static final int MODE_POWERED_OFF =
+ android.hardware.radio.satellite.SatelliteMode.POWERED_OFF;
+ /** Satellite modem is in out of service state and not searching for satellite signal */
+ public static final int MODE_OUT_OF_SERVICE_NOT_SEARCHING =
+ android.hardware.radio.satellite.SatelliteMode.OUT_OF_SERVICE_NOT_SEARCHING;
+ /** Satellite modem is in out of service state and searching for satellite signal */
+ public static final int MODE_OUT_OF_SERVICE_SEARCHING =
+ android.hardware.radio.satellite.SatelliteMode.OUT_OF_SERVICE_SEARCHING;
+ /** Satellite modem has found satellite signal and gets connected to the satellite network */
+ public static final int MODE_ACQUIRED = android.hardware.radio.satellite.SatelliteMode.ACQUIRED;
+ /** Satellite modem is sending and/or receiving messages */
+ public static final int MODE_MESSAGE_TRANSFERRING =
+ android.hardware.radio.satellite.SatelliteMode.MESSAGE_TRANSFERRING;
+}
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index a9af199ddf83..6e5696339c42 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -546,6 +546,22 @@ public interface RILConstants {
int RIL_REQUEST_UPDATE_IMS_CALL_STATUS = 240;
int RIL_REQUEST_SET_N1_MODE_ENABLED = 241;
int RIL_REQUEST_IS_N1_MODE_ENABLED = 242;
+ int RIL_REQUEST_SET_LOCATION_PRIVACY_SETTING = 243;
+ int RIL_REQUEST_GET_LOCATION_PRIVACY_SETTING = 244;
+ int RIL_REQUEST_GET_SATELLITE_CAPABILITIES = 245;
+ int RIL_REQUEST_SET_SATELLITE_POWER = 246;
+ int RIL_REQUEST_GET_SATELLITE_POWER = 247;
+ int RIL_REQUEST_PROVISION_SATELLITE_SERVICE = 248;
+ int RIL_REQUEST_ADD_ALLOWED_SATELLITE_CONTACTS = 249;
+ int RIL_REQUEST_REMOVE_ALLOWED_SATELLITE_CONTACTS = 250;
+ int RIL_REQUEST_SEND_SATELLITE_MESSAGES = 251;
+ int RIL_REQUEST_GET_PENDING_SATELLITE_MESSAGES = 252;
+ int RIL_REQUEST_GET_SATELLITE_MODE = 253;
+ int RIL_REQUEST_SET_SATELLITE_INDICATION_FILTER = 254;
+ int RIL_REQUEST_START_SENDING_SATELLITE_POINTING_INFO = 255;
+ int RIL_REQUEST_STOP_SENDING_SATELLITE_POINTING_INFO = 256;
+ int RIL_REQUEST_GET_MAX_CHARACTERS_PER_SATELLITE_TEXT_MESSAGE = 257;
+ int RIL_REQUEST_GET_TIME_FOR_NEXT_SATELLITE_VISIBILITY = 258;
/* Responses begin */
int RIL_RESPONSE_ACKNOWLEDGEMENT = 800;
@@ -607,6 +623,13 @@ public interface RILConstants {
int RIL_UNSOL_RESPONSE_SIM_PHONEBOOK_CHANGED = 1053;
int RIL_UNSOL_RESPONSE_SIM_PHONEBOOK_RECORDS_RECEIVED = 1054;
int RIL_UNSOL_SLICING_CONFIG_CHANGED = 1055;
+ int RIL_UNSOL_PENDING_SATELLITE_MESSAGE_COUNT = 1056;
+ int RIL_UNSOL_NEW_SATELLITE_MESSAGES = 1057;
+ int RIL_UNSOL_SATELLITE_MESSAGES_TRANSFER_COMPLETE = 1058;
+ int RIL_UNSOL_SATELLITE_POINTING_INFO_CHANGED = 1059;
+ int RIL_UNSOL_SATELLITE_MODE_CHANGED = 1060;
+ int RIL_UNSOL_SATELLITE_RADIO_TECHNOLOGY_CHANGED = 1061;
+ int RIL_UNSOL_SATELLITE_PROVISION_STATE_CHANGED = 1062;
/* The following unsols are not defined in RIL.h */
int RIL_UNSOL_HAL_NON_RIL_BASE = 1100;
diff --git a/tests/FlickerTests/AndroidManifest.xml b/tests/FlickerTests/AndroidManifest.xml
index 487a0c3bd465..462f91bc081c 100644
--- a/tests/FlickerTests/AndroidManifest.xml
+++ b/tests/FlickerTests/AndroidManifest.xml
@@ -43,7 +43,7 @@
<!-- ActivityOptions.makeCustomTaskAnimation() -->
<uses-permission android:name="android.permission.START_TASKS_FROM_RECENTS" />
<!-- Allow the test to write directly to /sdcard/ -->
- <application android:requestLegacyExternalStorage="true">
+ <application android:requestLegacyExternalStorage="true" android:largeHeap="true">
<uses-library android:name="android.test.runner"/>
<uses-library android:name="androidx.window.extensions" android:required="false"/>
</application>
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
index 8fe6aac4f727..18792a8dcd99 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
@@ -103,6 +103,54 @@ open class PipAppHelper(instrumentation: Instrumentation) :
}
/**
+ * Minimizes the PIP window my using the pinch in gesture.
+ *
+ * @param percent The percentage by which to decrease the pip window size.
+ * @throws IllegalArgumentException if percentage isn't between 0.0f and 1.0f
+ */
+ fun pinchInPipWindow(wmHelper: WindowManagerStateHelper, percent: Float, steps: Int) {
+ // the percentage must be between 0.0f and 1.0f
+ if (percent <= 0.0f || percent > 1.0f) {
+ throw IllegalArgumentException("Percent must be between 0.0f and 1.0f")
+ }
+
+ val windowRect = getWindowRect(wmHelper)
+
+ // first pointer's initial x coordinate is halfway between the left edge and the center
+ val initLeftX = (windowRect.centerX() - windowRect.width / 4).toFloat()
+ // second pointer's initial x coordinate is halfway between the right edge and the center
+ val initRightX = (windowRect.centerX() + windowRect.width / 4).toFloat()
+
+ // decrease by the distance specified through the percentage
+ val distDecrease = windowRect.width * percent
+
+ // get the final x-coordinates and make sure they are not passing the center of the window
+ val finalLeftX = Math.min(initLeftX + (distDecrease / 2), windowRect.centerX().toFloat())
+ val finalRightX = Math.max(initRightX - (distDecrease / 2), windowRect.centerX().toFloat())
+
+ // y-coordinate is the same throughout this animation
+ val yCoord = windowRect.centerY().toFloat()
+
+ var adjustedSteps = MIN_STEPS_TO_ANIMATE
+
+ // if distance per step is at least 1, then we can use the number of steps requested
+ if (distDecrease.toInt() / (steps * 2) >= 1) {
+ adjustedSteps = steps
+ }
+
+ // if the distance per step is less than 1, carry out the animation in two steps
+ gestureHelper.pinch(
+ Tuple(initLeftX, yCoord),
+ Tuple(initRightX, yCoord),
+ Tuple(finalLeftX, yCoord),
+ Tuple(finalRightX, yCoord),
+ adjustedSteps
+ )
+
+ waitForPipWindowToMinimizeFrom(wmHelper, Region.from(windowRect))
+ }
+
+ /**
* Launches the app through an intent instead of interacting with the launcher and waits until
* the app window is in PIP mode
*/
@@ -238,6 +286,24 @@ open class PipAppHelper(instrumentation: Instrumentation) :
.waitForAndVerify()
}
+ private fun waitForPipWindowToMinimizeFrom(
+ wmHelper: WindowManagerStateHelper,
+ windowRect: Region
+ ) {
+ wmHelper
+ .StateSyncBuilder()
+ .add("pipWindowMinimized") {
+ val pipAppWindow =
+ it.wmState.visibleWindows.firstOrNull { window ->
+ this.windowMatchesAnyOf(window)
+ }
+ ?: return@add false
+ val pipRegion = pipAppWindow.frameRegion
+ return@add windowRect.coversMoreThan(pipRegion)
+ }
+ .waitForAndVerify()
+ }
+
companion object {
private const val TAG = "PipAppHelper"
private const val ENTER_PIP_BUTTON_ID = "enter_pip"
diff --git a/tests/Input/src/com/android/test/input/InputDeviceTest.java b/tests/Input/src/com/android/test/input/InputDeviceTest.java
index ddcc8112d6ed..d075b5f01ef9 100644
--- a/tests/Input/src/com/android/test/input/InputDeviceTest.java
+++ b/tests/Input/src/com/android/test/input/InputDeviceTest.java
@@ -18,6 +18,7 @@ package android.view;
import static org.junit.Assert.assertEquals;
+import android.hardware.input.HostUsiVersion;
import android.os.Parcel;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -57,6 +58,8 @@ public class InputDeviceTest {
assertEquals(device.getKeyboardLanguageTag(), outDevice.getKeyboardLanguageTag());
assertEquals(device.getKeyboardLayoutType(), outDevice.getKeyboardLayoutType());
assertEquals(device.getMotionRanges().size(), outDevice.getMotionRanges().size());
+ assertEquals(device.getHostUsiVersion(), outDevice.getHostUsiVersion());
+ assertEquals(device.getAssociatedDisplayId(), outDevice.getAssociatedDisplayId());
KeyCharacterMap keyCharacterMap = device.getKeyCharacterMap();
KeyCharacterMap outKeyCharacterMap = outDevice.getKeyCharacterMap();
@@ -88,7 +91,7 @@ public class InputDeviceTest {
.setHasBattery(true)
.setKeyboardLanguageTag("en-US")
.setKeyboardLayoutType("qwerty")
- .setSupportsUsi(true);
+ .setUsiVersion(new HostUsiVersion(2, 0));
for (int i = 0; i < 30; i++) {
deviceBuilder.addMotionRange(
diff --git a/tests/Internal/src/com/android/internal/app/LocaleStoreTest.java b/tests/Internal/src/com/android/internal/app/LocaleStoreTest.java
index bf6ece1ac19a..e704cc29976e 100644
--- a/tests/Internal/src/com/android/internal/app/LocaleStoreTest.java
+++ b/tests/Internal/src/com/android/internal/app/LocaleStoreTest.java
@@ -17,8 +17,10 @@
package com.android.internal.app;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import android.os.LocaleList;
import android.view.inputmethod.InputMethodSubtype;
import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder;
@@ -27,23 +29,21 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.internal.app.LocaleStore.LocaleInfo;
-import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.IllformedLocaleException;
import java.util.List;
+import java.util.Locale;
import java.util.Set;
-/**
- * Unit tests for the {@link LocaleStore}.
- */
+/** Unit tests for the {@link LocaleStore}. */
@SmallTest
@RunWith(AndroidJUnit4.class)
public class LocaleStoreTest {
- @Before
- public void setUp() {
- }
-
@Test
public void testTransformImeLanguageTagToLocaleInfo() {
List<InputMethodSubtype> list = List.of(
@@ -60,4 +60,99 @@ public class LocaleStoreTest {
assertTrue(expectedLanguageTag.contains(info.getId()));
}
}
+
+ @Test
+ public void convertExplicitLocales_noExplicitLcoales_returnEmptyHashMap() {
+ Collection<LocaleInfo> supportedLocale = getFakeSupportedLocales();
+
+ HashMap<String, LocaleInfo> result =
+ LocaleStore.convertExplicitLocales(
+ LocaleList.getEmptyLocaleList(), supportedLocale);
+
+ assertTrue(result.isEmpty());
+ }
+
+ @Test
+ public void convertExplicitLocales_hasEmptyLocale_receiveException() {
+ Locale[] locales = {Locale.forLanguageTag(""), Locale.forLanguageTag("en-US")};
+ LocaleList localelist = new LocaleList(locales);
+ Collection<LocaleInfo> supportedLocale = getFakeSupportedLocales();
+
+ boolean isReceiveException = false;
+ try {
+ LocaleStore.convertExplicitLocales(localelist, supportedLocale);
+ } catch (IllformedLocaleException e) {
+ isReceiveException = true;
+ }
+
+ assertTrue(isReceiveException);
+ }
+
+ @Test
+ public void convertExplicitLocales_hasSameLocale_returnNonSameLocales() {
+ LocaleList locales = LocaleList.forLanguageTags("en-US,en-US");
+ Collection<LocaleInfo> supportedLocale = getFakeSupportedLocales();
+
+ HashMap<String, LocaleInfo> result =
+ LocaleStore.convertExplicitLocales(locales, supportedLocale);
+
+ // Only has "en" and "en-US".
+ assertTrue(result.size() == 2);
+ }
+
+ @Test
+ public void convertExplicitLocales_hasEnUs_resultHasParentEn() {
+ LocaleList locales = LocaleList.forLanguageTags("en-US,ja-JP");
+ Collection<LocaleInfo> supportedLocale = getFakeSupportedLocales();
+
+ HashMap<String, LocaleInfo> result =
+ LocaleStore.convertExplicitLocales(locales, supportedLocale);
+
+ assertEquals(result.get("en").getId(), "en");
+ }
+
+ @Test
+ public void convertExplicitLocales_hasZhTw_resultZhHantTw() {
+ LocaleList locales = LocaleList.forLanguageTags("zh-TW,en-US,en");
+ Collection<LocaleInfo> supportedLocale = getFakeSupportedLocales();
+
+ HashMap<String, LocaleInfo> result =
+ LocaleStore.convertExplicitLocales(locales, supportedLocale);
+
+ assertEquals("zh-Hant-TW", result.get("zh-Hant-TW").getId());
+ }
+
+ @Test
+ public void convertExplicitLocales_nonRegularFormat_resultEmptyContry() {
+ LocaleList locales = LocaleList.forLanguageTags("de-1996,de-1901");
+ Collection<LocaleInfo> supportedLocale = getFakeSupportedLocales();
+
+ HashMap<String, LocaleInfo> result =
+ LocaleStore.convertExplicitLocales(locales, supportedLocale);
+
+ assertEquals("de-1996", result.get("de-1996").getId());
+ assertTrue(result.get("de-1996").getLocale().getCountry().isEmpty());
+ }
+
+ @Test
+ public void convertExplicitLocales_differentEnFormat() {
+ LocaleList locales = LocaleList.forLanguageTags("en-Latn-US,en-US,en");
+ Collection<LocaleInfo> supportedLocale = getFakeSupportedLocales();
+
+ HashMap<String, LocaleInfo> result =
+ LocaleStore.convertExplicitLocales(locales, supportedLocale);
+
+ assertEquals("en", result.get("en").getId());
+ assertEquals("en-US", result.get("en-US").getId());
+ assertNull(result.get("en-Latn-US"));
+ }
+
+ private ArrayList<LocaleInfo> getFakeSupportedLocales() {
+ String[] locales = {"en-US", "zh-Hant-TW", "ja-JP", "en-GB"};
+ ArrayList<LocaleInfo> supportedLocales = new ArrayList<>();
+ for (String localeTag : locales) {
+ supportedLocales.add(LocaleStore.fromLocale(Locale.forLanguageTag(localeTag)));
+ }
+ return supportedLocales;
+ }
}
diff --git a/tests/RollbackTest/SampleRollbackApp/src/com/android/sample/rollbackapp/MainActivity.java b/tests/RollbackTest/SampleRollbackApp/src/com/android/sample/rollbackapp/MainActivity.java
index 79a2f1f5f4de..157d19762925 100644
--- a/tests/RollbackTest/SampleRollbackApp/src/com/android/sample/rollbackapp/MainActivity.java
+++ b/tests/RollbackTest/SampleRollbackApp/src/com/android/sample/rollbackapp/MainActivity.java
@@ -89,6 +89,7 @@ public class MainActivity extends Activity {
for (int i = 0; i < mIdsToRollback.size(); i++) {
Intent intent = new Intent(ACTION_NAME);
intent.putExtra(ROLLBACK_ID_EXTRA, mIdsToRollback.get(i));
+ intent.setPackage(getApplicationContext().getPackageName());
PendingIntent pendingIntent = PendingIntent.getBroadcast(
getApplicationContext(), 0, intent, FLAG_MUTABLE);
mRollbackManager.commitRollback(mIdsToRollback.get(i),