summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.bp5
-rw-r--r--PREUPLOAD.cfg2
-rw-r--r--apct-tests/perftests/core/src/android/graphics/perftests/TypefaceSerializationPerfTest.java20
-rw-r--r--apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java6
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java59
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/ComponentController.java50
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java247
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java20
-rw-r--r--apex/jobscheduler/service/java/com/android/server/tare/Agent.java150
-rw-r--r--apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java14
-rw-r--r--apex/jobscheduler/service/java/com/android/server/tare/CompleteEconomicPolicy.java12
-rw-r--r--apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java9
-rw-r--r--apex/jobscheduler/service/java/com/android/server/tare/InstalledPackageInfo.java38
-rw-r--r--apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java198
-rw-r--r--apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java14
-rw-r--r--apex/jobscheduler/service/java/com/android/server/tare/Scribe.java22
-rw-r--r--apex/jobscheduler/service/java/com/android/server/tare/TareShellCommand.java112
-rw-r--r--cmds/idmap2/include/idmap2/FabricatedOverlay.h11
-rw-r--r--cmds/idmap2/libidmap2/FabricatedOverlay.cpp108
-rw-r--r--cmds/idmap2/tests/FabricatedOverlayTests.cpp25
-rw-r--r--cmds/idmap2/tests/IdmapTests.cpp10
-rw-r--r--cmds/idmap2/tests/R.h1
-rw-r--r--cmds/idmap2/tests/ResourceMappingTests.cpp8
-rw-r--r--cmds/uiautomator/library/core-src/com/android/uiautomator/core/UiDevice.java4
-rw-r--r--core/api/current.txt89
-rw-r--r--core/api/system-current.txt13
-rw-r--r--core/api/test-current.txt14
-rw-r--r--core/java/android/accessibilityservice/AccessibilityServiceInfo.java21
-rw-r--r--core/java/android/app/ActivityManager.java35
-rw-r--r--core/java/android/app/ActivityManagerInternal.java10
-rw-r--r--core/java/android/app/ActivityThread.java3
-rw-r--r--core/java/android/app/AppOpsManager.java21
-rw-r--r--core/java/android/app/BroadcastOptions.java22
-rw-r--r--core/java/android/app/ContextImpl.java4
-rw-r--r--core/java/android/app/IActivityManager.aidl11
-rw-r--r--core/java/android/app/SystemServiceRegistry.java11
-rw-r--r--core/java/android/app/UiAutomationConnection.java1
-rw-r--r--core/java/android/content/AttributionSource.java134
-rw-r--r--core/java/android/content/PermissionChecker.java2
-rw-r--r--core/java/android/content/pm/OWNERS1
-rw-r--r--core/java/android/content/pm/UserProperties.aidl (renamed from lowpan/java/android/net/lowpan/LowpanIdentity.aidl)12
-rw-r--r--core/java/android/content/pm/UserProperties.java356
-rw-r--r--core/java/android/hardware/HardwareBuffer.java77
-rw-r--r--core/java/android/hardware/soundtrigger/SoundTrigger.java11
-rw-r--r--core/java/android/os/BatteryStats.java20
-rw-r--r--core/java/android/os/IUserManager.aidl2
-rw-r--r--core/java/android/os/PowerManager.java38
-rw-r--r--core/java/android/os/Process.java5
-rw-r--r--core/java/android/os/UserManager.java64
-rw-r--r--core/java/android/provider/DeviceConfig.java7
-rw-r--r--core/java/android/provider/Settings.java27
-rw-r--r--core/java/android/service/dreams/IDreamManager.aidl2
-rw-r--r--core/java/android/service/selectiontoolbar/DefaultSelectionToolbarRenderService.java2
-rw-r--r--core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java13
-rw-r--r--core/java/android/service/wallpaper/WallpaperService.java4
-rw-r--r--core/java/android/util/FeatureFlagUtils.java7
-rw-r--r--core/java/android/view/AccessibilityInteractionController.java77
-rw-r--r--core/java/android/view/IWindowSession.aidl53
-rw-r--r--core/java/android/view/InsetsController.java31
-rw-r--r--core/java/android/view/PendingInsetsController.java16
-rw-r--r--core/java/android/view/SurfaceControlViewHost.java10
-rw-r--r--core/java/android/view/SurfaceView.java209
-rw-r--r--core/java/android/view/View.java130
-rw-r--r--core/java/android/view/ViewRootImpl.java146
-rw-r--r--core/java/android/view/WindowInsetsController.java15
-rw-r--r--core/java/android/view/WindowManager.java8
-rw-r--r--core/java/android/view/WindowlessWindowManager.java39
-rw-r--r--core/java/android/view/accessibility/AccessibilityEvent.java57
-rw-r--r--core/java/android/view/accessibility/AccessibilityManager.java35
-rw-r--r--core/java/android/view/accessibility/AccessibilityNodeInfo.java25
-rw-r--r--core/java/android/view/accessibility/AccessibilityRecord.java24
-rw-r--r--core/java/android/view/inputmethod/InputMethodManager.java21
-rw-r--r--core/java/android/view/selectiontoolbar/ShowInfo.java39
-rw-r--r--core/java/android/view/translation/TranslationManager.java5
-rw-r--r--core/java/android/widget/TextView.java70
-rw-r--r--core/java/android/window/TaskFragmentOrganizer.java127
-rw-r--r--core/java/android/window/TaskFragmentTransaction.java8
-rw-r--r--core/java/com/android/internal/app/BilingualSuggestedLocaleAdapter.java59
-rw-r--r--core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java21
-rw-r--r--core/java/com/android/internal/os/BatteryStatsHistory.java1316
-rw-r--r--core/java/com/android/internal/os/BatteryStatsHistoryIterator.java8
-rw-r--r--core/java/com/android/internal/os/BinderCallsStats.java8
-rw-r--r--core/java/com/android/internal/policy/DecorContext.java9
-rw-r--r--core/java/com/android/internal/view/inline/InlineTooltipUi.java4
-rw-r--r--core/java/com/android/internal/widget/LocalImageResolver.java7
-rw-r--r--core/java/com/android/internal/widget/floatingtoolbar/RemoteFloatingToolbarPopup.java12
-rw-r--r--core/jni/OWNERS2
-rw-r--r--core/jni/android_hardware_HardwareBuffer.cpp20
-rw-r--r--core/jni/include/android_runtime/android_hardware_HardwareBuffer.h4
-rw-r--r--core/proto/android/providers/settings/secure.proto8
-rw-r--r--core/proto/android/server/peopleservice.proto5
-rw-r--r--core/proto/android/server/vibrator/vibratormanagerservice.proto38
-rw-r--r--core/res/res/color/system_bar_background_semi_transparent.xml19
-rw-r--r--core/res/res/drawable/bilingual_language_item_selection_indicator.xml7
-rw-r--r--core/res/res/drawable/language_picker_item_bg_selected.xml17
-rw-r--r--core/res/res/drawable/language_picker_item_text_color2_selector.xml6
-rw-r--r--core/res/res/drawable/language_picker_item_text_color_selector.xml6
-rw-r--r--core/res/res/layout/language_picker_bilingual_item.xml34
-rw-r--r--core/res/res/layout/side_fps_toast.xml4
-rw-r--r--core/res/res/values-night/colors.xml10
-rw-r--r--core/res/res/values/attrs.xml16
-rw-r--r--core/res/res/values/attrs_manifest.xml5
-rw-r--r--core/res/res/values/colors.xml13
-rw-r--r--core/res/res/values/config.xml12
-rw-r--r--core/res/res/values/strings.xml2
-rw-r--r--core/res/res/values/symbols.xml13
-rw-r--r--core/tests/coretests/src/android/view/InsetsControllerTest.java32
-rw-r--r--core/tests/coretests/src/android/view/PendingInsetsControllerTest.java19
-rw-r--r--core/tests/coretests/src/com/android/internal/widget/LocalImageResolverTest.java9
-rw-r--r--graphics/java/android/graphics/Bitmap.java64
-rw-r--r--graphics/java/android/graphics/text/LineBreakConfig.java72
-rw-r--r--ktfmt_includes.txt9
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java119
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java6
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java173
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java47
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java5
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java4
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java7
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java22
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java21
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java4
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java140
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java7
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java36
-rw-r--r--libs/WindowManager/Shell/res/animator/tv_window_menu_action_button_animator.xml (renamed from libs/WindowManager/Shell/res/animator/tv_pip_menu_action_button_animator.xml)0
-rw-r--r--libs/WindowManager/Shell/res/color/tv_window_menu_close_icon.xml (renamed from libs/WindowManager/Shell/res/color/tv_pip_menu_close_icon.xml)2
-rw-r--r--libs/WindowManager/Shell/res/color/tv_window_menu_close_icon_bg.xml (renamed from libs/WindowManager/Shell/res/color/tv_pip_menu_icon_bg.xml)6
-rw-r--r--libs/WindowManager/Shell/res/color/tv_window_menu_icon.xml (renamed from libs/WindowManager/Shell/res/color/tv_pip_menu_icon.xml)8
-rw-r--r--libs/WindowManager/Shell/res/color/tv_window_menu_icon_bg.xml (renamed from libs/WindowManager/Shell/res/color/tv_pip_menu_close_icon_bg.xml)4
-rw-r--r--libs/WindowManager/Shell/res/drawable/tv_pip_menu_border.xml2
-rw-r--r--libs/WindowManager/Shell/res/drawable/tv_window_button_bg.xml21
-rw-r--r--libs/WindowManager/Shell/res/layout/tv_pip_menu.xml10
-rw-r--r--libs/WindowManager/Shell/res/layout/tv_pip_menu_action_button.xml40
-rw-r--r--libs/WindowManager/Shell/res/layout/tv_window_menu_action_button.xml40
-rw-r--r--libs/WindowManager/Shell/res/values-television/dimen.xml3
-rw-r--r--libs/WindowManager/Shell/res/values-tvdpi/dimen.xml16
-rw-r--r--libs/WindowManager/Shell/res/values/colors_tv.xml16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java40
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java55
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuActionButton.java)38
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java27
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java27
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeConstants.java (renamed from packages/SystemUI/src/com/android/systemui/common/domain/model/Position.kt)27
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java137
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java23
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java60
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java17
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java22
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java145
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java27
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java7
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt5
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt25
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt46
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt187
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt26
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt7
-rw-r--r--libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_splitscreen.xml1
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java91
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java194
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java64
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java12
-rwxr-xr-xlibs/androidfw/ApkAssets.cpp3
-rw-r--r--libs/androidfw/LoadedArsc.cpp17
-rw-r--r--libs/androidfw/include/androidfw/LoadedArsc.h3
-rw-r--r--libs/androidfw/include/androidfw/ResourceTypes.h2
-rw-r--r--libs/hwui/FrameInfoVisualizer.cpp2
-rw-r--r--libs/hwui/hwui/Typeface.cpp6
-rw-r--r--libs/hwui/jni/FontFamily.cpp6
-rw-r--r--libs/hwui/jni/fonts/FontFamily.cpp6
-rw-r--r--libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp2
-rw-r--r--libs/hwui/pipeline/skia/SkiaProfileRenderer.cpp8
-rw-r--r--libs/hwui/pipeline/skia/SkiaProfileRenderer.h9
-rw-r--r--libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp4
-rw-r--r--libs/hwui/renderthread/CanvasContext.cpp18
-rw-r--r--libs/hwui/tests/unit/TypefaceTests.cpp2
-rw-r--r--location/java/android/location/util/identity/CallerIdentity.java29
-rw-r--r--lowpan/java/Android.bp17
-rw-r--r--lowpan/java/android/net/lowpan/ILowpanInterface.aidl155
-rw-r--r--lowpan/java/android/net/lowpan/ILowpanInterfaceListener.aidl45
-rw-r--r--lowpan/java/android/net/lowpan/ILowpanManager.aidl36
-rw-r--r--lowpan/java/android/net/lowpan/InterfaceDisabledException.java41
-rw-r--r--lowpan/java/android/net/lowpan/JoinFailedAtAuthException.java41
-rw-r--r--lowpan/java/android/net/lowpan/JoinFailedAtScanException.java41
-rw-r--r--lowpan/java/android/net/lowpan/JoinFailedException.java41
-rw-r--r--lowpan/java/android/net/lowpan/LowpanBeaconInfo.aidl19
-rw-r--r--lowpan/java/android/net/lowpan/LowpanBeaconInfo.java234
-rw-r--r--lowpan/java/android/net/lowpan/LowpanChannelInfo.aidl19
-rw-r--r--lowpan/java/android/net/lowpan/LowpanChannelInfo.java216
-rw-r--r--lowpan/java/android/net/lowpan/LowpanCommissioningSession.java223
-rw-r--r--lowpan/java/android/net/lowpan/LowpanCredential.aidl19
-rw-r--r--lowpan/java/android/net/lowpan/LowpanCredential.java172
-rw-r--r--lowpan/java/android/net/lowpan/LowpanEnergyScanResult.java53
-rw-r--r--lowpan/java/android/net/lowpan/LowpanException.java92
-rw-r--r--lowpan/java/android/net/lowpan/LowpanIdentity.java255
-rw-r--r--lowpan/java/android/net/lowpan/LowpanInterface.java824
-rw-r--r--lowpan/java/android/net/lowpan/LowpanManager.java335
-rw-r--r--lowpan/java/android/net/lowpan/LowpanProperties.java56
-rw-r--r--lowpan/java/android/net/lowpan/LowpanProperty.java34
-rw-r--r--lowpan/java/android/net/lowpan/LowpanProvision.aidl19
-rw-r--r--lowpan/java/android/net/lowpan/LowpanProvision.java149
-rw-r--r--lowpan/java/android/net/lowpan/LowpanRuntimeException.java42
-rw-r--r--lowpan/java/android/net/lowpan/LowpanScanner.java326
-rw-r--r--lowpan/java/android/net/lowpan/NetworkAlreadyExistsException.java41
-rw-r--r--lowpan/java/android/net/lowpan/OperationCanceledException.java41
-rw-r--r--lowpan/java/android/net/lowpan/WrongStateException.java41
-rw-r--r--lowpan/java/android/net/lowpan/package.html29
-rw-r--r--lowpan/tests/Android.bp50
-rw-r--r--lowpan/tests/AndroidManifest.xml39
-rw-r--r--lowpan/tests/AndroidTest.xml27
-rw-r--r--lowpan/tests/README.md50
-rwxr-xr-xlowpan/tests/runtests.sh24
-rw-r--r--lowpan/tests/src/android/net/lowpan/LowpanInterfaceTest.java92
-rw-r--r--lowpan/tests/src/android/net/lowpan/LowpanManagerTest.java177
-rw-r--r--media/java/android/media/AudioSystem.java8
-rw-r--r--media/java/android/media/BluetoothProfileConnectionInfo.java15
-rw-r--r--media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/BluetoothProfileConnectionInfoTest.java44
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java3
-rw-r--r--packages/SettingsLib/Spa/.idea/codeStyles/Project.xml129
-rw-r--r--packages/SettingsLib/Spa/.idea/codeStyles/codeStyleConfig.xml5
-rw-r--r--packages/SettingsLib/Spa/.idea/copyright/Apache_2.xml10
-rw-r--r--packages/SettingsLib/Spa/.idea/copyright/profiles_settings.xml8
-rw-r--r--packages/SettingsLib/Spa/.idea/vcs.xml15
-rw-r--r--packages/SettingsLib/Spa/build.gradle6
-rw-r--r--packages/SettingsLib/Spa/gallery/Android.bp (renamed from packages/SettingsLib/Spa/codelab/Android.bp)2
-rw-r--r--packages/SettingsLib/Spa/gallery/AndroidManifest.xml (renamed from packages/SettingsLib/Spa/codelab/AndroidManifest.xml)12
-rw-r--r--packages/SettingsLib/Spa/gallery/build.gradle (renamed from packages/SettingsLib/Spa/codelab/build.gradle)8
-rw-r--r--packages/SettingsLib/Spa/gallery/res/mipmap-anydpi-v26/ic_launcher.xml21
-rw-r--r--packages/SettingsLib/Spa/gallery/res/mipmap-xxxhdpi/ic_launcher.pngbin0 -> 29368 bytes
-rw-r--r--packages/SettingsLib/Spa/gallery/res/mipmap-xxxhdpi/ic_launcher_background.pngbin0 -> 30960 bytes
-rw-r--r--packages/SettingsLib/Spa/gallery/res/mipmap-xxxhdpi/ic_launcher_foreground.pngbin0 -> 19097 bytes
-rw-r--r--packages/SettingsLib/Spa/gallery/res/values/strings.xml22
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/MainActivity.kt (renamed from packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/MainActivity.kt)6
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt (renamed from packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/ArgumentPage.kt)2
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPage.kt71
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/HomePage.kt (renamed from packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/HomePage.kt)8
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/PageRepository.kt (renamed from packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/PageRepository.kt)8
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/PreferencePage.kt (renamed from packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/PreferencePage.kt)18
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SettingsPagerPage.kt66
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.kt (renamed from packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/SliderPage.kt)2
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SwitchPreferencePage.kt129
-rw-r--r--packages/SettingsLib/Spa/settings.gradle2
-rw-r--r--packages/SettingsLib/Spa/spa/Android.bp5
-rw-r--r--packages/SettingsLib/Spa/spa/build.gradle16
-rw-r--r--packages/SettingsLib/Spa/spa/res/values-night/themes.xml4
-rw-r--r--packages/SettingsLib/Spa/spa/res/values/themes.xml6
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaActivity.kt2
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/DrawablePainter.kt180
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/RuntimeUtils.kt8
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt1
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsShape.kt (renamed from lowpan/java/android/net/lowpan/ILowpanNetScanCallback.aidl)13
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt9
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BasePreference.kt13
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt18
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SwitchPreference.kt146
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsPager.kt60
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsTab.kt72
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Footer.kt55
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Icon.kt35
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Switch.kt47
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt52
-rw-r--r--packages/SettingsLib/Spa/tests/build.gradle8
-rw-r--r--packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/SwitchPreferenceTest.kt91
-rw-r--r--packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsPagerKtTest.kt82
-rw-r--r--packages/SettingsLib/SpaPrivileged/Android.bp32
-rw-r--r--packages/SettingsLib/SpaPrivileged/AndroidManifest.xml (renamed from packages/SettingsLib/Spa/codelab/res/values/strings.xml)9
-rw-r--r--packages/SettingsLib/SpaPrivileged/OWNERS1
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppOpsController.kt55
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppRecord.kt (renamed from lowpan/java/android/net/lowpan/ILowpanEnergyScanCallback.aidl)12
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppRepository.kt58
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/Apps.kt25
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/PackageManagers.kt (renamed from packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/KeyguardUseCaseModule.kt)23
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/enterprise/EnterpriseRepository.kt38
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt70
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfoPage.kt53
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt121
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListModel.kt44
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/WorkProfilePager.kt45
-rw-r--r--packages/SettingsLib/res/values/strings.xml22
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java3
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java4
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java4
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java6
-rw-r--r--packages/SystemUI/Android.bp3
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt26
-rw-r--r--packages/SystemUI/checks/src/com/android/internal/systemui/lint/GetMainLooperViaContextDetector.kt66
-rw-r--r--packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt66
-rw-r--r--packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt72
-rw-r--r--packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt9
-rw-r--r--packages/SystemUI/checks/tests/com/android/systemui/lint/GetMainLooperViaContextDetectorTest.kt135
-rw-r--r--packages/SystemUI/checks/tests/com/android/systemui/lint/RegisterReceiverViaContextDetectorTest.kt171
-rw-r--r--packages/SystemUI/checks/tests/com/android/systemui/lint/SoftwareBitmapDetectorTest.kt97
-rw-r--r--packages/SystemUI/compose/features/Android.bp1
-rw-r--r--packages/SystemUI/compose/features/AndroidManifest.xml35
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt233
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreenEmpty.kt124
-rw-r--r--packages/SystemUI/compose/gallery/Android.bp9
-rw-r--r--packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/GalleryApp.kt60
-rw-r--r--packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/PeopleScreen.kt46
-rw-r--r--packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/Screen.kt39
-rw-r--r--packages/SystemUI/compose/gallery/src/com/android/systemui/people/Fakes.kt156
-rw-r--r--packages/SystemUI/res-keyguard/values/strings.xml6
-rw-r--r--packages/SystemUI/res/drawable/accessibility_magnification_setting_view_bg.xml27
-rw-r--r--packages/SystemUI/res/drawable/accessibility_magnification_setting_view_btn_bg.xml28
-rw-r--r--packages/SystemUI/res/drawable/accessibility_magnifier_btn_bg.xml (renamed from libs/WindowManager/Shell/res/drawable/tv_pip_button_bg.xml)48
-rw-r--r--packages/SystemUI/res/drawable/fingerprint_dialog_error_to_fp.xml1
-rw-r--r--packages/SystemUI/res/drawable/fingerprint_dialog_error_to_unlock.xml1
-rw-r--r--packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_error.xml1
-rw-r--r--packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_unlock.xml1
-rw-r--r--packages/SystemUI/res/drawable/ic_magnification_menu_close.xml29
-rw-r--r--packages/SystemUI/res/drawable/ic_magnification_menu_large.xml25
-rw-r--r--packages/SystemUI/res/drawable/ic_magnification_menu_medium.xml25
-rw-r--r--packages/SystemUI/res/drawable/ic_magnification_menu_small.xml25
-rw-r--r--packages/SystemUI/res/drawable/ic_move_magnification.xml13
-rw-r--r--packages/SystemUI/res/drawable/ic_open_in_new_fullscreen.xml48
-rw-r--r--packages/SystemUI/res/layout/auth_biometric_contents.xml4
-rw-r--r--packages/SystemUI/res/layout/auth_biometric_fingerprint_view.xml2
-rw-r--r--packages/SystemUI/res/layout/window_magnification_settings_view.xml152
-rw-r--r--packages/SystemUI/res/layout/window_magnifier_view.xml36
-rw-r--r--packages/SystemUI/res/raw/fingerprint_dialogue_error_to_fingerprint_lottie.json1
-rw-r--r--packages/SystemUI/res/raw/fingerprint_dialogue_error_to_unlock_lottie.json1
-rw-r--r--packages/SystemUI/res/raw/fingerprint_dialogue_fingerprint_to_error_lottie.json1
-rw-r--r--packages/SystemUI/res/raw/fingerprint_dialogue_fingerprint_to_unlock_lottie.json1
-rw-r--r--packages/SystemUI/res/values/colors.xml4
-rw-r--r--packages/SystemUI/res/values/dimens.xml12
-rw-r--r--packages/SystemUI/res/values/strings.xml37
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java18
-rw-r--r--packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/MagnificationGestureDetector.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java260
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java592
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettingsCallback.java64
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnifierCallback.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt58
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricIconController.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java21
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java28
-rw-r--r--packages/SystemUI/src/com/android/systemui/common/coroutine/ChannelExt.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/common/shared/model/Position.kt (renamed from packages/SystemUI/src/com/android/systemui/common/data/model/Position.kt)2
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.kt51
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt (renamed from packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveIsKeyguardShowingUseCase.kt)26
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt136
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceModule.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceRegistry.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/LaunchKeyguardQuickAffordanceUseCase.kt76
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveBottomAreaAlphaUseCase.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveClockPositionUseCase.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveDozeAmountUseCase.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveIsDozingUseCase.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCase.kt75
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/OnKeyguardQuickAffordanceClickedUseCase.kt49
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetKeyguardBottomAreaAlphaUseCase.kt31
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetKeyguardBottomAreaAnimateDozingTransitionsUseCase.kt33
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt43
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt49
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/ShadeLog.java (renamed from packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetClockPositionUseCase.kt)26
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt31
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java32
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java31
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/people/ui/viewmodel/PeopleViewModel.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotifPanelEvents.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java85
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt48
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolver.kt27
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FeedbackInfo.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PartialConversationInfo.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoCollector.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoCollectorImpl.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityPipelineLogger.kt59
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt106
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiActivityModel.kt27
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiModel.kt (renamed from packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveAnimateBottomAreaTransitionsUseCase.kt)27
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/NetworkCapabilitiesRepo.kt (renamed from packages/SystemUI/src/com/android/systemui/statusbar/pipeline/repository/NetworkCapabilitiesRepo.kt)12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt103
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt50
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiConstants.kt49
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt45
-rw-r--r--packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java17
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationGestureDetectorTest.java42
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java24
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java9
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java49
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java187
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt64
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt24
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceRegistry.kt10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/FakeLaunchKeyguardQuickAffordanceUseCase.kt47
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/FakeObserveKeyguardQuickAffordanceUseCase.kt46
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorParameterizedTest.kt294
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCaseImplTest.kt)54
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/LaunchKeyguardQuickAffordanceUseCaseImplTest.kt178
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt130
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt82
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java32
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt104
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java19
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/testing/FakeNotifPanelEvents.kt37
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolverTest.kt132
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java49
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java76
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsTest.kt103
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java41
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessorTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ConnectivityPipelineLoggerTest.kt)2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt40
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/NetworkCapabilitiesRepoTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/repository/NetworkCapabilitiesRepoTest.kt)4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt175
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt163
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt124
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt2
-rw-r--r--proto/src/OWNERS1
-rw-r--r--proto/src/system_messages.proto4
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java25
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java1
-rw-r--r--services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java20
-rw-r--r--services/backup/java/com/android/server/backup/UserBackupManagerService.java15
-rw-r--r--services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java19
-rw-r--r--services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java27
-rw-r--r--services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java2
-rw-r--r--services/core/java/android/content/pm/PackageManagerInternal.java5
-rw-r--r--services/core/java/com/android/server/DropBoxManagerService.java4
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java62
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerShellCommand.java51
-rw-r--r--services/core/java/com/android/server/am/BroadcastQueue.java79
-rw-r--r--services/core/java/com/android/server/am/BroadcastRecord.java26
-rw-r--r--services/core/java/com/android/server/am/CachedAppOptimizer.java82
-rw-r--r--services/core/java/com/android/server/am/HostingRecord.java8
-rw-r--r--services/core/java/com/android/server/am/LmkdStatsReporter.java6
-rw-r--r--services/core/java/com/android/server/am/OomAdjuster.java25
-rw-r--r--services/core/java/com/android/server/am/ProcessList.java27
-rw-r--r--services/core/java/com/android/server/am/ProcessServiceRecord.java10
-rw-r--r--services/core/java/com/android/server/am/ProcessStateRecord.java1
-rw-r--r--services/core/java/com/android/server/am/SettingsToPropertiesMapper.java1
-rw-r--r--services/core/java/com/android/server/am/UserController.java108
-rw-r--r--services/core/java/com/android/server/app/GameManagerService.java301
-rw-r--r--services/core/java/com/android/server/app/GameManagerSettings.java174
-rw-r--r--services/core/java/com/android/server/app/GameManagerShellCommand.java3
-rw-r--r--services/core/java/com/android/server/attention/TEST_MAPPING24
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceBroker.java2
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceInventory.java14
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java47
-rw-r--r--services/core/java/com/android/server/audio/SpatializerHelper.java22
-rw-r--r--services/core/java/com/android/server/connectivity/Vpn.java77
-rw-r--r--services/core/java/com/android/server/display/AutomaticBrightnessController.java35
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerController.java45
-rw-r--r--services/core/java/com/android/server/display/brightness/BrightnessEvent.java180
-rw-r--r--services/core/java/com/android/server/display/color/ColorDisplayService.java4
-rw-r--r--services/core/java/com/android/server/dreams/DreamManagerService.java21
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodManagerService.java12
-rw-r--r--services/core/java/com/android/server/location/LocationPermissions.java3
-rw-r--r--services/core/java/com/android/server/location/geofence/GeofenceKey.java60
-rw-r--r--services/core/java/com/android/server/location/geofence/GeofenceManager.java109
-rw-r--r--services/core/java/com/android/server/location/gnss/GnssAntennaInfoProvider.java31
-rw-r--r--services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java77
-rw-r--r--services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java9
-rw-r--r--services/core/java/com/android/server/location/gnss/GnssNavigationMessageProvider.java10
-rw-r--r--services/core/java/com/android/server/location/gnss/GnssNmeaProvider.java4
-rw-r--r--services/core/java/com/android/server/location/gnss/GnssStatusProvider.java2
-rw-r--r--services/core/java/com/android/server/location/listeners/BinderListenerRegistration.java83
-rw-r--r--services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java348
-rw-r--r--services/core/java/com/android/server/location/listeners/ListenerRegistration.java31
-rw-r--r--services/core/java/com/android/server/location/listeners/PendingIntentListenerRegistration.java73
-rw-r--r--services/core/java/com/android/server/location/listeners/RemoteListenerRegistration.java75
-rw-r--r--services/core/java/com/android/server/location/listeners/RemovableListenerRegistration.java87
-rw-r--r--services/core/java/com/android/server/location/listeners/RequestListenerRegistration.java54
-rw-r--r--services/core/java/com/android/server/location/provider/LocationProviderManager.java761
-rw-r--r--services/core/java/com/android/server/location/provider/PassiveLocationProviderManager.java2
-rw-r--r--services/core/java/com/android/server/locksettings/LockSettingsService.java27
-rwxr-xr-xservices/core/java/com/android/server/notification/NotificationManagerService.java25
-rw-r--r--services/core/java/com/android/server/notification/ReviewNotificationPermissionsJobService.java4
-rw-r--r--services/core/java/com/android/server/notification/ZenLog.java27
-rw-r--r--services/core/java/com/android/server/notification/ZenModeFiltering.java48
-rw-r--r--services/core/java/com/android/server/notification/ZenModeHelper.java6
-rw-r--r--services/core/java/com/android/server/om/OverlayManagerService.java15
-rw-r--r--services/core/java/com/android/server/om/OverlayManagerShellCommand.java2
-rw-r--r--services/core/java/com/android/server/pm/AppsFilterBase.java5
-rw-r--r--services/core/java/com/android/server/pm/BroadcastHelper.java155
-rw-r--r--services/core/java/com/android/server/pm/BroadcastParams.java62
-rw-r--r--services/core/java/com/android/server/pm/DistractingPackageHelper.java38
-rw-r--r--services/core/java/com/android/server/pm/InstallPackageHelper.java4
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java23
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerShellCommand.java30
-rw-r--r--services/core/java/com/android/server/pm/StorageEventHelper.java2
-rw-r--r--services/core/java/com/android/server/pm/SuspendPackageHelper.java41
-rw-r--r--services/core/java/com/android/server/pm/UserManagerInternal.java10
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java316
-rw-r--r--services/core/java/com/android/server/pm/UserTypeDetails.java67
-rw-r--r--services/core/java/com/android/server/pm/UserTypeFactory.java14
-rw-r--r--services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java30
-rw-r--r--services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java1
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java93
-rw-r--r--services/core/java/com/android/server/policy/WindowManagerPolicy.java13
-rw-r--r--services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java10
-rw-r--r--services/core/java/com/android/server/power/Notifier.java5
-rw-r--r--services/core/java/com/android/server/power/PowerGroup.java27
-rw-r--r--services/core/java/com/android/server/power/PowerManagerService.java45
-rw-r--r--services/core/java/com/android/server/power/ThermalManagerService.java13
-rw-r--r--services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java2
-rw-r--r--services/core/java/com/android/server/power/stats/BatteryStatsImpl.java1807
-rw-r--r--services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java16
-rw-r--r--services/core/java/com/android/server/vibrator/AbstractVibratorStep.java4
-rw-r--r--services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java7
-rw-r--r--services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java5
-rw-r--r--services/core/java/com/android/server/vibrator/PerformPrebakedVibratorStep.java1
-rw-r--r--services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java4
-rw-r--r--services/core/java/com/android/server/vibrator/StartSequentialEffectStep.java6
-rw-r--r--services/core/java/com/android/server/vibrator/Vibration.java198
-rw-r--r--services/core/java/com/android/server/vibrator/VibrationStats.java395
-rw-r--r--services/core/java/com/android/server/vibrator/VibrationStepConductor.java85
-rw-r--r--services/core/java/com/android/server/vibrator/VibrationThread.java27
-rw-r--r--services/core/java/com/android/server/vibrator/VibratorFrameworkStatsLogger.java140
-rw-r--r--services/core/java/com/android/server/vibrator/VibratorManagerService.java302
-rw-r--r--services/core/java/com/android/server/wm/ActivityMetricsLogger.java42
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java4
-rw-r--r--services/core/java/com/android/server/wm/ActivityStartController.java3
-rw-r--r--services/core/java/com/android/server/wm/ActivityStarter.java2
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerService.java14
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskSupervisor.java6
-rw-r--r--services/core/java/com/android/server/wm/AppTransition.java24
-rw-r--r--services/core/java/com/android/server/wm/AppTransitionController.java30
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java15
-rw-r--r--services/core/java/com/android/server/wm/DisplayPolicy.java57
-rw-r--r--services/core/java/com/android/server/wm/DisplayRotation.java5
-rw-r--r--services/core/java/com/android/server/wm/InsetsPolicy.java2
-rw-r--r--services/core/java/com/android/server/wm/RecentsAnimationController.java6
-rw-r--r--services/core/java/com/android/server/wm/Session.java23
-rw-r--r--services/core/java/com/android/server/wm/TaskFragment.java5
-rw-r--r--services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java18
-rw-r--r--services/core/java/com/android/server/wm/Transition.java82
-rw-r--r--services/core/java/com/android/server/wm/TransitionController.java6
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerInternal.java21
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java250
-rw-r--r--services/core/java/com/android/server/wm/WindowProcessController.java11
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java43
-rw-r--r--services/core/xsd/display-device-config/autobrightness.xsd33
-rw-r--r--services/core/xsd/display-device-config/display-device-config.xsd17
-rw-r--r--services/people/java/com/android/server/people/data/ConversationInfo.java49
-rw-r--r--services/people/java/com/android/server/people/data/DataManager.java29
-rw-r--r--services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java22
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java87
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java3
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java181
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java82
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java3
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java3
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/listeners/ListenerMultiplexerTest.java48
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java5
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/BroadcastHelperTest.kt117
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/DistractingPackageHelperTest.kt70
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/PackageHelperTestBase.kt26
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt80
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/tare/AgentTest.java10
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/tare/AgentTrendCalculatorTest.java (renamed from services/tests/servicestests/src/com/android/server/tare/AgentTrendCalculatorTest.java)11
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/tare/AlarmManagerEconomicPolicyTest.java18
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/tare/CompleteEconomicPolicyTest.java10
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/tare/JobSchedulerEconomicPolicyTest.java18
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java4
-rw-r--r--services/tests/servicestests/res/xml/usertypes_test_profile.xml4
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInteractionControllerNodeRequestsTest.java5
-rw-r--r--services/tests/servicestests/src/com/android/server/am/BroadcastRecordTest.java3
-rw-r--r--services/tests/servicestests/src/com/android/server/app/GameManagerServiceSettingsTests.java167
-rw-r--r--services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java1
-rw-r--r--services/tests/servicestests/src/com/android/server/display/brightness/BrightnessEventTest.java39
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java5
-rw-r--r--services/tests/servicestests/src/com/android/server/people/data/ConversationInfoTest.java106
-rw-r--r--services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java73
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java185
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java24
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java28
-rw-r--r--services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java9
-rw-r--r--services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java20
-rw-r--r--services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java9
-rw-r--r--services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java12
-rw-r--r--services/tests/servicestests/src/com/android/server/vibrator/VibrationTest.java46
-rw-r--r--services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java108
-rw-r--r--services/tests/servicestests/src/com/android/server/vibrator/VibratorFrameworkStatsLoggerTest.java127
-rw-r--r--services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java401
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java15
-rwxr-xr-xservices/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java29
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ReviewNotificationPermissionsJobServiceTest.java5
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java30
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java5
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java15
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java85
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java19
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java6
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TransitionTests.java35
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java32
-rw-r--r--startop/view_compiler/dex_builder_test/Android.bp1
-rw-r--r--startop/view_compiler/dex_builder_test/src/android/startop/test/ApkLayoutCompilerTest.java57
-rw-r--r--telephony/OWNERS7
-rw-r--r--telephony/java/android/telephony/CarrierConfigManager.java114
-rw-r--r--telephony/java/android/telephony/SubscriptionManager.java15
-rw-r--r--telephony/java/android/telephony/TelephonyManager.java220
-rw-r--r--telephony/java/com/android/internal/telephony/ITelephony.aidl41
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt5
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt13
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt7
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest_ShellTransit.kt20
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt20
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt20
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt19
-rw-r--r--tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java23
-rw-r--r--tests/TouchLatency/app/src/main/java/com/prefabulated/touchlatency/TouchLatencyActivity.java14
-rw-r--r--tools/lint/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt3
-rw-r--r--tools/lint/checks/src/main/java/com/google/android/lint/Constants.kt36
-rw-r--r--tools/lint/checks/src/main/java/com/google/android/lint/PackageVisibilityDetector.kt31
-rw-r--r--tools/lint/checks/src/main/java/com/google/android/lint/aidl/Constants.kt73
-rw-r--r--tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt (renamed from tools/lint/checks/src/main/java/com/google/android/lint/EnforcePermissionDetector.kt)19
-rw-r--r--tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt118
-rw-r--r--tools/lint/checks/src/main/java/com/google/android/lint/aidl/ManualPermissionCheckDetector.kt199
-rw-r--r--tools/lint/checks/src/main/java/com/google/android/lint/model/Method.kt (renamed from lowpan/java/android/net/lowpan/ILowpanManagerListener.aidl)17
-rw-r--r--tools/lint/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt (renamed from tools/lint/checks/src/test/java/com/google/android/lint/EnforcePermissionDetectorTest.kt)2
-rw-r--r--tools/lint/checks/src/test/java/com/google/android/lint/aidl/ManualPermissionCheckDetectorTest.kt211
671 files changed, 20924 insertions, 12105 deletions
diff --git a/Android.bp b/Android.bp
index eae0d73f449f..ef25ec2b9de0 100644
--- a/Android.bp
+++ b/Android.bp
@@ -72,7 +72,6 @@ filegroup {
":framework-keystore-sources",
":framework-identity-sources",
":framework-location-sources",
- ":framework-lowpan-sources",
":framework-mca-effect-sources",
":framework-mca-filterfw-sources",
":framework-mca-filterpacks-sources",
@@ -167,7 +166,6 @@ java_defaults {
"identity/java",
"keystore/java",
"location/java",
- "lowpan/java",
"media/java",
"media/mca/effect/java",
"media/mca/filterfw/java",
@@ -277,7 +275,6 @@ java_defaults {
":framework-keystore-sources",
":framework-identity-sources",
":framework-location-sources",
- ":framework-lowpan-sources",
":framework-mca-effect-sources",
":framework-mca-filterfw-sources",
":framework-mca-filterpacks-sources",
@@ -329,6 +326,7 @@ java_defaults {
"packages/modules/Connectivity/framework/aidl-export",
"packages/modules/Media/apex/aidl/stable",
"hardware/interfaces/graphics/common/aidl",
+ "frameworks/native/libs/permission/aidl",
],
},
dxflags: [
@@ -598,6 +596,7 @@ stubs_defaults {
"packages/modules/Connectivity/framework/aidl-export",
"packages/modules/Media/apex/aidl/stable",
"hardware/interfaces/graphics/common/aidl",
+ "frameworks/native/libs/permission/aidl",
],
},
// These are libs from framework-internal-utils that are required (i.e. being referenced)
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index f8aa7e9bab30..1d92778a9117 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -25,4 +25,6 @@ hidden_api_txt_checksorted_hook = ${REPO_ROOT}/tools/platform-compat/hiddenapi/c
hidden_api_txt_exclude_hook = ${REPO_ROOT}/frameworks/base/tools/hiddenapi/exclude.sh ${PREUPLOAD_COMMIT} ${REPO_ROOT}
+ktfmt_hook = ${REPO_ROOT}/external/ktfmt/ktfmt.py --check -i ${REPO_ROOT}/frameworks/base/ktfmt_includes.txt ${PREUPLOAD_FILES}
+
ktlint_hook = ${REPO_ROOT}/prebuilts/ktlint/ktlint.py -f ${PREUPLOAD_FILES}
diff --git a/apct-tests/perftests/core/src/android/graphics/perftests/TypefaceSerializationPerfTest.java b/apct-tests/perftests/core/src/android/graphics/perftests/TypefaceSerializationPerfTest.java
index 1e2650dbfdc4..bc8fc53cc448 100644
--- a/apct-tests/perftests/core/src/android/graphics/perftests/TypefaceSerializationPerfTest.java
+++ b/apct-tests/perftests/core/src/android/graphics/perftests/TypefaceSerializationPerfTest.java
@@ -28,6 +28,7 @@ import android.util.Log;
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
+import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -50,9 +51,16 @@ public class TypefaceSerializationPerfTest {
@Rule
public PerfManualStatusReporter mPerfManualStatusReporter = new PerfManualStatusReporter();
- @ManualBenchmarkState.ManualBenchmarkTest(
- warmupDurationNs = WARMUP_DURATION_NS,
- targetTestDurationNs = TARGET_TEST_DURATION_NS)
+ @Before
+ public void setUp() {
+ // Parse and load the preinstalled fonts in the test process so that:
+ // (1) Updated fonts do not affect test results.
+ // (2) Lazy-loading of fonts does not affect test results (esp. testSerializeFontMap).
+ Typeface.loadPreinstalledSystemFontMap();
+ }
+
+ // testSerializeFontMap uses the default targetTestDurationNs, which is much longer than
+ // TARGET_TEST_DURATION_NS, in order to stabilize test results.
@Test
public void testSerializeFontMap() throws Exception {
Map<String, Typeface> systemFontMap = Typeface.getSystemFontMap();
@@ -61,8 +69,12 @@ public class TypefaceSerializationPerfTest {
long elapsedTime = 0;
while (state.keepRunning(elapsedTime)) {
long startTime = System.nanoTime();
- Typeface.serializeFontMap(systemFontMap);
+ SharedMemory sharedMemory = Typeface.serializeFontMap(systemFontMap);
elapsedTime = System.nanoTime() - startTime;
+ sharedMemory.close();
+ android.util.Log.i(TAG,
+ "testSerializeFontMap isWarmingUp=" + state.isWarmingUp()
+ + " elapsedTime=" + elapsedTime);
}
}
diff --git a/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java b/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java
index c92c6340a6b4..fb62920681de 100644
--- a/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java
+++ b/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java
@@ -153,9 +153,9 @@ public class RelayoutPerfTest extends WindowManagerPerfTestBase
final IWindowSession session = WindowManagerGlobal.getWindowSession();
while (state.keepRunning()) {
session.relayout(mWindow, mParams, mWidth, mHeight,
- mViewVisibility.getAsInt(), mFlags, mOutFrames,
- mOutMergedConfiguration, mOutSurfaceControl, mOutInsetsState, mOutControls,
- new Bundle());
+ mViewVisibility.getAsInt(), mFlags, 0 /* seq */, 0 /* lastSyncSeqId */,
+ mOutFrames, mOutMergedConfiguration, mOutSurfaceControl, mOutInsetsState,
+ mOutControls, new Bundle());
}
}
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index 794362bd682b..4d5eef2f65f5 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -310,11 +310,25 @@ public class JobSchedulerService extends com.android.server.SystemService
*/
boolean mReportedActive;
+ /**
+ * Track the most recently completed jobs (that had been executing and were stopped for any
+ * reason, including successful completion).
+ */
private int mLastCompletedJobIndex = 0;
private final JobStatus[] mLastCompletedJobs = new JobStatus[NUM_COMPLETED_JOB_HISTORY];
private final long[] mLastCompletedJobTimeElapsed = new long[NUM_COMPLETED_JOB_HISTORY];
/**
+ * Track the most recently cancelled jobs (that had internal reason
+ * {@link JobParameters#INTERNAL_STOP_REASON_CANCELED}.
+ */
+ private int mLastCancelledJobIndex = 0;
+ private final JobStatus[] mLastCancelledJobs =
+ new JobStatus[DEBUG ? NUM_COMPLETED_JOB_HISTORY : 0];
+ private final long[] mLastCancelledJobTimeElapsed =
+ new long[DEBUG ? NUM_COMPLETED_JOB_HISTORY : 0];
+
+ /**
* A mapping of which uids are currently in the foreground to their effective bias.
*/
final SparseIntArray mUidBiasOverride = new SparseIntArray();
@@ -1398,6 +1412,12 @@ public class JobSchedulerService extends com.android.server.SystemService
startTrackingJobLocked(incomingJob, cancelled);
}
reportActiveLocked();
+ if (mLastCancelledJobs.length > 0
+ && internalReasonCode == JobParameters.INTERNAL_STOP_REASON_CANCELED) {
+ mLastCancelledJobs[mLastCancelledJobIndex] = cancelled;
+ mLastCancelledJobTimeElapsed[mLastCancelledJobIndex] = sElapsedRealtimeClock.millis();
+ mLastCancelledJobIndex = (mLastCancelledJobIndex + 1) % mLastCancelledJobs.length;
+ }
}
void updateUidState(int uid, int procState) {
@@ -2555,9 +2575,9 @@ public class JobSchedulerService extends com.android.server.SystemService
}
private boolean isComponentUsable(@NonNull JobStatus job) {
- final ServiceInfo service = job.serviceInfo;
+ final String processName = job.serviceProcessName;
- if (service == null) {
+ if (processName == null) {
if (DEBUG) {
Slog.v(TAG, "isComponentUsable: " + job.toShortString()
+ " component not present");
@@ -2566,8 +2586,7 @@ public class JobSchedulerService extends com.android.server.SystemService
}
// Everything else checked out so far, so this is the final yes/no check
- final boolean appIsBad = mActivityManagerInternal.isAppBad(
- service.processName, service.applicationInfo.uid);
+ final boolean appIsBad = mActivityManagerInternal.isAppBad(processName, job.getUid());
if (DEBUG && appIsBad) {
Slog.i(TAG, "App is bad for " + job.toShortString() + " so not runnable");
}
@@ -3848,6 +3867,38 @@ public class JobSchedulerService extends com.android.server.SystemService
pw.decreaseIndent();
pw.println();
+ boolean recentCancellationsPrinted = false;
+ for (int r = 1; r <= mLastCancelledJobs.length; ++r) {
+ // Print most recent first
+ final int idx = (mLastCancelledJobIndex + mLastCancelledJobs.length - r)
+ % mLastCancelledJobs.length;
+ job = mLastCancelledJobs[idx];
+ if (job != null) {
+ if (!predicate.test(job)) {
+ continue;
+ }
+ if (!recentCancellationsPrinted) {
+ pw.println();
+ pw.println("Recently cancelled jobs:");
+ pw.increaseIndent();
+ recentCancellationsPrinted = true;
+ }
+ TimeUtils.formatDuration(mLastCancelledJobTimeElapsed[idx], nowElapsed, pw);
+ pw.println();
+ // Double indent for readability
+ pw.increaseIndent();
+ pw.increaseIndent();
+ pw.println(job.toShortString());
+ job.dump(pw, true, nowElapsed);
+ pw.decreaseIndent();
+ pw.decreaseIndent();
+ }
+ }
+ if (!recentCancellationsPrinted) {
+ pw.decreaseIndent();
+ pw.println();
+ }
+
if (filterUid == -1) {
pw.println();
pw.print("mReadyToRock="); pw.println(mReadyToRock);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ComponentController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ComponentController.java
index aca381ff2043..9b5956094e48 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ComponentController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ComponentController.java
@@ -93,7 +93,12 @@ public class ComponentController extends StateController {
}
};
- private final SparseArrayMap<ComponentName, ServiceInfo> mServiceInfoCache =
+ /**
+ * Cache containing the processName of the ServiceInfo (see {@link ServiceInfo#processName})
+ * if the Service exists and is available.
+ * {@code null} will be stored if the service is currently unavailable.
+ */
+ private final SparseArrayMap<ComponentName, String> mServiceProcessCache =
new SparseArrayMap<>();
private final ComponentStateUpdateFunctor mComponentStateUpdateFunctor =
@@ -135,18 +140,18 @@ public class ComponentController extends StateController {
@Override
@GuardedBy("mLock")
public void onUserRemovedLocked(int userId) {
- mServiceInfoCache.delete(userId);
+ mServiceProcessCache.delete(userId);
}
@Nullable
@GuardedBy("mLock")
- private ServiceInfo getServiceInfoLocked(JobStatus jobStatus) {
+ private String getServiceProcessLocked(JobStatus jobStatus) {
final ComponentName service = jobStatus.getServiceComponent();
final int userId = jobStatus.getUserId();
- if (mServiceInfoCache.contains(userId, service)) {
+ if (mServiceProcessCache.contains(userId, service)) {
// Return whatever is in the cache, even if it's null. When something changes, we
// clear the cache.
- return mServiceInfoCache.get(userId, service);
+ return mServiceProcessCache.get(userId, service);
}
ServiceInfo si;
@@ -165,30 +170,31 @@ public class ComponentController extends StateController {
// Write null to the cache so we don't keep querying PM.
si = null;
}
- mServiceInfoCache.add(userId, service, si);
+ final String processName = si == null ? null : si.processName;
+ mServiceProcessCache.add(userId, service, processName);
- return si;
+ return processName;
}
@GuardedBy("mLock")
private boolean updateComponentEnabledStateLocked(JobStatus jobStatus) {
- final ServiceInfo service = getServiceInfoLocked(jobStatus);
+ final String processName = getServiceProcessLocked(jobStatus);
- if (DEBUG && service == null) {
+ if (DEBUG && processName == null) {
Slog.v(TAG, jobStatus.toShortString() + " component not present");
}
- final ServiceInfo ogService = jobStatus.serviceInfo;
- jobStatus.serviceInfo = service;
- return !Objects.equals(ogService, service);
+ final String ogProcess = jobStatus.serviceProcessName;
+ jobStatus.serviceProcessName = processName;
+ return !Objects.equals(ogProcess, processName);
}
@GuardedBy("mLock")
private void clearComponentsForPackageLocked(final int userId, final String pkg) {
- final int uIdx = mServiceInfoCache.indexOfKey(userId);
- for (int c = mServiceInfoCache.numElementsForKey(userId) - 1; c >= 0; --c) {
- final ComponentName cn = mServiceInfoCache.keyAt(uIdx, c);
+ final int uIdx = mServiceProcessCache.indexOfKey(userId);
+ for (int c = mServiceProcessCache.numElementsForKey(userId) - 1; c >= 0; --c) {
+ final ComponentName cn = mServiceProcessCache.keyAt(uIdx, c);
if (cn.getPackageName().equals(pkg)) {
- mServiceInfoCache.delete(userId, cn);
+ mServiceProcessCache.delete(userId, cn);
}
}
}
@@ -207,7 +213,7 @@ public class ComponentController extends StateController {
private void updateComponentStateForUser(final int userId) {
synchronized (mLock) {
- mServiceInfoCache.delete(userId);
+ mServiceProcessCache.delete(userId);
updateComponentStatesLocked(jobStatus -> {
// Using user ID instead of source user ID because the service will run under the
// user ID, not source user ID.
@@ -247,15 +253,15 @@ public class ComponentController extends StateController {
@Override
@GuardedBy("mLock")
public void dumpControllerStateLocked(IndentingPrintWriter pw, Predicate<JobStatus> predicate) {
- for (int u = 0; u < mServiceInfoCache.numMaps(); ++u) {
- final int userId = mServiceInfoCache.keyAt(u);
- for (int p = 0; p < mServiceInfoCache.numElementsForKey(userId); ++p) {
- final ComponentName componentName = mServiceInfoCache.keyAt(u, p);
+ for (int u = 0; u < mServiceProcessCache.numMaps(); ++u) {
+ final int userId = mServiceProcessCache.keyAt(u);
+ for (int p = 0; p < mServiceProcessCache.numElementsForKey(userId); ++p) {
+ final ComponentName componentName = mServiceProcessCache.keyAt(u, p);
pw.print(userId);
pw.print("-");
pw.print(componentName);
pw.print(": ");
- pw.print(mServiceInfoCache.valueAt(u, p));
+ pw.print(mServiceProcessCache.valueAt(u, p));
pw.println();
}
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
index e69cbedb955e..2e41dfd2888c 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
@@ -46,6 +46,7 @@ import com.android.server.job.JobSchedulerService;
import com.android.server.utils.AlarmQueue;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
@@ -68,56 +69,60 @@ public final class FlexibilityController extends StateController {
private static final int FLEXIBLE_CONSTRAINTS =
JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS | SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS;
- @VisibleForTesting
- static final int NUM_JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS =
+ private static final int NUM_JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS =
Integer.bitCount(JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS);
static final int NUM_SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS =
Integer.bitCount(SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS);
- @VisibleForTesting
static final int NUM_FLEXIBLE_CONSTRAINTS = Integer.bitCount(FLEXIBLE_CONSTRAINTS);
private static final long NO_LIFECYCLE_END = Long.MAX_VALUE;
+ /**
+ * Keeps track of what flexible constraints are satisfied at the moment.
+ * Is updated by the other controllers.
+ */
+ @VisibleForTesting
+ @GuardedBy("mLock")
+ int mSatisfiedFlexibleConstraints;
+
/** Hard cutoff to remove flexible constraints. */
- private static final long DEADLINE_PROXIMITY_LIMIT_MS = 15 * MINUTE_IN_MILLIS;
+ private long mDeadlineProximityLimitMs =
+ FcConfig.DEFAULT_DEADLINE_PROXIMITY_LIMIT_MS;
/**
* The default deadline that all flexible constraints should be dropped by if a job lacks
* a deadline.
*/
+ private long mFallbackFlexibilityDeadlineMs =
+ FcConfig.DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS;
+
+ @GuardedBy("mLock")
@VisibleForTesting
- static final long DEFAULT_FLEXIBILITY_DEADLINE = 72 * HOUR_IN_MILLIS;
+ boolean mFlexibilityEnabled = FcConfig.DEFAULT_FLEXIBILITY_ENABLED;
+
+ private long mMinTimeBetweenFlexibilityAlarmsMs =
+ FcConfig.DEFAULT_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS;
/**
- * Keeps track of what flexible constraints are satisfied at the moment.
- * Is updated by the other controllers.
+ * The percent of a job's lifecycle to drop number of required constraints.
+ * mPercentToDropConstraints[i] denotes that at x% of a Jobs lifecycle,
+ * the controller should have i+1 constraints dropped.
*/
- @VisibleForTesting
- @GuardedBy("mLock")
- int mSatisfiedFlexibleConstraints;
- @GuardedBy("mLock")
- private boolean mFlexibilityEnabled = FcConstants.DEFAULT_FLEXIBILITY_ENABLED;
+ private int[] mPercentToDropConstraints;
@VisibleForTesting
@GuardedBy("mLock")
final FlexibilityTracker mFlexibilityTracker;
- private final FcConstants mFcConstants;
-
@VisibleForTesting
- final PrefetchController mPrefetchController;
-
@GuardedBy("mLock")
- private final FlexibilityAlarmQueue mFlexibilityAlarmQueue;
- private static final long MIN_TIME_BETWEEN_ALARMS_MS = MINUTE_IN_MILLIS;
+ final FlexibilityAlarmQueue mFlexibilityAlarmQueue;
+ @VisibleForTesting
+ final FcConfig mFcConfig;
- /**
- * The percent of a Jobs lifecycle to drop number of required constraints.
- * PERCENT_TO_DROP_CONSTRAINTS[i] denotes that at x% of a Jobs lifecycle,
- * the controller should have i+1 constraints dropped.
- */
- private static final int[] PERCENT_TO_DROP_CONSTRAINTS = {50, 60, 70, 80};
+ @VisibleForTesting
+ final PrefetchController mPrefetchController;
/**
* Stores the beginning of prefetch jobs lifecycle per app as a maximum of
@@ -164,9 +169,11 @@ public final class FlexibilityController extends StateController {
JobSchedulerService service, PrefetchController prefetchController) {
super(service);
mFlexibilityTracker = new FlexibilityTracker(NUM_FLEXIBLE_CONSTRAINTS);
- mFcConstants = new FcConstants();
+ mFcConfig = new FcConfig();
mFlexibilityAlarmQueue = new FlexibilityAlarmQueue(
mContext, JobSchedulerBackgroundThread.get().getLooper());
+ mPercentToDropConstraints =
+ mFcConfig.DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS;
mPrefetchController = prefetchController;
if (mFlexibilityEnabled) {
mPrefetchController.registerPrefetchChangedListener(mPrefetchChangedListener);
@@ -317,8 +324,7 @@ public final class FlexibilityController extends StateController {
return NO_LIFECYCLE_END;
}
return js.getLatestRunTimeElapsed() == JobStatus.NO_LATEST_RUNTIME
- ? earliest + DEFAULT_FLEXIBILITY_DEADLINE
- : js.getLatestRunTimeElapsed();
+ ? earliest + mFallbackFlexibilityDeadlineMs : js.getLatestRunTimeElapsed();
}
@VisibleForTesting
@@ -327,7 +333,7 @@ public final class FlexibilityController extends StateController {
final long earliest = getLifeCycleBeginningElapsedLocked(js);
final long latest = getLifeCycleEndElapsedLocked(js, earliest);
final long nowElapsed = sElapsedRealtimeClock.millis();
- if (latest == NO_LIFECYCLE_END || earliest > nowElapsed) {
+ if (latest == NO_LIFECYCLE_END || earliest >= nowElapsed) {
return 0;
}
if (nowElapsed > latest || latest == earliest) {
@@ -344,11 +350,19 @@ public final class FlexibilityController extends StateController {
long getNextConstraintDropTimeElapsedLocked(JobStatus js) {
final long earliest = getLifeCycleBeginningElapsedLocked(js);
final long latest = getLifeCycleEndElapsedLocked(js, earliest);
+ return getNextConstraintDropTimeElapsedLocked(js, earliest, latest);
+ }
+
+ /** The elapsed time that marks when the next constraint should be dropped. */
+ @VisibleForTesting
+ @ElapsedRealtimeLong
+ @GuardedBy("mLock")
+ long getNextConstraintDropTimeElapsedLocked(JobStatus js, long earliest, long latest) {
if (latest == NO_LIFECYCLE_END
- || js.getNumDroppedFlexibleConstraints() == PERCENT_TO_DROP_CONSTRAINTS.length) {
+ || js.getNumDroppedFlexibleConstraints() == mPercentToDropConstraints.length) {
return NO_LIFECYCLE_END;
}
- final int percent = PERCENT_TO_DROP_CONSTRAINTS[js.getNumDroppedFlexibleConstraints()];
+ final int percent = mPercentToDropConstraints[js.getNumDroppedFlexibleConstraints()];
final long percentInTime = ((latest - earliest) * percent) / 100;
return earliest + percentInTime;
}
@@ -390,8 +404,7 @@ public final class FlexibilityController extends StateController {
@Override
@GuardedBy("mLock")
public void onConstantsUpdatedLocked() {
- if (mFcConstants.mShouldReevaluateConstraints) {
- // Update job bookkeeping out of band.
+ if (mFcConfig.mShouldReevaluateConstraints) {
JobSchedulerBackgroundThread.getHandler().post(() -> {
final ArraySet<JobStatus> changedJobs = new ArraySet<>();
synchronized (mLock) {
@@ -401,6 +414,8 @@ public final class FlexibilityController extends StateController {
.getJobsByNumRequiredConstraints(j);
for (int i = 0; i < jobs.size(); i++) {
JobStatus js = jobs.valueAt(i);
+ mFlexibilityTracker.resetJobNumDroppedConstraints(js);
+ mFlexibilityAlarmQueue.scheduleDropNumConstraintsAlarm(js);
if (js.setFlexibilityConstraintSatisfied(
nowElapsed, isFlexibilitySatisfiedLocked(js))) {
changedJobs.add(js);
@@ -418,7 +433,13 @@ public final class FlexibilityController extends StateController {
@Override
@GuardedBy("mLock")
public void prepareForUpdatedConstantsLocked() {
- mFcConstants.mShouldReevaluateConstraints = false;
+ mFcConfig.mShouldReevaluateConstraints = false;
+ }
+
+ @Override
+ @GuardedBy("mLock")
+ public void processConstantLocked(DeviceConfig.Properties properties, String key) {
+ mFcConfig.processConstantLocked(properties, key);
}
@VisibleForTesting
@@ -461,8 +482,10 @@ public final class FlexibilityController extends StateController {
public void resetJobNumDroppedConstraints(JobStatus js) {
final int curPercent = getCurPercentOfLifecycleLocked(js);
int toDrop = 0;
- for (int i = 0; i < PERCENT_TO_DROP_CONSTRAINTS.length; i++) {
- if (curPercent >= PERCENT_TO_DROP_CONSTRAINTS[i]) {
+ final int jsMaxFlexibleConstraints = NUM_SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS
+ + (js.getPreferUnmetered() ? 1 : 0);
+ for (int i = 0; i < jsMaxFlexibleConstraints; i++) {
+ if (curPercent >= mPercentToDropConstraints[i]) {
toDrop++;
}
}
@@ -480,6 +503,9 @@ public final class FlexibilityController extends StateController {
* Jobs with 0 required flexible constraints are removed from the tracker.
*/
public boolean adjustJobsRequiredConstraints(JobStatus js, int n) {
+ if (n == 0) {
+ return false;
+ }
remove(js);
js.adjustNumRequiredFlexibleConstraints(n);
final long nowElapsed = sElapsedRealtimeClock.millis();
@@ -499,7 +525,7 @@ public final class FlexibilityController extends StateController {
public void dump(IndentingPrintWriter pw, Predicate<JobStatus> predicate) {
for (int i = 0; i < mTrackedJobs.size(); i++) {
ArraySet<JobStatus> jobs = mTrackedJobs.get(i);
- for (int j = 0; j < mTrackedJobs.size(); j++) {
+ for (int j = 0; j < jobs.size(); j++) {
final JobStatus js = jobs.valueAt(j);
if (!predicate.test(js)) {
continue;
@@ -514,11 +540,12 @@ public final class FlexibilityController extends StateController {
}
}
- private class FlexibilityAlarmQueue extends AlarmQueue<JobStatus> {
+ @VisibleForTesting
+ class FlexibilityAlarmQueue extends AlarmQueue<JobStatus> {
private FlexibilityAlarmQueue(Context context, Looper looper) {
super(context, looper, "*job.flexibility_check*",
"Flexible Constraint Check", false,
- MIN_TIME_BETWEEN_ALARMS_MS);
+ mMinTimeBetweenFlexibilityAlarmsMs);
}
@Override
@@ -526,16 +553,24 @@ public final class FlexibilityController extends StateController {
return js.getSourceUserId() == userId;
}
- protected void scheduleDropNumConstraintsAlarm(JobStatus js) {
+ public void scheduleDropNumConstraintsAlarm(JobStatus js) {
long nextTimeElapsed;
synchronized (mLock) {
- nextTimeElapsed = getNextConstraintDropTimeElapsedLocked(js);
+ final long earliest = getLifeCycleBeginningElapsedLocked(js);
+ final long latest = getLifeCycleEndElapsedLocked(js, earliest);
+ nextTimeElapsed = getNextConstraintDropTimeElapsedLocked(js, earliest, latest);
if (nextTimeElapsed == NO_LIFECYCLE_END) {
// There is no known or estimated next time to drop a constraint.
removeAlarmForKey(js);
return;
}
- mFlexibilityAlarmQueue.addAlarm(js, nextTimeElapsed);
+
+ if (latest - nextTimeElapsed < mDeadlineProximityLimitMs) {
+ mFlexibilityTracker.adjustJobsRequiredConstraints(
+ js, -js.getNumRequiredFlexibleConstraints());
+ return;
+ }
+ addAlarm(js, nextTimeElapsed);
}
}
@@ -546,13 +581,21 @@ public final class FlexibilityController extends StateController {
for (int i = 0; i < expired.size(); i++) {
JobStatus js = expired.valueAt(i);
boolean wasFlexibilitySatisfied = js.isConstraintSatisfied(CONSTRAINT_FLEXIBLE);
- long time = getNextConstraintDropTimeElapsedLocked(js);
- int toDecrease =
- js.getLatestRunTimeElapsed() - time < DEADLINE_PROXIMITY_LIMIT_MS
- ? -js.getNumRequiredFlexibleConstraints() : -1;
- if (mFlexibilityTracker.adjustJobsRequiredConstraints(js, toDecrease)
- && time != NO_LIFECYCLE_END) {
- mFlexibilityAlarmQueue.addAlarm(js, time);
+
+ final long earliest = getLifeCycleBeginningElapsedLocked(js);
+ final long latest = getLifeCycleEndElapsedLocked(js, earliest);
+ final long nowElapsed = sElapsedRealtimeClock.millis();
+
+ if (latest - nowElapsed < mDeadlineProximityLimitMs) {
+ mFlexibilityTracker.adjustJobsRequiredConstraints(js,
+ -js.getNumRequiredFlexibleConstraints());
+ } else {
+ long nextTimeElapsed =
+ getNextConstraintDropTimeElapsedLocked(js, earliest, latest);
+ if (mFlexibilityTracker.adjustJobsRequiredConstraints(js, -1)
+ && nextTimeElapsed != NO_LIFECYCLE_END) {
+ mFlexibilityAlarmQueue.addAlarm(js, nextTimeElapsed);
+ }
}
if (wasFlexibilitySatisfied != js.isConstraintSatisfied(CONSTRAINT_FLEXIBLE)) {
changedJobs.add(js);
@@ -564,19 +607,46 @@ public final class FlexibilityController extends StateController {
}
@VisibleForTesting
- class FcConstants {
+ class FcConfig {
private boolean mShouldReevaluateConstraints = false;
+ /** Prefix to use with all constant keys in order to "sub-namespace" the keys. */
+ private static final String FC_CONFIG_PREFIX = "fc_";
+
+ static final String KEY_FLEXIBILITY_ENABLED = FC_CONFIG_PREFIX + "enable_flexibility";
+ static final String KEY_DEADLINE_PROXIMITY_LIMIT =
+ FC_CONFIG_PREFIX + "flexibility_deadline_proximity_limit_ms";
+ static final String KEY_FALLBACK_FLEXIBILITY_DEADLINE =
+ FC_CONFIG_PREFIX + "fallback_flexibility_deadline_ms";
+ static final String KEY_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS =
+ FC_CONFIG_PREFIX + "min_alarm_time_flexibility_ms";
+ static final String KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS =
+ FC_CONFIG_PREFIX + "percents_to_drop_num_flexible_constraints";
+
private static final boolean DEFAULT_FLEXIBILITY_ENABLED = false;
+ @VisibleForTesting
+ static final long DEFAULT_DEADLINE_PROXIMITY_LIMIT_MS = 15 * MINUTE_IN_MILLIS;
+ @VisibleForTesting
+ static final long DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS = 72 * HOUR_IN_MILLIS;
+ private static final long DEFAULT_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS = MINUTE_IN_MILLIS;
+ @VisibleForTesting
+ final int[] DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS = {50, 60, 70, 80};
+ /**
+ * If false the controller will not track new jobs
+ * and the flexibility constraint will always be satisfied.
+ */
public boolean FLEXIBILITY_ENABLED = DEFAULT_FLEXIBILITY_ENABLED;
+ /** How close to a jobs' deadline all flexible constraints will be dropped. */
+ public long DEADLINE_PROXIMITY_LIMIT_MS = DEFAULT_DEADLINE_PROXIMITY_LIMIT_MS;
+ /** For jobs that lack a deadline, the time that will be used to drop all constraints by. */
+ public long FALLBACK_FLEXIBILITY_DEADLINE_MS = DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS;
+ public long MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS =
+ DEFAULT_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS;
+ /** The percentages of a jobs' lifecycle to drop the number of required constraints. */
+ public int[] PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS =
+ DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS;
- /** Prefix to use with all constant keys in order to "sub-namespace" the keys. */
- private static final String FC_CONSTANT_PREFIX = "fc_";
-
- static final String KEY_FLEXIBILITY_ENABLED = FC_CONSTANT_PREFIX + "enable_flexibility";
-
- // TODO(b/239925946): properly handle DeviceConfig and changing variables
@GuardedBy("mLock")
public void processConstantLocked(@NonNull DeviceConfig.Properties properties,
@NonNull String key) {
@@ -595,7 +665,68 @@ public final class FlexibilityController extends StateController {
}
}
break;
+ case KEY_DEADLINE_PROXIMITY_LIMIT:
+ DEADLINE_PROXIMITY_LIMIT_MS =
+ properties.getLong(key, DEFAULT_DEADLINE_PROXIMITY_LIMIT_MS);
+ if (mDeadlineProximityLimitMs != DEADLINE_PROXIMITY_LIMIT_MS) {
+ mDeadlineProximityLimitMs = DEADLINE_PROXIMITY_LIMIT_MS;
+ mShouldReevaluateConstraints = true;
+ }
+ break;
+ case KEY_FALLBACK_FLEXIBILITY_DEADLINE:
+ FALLBACK_FLEXIBILITY_DEADLINE_MS =
+ properties.getLong(key, DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS);
+ if (mFallbackFlexibilityDeadlineMs != FALLBACK_FLEXIBILITY_DEADLINE_MS) {
+ mFallbackFlexibilityDeadlineMs = FALLBACK_FLEXIBILITY_DEADLINE_MS;
+ mShouldReevaluateConstraints = true;
+ }
+ break;
+ case KEY_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS:
+ MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS =
+ properties.getLong(key, DEFAULT_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS);
+ if (mMinTimeBetweenFlexibilityAlarmsMs
+ != MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS) {
+ mMinTimeBetweenFlexibilityAlarmsMs = MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS;
+ mShouldReevaluateConstraints = true;
+ }
+ break;
+ case KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS:
+ String dropPercentString = properties.getString(key, "");
+ PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS =
+ parsePercentToDropString(dropPercentString);
+ if (PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS != null
+ && !Arrays.equals(mPercentToDropConstraints,
+ PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS)) {
+ mPercentToDropConstraints = PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS;
+ mShouldReevaluateConstraints = true;
+ }
+ break;
+ }
+ }
+
+ private int[] parsePercentToDropString(String s) {
+ String[] dropPercentString = s.split(",");
+ int[] dropPercentInt = new int[NUM_FLEXIBLE_CONSTRAINTS];
+ if (dropPercentInt.length != dropPercentString.length) {
+ return DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS;
}
+ int prevPercent = 0;
+ for (int i = 0; i < dropPercentString.length; i++) {
+ try {
+ dropPercentInt[i] =
+ Integer.parseInt(dropPercentString[i]);
+ } catch (NumberFormatException ex) {
+ Slog.e(TAG, "Provided string was improperly formatted.", ex);
+ return DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS;
+ }
+ if (dropPercentInt[i] < prevPercent) {
+ Slog.wtf(TAG, "Percents to drop constraints were not in increasing order.");
+ return DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS;
+ }
+ prevPercent = dropPercentInt[i];
+ }
+
+ return dropPercentInt;
}
private void dump(IndentingPrintWriter pw) {
@@ -612,8 +743,8 @@ public final class FlexibilityController extends StateController {
@VisibleForTesting
@NonNull
- FcConstants getFcConstants() {
- return mFcConstants;
+ FcConfig getFcConfig() {
+ return mFcConfig;
}
@Override
@@ -623,6 +754,6 @@ public final class FlexibilityController extends StateController {
pw.println();
mFlexibilityTracker.dump(pw, predicate);
- mFcConstants.dump(pw);
+ mFcConfig.dump(pw);
}
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index 42e60e419de0..4ce6b32166c1 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -35,7 +35,6 @@ import android.app.job.JobParameters;
import android.app.job.JobWorkItem;
import android.content.ClipData;
import android.content.ComponentName;
-import android.content.pm.ServiceInfo;
import android.net.Network;
import android.net.NetworkRequest;
import android.net.Uri;
@@ -51,6 +50,7 @@ import android.util.Slog;
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.LocalServices;
@@ -355,7 +355,7 @@ public final class JobStatus {
public ArraySet<Uri> changedUris;
public ArraySet<String> changedAuthorities;
public Network network;
- public ServiceInfo serviceInfo;
+ public String serviceProcessName;
/** The evaluated bias of the job when it started running. */
public int lastEvaluatedBias;
@@ -1668,7 +1668,8 @@ public final class JobStatus {
return readinessStatusWithConstraint(constraint, true);
}
- private boolean readinessStatusWithConstraint(int constraint, boolean value) {
+ @VisibleForTesting
+ boolean readinessStatusWithConstraint(int constraint, boolean value) {
boolean oldValue = false;
int satisfied = mSatisfiedConstraintsOfInterest;
switch (constraint) {
@@ -1704,6 +1705,15 @@ public final class JobStatus {
break;
}
+ // The flexibility constraint relies on other constraints to be satisfied.
+ // This function lacks the information to determine if flexibility will be satisfied.
+ // But for the purposes of this function it is still useful to know the jobs' readiness
+ // not including the flexibility constraint. If flexibility is the constraint in question
+ // we can proceed as normal.
+ if (constraint != CONSTRAINT_FLEXIBLE) {
+ satisfied |= CONSTRAINT_FLEXIBLE;
+ }
+
boolean toReturn = isReady(satisfied);
switch (constraint) {
@@ -1746,7 +1756,7 @@ public final class JobStatus {
// run if its constraints are satisfied).
// DeviceNotDozing implicit constraint must be satisfied
// NotRestrictedInBackground implicit constraint must be satisfied
- return mReadyNotDozing && mReadyNotRestrictedInBg && (serviceInfo != null)
+ return mReadyNotDozing && mReadyNotRestrictedInBg && (serviceProcessName != null)
&& (mReadyDeadlineSatisfied || isConstraintsSatisfied(satisfiedConstraints));
}
@@ -2285,7 +2295,7 @@ public final class JobStatus {
pw.println(mReadyDynamicSatisfied);
}
pw.print("readyComponentEnabled: ");
- pw.println(serviceInfo != null);
+ pw.println(serviceProcessName != null);
if ((getFlags() & JobInfo.FLAG_EXPEDITED) != 0) {
pw.print("expeditedQuotaApproved: ");
pw.print(mExpeditedQuotaApproved);
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/Agent.java b/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
index 8b8a57d248b9..d4a1cd234c39 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
@@ -19,6 +19,8 @@ package com.android.server.tare;
import static android.text.format.DateUtils.DAY_IN_MILLIS;
import static com.android.server.tare.EconomicPolicy.REGULATION_BASIC_INCOME;
+import static com.android.server.tare.EconomicPolicy.REGULATION_BG_RESTRICTED;
+import static com.android.server.tare.EconomicPolicy.REGULATION_BG_UNRESTRICTED;
import static com.android.server.tare.EconomicPolicy.REGULATION_BIRTHRIGHT;
import static com.android.server.tare.EconomicPolicy.REGULATION_DEMOTION;
import static com.android.server.tare.EconomicPolicy.REGULATION_PROMOTION;
@@ -34,8 +36,6 @@ import static com.android.server.tare.TareUtils.getCurrentTimeMillis;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -46,6 +46,7 @@ import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArrayMap;
+import android.util.SparseSetArray;
import android.util.TimeUtils;
import com.android.internal.annotations.GuardedBy;
@@ -284,6 +285,7 @@ class Agent {
for (int i = 0; i < pkgNames.size(); ++i) {
final String pkgName = pkgNames.valueAt(i);
+ final boolean isVip = mIrs.isVip(userId, pkgName);
SparseArrayMap<String, OngoingEvent> ongoingEvents =
mCurrentOngoingEvents.get(userId, pkgName);
if (ongoingEvents != null) {
@@ -298,8 +300,8 @@ class Agent {
for (int n = 0; n < size; ++n) {
final ActionAffordabilityNote note = actionAffordabilityNotes.valueAt(n);
note.recalculateCosts(economicPolicy, userId, pkgName);
- final boolean isAffordable =
- isAffordableLocked(newBalance,
+ final boolean isAffordable = isVip
+ || isAffordableLocked(newBalance,
note.getCachedModifiedPrice(), note.getCtp());
if (note.isCurrentlyAffordable() != isAffordable) {
note.setNewAffordability(isAffordable);
@@ -313,6 +315,51 @@ class Agent {
}
@GuardedBy("mLock")
+ void onVipStatusChangedLocked(final int userId, @NonNull String pkgName) {
+ final long now = getCurrentTimeMillis();
+ final long nowElapsed = SystemClock.elapsedRealtime();
+ final CompleteEconomicPolicy economicPolicy = mIrs.getCompleteEconomicPolicyLocked();
+
+ final boolean isVip = mIrs.isVip(userId, pkgName);
+ SparseArrayMap<String, OngoingEvent> ongoingEvents =
+ mCurrentOngoingEvents.get(userId, pkgName);
+ if (ongoingEvents != null) {
+ mOngoingEventUpdater.reset(userId, pkgName, now, nowElapsed);
+ ongoingEvents.forEach(mOngoingEventUpdater);
+ }
+ final ArraySet<ActionAffordabilityNote> actionAffordabilityNotes =
+ mActionAffordabilityNotes.get(userId, pkgName);
+ if (actionAffordabilityNotes != null) {
+ final int size = actionAffordabilityNotes.size();
+ final long newBalance =
+ mScribe.getLedgerLocked(userId, pkgName).getCurrentBalance();
+ for (int n = 0; n < size; ++n) {
+ final ActionAffordabilityNote note = actionAffordabilityNotes.valueAt(n);
+ note.recalculateCosts(economicPolicy, userId, pkgName);
+ final boolean isAffordable = isVip
+ || isAffordableLocked(newBalance,
+ note.getCachedModifiedPrice(), note.getCtp());
+ if (note.isCurrentlyAffordable() != isAffordable) {
+ note.setNewAffordability(isAffordable);
+ mIrs.postAffordabilityChanged(userId, pkgName, note);
+ }
+ }
+ }
+ scheduleBalanceCheckLocked(userId, pkgName);
+ }
+
+ @GuardedBy("mLock")
+ void onVipStatusChangedLocked(@NonNull SparseSetArray<String> pkgs) {
+ for (int u = pkgs.size() - 1; u >= 0; --u) {
+ final int userId = pkgs.keyAt(u);
+
+ for (int p = pkgs.sizeAt(u) - 1; p >= 0; --p) {
+ onVipStatusChangedLocked(userId, pkgs.valueAt(u, p));
+ }
+ }
+ }
+
+ @GuardedBy("mLock")
private void onAnythingChangedLocked(final boolean updateOngoingEvents) {
final long now = getCurrentTimeMillis();
final long nowElapsed = SystemClock.elapsedRealtime();
@@ -349,11 +396,12 @@ class Agent {
if (actionAffordabilityNotes != null) {
final int size = actionAffordabilityNotes.size();
final long newBalance = getBalanceLocked(userId, pkgName);
+ final boolean isVip = mIrs.isVip(userId, pkgName);
for (int n = 0; n < size; ++n) {
final ActionAffordabilityNote note = actionAffordabilityNotes.valueAt(n);
note.recalculateCosts(economicPolicy, userId, pkgName);
- final boolean isAffordable =
- isAffordableLocked(newBalance,
+ final boolean isAffordable = isVip
+ || isAffordableLocked(newBalance,
note.getCachedModifiedPrice(), note.getCtp());
if (note.isCurrentlyAffordable() != isAffordable) {
note.setNewAffordability(isAffordable);
@@ -454,14 +502,22 @@ class Agent {
"Tried to adjust system balance for " + appToString(userId, pkgName));
return;
}
+ if (mIrs.isVip(userId, pkgName)) {
+ // This could happen if the app was made a VIP after it started performing actions.
+ // Continue recording the transaction for debugging purposes, but don't let it change
+ // any numbers.
+ transaction = new Ledger.Transaction(
+ transaction.startTimeMs, transaction.endTimeMs,
+ transaction.eventId, transaction.tag, 0 /* delta */, transaction.ctp);
+ }
final CompleteEconomicPolicy economicPolicy = mIrs.getCompleteEconomicPolicyLocked();
final long originalBalance = ledger.getCurrentBalance();
+ final long maxBalance = economicPolicy.getMaxSatiatedBalance(userId, pkgName);
if (transaction.delta > 0
- && originalBalance + transaction.delta > economicPolicy.getMaxSatiatedBalance()) {
+ && originalBalance + transaction.delta > maxBalance) {
// Set lower bound at 0 so we don't accidentally take away credits when we were trying
// to _give_ the app credits.
- final long newDelta =
- Math.max(0, economicPolicy.getMaxSatiatedBalance() - originalBalance);
+ final long newDelta = Math.max(0, maxBalance - originalBalance);
Slog.i(TAG, "Would result in becoming too rich. Decreasing transaction "
+ eventToString(transaction.eventId)
+ (transaction.tag == null ? "" : ":" + transaction.tag)
@@ -479,10 +535,11 @@ class Agent {
mActionAffordabilityNotes.get(userId, pkgName);
if (actionAffordabilityNotes != null) {
final long newBalance = ledger.getCurrentBalance();
+ final boolean isVip = mIrs.isVip(userId, pkgName);
for (int i = 0; i < actionAffordabilityNotes.size(); ++i) {
final ActionAffordabilityNote note = actionAffordabilityNotes.valueAt(i);
- final boolean isAffordable =
- isAffordableLocked(newBalance,
+ final boolean isAffordable = isVip
+ || isAffordableLocked(newBalance,
note.getCachedModifiedPrice(), note.getCtp());
if (note.isCurrentlyAffordable() != isAffordable) {
note.setNewAffordability(isAffordable);
@@ -605,16 +662,57 @@ class Agent {
}
}
+ /**
+ * Reclaim all ARCs from an app that was just restricted.
+ */
+ @GuardedBy("mLock")
+ void onAppRestrictedLocked(final int userId, @NonNull final String pkgName) {
+ final long curBalance = getBalanceLocked(userId, pkgName);
+ final long minBalance = mIrs.getMinBalanceLocked(userId, pkgName);
+ if (curBalance <= minBalance) {
+ return;
+ }
+ if (DEBUG) {
+ Slog.i(TAG, "App restricted! Taking " + curBalance
+ + " from " + appToString(userId, pkgName));
+ }
+
+ final long now = getCurrentTimeMillis();
+ final Ledger ledger = mScribe.getLedgerLocked(userId, pkgName);
+ recordTransactionLocked(userId, pkgName, ledger,
+ new Ledger.Transaction(now, now, REGULATION_BG_RESTRICTED, null, -curBalance, 0),
+ true);
+ }
+
+ /**
+ * Give an app that was just unrestricted some ARCs.
+ */
+ @GuardedBy("mLock")
+ void onAppUnrestrictedLocked(final int userId, @NonNull final String pkgName) {
+ final Ledger ledger = mScribe.getLedgerLocked(userId, pkgName);
+ if (ledger.getCurrentBalance() > 0) {
+ Slog.wtf(TAG, "App " + pkgName + " had credits while it was restricted");
+ // App already got credits somehow. Move along.
+ return;
+ }
+
+ final long now = getCurrentTimeMillis();
+
+ recordTransactionLocked(userId, pkgName, ledger,
+ new Ledger.Transaction(now, now, REGULATION_BG_UNRESTRICTED, null,
+ mIrs.getMinBalanceLocked(userId, pkgName), 0), true);
+ }
+
/** Returns true if an app should be given credits in the general distributions. */
- private boolean shouldGiveCredits(@NonNull PackageInfo packageInfo) {
- final ApplicationInfo applicationInfo = packageInfo.applicationInfo;
+ private boolean shouldGiveCredits(@NonNull InstalledPackageInfo packageInfo) {
// Skip apps that wouldn't be doing any work. Giving them ARCs would be wasteful.
- if (applicationInfo == null || !applicationInfo.hasCode()) {
+ if (!packageInfo.hasCode) {
return false;
}
- final int userId = UserHandle.getUserId(packageInfo.applicationInfo.uid);
+ final int userId = UserHandle.getUserId(packageInfo.uid);
// No point allocating ARCs to the system. It can do whatever it wants.
- return !mIrs.isSystem(userId, packageInfo.packageName);
+ return !mIrs.isSystem(userId, packageInfo.packageName)
+ && !mIrs.isPackageRestricted(userId, packageInfo.packageName);
}
void onCreditSupplyChanged() {
@@ -623,15 +721,15 @@ class Agent {
@GuardedBy("mLock")
void distributeBasicIncomeLocked(int batteryLevel) {
- List<PackageInfo> pkgs = mIrs.getInstalledPackages();
+ final List<InstalledPackageInfo> pkgs = mIrs.getInstalledPackages();
final long now = getCurrentTimeMillis();
for (int i = 0; i < pkgs.size(); ++i) {
- final PackageInfo pkgInfo = pkgs.get(i);
+ final InstalledPackageInfo pkgInfo = pkgs.get(i);
if (!shouldGiveCredits(pkgInfo)) {
continue;
}
- final int userId = UserHandle.getUserId(pkgInfo.applicationInfo.uid);
+ final int userId = UserHandle.getUserId(pkgInfo.uid);
final String pkgName = pkgInfo.packageName;
final Ledger ledger = mScribe.getLedgerLocked(userId, pkgName);
final long minBalance = mIrs.getMinBalanceLocked(userId, pkgName);
@@ -659,11 +757,11 @@ class Agent {
@GuardedBy("mLock")
void grantBirthrightsLocked(final int userId) {
- final List<PackageInfo> pkgs = mIrs.getInstalledPackages(userId);
+ final List<InstalledPackageInfo> pkgs = mIrs.getInstalledPackages(userId);
final long now = getCurrentTimeMillis();
for (int i = 0; i < pkgs.size(); ++i) {
- final PackageInfo packageInfo = pkgs.get(i);
+ final InstalledPackageInfo packageInfo = pkgs.get(i);
if (!shouldGiveCredits(packageInfo)) {
continue;
}
@@ -869,7 +967,7 @@ class Agent {
private void scheduleBalanceCheckLocked(final int userId, @NonNull final String pkgName) {
SparseArrayMap<String, OngoingEvent> ongoingEvents =
mCurrentOngoingEvents.get(userId, pkgName);
- if (ongoingEvents == null) {
+ if (ongoingEvents == null || mIrs.isVip(userId, pkgName)) {
// No ongoing transactions. No reason to schedule
mBalanceThresholdAlarmQueue.removeAlarmForKey(new Package(userId, pkgName));
return;
@@ -1062,9 +1160,10 @@ class Agent {
note.setNewAffordability(true);
return;
}
+ final boolean isVip = mIrs.isVip(userId, pkgName);
note.recalculateCosts(economicPolicy, userId, pkgName);
- note.setNewAffordability(
- isAffordableLocked(getBalanceLocked(userId, pkgName),
+ note.setNewAffordability(isVip
+ || isAffordableLocked(getBalanceLocked(userId, pkgName),
note.getCachedModifiedPrice(), note.getCtp()));
mIrs.postAffordabilityChanged(userId, pkgName, note);
// Update ongoing alarm
@@ -1203,11 +1302,12 @@ class Agent {
if (actionAffordabilityNotes != null
&& actionAffordabilityNotes.size() > 0) {
final long newBalance = getBalanceLocked(userId, pkgName);
+ final boolean isVip = mIrs.isVip(userId, pkgName);
for (int i = 0; i < actionAffordabilityNotes.size(); ++i) {
final ActionAffordabilityNote note =
actionAffordabilityNotes.valueAt(i);
- final boolean isAffordable = isAffordableLocked(
+ final boolean isAffordable = isVip || isAffordableLocked(
newBalance, note.getCachedModifiedPrice(), note.getCtp());
if (note.isCurrentlyAffordable() != isAffordable) {
note.setNewAffordability(isAffordable);
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java
index a46430feb688..e791e98a6698 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java
@@ -149,7 +149,6 @@ public class AlarmManagerEconomicPolicy extends EconomicPolicy {
private long mHardSatiatedConsumptionLimit;
private final KeyValueListParser mParser = new KeyValueListParser(',');
- private final InternalResourceService mInternalResourceService;
private final Injector mInjector;
private final SparseArray<Action> mActions = new SparseArray<>();
@@ -157,7 +156,6 @@ public class AlarmManagerEconomicPolicy extends EconomicPolicy {
AlarmManagerEconomicPolicy(InternalResourceService irs, Injector injector) {
super(irs);
- mInternalResourceService = irs;
mInjector = injector;
loadConstants("", null);
}
@@ -165,14 +163,17 @@ public class AlarmManagerEconomicPolicy extends EconomicPolicy {
@Override
void setup(@NonNull DeviceConfig.Properties properties) {
super.setup(properties);
- ContentResolver resolver = mInternalResourceService.getContext().getContentResolver();
+ ContentResolver resolver = mIrs.getContext().getContentResolver();
loadConstants(mInjector.getSettingsGlobalString(resolver, TARE_ALARM_MANAGER_CONSTANTS),
properties);
}
@Override
long getMinSatiatedBalance(final int userId, @NonNull final String pkgName) {
- if (mInternalResourceService.isPackageExempted(userId, pkgName)) {
+ if (mIrs.isPackageRestricted(userId, pkgName)) {
+ return 0;
+ }
+ if (mIrs.isPackageExempted(userId, pkgName)) {
return mMinSatiatedBalanceExempted;
}
// TODO: take other exemptions into account
@@ -180,7 +181,10 @@ public class AlarmManagerEconomicPolicy extends EconomicPolicy {
}
@Override
- long getMaxSatiatedBalance() {
+ long getMaxSatiatedBalance(int userId, @NonNull String pkgName) {
+ if (mIrs.isPackageRestricted(userId, pkgName)) {
+ return 0;
+ }
// TODO(230501287): adjust balance based on whether the app has the SCHEDULE_EXACT_ALARM
// permission granted. Apps without the permission granted shouldn't need a high balance
// since they won't be able to use exact alarms. Apps with the permission granted could
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/CompleteEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/CompleteEconomicPolicy.java
index 5d9cce84a7ac..625f99d64ef4 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/CompleteEconomicPolicy.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/CompleteEconomicPolicy.java
@@ -36,7 +36,6 @@ public class CompleteEconomicPolicy extends EconomicPolicy {
/** Lazily populated set of rewards covered by this policy. */
private final SparseArray<Reward> mRewards = new SparseArray<>();
private final int[] mCostModifiers;
- private long mMaxSatiatedBalance;
private long mInitialConsumptionLimit;
private long mHardConsumptionLimit;
@@ -80,16 +79,13 @@ public class CompleteEconomicPolicy extends EconomicPolicy {
}
private void updateLimits() {
- long maxSatiatedBalance = 0;
long initialConsumptionLimit = 0;
long hardConsumptionLimit = 0;
for (int i = 0; i < mEnabledEconomicPolicies.size(); ++i) {
final EconomicPolicy economicPolicy = mEnabledEconomicPolicies.valueAt(i);
- maxSatiatedBalance += economicPolicy.getMaxSatiatedBalance();
initialConsumptionLimit += economicPolicy.getInitialSatiatedConsumptionLimit();
hardConsumptionLimit += economicPolicy.getHardSatiatedConsumptionLimit();
}
- mMaxSatiatedBalance = maxSatiatedBalance;
mInitialConsumptionLimit = initialConsumptionLimit;
mHardConsumptionLimit = hardConsumptionLimit;
}
@@ -104,8 +100,12 @@ public class CompleteEconomicPolicy extends EconomicPolicy {
}
@Override
- long getMaxSatiatedBalance() {
- return mMaxSatiatedBalance;
+ long getMaxSatiatedBalance(int userId, @NonNull String pkgName) {
+ long max = 0;
+ for (int i = 0; i < mEnabledEconomicPolicies.size(); ++i) {
+ max += mEnabledEconomicPolicies.valueAt(i).getMaxSatiatedBalance(userId, pkgName);
+ }
+ return max;
}
@Override
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java
index 0937e7ba4055..2fb0c1a36e07 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java
@@ -67,6 +67,9 @@ public abstract class EconomicPolicy {
static final int REGULATION_WEALTH_RECLAMATION = TYPE_REGULATION | 2;
static final int REGULATION_PROMOTION = TYPE_REGULATION | 3;
static final int REGULATION_DEMOTION = TYPE_REGULATION | 4;
+ /** App is fully restricted from running in the background. */
+ static final int REGULATION_BG_RESTRICTED = TYPE_REGULATION | 5;
+ static final int REGULATION_BG_UNRESTRICTED = TYPE_REGULATION | 6;
static final int REWARD_NOTIFICATION_SEEN = TYPE_REWARD | 0;
static final int REWARD_NOTIFICATION_INTERACTION = TYPE_REWARD | 1;
@@ -169,9 +172,11 @@ public abstract class EconomicPolicy {
}
}
+ protected final InternalResourceService mIrs;
private static final Modifier[] COST_MODIFIER_BY_INDEX = new Modifier[NUM_COST_MODIFIERS];
EconomicPolicy(@NonNull InternalResourceService irs) {
+ mIrs = irs;
for (int mId : getCostModifiers()) {
initModifier(mId, irs);
}
@@ -208,7 +213,7 @@ public abstract class EconomicPolicy {
* exists to ensure that no single app accumulate all available resources and increases fairness
* for all apps.
*/
- abstract long getMaxSatiatedBalance();
+ abstract long getMaxSatiatedBalance(int userId, @NonNull String pkgName);
/**
* Returns the maximum number of cakes that should be consumed during a full 100% discharge
@@ -240,7 +245,7 @@ public abstract class EconomicPolicy {
@NonNull
final Cost getCostOfAction(int actionId, int userId, @NonNull String pkgName) {
final Action action = getAction(actionId);
- if (action == null) {
+ if (action == null || mIrs.isVip(userId, pkgName)) {
return new Cost(0, 0);
}
long ctp = action.costToProduce;
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/InstalledPackageInfo.java b/apex/jobscheduler/service/java/com/android/server/tare/InstalledPackageInfo.java
new file mode 100644
index 000000000000..da544bb6a2eb
--- /dev/null
+++ b/apex/jobscheduler/service/java/com/android/server/tare/InstalledPackageInfo.java
@@ -0,0 +1,38 @@
+/*
+ * 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.tare;
+
+import android.annotation.NonNull;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.os.UserHandle;
+
+/** POJO to cache only the information about installed packages that TARE cares about. */
+class InstalledPackageInfo {
+ static final int NO_UID = -1;
+
+ public final int uid;
+ public final String packageName;
+ public final boolean hasCode;
+
+ InstalledPackageInfo(@NonNull PackageInfo packageInfo) {
+ final ApplicationInfo applicationInfo = packageInfo.applicationInfo;
+ this.uid = applicationInfo == null ? NO_UID : applicationInfo.uid;
+ this.packageName = packageInfo.packageName;
+ this.hasCode = applicationInfo != null && applicationInfo.hasCode();
+ }
+}
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 6d5c16021ea9..2b8272208c3e 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
@@ -29,6 +29,7 @@ import static com.android.server.tare.TareUtils.getCurrentTimeMillis;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AlarmManager;
+import android.app.AppOpsManager;
import android.app.tare.IEconomyManager;
import android.app.usage.UsageEvents;
import android.app.usage.UsageStatsManagerInternal;
@@ -48,6 +49,7 @@ import android.os.Handler;
import android.os.IDeviceIdleController;
import android.os.Looper;
import android.os.Message;
+import android.os.ParcelFileDescriptor;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -63,6 +65,8 @@ import android.util.SparseArrayMap;
import android.util.SparseSetArray;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.app.IAppOpsCallback;
+import com.android.internal.app.IAppOpsService;
import com.android.internal.os.SomeArgs;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
@@ -118,6 +122,7 @@ public class InternalResourceService extends SystemService {
private final PackageManager mPackageManager;
private final PackageManagerInternal mPackageManagerInternal;
+ private IAppOpsService mAppOpsService;
private IDeviceIdleController mDeviceIdleController;
private final Agent mAgent;
@@ -131,7 +136,7 @@ public class InternalResourceService extends SystemService {
@NonNull
@GuardedBy("mLock")
- private final List<PackageInfo> mPkgCache = new ArrayList<>();
+ private final List<InstalledPackageInfo> mPkgCache = new ArrayList<>();
/** Cached mapping of UIDs (for all users) to a list of packages in the UID. */
@GuardedBy("mLock")
@@ -144,11 +149,20 @@ public class InternalResourceService extends SystemService {
private final CopyOnWriteArraySet<TareStateChangeListener> mStateChangeListeners =
new CopyOnWriteArraySet<>();
+ /**
+ * List of packages that are fully restricted and shouldn't be allowed to run in the background.
+ */
+ @GuardedBy("mLock")
+ private final SparseSetArray<String> mRestrictedApps = new SparseSetArray<>();
+
/** List of packages that are "exempted" from battery restrictions. */
// TODO(144864180): include userID
@GuardedBy("mLock")
private ArraySet<String> mExemptedApps = new ArraySet<>();
+ @GuardedBy("mLock")
+ private final SparseArrayMap<String, Boolean> mVipOverrides = new SparseArrayMap<>();
+
private volatile boolean mIsEnabled;
private volatile int mBootPhase;
private volatile boolean mExemptListLoaded;
@@ -156,6 +170,30 @@ public class InternalResourceService extends SystemService {
@GuardedBy("mLock")
private int mCurrentBatteryLevel;
+ private final IAppOpsCallback mApbListener = new IAppOpsCallback.Stub() {
+ @Override
+ public void opChanged(int op, int uid, String packageName) {
+ boolean restricted = false;
+ try {
+ restricted = mAppOpsService.checkOperation(
+ AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, uid, packageName)
+ != AppOpsManager.MODE_ALLOWED;
+ } catch (RemoteException e) {
+ // Shouldn't happen
+ }
+ final int userId = UserHandle.getUserId(uid);
+ synchronized (mLock) {
+ if (restricted) {
+ if (mRestrictedApps.add(userId, packageName)) {
+ mAgent.onAppRestrictedLocked(userId, packageName);
+ }
+ } else if (mRestrictedApps.remove(UserHandle.getUserId(uid), packageName)) {
+ mAgent.onAppUnrestrictedLocked(userId, packageName);
+ }
+ }
+ }
+ };
+
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Nullable
private String getPackageName(Intent intent) {
@@ -276,9 +314,11 @@ public class InternalResourceService extends SystemService {
switch (phase) {
case PHASE_SYSTEM_SERVICES_READY:
- mConfigObserver.start();
+ mAppOpsService = IAppOpsService.Stub.asInterface(
+ ServiceManager.getService(Context.APP_OPS_SERVICE));
mDeviceIdleController = IDeviceIdleController.Stub.asInterface(
ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
+ mConfigObserver.start();
onBootPhaseSystemServicesReady();
break;
case PHASE_THIRD_PARTY_APPS_CAN_START:
@@ -303,7 +343,7 @@ public class InternalResourceService extends SystemService {
}
@NonNull
- List<PackageInfo> getInstalledPackages() {
+ List<InstalledPackageInfo> getInstalledPackages() {
synchronized (mLock) {
return mPkgCache;
}
@@ -311,13 +351,12 @@ public class InternalResourceService extends SystemService {
/** Returns the installed packages for the specified user. */
@NonNull
- List<PackageInfo> getInstalledPackages(final int userId) {
- final List<PackageInfo> userPkgs = new ArrayList<>();
+ List<InstalledPackageInfo> getInstalledPackages(final int userId) {
+ final List<InstalledPackageInfo> userPkgs = new ArrayList<>();
synchronized (mLock) {
for (int i = 0; i < mPkgCache.size(); ++i) {
- final PackageInfo packageInfo = mPkgCache.get(i);
- if (packageInfo.applicationInfo != null
- && UserHandle.getUserId(packageInfo.applicationInfo.uid) == userId) {
+ final InstalledPackageInfo packageInfo = mPkgCache.get(i);
+ if (UserHandle.getUserId(packageInfo.uid) == userId) {
userPkgs.add(packageInfo);
}
}
@@ -362,6 +401,12 @@ public class InternalResourceService extends SystemService {
}
}
+ boolean isPackageRestricted(final int userId, @NonNull String pkgName) {
+ synchronized (mLock) {
+ return mRestrictedApps.contains(userId, pkgName);
+ }
+ }
+
boolean isSystem(final int userId, @NonNull String pkgName) {
if ("android".equals(pkgName)) {
return true;
@@ -369,6 +414,21 @@ public class InternalResourceService extends SystemService {
return UserHandle.isCore(getUid(userId, pkgName));
}
+ boolean isVip(final int userId, @NonNull String pkgName) {
+ synchronized (mLock) {
+ final Boolean override = mVipOverrides.get(userId, pkgName);
+ if (override != null) {
+ return override;
+ }
+ }
+ if (isSystem(userId, pkgName)) {
+ // The government, I mean the system, can create ARCs as it needs to in order to
+ // operate.
+ return true;
+ }
+ return false;
+ }
+
void onBatteryLevelChanged() {
synchronized (mLock) {
final int newBatteryLevel = getCurrentBatteryLevel();
@@ -451,7 +511,7 @@ public class InternalResourceService extends SystemService {
mPackageToUidCache.add(userId, pkgName, uid);
}
synchronized (mLock) {
- mPkgCache.add(packageInfo);
+ mPkgCache.add(new InstalledPackageInfo(packageInfo));
mUidToPackageCache.add(uid, pkgName);
// TODO: only do this when the user first launches the app (app leaves stopped state)
mAgent.grantBirthrightLocked(userId, pkgName);
@@ -471,9 +531,10 @@ public class InternalResourceService extends SystemService {
}
synchronized (mLock) {
mUidToPackageCache.remove(uid, pkgName);
+ mVipOverrides.delete(userId, pkgName);
for (int i = 0; i < mPkgCache.size(); ++i) {
- PackageInfo pkgInfo = mPkgCache.get(i);
- if (UserHandle.getUserId(pkgInfo.applicationInfo.uid) == userId
+ final InstalledPackageInfo pkgInfo = mPkgCache.get(i);
+ if (UserHandle.getUserId(pkgInfo.uid) == userId
&& pkgName.equals(pkgInfo.packageName)) {
mPkgCache.remove(i);
break;
@@ -496,20 +557,24 @@ public class InternalResourceService extends SystemService {
void onUserAdded(final int userId) {
synchronized (mLock) {
- mPkgCache.addAll(
- mPackageManager.getInstalledPackagesAsUser(PACKAGE_QUERY_FLAGS, userId));
+ final List<PackageInfo> pkgs =
+ mPackageManager.getInstalledPackagesAsUser(PACKAGE_QUERY_FLAGS, userId);
+ for (int i = pkgs.size() - 1; i >= 0; --i) {
+ mPkgCache.add(new InstalledPackageInfo(pkgs.get(i)));
+ }
mAgent.grantBirthrightsLocked(userId);
}
}
void onUserRemoved(final int userId) {
synchronized (mLock) {
+ mVipOverrides.delete(userId);
ArrayList<String> removedPkgs = new ArrayList<>();
for (int i = mPkgCache.size() - 1; i >= 0; --i) {
- PackageInfo pkgInfo = mPkgCache.get(i);
- if (UserHandle.getUserId(pkgInfo.applicationInfo.uid) == userId) {
+ final InstalledPackageInfo pkgInfo = mPkgCache.get(i);
+ if (UserHandle.getUserId(pkgInfo.uid) == userId) {
removedPkgs.add(pkgInfo.packageName);
- mUidToPackageCache.remove(pkgInfo.applicationInfo.uid);
+ mUidToPackageCache.remove(pkgInfo.uid);
mPkgCache.remove(i);
break;
}
@@ -659,8 +724,11 @@ public class InternalResourceService extends SystemService {
LocalServices.getService(UserManagerInternal.class);
final int[] userIds = userManagerInternal.getUserIds();
for (int userId : userIds) {
- mPkgCache.addAll(
- mPackageManager.getInstalledPackagesAsUser(PACKAGE_QUERY_FLAGS, userId));
+ final List<PackageInfo> pkgs =
+ mPackageManager.getInstalledPackagesAsUser(PACKAGE_QUERY_FLAGS, userId);
+ for (int i = pkgs.size() - 1; i >= 0; --i) {
+ mPkgCache.add(new InstalledPackageInfo(pkgs.get(i)));
+ }
}
}
@@ -685,6 +753,13 @@ public class InternalResourceService extends SystemService {
UsageStatsManagerInternal usmi = LocalServices.getService(UsageStatsManagerInternal.class);
usmi.registerListener(mSurveillanceAgent);
+
+ try {
+ mAppOpsService
+ .startWatchingMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, null, mApbListener);
+ } catch (RemoteException e) {
+ // shouldn't happen.
+ }
}
/** Perform long-running and/or heavy setup work. This should be called off the main thread. */
@@ -708,6 +783,9 @@ public class InternalResourceService extends SystemService {
// Reset the consumption limit since several factors may have changed.
mScribe.setConsumptionLimitLocked(
mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit());
+ } else {
+ // Adjust the supply in case battery level changed while the device was off.
+ adjustCreditSupplyLocked(true);
}
}
scheduleUnusedWealthReclamationLocked();
@@ -789,6 +867,11 @@ public class InternalResourceService extends SystemService {
UsageStatsManagerInternal usmi =
LocalServices.getService(UsageStatsManagerInternal.class);
usmi.unregisterListener(mSurveillanceAgent);
+ try {
+ mAppOpsService.stopWatchingMode(mApbListener);
+ } catch (RemoteException e) {
+ // shouldn't happen.
+ }
}
synchronized (mPackageToUidCache) {
mPackageToUidCache.clear();
@@ -881,6 +964,15 @@ public class InternalResourceService extends SystemService {
Binder.restoreCallingIdentity(identityToken);
}
}
+
+ @Override
+ public int handleShellCommand(@NonNull ParcelFileDescriptor in,
+ @NonNull ParcelFileDescriptor out, @NonNull ParcelFileDescriptor err,
+ @NonNull String[] args) {
+ return (new TareShellCommand(InternalResourceService.this)).exec(
+ this, in.getFileDescriptor(), out.getFileDescriptor(), err.getFileDescriptor(),
+ args);
+ }
}
private final class LocalService implements EconomyManagerInternal {
@@ -932,9 +1024,9 @@ public class InternalResourceService extends SystemService {
if (!mIsEnabled) {
return true;
}
- if (isSystem(userId, pkgName)) {
+ if (isVip(userId, pkgName)) {
// The government, I mean the system, can create ARCs as it needs to in order to
- // operate.
+ // allow VIPs to operate.
return true;
}
// TODO: take temp-allowlist into consideration
@@ -960,7 +1052,7 @@ public class InternalResourceService extends SystemService {
if (!mIsEnabled) {
return FOREVER_MS;
}
- if (isSystem(userId, pkgName)) {
+ if (isVip(userId, pkgName)) {
return FOREVER_MS;
}
long totalCostPerSecond = 0;
@@ -1131,6 +1223,47 @@ public class InternalResourceService extends SystemService {
}
}
+ // Shell command infrastructure
+ int executeClearVip(@NonNull PrintWriter pw) {
+ synchronized (mLock) {
+ final SparseSetArray<String> changedPkgs = new SparseSetArray<>();
+ for (int u = mVipOverrides.numMaps() - 1; u >= 0; --u) {
+ final int userId = mVipOverrides.keyAt(u);
+
+ for (int p = mVipOverrides.numElementsForKeyAt(u) - 1; p >= 0; --p) {
+ changedPkgs.add(userId, mVipOverrides.keyAt(u, p));
+ }
+ }
+ mVipOverrides.clear();
+ if (mIsEnabled) {
+ mAgent.onVipStatusChangedLocked(changedPkgs);
+ }
+ }
+ pw.println("Cleared all VIP statuses");
+ return TareShellCommand.COMMAND_SUCCESS;
+ }
+
+ int executeSetVip(@NonNull PrintWriter pw,
+ int userId, @NonNull String pkgName, @Nullable Boolean newVipState) {
+ final boolean changed;
+ synchronized (mLock) {
+ final boolean wasVip = isVip(userId, pkgName);
+ if (newVipState == null) {
+ mVipOverrides.delete(userId, pkgName);
+ } else {
+ mVipOverrides.add(userId, pkgName, newVipState);
+ }
+ changed = isVip(userId, pkgName) != wasVip;
+ if (mIsEnabled && changed) {
+ mAgent.onVipStatusChangedLocked(userId, pkgName);
+ }
+ }
+ pw.println(appToString(userId, pkgName) + " VIP status set to " + newVipState + "."
+ + " Final VIP state changed? " + changed);
+ return TareShellCommand.COMMAND_SUCCESS;
+ }
+
+ // Dump infrastructure
private static void dumpHelp(PrintWriter pw) {
pw.println("Resource Economy (economy) dump options:");
pw.println(" [-h|--help] [package] ...");
@@ -1168,6 +1301,29 @@ public class InternalResourceService extends SystemService {
pw.print("Exempted apps", mExemptedApps);
pw.println();
+ boolean printedVips = false;
+ pw.println();
+ pw.print("VIPs:");
+ for (int u = 0; u < mVipOverrides.numMaps(); ++u) {
+ final int userId = mVipOverrides.keyAt(u);
+
+ for (int p = 0; p < mVipOverrides.numElementsForKeyAt(u); ++p) {
+ final String pkgName = mVipOverrides.keyAt(u, p);
+
+ printedVips = true;
+ pw.println();
+ pw.print(appToString(userId, pkgName));
+ pw.print("=");
+ pw.print(mVipOverrides.valueAt(u, p));
+ }
+ }
+ if (printedVips) {
+ pw.println();
+ } else {
+ pw.print(" None");
+ }
+ pw.println();
+
pw.println();
mCompleteEconomicPolicy.dump(pw);
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java
index e7db1adc859e..cbb88c0f5e31 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java
@@ -151,7 +151,6 @@ public class JobSchedulerEconomicPolicy extends EconomicPolicy {
private long mHardSatiatedConsumptionLimit;
private final KeyValueListParser mParser = new KeyValueListParser(',');
- private final InternalResourceService mInternalResourceService;
private final Injector mInjector;
private final SparseArray<Action> mActions = new SparseArray<>();
@@ -159,7 +158,6 @@ public class JobSchedulerEconomicPolicy extends EconomicPolicy {
JobSchedulerEconomicPolicy(InternalResourceService irs, Injector injector) {
super(irs);
- mInternalResourceService = irs;
mInjector = injector;
loadConstants("", null);
}
@@ -167,14 +165,17 @@ public class JobSchedulerEconomicPolicy extends EconomicPolicy {
@Override
void setup(@NonNull DeviceConfig.Properties properties) {
super.setup(properties);
- ContentResolver resolver = mInternalResourceService.getContext().getContentResolver();
+ final ContentResolver resolver = mIrs.getContext().getContentResolver();
loadConstants(mInjector.getSettingsGlobalString(resolver, TARE_JOB_SCHEDULER_CONSTANTS),
properties);
}
@Override
long getMinSatiatedBalance(final int userId, @NonNull final String pkgName) {
- if (mInternalResourceService.isPackageExempted(userId, pkgName)) {
+ if (mIrs.isPackageRestricted(userId, pkgName)) {
+ return 0;
+ }
+ if (mIrs.isPackageExempted(userId, pkgName)) {
return mMinSatiatedBalanceExempted;
}
// TODO: take other exemptions into account
@@ -182,7 +183,10 @@ public class JobSchedulerEconomicPolicy extends EconomicPolicy {
}
@Override
- long getMaxSatiatedBalance() {
+ long getMaxSatiatedBalance(int userId, @NonNull String pkgName) {
+ if (mIrs.isPackageRestricted(userId, pkgName)) {
+ return 0;
+ }
return mMaxSatiatedBalance;
}
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 8f7657e6c414..2cae83f4aad5 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Scribe.java
@@ -19,10 +19,10 @@ package com.android.server.tare;
import static android.text.format.DateUtils.HOUR_IN_MILLIS;
import static com.android.server.tare.TareUtils.appToString;
+import static com.android.server.tare.TareUtils.cakeToString;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.content.pm.PackageInfo;
import android.os.Environment;
import android.os.UserHandle;
import android.util.ArraySet;
@@ -137,9 +137,15 @@ public class Scribe {
@GuardedBy("mIrs.getLock()")
void adjustRemainingConsumableCakesLocked(long delta) {
- if (delta != 0) {
- // No point doing any work if the change is 0.
- mRemainingConsumableCakes += delta;
+ final long staleCakes = mRemainingConsumableCakes;
+ mRemainingConsumableCakes += delta;
+ if (mRemainingConsumableCakes < 0) {
+ Slog.w(TAG, "Overdrew consumable cakes by " + cakeToString(-mRemainingConsumableCakes));
+ // A negative value would interfere with allowing free actions, so set the minimum as 0.
+ mRemainingConsumableCakes = 0;
+ }
+ if (mRemainingConsumableCakes != staleCakes) {
+ // No point doing any work if there was no functional change.
postWrite();
}
}
@@ -210,11 +216,11 @@ public class Scribe {
mRemainingConsumableCakes = 0;
final SparseArray<ArraySet<String>> installedPackagesPerUser = new SparseArray<>();
- final List<PackageInfo> installedPackages = mIrs.getInstalledPackages();
+ final List<InstalledPackageInfo> installedPackages = mIrs.getInstalledPackages();
for (int i = 0; i < installedPackages.size(); ++i) {
- final PackageInfo packageInfo = installedPackages.get(i);
- if (packageInfo.applicationInfo != null) {
- final int userId = UserHandle.getUserId(packageInfo.applicationInfo.uid);
+ final InstalledPackageInfo packageInfo = installedPackages.get(i);
+ if (packageInfo.uid != InstalledPackageInfo.NO_UID) {
+ final int userId = UserHandle.getUserId(packageInfo.uid);
ArraySet<String> pkgsForUser = installedPackagesPerUser.get(userId);
if (pkgsForUser == null) {
pkgsForUser = new ArraySet<>();
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/TareShellCommand.java b/apex/jobscheduler/service/java/com/android/server/tare/TareShellCommand.java
new file mode 100644
index 000000000000..5e380b408d01
--- /dev/null
+++ b/apex/jobscheduler/service/java/com/android/server/tare/TareShellCommand.java
@@ -0,0 +1,112 @@
+/*
+ * 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.tare;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.content.pm.PackageManager;
+import android.os.Binder;
+
+import com.android.modules.utils.BasicShellCommandHandler;
+
+import java.io.PrintWriter;
+
+/**
+ * Shell command handler for TARE.
+ */
+public class TareShellCommand extends BasicShellCommandHandler {
+ static final int COMMAND_ERROR = -1;
+ static final int COMMAND_SUCCESS = 0;
+
+ private final InternalResourceService mIrs;
+
+ public TareShellCommand(@NonNull InternalResourceService irs) {
+ mIrs = irs;
+ }
+
+ @Override
+ public int onCommand(String cmd) {
+ final PrintWriter pw = getOutPrintWriter();
+ try {
+ switch (cmd != null ? cmd : "") {
+ case "clear-vip":
+ return runClearVip(pw);
+ case "set-vip":
+ return runSetVip(pw);
+ default:
+ return handleDefaultCommands(cmd);
+ }
+ } catch (Exception e) {
+ pw.println("Exception: " + e);
+ }
+ return COMMAND_ERROR;
+ }
+
+ @Override
+ public void onHelp() {
+ final PrintWriter pw = getOutPrintWriter();
+
+ pw.println("TARE commands:");
+ pw.println(" help");
+ pw.println(" Print this help text.");
+ pw.println(" clear-vip");
+ pw.println(" Clears all VIP settings resulting from previous calls using `set-vip` and");
+ pw.println(" resets them all to default.");
+ pw.println(" set-vip <USER_ID> <PACKAGE> <true|false|default>");
+ pw.println(" Designate the app as a Very Important Package or not. A VIP is allowed to");
+ pw.println(" do as much work as it wants, regardless of TARE state.");
+ pw.println(" The user ID must be an explicit user ID. USER_ALL, CURRENT, etc. are not");
+ pw.println(" supported.");
+ pw.println();
+ }
+
+ private void checkPermission(@NonNull String operation) throws Exception {
+ final int perm = mIrs.getContext()
+ .checkCallingOrSelfPermission(Manifest.permission.CHANGE_APP_IDLE_STATE);
+ if (perm != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Uid " + Binder.getCallingUid()
+ + " not permitted to " + operation);
+ }
+ }
+
+ private int runClearVip(@NonNull PrintWriter pw) throws Exception {
+ checkPermission("clear vip");
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ return mIrs.executeClearVip(pw);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ private int runSetVip(@NonNull PrintWriter pw) throws Exception {
+ checkPermission("modify vip");
+
+ final int userId = Integer.parseInt(getNextArgRequired());
+ final String pkgName = getNextArgRequired();
+ final String vipState = getNextArgRequired();
+ final Boolean isVip = "default".equals(vipState) ? null : Boolean.valueOf(vipState);
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ return mIrs.executeSetVip(pw, userId, pkgName, isVip);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+}
diff --git a/cmds/idmap2/include/idmap2/FabricatedOverlay.h b/cmds/idmap2/include/idmap2/FabricatedOverlay.h
index 65916d7ebf49..2fc4d43b798a 100644
--- a/cmds/idmap2/include/idmap2/FabricatedOverlay.h
+++ b/cmds/idmap2/include/idmap2/FabricatedOverlay.h
@@ -66,18 +66,21 @@ struct FabricatedOverlay {
private:
struct SerializedData {
- std::unique_ptr<uint8_t[]> data;
- size_t data_size;
- uint32_t crc;
- };
+ std::unique_ptr<uint8_t[]> pb_data;
+ size_t pb_data_size;
+ uint32_t pb_crc;
+ std::string sp_data;
+ };
Result<SerializedData*> InitializeData() const;
Result<uint32_t> GetCrc() const;
explicit FabricatedOverlay(pb::FabricatedOverlay&& overlay,
+ std::string&& string_pool_data_,
std::optional<uint32_t> crc_from_disk = {});
pb::FabricatedOverlay overlay_pb_;
+ std::string string_pool_data_;
std::optional<uint32_t> crc_from_disk_;
mutable std::optional<SerializedData> data_;
diff --git a/cmds/idmap2/libidmap2/FabricatedOverlay.cpp b/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
index 4d49674efce3..5bbe08524c18 100644
--- a/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
+++ b/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
@@ -17,8 +17,9 @@
#include "idmap2/FabricatedOverlay.h"
#include <androidfw/ResourceUtils.h>
+#include <androidfw/StringPool.h>
#include <google/protobuf/io/coded_stream.h>
-#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
+#include <google/protobuf/io/zero_copy_stream_impl.h>
#include <utils/ByteOrder.h>
#include <zlib.h>
@@ -30,6 +31,8 @@
namespace android::idmap2 {
+constexpr auto kBufferSize = 1024;
+
namespace {
bool Read32(std::istream& stream, uint32_t* out) {
uint32_t value;
@@ -47,8 +50,11 @@ void Write32(std::ostream& stream, uint32_t value) {
} // namespace
FabricatedOverlay::FabricatedOverlay(pb::FabricatedOverlay&& overlay,
+ std::string&& string_pool_data,
std::optional<uint32_t> crc_from_disk)
- : overlay_pb_(std::forward<pb::FabricatedOverlay>(overlay)), crc_from_disk_(crc_from_disk) {
+ : overlay_pb_(std::forward<pb::FabricatedOverlay>(overlay)),
+ string_pool_data_(std::move(string_pool_data)),
+ crc_from_disk_(crc_from_disk) {
}
FabricatedOverlay::Builder::Builder(const std::string& package_name, const std::string& name,
@@ -76,7 +82,11 @@ FabricatedOverlay::Builder& FabricatedOverlay::Builder::SetResourceValue(
}
Result<FabricatedOverlay> FabricatedOverlay::Builder::Build() {
- std::map<std::string, std::map<std::string, std::map<std::string, TargetValue>>> entries;
+ using EntryMap = std::map<std::string, TargetValue>;
+ using TypeMap = std::map<std::string, EntryMap>;
+ using PackageMap = std::map<std::string, TypeMap>;
+ PackageMap package_map;
+ android::StringPool string_pool;
for (const auto& res_entry : entries_) {
StringPiece package_substr;
StringPiece type_name;
@@ -96,11 +106,10 @@ Result<FabricatedOverlay> FabricatedOverlay::Builder::Build() {
return Error("resource name '%s' missing entry name", res_entry.resource_name.c_str());
}
- auto package = entries.find(package_name);
- if (package == entries.end()) {
- package = entries
- .insert(std::make_pair(
- package_name, std::map<std::string, std::map<std::string, TargetValue>>()))
+ auto package = package_map.find(package_name);
+ if (package == package_map.end()) {
+ package = package_map
+ .insert(std::make_pair(package_name, TypeMap()))
.first;
}
@@ -108,7 +117,7 @@ Result<FabricatedOverlay> FabricatedOverlay::Builder::Build() {
if (type == package->second.end()) {
type =
package->second
- .insert(std::make_pair(type_name.to_string(), std::map<std::string, TargetValue>()))
+ .insert(std::make_pair(type_name.to_string(), EntryMap()))
.first;
}
@@ -127,25 +136,32 @@ Result<FabricatedOverlay> FabricatedOverlay::Builder::Build() {
overlay_pb.set_target_package_name(target_package_name_);
overlay_pb.set_target_overlayable(target_overlayable_);
- for (const auto& package : entries) {
+ for (auto& package : package_map) {
auto package_pb = overlay_pb.add_packages();
package_pb->set_name(package.first);
- for (const auto& type : package.second) {
+ for (auto& type : package.second) {
auto type_pb = package_pb->add_types();
type_pb->set_name(type.first);
- for (const auto& entry : type.second) {
+ for (auto& entry : type.second) {
auto entry_pb = type_pb->add_entries();
entry_pb->set_name(entry.first);
pb::ResourceValue* value = entry_pb->mutable_res_value();
value->set_data_type(entry.second.data_type);
- value->set_data_value(entry.second.data_value);
+ if (entry.second.data_type == Res_value::TYPE_STRING) {
+ auto ref = string_pool.MakeRef(entry.second.data_string_value);
+ value->set_data_value(ref.index());
+ } else {
+ value->set_data_value(entry.second.data_value);
+ }
}
}
}
- return FabricatedOverlay(std::move(overlay_pb));
+ android::BigBuffer string_buffer(kBufferSize);
+ android::StringPool::FlattenUtf8(&string_buffer, string_pool, nullptr);
+ return FabricatedOverlay(std::move(overlay_pb), string_buffer.to_string());
}
Result<FabricatedOverlay> FabricatedOverlay::FromBinaryStream(std::istream& stream) {
@@ -163,48 +179,67 @@ Result<FabricatedOverlay> FabricatedOverlay::FromBinaryStream(std::istream& stre
return Error("Failed to read fabricated overlay version.");
}
- if (version != 1) {
+ if (version != 1 && version != 2) {
return Error("Invalid fabricated overlay version '%u'.", version);
}
uint32_t crc;
if (!Read32(stream, &crc)) {
- return Error("Failed to read fabricated overlay version.");
+ return Error("Failed to read fabricated overlay crc.");
}
pb::FabricatedOverlay overlay{};
- if (!overlay.ParseFromIstream(&stream)) {
- return Error("Failed read fabricated overlay proto.");
+ std::string sp_data;
+ if (version == 2) {
+ uint32_t sp_size;
+ if (!Read32(stream, &sp_size)) {
+ return Error("Failed read string pool size.");
+ }
+ std::string buf(sp_size, '\0');
+ if (!stream.read(buf.data(), sp_size)) {
+ return Error("Failed to read string pool.");
+ }
+ sp_data = buf;
+
+ if (!overlay.ParseFromIstream(&stream)) {
+ return Error("Failed read fabricated overlay proto.");
+ }
+ } else {
+ if (!overlay.ParseFromIstream(&stream)) {
+ return Error("Failed read fabricated overlay proto.");
+ }
}
// If the proto version is the latest version, then the contents of the proto must be the same
// when the proto is re-serialized; otherwise, the crc must be calculated because migrating the
// proto to the latest version will likely change the contents of the fabricated overlay.
- return FabricatedOverlay(std::move(overlay), version == kFabricatedOverlayCurrentVersion
+ return FabricatedOverlay(std::move(overlay), std::move(sp_data),
+ version == kFabricatedOverlayCurrentVersion
? std::optional<uint32_t>(crc)
: std::nullopt);
}
Result<FabricatedOverlay::SerializedData*> FabricatedOverlay::InitializeData() const {
if (!data_.has_value()) {
- auto size = overlay_pb_.ByteSizeLong();
- auto data = std::unique_ptr<uint8_t[]>(new uint8_t[size]);
+ auto pb_size = overlay_pb_.ByteSizeLong();
+ auto pb_data = std::unique_ptr<uint8_t[]>(new uint8_t[pb_size]);
// Ensure serialization is deterministic
- google::protobuf::io::ArrayOutputStream array_stream(data.get(), size);
+ google::protobuf::io::ArrayOutputStream array_stream(pb_data.get(), pb_size);
google::protobuf::io::CodedOutputStream output_stream(&array_stream);
output_stream.SetSerializationDeterministic(true);
overlay_pb_.SerializeWithCachedSizes(&output_stream);
- if (output_stream.HadError() || size != output_stream.ByteCount()) {
+ if (output_stream.HadError() || pb_size != output_stream.ByteCount()) {
return Error("Failed to serialize fabricated overlay.");
}
// Calculate the crc using the proto data and the version.
- uint32_t crc = crc32(0L, Z_NULL, 0);
- crc = crc32(crc, reinterpret_cast<const uint8_t*>(&kFabricatedOverlayCurrentVersion),
+ uint32_t pb_crc = crc32(0L, Z_NULL, 0);
+ pb_crc = crc32(pb_crc, reinterpret_cast<const uint8_t*>(&kFabricatedOverlayCurrentVersion),
sizeof(uint32_t));
- crc = crc32(crc, data.get(), size);
- data_ = SerializedData{std::move(data), size, crc};
+ pb_crc = crc32(pb_crc, pb_data.get(), pb_size);
+
+ data_ = SerializedData{std::move(pb_data), pb_size, pb_crc, string_pool_data_};
}
return &(*data_);
}
@@ -216,7 +251,7 @@ Result<uint32_t> FabricatedOverlay::GetCrc() const {
if (!data) {
return data.GetError();
}
- return (*data)->crc;
+ return (*data)->pb_crc;
}
Result<Unit> FabricatedOverlay::ToBinaryStream(std::ostream& stream) const {
@@ -227,8 +262,13 @@ Result<Unit> FabricatedOverlay::ToBinaryStream(std::ostream& stream) const {
Write32(stream, kFabricatedOverlayMagic);
Write32(stream, kFabricatedOverlayCurrentVersion);
- Write32(stream, (*data)->crc);
- stream.write(reinterpret_cast<const char*>((*data)->data.get()), (*data)->data_size);
+ Write32(stream, (*data)->pb_crc);
+ Write32(stream, (*data)->sp_data.length());
+ stream.write((*data)->sp_data.data(), (*data)->sp_data.length());
+ if (stream.bad()) {
+ return Error("Failed to write string pool data.");
+ }
+ stream.write(reinterpret_cast<const char*>((*data)->pb_data.get()), (*data)->pb_data_size);
if (stream.bad()) {
return Error("Failed to write serialized fabricated overlay.");
}
@@ -295,6 +335,14 @@ Result<OverlayData> FabContainer::GetOverlayData(const OverlayManifestInfo& info
}
}
}
+ const uint32_t string_pool_data_length = overlay_.string_pool_data_.length();
+ result.string_pool_data = OverlayData::InlineStringPoolData{
+ .data = std::unique_ptr<uint8_t[]>(new uint8_t[string_pool_data_length]),
+ .data_length = string_pool_data_length,
+ .string_pool_offset = 0,
+ };
+ memcpy(result.string_pool_data->data.get(), overlay_.string_pool_data_.data(),
+ string_pool_data_length);
return result;
}
diff --git a/cmds/idmap2/tests/FabricatedOverlayTests.cpp b/cmds/idmap2/tests/FabricatedOverlayTests.cpp
index 468ea0c634c1..91331ca1cc76 100644
--- a/cmds/idmap2/tests/FabricatedOverlayTests.cpp
+++ b/cmds/idmap2/tests/FabricatedOverlayTests.cpp
@@ -46,6 +46,7 @@ TEST(FabricatedOverlayTests, SetResourceValue) {
.SetResourceValue("com.example.target:integer/int1", Res_value::TYPE_INT_DEC, 1U)
.SetResourceValue("com.example.target.split:integer/int2", Res_value::TYPE_INT_DEC, 2U)
.SetResourceValue("string/int3", Res_value::TYPE_REFERENCE, 0x7f010000)
+ .SetResourceValue("com.example.target:string/string1", Res_value::TYPE_STRING, "foobar")
.Build();
ASSERT_TRUE(overlay);
auto container = FabricatedOverlayContainer::FromOverlay(std::move(*overlay));
@@ -59,8 +60,9 @@ TEST(FabricatedOverlayTests, SetResourceValue) {
auto pairs = container->GetOverlayData(*info);
ASSERT_TRUE(pairs);
- EXPECT_FALSE(pairs->string_pool_data.has_value());
- ASSERT_EQ(3U, pairs->pairs.size());
+ ASSERT_EQ(4U, pairs->pairs.size());
+ auto string_pool = ResStringPool(pairs->string_pool_data->data.get(),
+ pairs->string_pool_data->data_length, false);
auto& it = pairs->pairs[0];
ASSERT_EQ("com.example.target:integer/int1", it.resource_name);
@@ -77,6 +79,13 @@ TEST(FabricatedOverlayTests, SetResourceValue) {
ASSERT_EQ(Res_value::TYPE_REFERENCE, entry->data_type);
it = pairs->pairs[2];
+ ASSERT_EQ("com.example.target:string/string1", it.resource_name);
+ entry = std::get_if<TargetValue>(&it.value);
+ ASSERT_NE(nullptr, entry);
+ ASSERT_EQ(Res_value::TYPE_STRING, entry->data_type);
+ ASSERT_EQ(std::string("foobar"), string_pool.string8At(entry->data_value).value_or(""));
+
+ it = pairs->pairs[3];
ASSERT_EQ("com.example.target.split:integer/int2", it.resource_name);
entry = std::get_if<TargetValue>(&it.value);
ASSERT_NE(nullptr, entry);
@@ -104,6 +113,7 @@ TEST(FabricatedOverlayTests, SerializeAndDeserialize) {
FabricatedOverlay::Builder("com.example.overlay", "SandTheme", "com.example.target")
.SetOverlayable("TestResources")
.SetResourceValue("com.example.target:integer/int1", Res_value::TYPE_INT_DEC, 1U)
+ .SetResourceValue("com.example.target:string/string1", Res_value::TYPE_STRING, "foobar")
.Build();
ASSERT_TRUE(overlay);
TemporaryFile tf;
@@ -126,7 +136,9 @@ TEST(FabricatedOverlayTests, SerializeAndDeserialize) {
auto pairs = (*container)->GetOverlayData(*info);
ASSERT_TRUE(pairs) << pairs.GetErrorMessage();
- EXPECT_EQ(1U, pairs->pairs.size());
+ EXPECT_EQ(2U, pairs->pairs.size());
+ auto string_pool = ResStringPool(pairs->string_pool_data->data.get(),
+ pairs->string_pool_data->data_length, false);
auto& it = pairs->pairs[0];
ASSERT_EQ("com.example.target:integer/int1", it.resource_name);
@@ -134,6 +146,13 @@ TEST(FabricatedOverlayTests, SerializeAndDeserialize) {
ASSERT_NE(nullptr, entry);
EXPECT_EQ(1U, entry->data_value);
EXPECT_EQ(Res_value::TYPE_INT_DEC, entry->data_type);
+
+ it = pairs->pairs[1];
+ ASSERT_EQ("com.example.target:string/string1", it.resource_name);
+ entry = std::get_if<TargetValue>(&it.value);
+ ASSERT_NE(nullptr, entry);
+ ASSERT_EQ(Res_value::TYPE_STRING, entry->data_type);
+ ASSERT_EQ(std::string("foobar"), string_pool.string8At(entry->data_value).value_or(""));
}
} // namespace android::idmap2
diff --git a/cmds/idmap2/tests/IdmapTests.cpp b/cmds/idmap2/tests/IdmapTests.cpp
index 738b9cf237c9..a3799f98c90c 100644
--- a/cmds/idmap2/tests/IdmapTests.cpp
+++ b/cmds/idmap2/tests/IdmapTests.cpp
@@ -263,6 +263,7 @@ TEST(IdmapTests, FabricatedOverlay) {
.SetOverlayable("TestResources")
.SetResourceValue("integer/int1", Res_value::TYPE_INT_DEC, 2U)
.SetResourceValue("string/str1", Res_value::TYPE_REFERENCE, 0x7f010000)
+ .SetResourceValue("string/str2", Res_value::TYPE_STRING, "foobar")
.Build();
ASSERT_TRUE(frro);
@@ -288,12 +289,19 @@ TEST(IdmapTests, FabricatedOverlay) {
ASSERT_EQ(data->GetTargetEntries().size(), 0U);
ASSERT_EQ(data->GetOverlayEntries().size(), 0U);
+ auto string_pool_data = data->GetStringPoolData();
+ auto string_pool = ResStringPool(string_pool_data.data(), string_pool_data.size(), false);
+
+
const auto& target_inline_entries = data->GetTargetInlineEntries();
- ASSERT_EQ(target_inline_entries.size(), 2U);
+ ASSERT_EQ(target_inline_entries.size(), 3U);
ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[0], R::target::integer::int1,
Res_value::TYPE_INT_DEC, 2U);
ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[1], R::target::string::str1,
Res_value::TYPE_REFERENCE, 0x7f010000);
+ ASSERT_TARGET_INLINE_ENTRY(target_inline_entries[2], R::target::string::str2,
+ Res_value::TYPE_STRING,
+ (uint32_t) (string_pool.indexOfString(u"foobar", 6)).value_or(-1));
}
TEST(IdmapTests, FailCreateIdmapInvalidName) {
diff --git a/cmds/idmap2/tests/R.h b/cmds/idmap2/tests/R.h
index 89219c9c8213..ad998b9a7a4c 100644
--- a/cmds/idmap2/tests/R.h
+++ b/cmds/idmap2/tests/R.h
@@ -41,6 +41,7 @@ namespace R::target {
constexpr ResourceId policy_system = 0x7f02000c;
constexpr ResourceId policy_system_vendor = 0x7f02000d;
constexpr ResourceId str1 = 0x7f02000e;
+ constexpr ResourceId str2 = 0x7f02000f;
constexpr ResourceId str3 = 0x7f020010;
constexpr ResourceId str4 = 0x7f020011;
} // namespace string
diff --git a/cmds/idmap2/tests/ResourceMappingTests.cpp b/cmds/idmap2/tests/ResourceMappingTests.cpp
index 32b3d1326d92..c05abcf31532 100644
--- a/cmds/idmap2/tests/ResourceMappingTests.cpp
+++ b/cmds/idmap2/tests/ResourceMappingTests.cpp
@@ -196,6 +196,7 @@ TEST(ResourceMappingTests, FabricatedOverlay) {
.SetOverlayable("TestResources")
.SetResourceValue("integer/int1", Res_value::TYPE_INT_DEC, 2U)
.SetResourceValue("string/str1", Res_value::TYPE_REFERENCE, 0x7f010000)
+ .SetResourceValue("string/str2", Res_value::TYPE_STRING, "foobar")
.Build();
ASSERT_TRUE(frro);
@@ -209,9 +210,14 @@ TEST(ResourceMappingTests, FabricatedOverlay) {
ASSERT_TRUE(resources) << resources.GetErrorMessage();
auto& res = *resources;
- ASSERT_EQ(res.GetTargetToOverlayMap().size(), 2U);
+ auto string_pool_data = res.GetStringPoolData();
+ auto string_pool = ResStringPool(string_pool_data.data(), string_pool_data.size(), false);
+
+ ASSERT_EQ(res.GetTargetToOverlayMap().size(), 3U);
ASSERT_EQ(res.GetOverlayToTargetMap().size(), 0U);
ASSERT_RESULT(MappingExists(res, R::target::string::str1, Res_value::TYPE_REFERENCE, 0x7f010000));
+ ASSERT_RESULT(MappingExists(res, R::target::string::str2, Res_value::TYPE_STRING,
+ (uint32_t) (string_pool.indexOfString(u"foobar", 6)).value_or(-1)));
ASSERT_RESULT(MappingExists(res, R::target::integer::int1, Res_value::TYPE_INT_DEC, 2U));
}
diff --git a/cmds/uiautomator/library/core-src/com/android/uiautomator/core/UiDevice.java b/cmds/uiautomator/library/core-src/com/android/uiautomator/core/UiDevice.java
index b1b432bf79ab..6fd2bf250e2c 100644
--- a/cmds/uiautomator/library/core-src/com/android/uiautomator/core/UiDevice.java
+++ b/cmds/uiautomator/library/core-src/com/android/uiautomator/core/UiDevice.java
@@ -380,7 +380,7 @@ public class UiDevice {
Tracer.trace();
Display display = getAutomatorBridge().getDefaultDisplay();
Point p = new Point();
- display.getSize(p);
+ display.getRealSize(p);
return p.x;
}
@@ -394,7 +394,7 @@ public class UiDevice {
Tracer.trace();
Display display = getAutomatorBridge().getDefaultDisplay();
Point p = new Point();
- display.getSize(p);
+ display.getRealSize(p);
return p.y;
}
diff --git a/core/api/current.txt b/core/api/current.txt
index 4734e8a75de6..9ebd11888822 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -9126,6 +9126,7 @@ package android.content {
method @Nullable public String getAttributionTag();
method @Nullable public android.content.AttributionSource getNext();
method @Nullable public String getPackageName();
+ method public int getPid();
method public int getUid();
method public boolean isTrusted(@NonNull android.content.Context);
method @NonNull public static android.content.AttributionSource myAttributionSource();
@@ -9140,6 +9141,7 @@ package android.content {
method @NonNull public android.content.AttributionSource.Builder setAttributionTag(@Nullable String);
method @NonNull public android.content.AttributionSource.Builder setNext(@Nullable android.content.AttributionSource);
method @NonNull public android.content.AttributionSource.Builder setPackageName(@Nullable String);
+ method @NonNull public android.content.AttributionSource.Builder setPid(int);
}
public abstract class BroadcastReceiver {
@@ -12448,6 +12450,16 @@ package android.content.pm {
field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.SigningInfo> CREATOR;
}
+ public final class UserProperties implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getShowInLauncher();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.UserProperties> CREATOR;
+ field public static final int SHOW_IN_LAUNCHER_NO = 2; // 0x2
+ field public static final int SHOW_IN_LAUNCHER_SEPARATE = 1; // 0x1
+ field public static final int SHOW_IN_LAUNCHER_WITH_PARENT = 0; // 0x0
+ }
+
public final class VersionedPackage implements android.os.Parcelable {
ctor public VersionedPackage(@NonNull String, int);
ctor public VersionedPackage(@NonNull String, long);
@@ -14006,49 +14018,49 @@ package android.graphics {
public final class Bitmap implements android.os.Parcelable {
method @NonNull public android.graphics.Bitmap asShared();
- method @WorkerThread public boolean compress(android.graphics.Bitmap.CompressFormat, int, java.io.OutputStream);
- method public android.graphics.Bitmap copy(android.graphics.Bitmap.Config, boolean);
- method public void copyPixelsFromBuffer(java.nio.Buffer);
- method public void copyPixelsToBuffer(java.nio.Buffer);
- method public static android.graphics.Bitmap createBitmap(@NonNull android.graphics.Bitmap);
- method public static android.graphics.Bitmap createBitmap(@NonNull android.graphics.Bitmap, int, int, int, int);
- method public static android.graphics.Bitmap createBitmap(@NonNull android.graphics.Bitmap, int, int, int, int, @Nullable android.graphics.Matrix, boolean);
- method public static android.graphics.Bitmap createBitmap(int, int, @NonNull android.graphics.Bitmap.Config);
- method public static android.graphics.Bitmap createBitmap(@Nullable android.util.DisplayMetrics, int, int, @NonNull android.graphics.Bitmap.Config);
- method public static android.graphics.Bitmap createBitmap(int, int, @NonNull android.graphics.Bitmap.Config, boolean);
- method public static android.graphics.Bitmap createBitmap(int, int, @NonNull android.graphics.Bitmap.Config, boolean, @NonNull android.graphics.ColorSpace);
- method public static android.graphics.Bitmap createBitmap(@Nullable android.util.DisplayMetrics, int, int, @NonNull android.graphics.Bitmap.Config, boolean);
- method public static android.graphics.Bitmap createBitmap(@Nullable android.util.DisplayMetrics, int, int, @NonNull android.graphics.Bitmap.Config, boolean, @NonNull android.graphics.ColorSpace);
- method public static android.graphics.Bitmap createBitmap(@ColorInt @NonNull int[], int, int, int, int, @NonNull android.graphics.Bitmap.Config);
- method public static android.graphics.Bitmap createBitmap(@NonNull android.util.DisplayMetrics, @ColorInt @NonNull int[], int, int, int, int, @NonNull android.graphics.Bitmap.Config);
- method public static android.graphics.Bitmap createBitmap(@ColorInt @NonNull int[], int, int, android.graphics.Bitmap.Config);
- method public static android.graphics.Bitmap createBitmap(@Nullable android.util.DisplayMetrics, @ColorInt @NonNull int[], int, int, @NonNull android.graphics.Bitmap.Config);
+ method @WorkerThread public boolean compress(@NonNull android.graphics.Bitmap.CompressFormat, int, @NonNull java.io.OutputStream);
+ method public android.graphics.Bitmap copy(@NonNull android.graphics.Bitmap.Config, boolean);
+ method public void copyPixelsFromBuffer(@NonNull java.nio.Buffer);
+ method public void copyPixelsToBuffer(@NonNull java.nio.Buffer);
+ method @NonNull public static android.graphics.Bitmap createBitmap(@NonNull android.graphics.Bitmap);
+ method @NonNull public static android.graphics.Bitmap createBitmap(@NonNull android.graphics.Bitmap, int, int, int, int);
+ method @NonNull public static android.graphics.Bitmap createBitmap(@NonNull android.graphics.Bitmap, int, int, int, int, @Nullable android.graphics.Matrix, boolean);
+ method @NonNull public static android.graphics.Bitmap createBitmap(int, int, @NonNull android.graphics.Bitmap.Config);
+ method @NonNull public static android.graphics.Bitmap createBitmap(@Nullable android.util.DisplayMetrics, int, int, @NonNull android.graphics.Bitmap.Config);
+ method @NonNull public static android.graphics.Bitmap createBitmap(int, int, @NonNull android.graphics.Bitmap.Config, boolean);
+ method @NonNull public static android.graphics.Bitmap createBitmap(int, int, @NonNull android.graphics.Bitmap.Config, boolean, @NonNull android.graphics.ColorSpace);
+ method @NonNull public static android.graphics.Bitmap createBitmap(@Nullable android.util.DisplayMetrics, int, int, @NonNull android.graphics.Bitmap.Config, boolean);
+ method @NonNull public static android.graphics.Bitmap createBitmap(@Nullable android.util.DisplayMetrics, int, int, @NonNull android.graphics.Bitmap.Config, boolean, @NonNull android.graphics.ColorSpace);
+ method @NonNull public static android.graphics.Bitmap createBitmap(@ColorInt @NonNull int[], int, int, int, int, @NonNull android.graphics.Bitmap.Config);
+ method @NonNull public static android.graphics.Bitmap createBitmap(@NonNull android.util.DisplayMetrics, @ColorInt @NonNull int[], int, int, int, int, @NonNull android.graphics.Bitmap.Config);
+ method @NonNull public static android.graphics.Bitmap createBitmap(@ColorInt @NonNull int[], int, int, android.graphics.Bitmap.Config);
+ method @NonNull public static android.graphics.Bitmap createBitmap(@Nullable android.util.DisplayMetrics, @ColorInt @NonNull int[], int, int, @NonNull android.graphics.Bitmap.Config);
method @NonNull public static android.graphics.Bitmap createBitmap(@NonNull android.graphics.Picture);
method @NonNull public static android.graphics.Bitmap createBitmap(@NonNull android.graphics.Picture, int, int, @NonNull android.graphics.Bitmap.Config);
- method public static android.graphics.Bitmap createScaledBitmap(@NonNull android.graphics.Bitmap, int, int, boolean);
+ method @NonNull public static android.graphics.Bitmap createScaledBitmap(@NonNull android.graphics.Bitmap, int, int, boolean);
method public int describeContents();
method public void eraseColor(@ColorInt int);
method public void eraseColor(@ColorLong long);
- method @CheckResult public android.graphics.Bitmap extractAlpha();
- method @CheckResult public android.graphics.Bitmap extractAlpha(android.graphics.Paint, int[]);
+ method @CheckResult @NonNull public android.graphics.Bitmap extractAlpha();
+ method @CheckResult @NonNull public android.graphics.Bitmap extractAlpha(@Nullable android.graphics.Paint, int[]);
method public int getAllocationByteCount();
method public int getByteCount();
method @NonNull public android.graphics.Color getColor(int, int);
method @Nullable public android.graphics.ColorSpace getColorSpace();
- method public android.graphics.Bitmap.Config getConfig();
+ method @NonNull public android.graphics.Bitmap.Config getConfig();
method public int getDensity();
method public int getGenerationId();
method @NonNull public android.hardware.HardwareBuffer getHardwareBuffer();
method public int getHeight();
- method public byte[] getNinePatchChunk();
+ method @Nullable public byte[] getNinePatchChunk();
method @ColorInt public int getPixel(int, int);
- method public void getPixels(@ColorInt int[], int, int, int, int, int, int);
+ method public void getPixels(@ColorInt @NonNull int[], int, int, int, int, int, int);
method public int getRowBytes();
- method public int getScaledHeight(android.graphics.Canvas);
- method public int getScaledHeight(android.util.DisplayMetrics);
+ method public int getScaledHeight(@NonNull android.graphics.Canvas);
+ method public int getScaledHeight(@NonNull android.util.DisplayMetrics);
method public int getScaledHeight(int);
- method public int getScaledWidth(android.graphics.Canvas);
- method public int getScaledWidth(android.util.DisplayMetrics);
+ method public int getScaledWidth(@NonNull android.graphics.Canvas);
+ method public int getScaledWidth(@NonNull android.util.DisplayMetrics);
method public int getScaledWidth(int);
method public int getWidth();
method public boolean hasAlpha();
@@ -14057,21 +14069,21 @@ package android.graphics {
method public boolean isPremultiplied();
method public boolean isRecycled();
method public void prepareToDraw();
- method public void reconfigure(int, int, android.graphics.Bitmap.Config);
+ method public void reconfigure(int, int, @NonNull android.graphics.Bitmap.Config);
method public void recycle();
- method public boolean sameAs(android.graphics.Bitmap);
+ method @WorkerThread public boolean sameAs(@Nullable android.graphics.Bitmap);
method public void setColorSpace(@NonNull android.graphics.ColorSpace);
- method public void setConfig(android.graphics.Bitmap.Config);
+ method public void setConfig(@NonNull android.graphics.Bitmap.Config);
method public void setDensity(int);
method public void setHasAlpha(boolean);
method public void setHasMipMap(boolean);
method public void setHeight(int);
method public void setPixel(int, int, @ColorInt int);
- method public void setPixels(@ColorInt int[], int, int, int, int, int, int);
+ method public void setPixels(@ColorInt @NonNull int[], int, int, int, int, int, int);
method public void setPremultiplied(boolean);
method public void setWidth(int);
method @Nullable public static android.graphics.Bitmap wrapHardwareBuffer(@NonNull android.hardware.HardwareBuffer, @Nullable android.graphics.ColorSpace);
- method public void writeToParcel(android.os.Parcel, int);
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.graphics.Bitmap> CREATOR;
field public static final int DENSITY_NONE = 0; // 0x0
}
@@ -16903,6 +16915,7 @@ package android.hardware {
method public int describeContents();
method public int getFormat();
method public int getHeight();
+ method public long getId();
method public int getLayers();
method public long getUsage();
method public int getWidth();
@@ -31994,6 +32007,7 @@ package android.os {
method @Deprecated public static final boolean supportsProcesses();
field public static final int BLUETOOTH_UID = 1002; // 0x3ea
field public static final int FIRST_APPLICATION_UID = 10000; // 0x2710
+ field public static final int INVALID_PID = -1; // 0xffffffff
field public static final int INVALID_UID = -1; // 0xffffffff
field public static final int LAST_APPLICATION_UID = 19999; // 0x4e1f
field public static final int PHONE_UID = 1001; // 0x3e9
@@ -32250,6 +32264,7 @@ package android.os {
method public android.os.UserHandle getUserForSerialNumber(long);
method @NonNull @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.CREATE_USERS", "android.permission.QUERY_USERS", android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED}) public String getUserName();
method public java.util.List<android.os.UserHandle> getUserProfiles();
+ method @NonNull @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.QUERY_USERS", "android.permission.INTERACT_ACROSS_USERS"}, conditional=true) public android.content.pm.UserProperties getUserProperties(@NonNull android.os.UserHandle);
method public android.os.Bundle getUserRestrictions();
method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.INTERACT_ACROSS_USERS"}, conditional=true) public android.os.Bundle getUserRestrictions(android.os.UserHandle);
method public boolean hasUserRestriction(String);
@@ -41342,6 +41357,8 @@ package android.telephony {
field public static final String KEY_CARRIER_INSTANT_LETTERING_ESCAPED_CHARS_STRING = "carrier_instant_lettering_escaped_chars_string";
field public static final String KEY_CARRIER_INSTANT_LETTERING_INVALID_CHARS_STRING = "carrier_instant_lettering_invalid_chars_string";
field public static final String KEY_CARRIER_INSTANT_LETTERING_LENGTH_LIMIT_INT = "carrier_instant_lettering_length_limit_int";
+ field public static final String KEY_CARRIER_METERED_APN_TYPES_STRINGS = "carrier_metered_apn_types_strings";
+ field public static final String KEY_CARRIER_METERED_ROAMING_APN_TYPES_STRINGS = "carrier_metered_roaming_apn_types_strings";
field public static final String KEY_CARRIER_NAME_OVERRIDE_BOOL = "carrier_name_override_bool";
field public static final String KEY_CARRIER_NAME_STRING = "carrier_name_string";
field public static final String KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY = "carrier_nr_availabilities_int_array";
@@ -49957,7 +49974,6 @@ package android.view {
method public void invalidate();
method public void invalidateDrawable(@NonNull android.graphics.drawable.Drawable);
method public void invalidateOutline();
- method public boolean isAccessibilityDataPrivate();
method public boolean isAccessibilityFocused();
method public boolean isAccessibilityHeading();
method public boolean isActivated();
@@ -50135,7 +50151,6 @@ package android.view {
method public void scrollTo(int, int);
method public void sendAccessibilityEvent(int);
method public void sendAccessibilityEventUnchecked(android.view.accessibility.AccessibilityEvent);
- method public void setAccessibilityDataPrivate(int);
method public void setAccessibilityDelegate(@Nullable android.view.View.AccessibilityDelegate);
method public void setAccessibilityHeading(boolean);
method public void setAccessibilityLiveRegion(int);
@@ -50316,9 +50331,6 @@ package android.view {
method @CallSuper protected boolean verifyDrawable(@NonNull android.graphics.drawable.Drawable);
method @Deprecated public boolean willNotCacheDrawing();
method public boolean willNotDraw();
- field public static final int ACCESSIBILITY_DATA_PRIVATE_AUTO = 0; // 0x0
- field public static final int ACCESSIBILITY_DATA_PRIVATE_NO = 2; // 0x2
- field public static final int ACCESSIBILITY_DATA_PRIVATE_YES = 1; // 0x1
field public static final int ACCESSIBILITY_LIVE_REGION_ASSERTIVE = 2; // 0x2
field public static final int ACCESSIBILITY_LIVE_REGION_NONE = 0; // 0x0
field public static final int ACCESSIBILITY_LIVE_REGION_POLITE = 1; // 0x1
@@ -51771,11 +51783,9 @@ package android.view.accessibility {
method public int getSpeechStateChangeTypes();
method public int getWindowChanges();
method public void initFromParcel(android.os.Parcel);
- method public boolean isAccessibilityDataPrivate();
method @Deprecated public static android.view.accessibility.AccessibilityEvent obtain(int);
method @Deprecated public static android.view.accessibility.AccessibilityEvent obtain(android.view.accessibility.AccessibilityEvent);
method @Deprecated public static android.view.accessibility.AccessibilityEvent obtain();
- method public void setAccessibilityDataPrivate(boolean);
method public void setAction(int);
method public void setContentChangeTypes(int);
method public void setEventTime(long);
@@ -51866,7 +51876,6 @@ package android.view.accessibility {
method public static boolean isAccessibilityButtonSupported();
method public boolean isAudioDescriptionRequested();
method public boolean isEnabled();
- method public boolean isRequestFromAccessibilityTool();
method public boolean isTouchExplorationEnabled();
method public void removeAccessibilityRequestPreparer(android.view.accessibility.AccessibilityRequestPreparer);
method public boolean removeAccessibilityServicesStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 572101c11bd3..be84032df9a4 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -13398,6 +13398,7 @@ package android.telephony {
method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telephony.PinResult changeIccLockPin(@NonNull String, @NonNull String);
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int checkCarrierPrivilegesForPackage(String);
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int checkCarrierPrivilegesForPackageAnyPhone(String);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void clearRadioPowerOffForReason(int);
method public void dial(String);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean disableDataConnectivity();
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean enableDataConnectivity();
@@ -13443,6 +13444,7 @@ package android.telephony {
method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String[] getMergedImsisFromGroup();
method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public android.telephony.PhoneCapability getPhoneCapability();
method @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public long getPreferredNetworkTypeBitmask();
+ method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public java.util.Set<java.lang.Integer> getRadioPowerOffReasons();
method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public int getRadioPowerState();
method public int getSimApplicationState();
method @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getSimApplicationState(int);
@@ -13497,6 +13499,7 @@ package android.telephony {
method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.MODIFY_PHONE_STATE}) public void requestCellInfoUpdate(@NonNull android.os.WorkSource, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CellInfoCallback);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void requestModemActivityInfo(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.telephony.ModemActivityInfo,android.telephony.TelephonyManager.ModemActivityInfoException>);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void requestNumberVerification(@NonNull android.telephony.PhoneNumberRange, long, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.NumberVerificationCallback);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void requestRadioPowerOffForReason(int);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void resetAllCarrierActions();
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void resetCarrierKeysForImsiEncryption();
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public void resetIms(int);
@@ -13521,9 +13524,9 @@ package android.telephony {
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setNrDualConnectivityState(int);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setOpportunisticNetworkState(boolean);
method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setPreferredNetworkTypeBitmask(long);
- method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setRadio(boolean);
- method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setRadioEnabled(boolean);
- method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setRadioPower(boolean);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setRadio(boolean);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setRadioEnabled(boolean);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setRadioPower(boolean);
method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setSimPowerState(int);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setSimPowerState(int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setSimPowerStateForSlot(int, int);
@@ -13605,6 +13608,10 @@ package android.telephony {
field public static final int PREPARE_UNATTENDED_REBOOT_SUCCESS = 0; // 0x0
field public static final int RADIO_POWER_OFF = 0; // 0x0
field public static final int RADIO_POWER_ON = 1; // 0x1
+ field public static final int RADIO_POWER_REASON_CARRIER = 2; // 0x2
+ field public static final int RADIO_POWER_REASON_NEARBY_DEVICE = 3; // 0x3
+ field public static final int RADIO_POWER_REASON_THERMAL = 1; // 0x1
+ field public static final int RADIO_POWER_REASON_USER = 0; // 0x0
field public static final int RADIO_POWER_UNAVAILABLE = 2; // 0x2
field public static final int SET_CARRIER_RESTRICTION_ERROR = 2; // 0x2
field public static final int SET_CARRIER_RESTRICTION_NOT_SUPPORTED = 1; // 0x1
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index c87ea2adbd77..fefdfd8d7b15 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -90,7 +90,6 @@ package android.accessibilityservice {
public class AccessibilityServiceInfo implements android.os.Parcelable {
method @NonNull public android.content.ComponentName getComponentName();
- method public void setAccessibilityTool(boolean);
}
}
@@ -132,6 +131,7 @@ package android.app {
method public static void resumeAppSwitches() throws android.os.RemoteException;
method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public void scheduleApplicationInfoChanged(java.util.List<java.lang.String>, int);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public void setStopUserOnSwitch(int);
+ method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean startUserInBackgroundOnSecondaryDisplay(int, int);
method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public boolean stopUser(int, boolean);
method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public boolean updateMccMncConfiguration(@NonNull String, @NonNull String);
method @RequiresPermission(android.Manifest.permission.DUMP) public void waitForBroadcastIdle();
@@ -1876,6 +1876,7 @@ package android.os {
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 public static boolean isUsersOnSecondaryDisplaysEnabled();
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;
}
@@ -3332,11 +3333,12 @@ package android.window {
ctor public TaskFragmentOrganizer(@NonNull java.util.concurrent.Executor);
method @NonNull public java.util.concurrent.Executor getExecutor();
method @NonNull public android.window.TaskFragmentOrganizerToken getOrganizerToken();
- method public void onTaskFragmentAppeared(@NonNull android.window.TaskFragmentInfo);
- method public void onTaskFragmentError(@NonNull android.os.IBinder, @Nullable android.window.TaskFragmentInfo, int, @NonNull Throwable);
- method public void onTaskFragmentInfoChanged(@NonNull android.window.TaskFragmentInfo);
- method public void onTaskFragmentParentInfoChanged(@NonNull android.os.IBinder, @NonNull android.content.res.Configuration);
- method public void onTaskFragmentVanished(@NonNull android.window.TaskFragmentInfo);
+ method public void onActivityReparentedToTask(@NonNull android.window.WindowContainerTransaction, int, @NonNull android.content.Intent, @NonNull android.os.IBinder);
+ method public void onTaskFragmentAppeared(@NonNull android.window.WindowContainerTransaction, @NonNull android.window.TaskFragmentInfo);
+ method public void onTaskFragmentError(@NonNull android.window.WindowContainerTransaction, @NonNull android.os.IBinder, @Nullable android.window.TaskFragmentInfo, int, @NonNull Throwable);
+ method public void onTaskFragmentInfoChanged(@NonNull android.window.WindowContainerTransaction, @NonNull android.window.TaskFragmentInfo);
+ method public void onTaskFragmentParentInfoChanged(@NonNull android.window.WindowContainerTransaction, int, @NonNull android.content.res.Configuration);
+ method public void onTaskFragmentVanished(@NonNull android.window.WindowContainerTransaction, @NonNull android.window.TaskFragmentInfo);
method @CallSuper public void registerOrganizer();
method @CallSuper public void unregisterOrganizer();
}
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
index 8f6bfd3b13db..2e89ce83cd36 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -784,7 +784,6 @@ public class AccessibilityServiceInfo implements Parcelable {
mNonInteractiveUiTimeout = other.mNonInteractiveUiTimeout;
mInteractiveUiTimeout = other.mInteractiveUiTimeout;
flags = other.flags;
- mIsAccessibilityTool = other.mIsAccessibilityTool;
}
private boolean isRequestAccessibilityButtonChangeEnabled(IPlatformCompat platformCompat) {
@@ -1113,26 +1112,6 @@ public class AccessibilityServiceInfo implements Parcelable {
}
/**
- * Sets whether the service is used to assist users with disabilities.
- *
- * <p>
- * This property is normally provided in the service's {@link #mResolveInfo ResolveInfo}.
- * </p>
- *
- * <p>
- * This method is helpful for unit testing. However, this property is not dynamically
- * configurable by a standard {@link AccessibilityService} so it's not possible to update the
- * copy held by the system with this method.
- * </p>
- *
- * @hide
- */
- @TestApi
- public void setAccessibilityTool(boolean isAccessibilityTool) {
- mIsAccessibilityTool = isAccessibilityTool;
- }
-
- /**
* Indicates if the service is used to assist users with disabilities.
*
* @return {@code true} if the property is set to true.
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 449729e18376..182b0a3e5149 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -4336,13 +4336,42 @@ public class ActivityManager {
@RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
android.Manifest.permission.CREATE_USERS})
public boolean switchUser(@NonNull UserHandle user) {
- if (user == null) {
- throw new IllegalArgumentException("UserHandle cannot be null.");
- }
+ Preconditions.checkNotNull(user, "UserHandle cannot be null.");
+
return switchUser(user.getIdentifier());
}
/**
+ * Starts the given user in background and associate the user with the given display.
+ *
+ * <p>This method will allow the user to launch activities on that display, and it's typically
+ * used only on automotive builds when the vehicle has multiple displays (you can verify if it's
+ * supported by calling {@link UserManager#isBackgroundUsersOnSecondaryDisplaysSupported()}).
+ *
+ * @return whether the user was started.
+ *
+ * @throws UnsupportedOperationException if the device does not support background users on
+ * secondary displays.
+ * @throws IllegalArgumentException if the display does not exist.
+ * @throws IllegalStateException if the user cannot be started on that display (for example, if
+ * there's already a user using that display or if the user is already associated with other
+ * display).
+ *
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.CREATE_USERS})
+ public boolean startUserInBackgroundOnSecondaryDisplay(@UserIdInt int userId,
+ int displayId) {
+ try {
+ return getService().startUserInBackgroundOnSecondaryDisplay(userId, displayId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Gets the message that is shown when a user is switched from.
*
* @hide
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 92109584627d..419b8e1ef016 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -47,6 +47,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.function.BiFunction;
/**
* Activity manager local system service interface.
@@ -623,6 +624,11 @@ public abstract class ActivityManagerInternal {
* broadcast my be sent to; any app Ids < {@link android.os.Process#FIRST_APPLICATION_UID} are
* automatically allowlisted.
*
+ * @param filterExtrasForReceiver A function to filter intent extras for the given receiver by
+ * using the rules of package visibility. Returns extras with legitimate package info that the
+ * receiver is able to access, or {@code null} if none of the packages is visible to the
+ * receiver.
+ *
* @see com.android.server.am.ActivityManagerService#broadcastIntentWithFeature(
* IApplicationThread, String, Intent, String, IIntentReceiver, int, String, Bundle,
* String[], int, Bundle, boolean, boolean, int)
@@ -630,7 +636,9 @@ public abstract class ActivityManagerInternal {
public abstract int broadcastIntent(Intent intent,
IIntentReceiver resultTo,
String[] requiredPermissions, boolean serialized,
- int userId, int[] appIdAllowList, @Nullable Bundle bOptions);
+ int userId, int[] appIdAllowList,
+ @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
+ @Nullable Bundle bOptions);
/**
* Add uid to the ActivityManagerService PendingStartActivityUids list.
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 6b3dc820635e..b383d7daafd0 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -107,7 +107,6 @@ import android.net.ConnectivityManager;
import android.net.Proxy;
import android.net.TrafficStats;
import android.net.Uri;
-import android.net.wifi.WifiFrameworkInitializer;
import android.os.AsyncTask;
import android.os.Binder;
import android.os.BluetoothServiceManager;
@@ -7901,8 +7900,6 @@ public final class ActivityThread extends ClientTransactionHandler
BluetoothFrameworkInitializer.setBluetoothServiceManager(new BluetoothServiceManager());
BluetoothFrameworkInitializer.setBinderCallsStatsInitializer(context -> {
BinderCallsStats.startForBluetooth(context); });
- WifiFrameworkInitializer.setBinderCallsStatsInitializer(context -> {
- BinderCallsStats.startForWifi(context); });
}
private void purgePendingResources() {
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 9072b5023ce8..2f282f71397a 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -8578,9 +8578,9 @@ public class AppOpsManager {
public int noteProxyOp(int op, @Nullable String proxiedPackageName, int proxiedUid,
@Nullable String proxiedAttributionTag, @Nullable String message) {
return noteProxyOp(op, new AttributionSource(mContext.getAttributionSource(),
- new AttributionSource(proxiedUid, proxiedPackageName, proxiedAttributionTag,
- mContext.getAttributionSource().getToken())), message,
- /*skipProxyOperation*/ false);
+ new AttributionSource(proxiedUid, /*pid*/ -1, proxiedPackageName,
+ proxiedAttributionTag, mContext.getAttributionSource().getToken())),
+ message, /*skipProxyOperation*/ false);
}
/**
@@ -8664,7 +8664,7 @@ public class AppOpsManager {
public int noteProxyOpNoThrow(@NonNull String op, @Nullable String proxiedPackageName,
int proxiedUid, @Nullable String proxiedAttributionTag, @Nullable String message) {
return noteProxyOpNoThrow(strOpToOp(op), new AttributionSource(
- mContext.getAttributionSource(), new AttributionSource(proxiedUid,
+ mContext.getAttributionSource(), new AttributionSource(proxiedUid, /*pid*/ -1,
proxiedPackageName, proxiedAttributionTag, mContext.getAttributionSource()
.getToken())), message,/*skipProxyOperation*/ false);
}
@@ -9076,9 +9076,9 @@ public class AppOpsManager {
public int startProxyOp(@NonNull String op, int proxiedUid, @NonNull String proxiedPackageName,
@Nullable String proxiedAttributionTag, @Nullable String message) {
return startProxyOp(op, new AttributionSource(mContext.getAttributionSource(),
- new AttributionSource(proxiedUid, proxiedPackageName, proxiedAttributionTag,
- mContext.getAttributionSource().getToken())), message,
- /*skipProxyOperation*/ false);
+ new AttributionSource(proxiedUid, /*pid*/ -1, proxiedPackageName,
+ proxiedAttributionTag, mContext.getAttributionSource().getToken())),
+ message, /*skipProxyOperation*/ false);
}
/**
@@ -9124,7 +9124,7 @@ public class AppOpsManager {
@Nullable String message) {
return startProxyOpNoThrow(AppOpsManager.strOpToOp(op), new AttributionSource(
mContext.getAttributionSource(), new AttributionSource(proxiedUid,
- proxiedPackageName, proxiedAttributionTag,
+ /*pid*/ -1, proxiedPackageName, proxiedAttributionTag,
mContext.getAttributionSource().getToken())), message,
/*skipProxyOperation*/ false);
}
@@ -9270,8 +9270,9 @@ public class AppOpsManager {
public void finishProxyOp(@NonNull String op, int proxiedUid,
@NonNull String proxiedPackageName, @Nullable String proxiedAttributionTag) {
finishProxyOp(op, new AttributionSource(mContext.getAttributionSource(),
- new AttributionSource(proxiedUid, proxiedPackageName, proxiedAttributionTag,
- mContext.getAttributionSource().getToken())), /*skipProxyOperation*/ false);
+ new AttributionSource(proxiedUid, /*pid*/ -1, proxiedPackageName,
+ proxiedAttributionTag, mContext.getAttributionSource().getToken())),
+ /*skipProxyOperation*/ false);
}
/**
diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java
index f0e14483d98a..aa5fa5b19117 100644
--- a/core/java/android/app/BroadcastOptions.java
+++ b/core/java/android/app/BroadcastOptions.java
@@ -528,6 +528,28 @@ public class BroadcastOptions extends ComponentOptions {
return mIsAlarmBroadcast;
}
+ /**
+ * Did this broadcast originate from a push message from the server?
+ *
+ * @return true if this broadcast is a push message, false otherwise.
+ * @hide
+ */
+ public boolean isPushMessagingBroadcast() {
+ return mTemporaryAppAllowlistReasonCode == PowerExemptionManager.REASON_PUSH_MESSAGING;
+ }
+
+ /**
+ * Did this broadcast originate from a push message from the server which was over the allowed
+ * quota?
+ *
+ * @return true if this broadcast is a push message over quota, false otherwise.
+ * @hide
+ */
+ public boolean isPushMessagingOverQuotaBroadcast() {
+ return mTemporaryAppAllowlistReasonCode
+ == PowerExemptionManager.REASON_PUSH_MESSAGING_OVER_QUOTA;
+ }
+
/** {@hide} */
public long getRequireCompatChangeId() {
return mRequireCompatChangeId;
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 4c7bc6da7fb2..c1a2183a74fd 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -3227,7 +3227,9 @@ class ContextImpl extends Context {
@Nullable AttributionSource nextAttributionSource,
@Nullable Set<String> renouncedPermissions) {
AttributionSource attributionSource = new AttributionSource(Process.myUid(),
- mOpPackageName, attributionTag, renouncedPermissions, nextAttributionSource);
+ Process.myPid(), mOpPackageName, attributionTag,
+ (renouncedPermissions != null) ? renouncedPermissions.toArray(new String[0]) : null,
+ nextAttributionSource);
// If we want to access protected data on behalf of another app we need to
// tell the OS that we opt in to participate in the attribution chain.
if (nextAttributionSource != null) {
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 8367441b1b95..bd999fc04844 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -760,4 +760,15 @@ interface IActivityManager {
* </p>
*/
int getBackgroundRestrictionExemptionReason(int uid);
+
+ // Start (?) of T transactions
+ /**
+ * Similar to {@link #startUserInBackgroundWithListener(int userId, IProgressListener unlockProgressListener),
+ * but setting the user as the visible user of that display (i.e., allowing the user and its
+ * running profiles to launch activities on that display).
+ *
+ * <p>Typically used only by automotive builds when the vehicle has multiple displays.
+ */
+ boolean startUserInBackgroundOnSecondaryDisplay(int userid, int displayId);
+
}
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 36e1eee79d35..4da957c17a56 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -147,8 +147,6 @@ import android.net.NetworkWatchlistManager;
import android.net.PacProxyManager;
import android.net.TetheringManager;
import android.net.VpnManager;
-import android.net.lowpan.ILowpanManager;
-import android.net.lowpan.LowpanManager;
import android.net.vcn.IVcnManagementService;
import android.net.vcn.VcnManager;
import android.net.wifi.WifiFrameworkInitializer;
@@ -779,15 +777,6 @@ public final class SystemServiceRegistry {
ctx.mMainThread.getHandler());
}});
- registerService(Context.LOWPAN_SERVICE, LowpanManager.class,
- new CachedServiceFetcher<LowpanManager>() {
- @Override
- public LowpanManager createService(ContextImpl ctx) throws ServiceNotFoundException {
- IBinder b = ServiceManager.getServiceOrThrow(Context.LOWPAN_SERVICE);
- ILowpanManager service = ILowpanManager.Stub.asInterface(b);
- return new LowpanManager(ctx.getOuterContext(), service);
- }});
-
registerService(Context.WIFI_NL80211_SERVICE, WifiNl80211Manager.class,
new CachedServiceFetcher<WifiNl80211Manager>() {
@Override
diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java
index 8d57e32a763c..a045157e02db 100644
--- a/core/java/android/app/UiAutomationConnection.java
+++ b/core/java/android/app/UiAutomationConnection.java
@@ -550,7 +550,6 @@ public final class UiAutomationConnection extends IUiAutomationConnection.Stub {
info.setCapabilities(AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT
| AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION
| AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS);
- info.setAccessibilityTool(true);
try {
// Calling out with a lock held is fine since if the system
// process is gone the client calling in will be killed.
diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java
index 3f2fa2188d24..272e23532bfe 100644
--- a/core/java/android/content/AttributionSource.java
+++ b/core/java/android/content/AttributionSource.java
@@ -100,22 +100,28 @@ public final class AttributionSource implements Parcelable {
@TestApi
public AttributionSource(int uid, @Nullable String packageName,
@Nullable String attributionTag) {
- this(uid, packageName, attributionTag, sDefaultToken);
+ this(uid, Process.INVALID_PID, packageName, attributionTag, sDefaultToken);
+ }
+
+ /** @hide */
+ public AttributionSource(int uid, int pid, @Nullable String packageName,
+ @Nullable String attributionTag) {
+ this(uid, pid, packageName, attributionTag, sDefaultToken);
}
/** @hide */
@TestApi
public AttributionSource(int uid, @Nullable String packageName,
@Nullable String attributionTag, @NonNull IBinder token) {
- this(uid, packageName, attributionTag, token, /*renouncedPermissions*/ null,
- /*next*/ null);
+ this(uid, Process.INVALID_PID, packageName, attributionTag, token,
+ /*renouncedPermissions*/ null, /*next*/ null);
}
/** @hide */
- public AttributionSource(int uid, @Nullable String packageName,
- @Nullable String attributionTag, @NonNull IBinder token,
- @Nullable AttributionSource next) {
- this(uid, packageName, attributionTag, token, /*renouncedPermissions*/ null, next);
+ public AttributionSource(int uid, int pid, @Nullable String packageName,
+ @Nullable String attributionTag, @NonNull IBinder token) {
+ this(uid, pid, packageName, attributionTag, token, /*renouncedPermissions*/ null,
+ /*next*/ null);
}
/** @hide */
@@ -123,26 +129,33 @@ public final class AttributionSource implements Parcelable {
public AttributionSource(int uid, @Nullable String packageName,
@Nullable String attributionTag, @Nullable Set<String> renouncedPermissions,
@Nullable AttributionSource next) {
- this(uid, packageName, attributionTag, (renouncedPermissions != null)
- ? renouncedPermissions.toArray(new String[0]) : null, next);
+ this(uid, Process.INVALID_PID, packageName, attributionTag, sDefaultToken,
+ (renouncedPermissions != null)
+ ? renouncedPermissions.toArray(new String[0]) : null, /*next*/ next);
}
/** @hide */
public AttributionSource(@NonNull AttributionSource current, @Nullable AttributionSource next) {
- this(current.getUid(), current.getPackageName(), current.getAttributionTag(),
- current.getToken(), current.mAttributionSourceState.renouncedPermissions, next);
+ this(current.getUid(), current.getPid(), current.getPackageName(),
+ current.getAttributionTag(), current.getToken(),
+ current.mAttributionSourceState.renouncedPermissions, next);
}
- AttributionSource(int uid, @Nullable String packageName, @Nullable String attributionTag,
- @Nullable String[] renouncedPermissions, @Nullable AttributionSource next) {
- this(uid, packageName, attributionTag, sDefaultToken, renouncedPermissions, next);
+ /** @hide */
+ public AttributionSource(int uid, int pid, @Nullable String packageName,
+ @Nullable String attributionTag, @Nullable String[] renouncedPermissions,
+ @Nullable AttributionSource next) {
+ this(uid, pid, packageName, attributionTag, sDefaultToken, renouncedPermissions, next);
}
- AttributionSource(int uid, @Nullable String packageName, @Nullable String attributionTag,
- @NonNull IBinder token, @Nullable String[] renouncedPermissions,
+ /** @hide */
+ public AttributionSource(int uid, int pid, @Nullable String packageName,
+ @Nullable String attributionTag, @NonNull IBinder token,
+ @Nullable String[] renouncedPermissions,
@Nullable AttributionSource next) {
mAttributionSourceState = new AttributionSourceState();
mAttributionSourceState.uid = uid;
+ mAttributionSourceState.pid = pid;
mAttributionSourceState.token = token;
mAttributionSourceState.packageName = packageName;
mAttributionSourceState.attributionTag = attributionTag;
@@ -156,7 +169,17 @@ public final class AttributionSource implements Parcelable {
// Since we just unpacked this object as part of it transiting a Binder
// call, this is the perfect time to enforce that its UID and PID can be trusted
- enforceCallingUidAndPid();
+ enforceCallingUid();
+
+ // If this object is being constructed as part of a oneway Binder call, getCallingPid will
+ // return 0 instead of the true PID. In that case, invalidate the PID by setting it to
+ // INVALID_PID (-1).
+ final int callingPid = Binder.getCallingPid();
+ if (callingPid == 0) {
+ mAttributionSourceState.pid = Process.INVALID_PID;
+ }
+
+ enforceCallingPid();
}
/** @hide */
@@ -166,19 +189,19 @@ public final class AttributionSource implements Parcelable {
/** @hide */
public AttributionSource withNextAttributionSource(@Nullable AttributionSource next) {
- return new AttributionSource(getUid(), getPackageName(), getAttributionTag(),
- mAttributionSourceState.renouncedPermissions, next);
+ return new AttributionSource(getUid(), getPid(), getPackageName(), getAttributionTag(),
+ getToken(), mAttributionSourceState.renouncedPermissions, next);
}
/** @hide */
public AttributionSource withPackageName(@Nullable String packageName) {
- return new AttributionSource(getUid(), packageName, getAttributionTag(),
- mAttributionSourceState.renouncedPermissions, getNext());
+ return new AttributionSource(getUid(), getPid(), packageName, getAttributionTag(),
+ getToken(), mAttributionSourceState.renouncedPermissions, getNext());
}
/** @hide */
public AttributionSource withToken(@NonNull Binder token) {
- return new AttributionSource(getUid(), getPackageName(), getAttributionTag(),
+ return new AttributionSource(getUid(), getPid(), getPackageName(), getAttributionTag(),
token, mAttributionSourceState.renouncedPermissions, getNext());
}
@@ -222,6 +245,7 @@ public final class AttributionSource implements Parcelable {
}
try {
return new AttributionSource.Builder(uid)
+ .setPid(Process.myPid())
.setPackageName(AppGlobals.getPackageManager().getPackagesForUid(uid)[0])
.build();
} catch (Exception ignored) {
@@ -259,18 +283,6 @@ public final class AttributionSource implements Parcelable {
}
/**
- * If you are handling an IPC and you don't trust the caller you need to validate whether the
- * attribution source is one for the calling app to prevent the caller to pass you a source from
- * another app without including themselves in the attribution chain.
- *
- * @throws SecurityException if the attribution source cannot be trusted to be from the caller.
- */
- private void enforceCallingUidAndPid() {
- enforceCallingUid();
- enforceCallingPid();
- }
-
- /**
* If you are handling an IPC and you don't trust the caller you need to validate
* whether the attribution source is one for the calling app to prevent the caller
* to pass you a source from another app without including themselves in the
@@ -306,7 +318,10 @@ public final class AttributionSource implements Parcelable {
}
/**
- * Validate that the pid being claimed for the calling app is not spoofed
+ * Validate that the pid being claimed for the calling app is not spoofed.
+ *
+ * Note that the PID may be unavailable, for example if we're in a oneway Binder call. In this
+ * case, calling enforceCallingPid is guaranteed to fail. The caller should anticipate this.
*
* @throws SecurityException if the attribution source cannot be trusted to be from the caller.
* @hide
@@ -314,8 +329,12 @@ public final class AttributionSource implements Parcelable {
@TestApi
public void enforceCallingPid() {
if (!checkCallingPid()) {
- throw new SecurityException("Calling pid: " + Binder.getCallingPid()
- + " doesn't match source pid: " + mAttributionSourceState.pid);
+ if (Binder.getCallingPid() == 0) {
+ throw new SecurityException("Calling pid unavailable due to oneway Binder call.");
+ } else {
+ throw new SecurityException("Calling pid: " + Binder.getCallingPid()
+ + " doesn't match source pid: " + mAttributionSourceState.pid);
+ }
}
}
@@ -326,7 +345,8 @@ public final class AttributionSource implements Parcelable {
*/
private boolean checkCallingPid() {
final int callingPid = Binder.getCallingPid();
- if (mAttributionSourceState.pid != -1 && callingPid != mAttributionSourceState.pid) {
+ if (mAttributionSourceState.pid != Process.INVALID_PID
+ && callingPid != mAttributionSourceState.pid) {
return false;
}
return true;
@@ -443,6 +463,13 @@ public final class AttributionSource implements Parcelable {
}
/**
+ * The PID that is accessing the permission protected data.
+ */
+ public int getPid() {
+ return mAttributionSourceState.pid;
+ }
+
+ /**
* The package that is accessing the permission protected data.
*/
public @Nullable String getPackageName() {
@@ -550,6 +577,7 @@ public final class AttributionSource implements Parcelable {
throw new IllegalArgumentException("current AttributionSource can not be null");
}
mAttributionSourceState.uid = current.getUid();
+ mAttributionSourceState.pid = current.getPid();
mAttributionSourceState.packageName = current.getPackageName();
mAttributionSourceState.attributionTag = current.getAttributionTag();
mAttributionSourceState.token = current.getToken();
@@ -558,11 +586,24 @@ public final class AttributionSource implements Parcelable {
}
/**
+ * The PID of the process that is accessing the permission protected data.
+ *
+ * If not called, pid will default to Process.INVALID_PID (-1). This indicates that the PID
+ * data is missing. Supplying a PID is not required, but recommended when accessible.
+ */
+ public @NonNull Builder setPid(int value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x2;
+ mAttributionSourceState.pid = value;
+ return this;
+ }
+
+ /**
* The package that is accessing the permission protected data.
*/
public @NonNull Builder setPackageName(@Nullable String value) {
checkNotUsed();
- mBuilderFieldsSet |= 0x2;
+ mBuilderFieldsSet |= 0x4;
mAttributionSourceState.packageName = value;
return this;
}
@@ -572,7 +613,7 @@ public final class AttributionSource implements Parcelable {
*/
public @NonNull Builder setAttributionTag(@Nullable String value) {
checkNotUsed();
- mBuilderFieldsSet |= 0x4;
+ mBuilderFieldsSet |= 0x8;
mAttributionSourceState.attributionTag = value;
return this;
}
@@ -605,7 +646,7 @@ public final class AttributionSource implements Parcelable {
@RequiresPermission(android.Manifest.permission.RENOUNCE_PERMISSIONS)
public @NonNull Builder setRenouncedPermissions(@Nullable Set<String> value) {
checkNotUsed();
- mBuilderFieldsSet |= 0x8;
+ mBuilderFieldsSet |= 0x10;
mAttributionSourceState.renouncedPermissions = (value != null)
? value.toArray(new String[0]) : null;
return this;
@@ -616,7 +657,7 @@ public final class AttributionSource implements Parcelable {
*/
public @NonNull Builder setNext(@Nullable AttributionSource value) {
checkNotUsed();
- mBuilderFieldsSet |= 0x10;
+ mBuilderFieldsSet |= 0x20;
mAttributionSourceState.next = (value != null) ? new AttributionSourceState[]
{value.mAttributionSourceState} : mAttributionSourceState.next;
return this;
@@ -628,15 +669,18 @@ public final class AttributionSource implements Parcelable {
mBuilderFieldsSet |= 0x40; // Mark builder used
if ((mBuilderFieldsSet & 0x2) == 0) {
- mAttributionSourceState.packageName = null;
+ mAttributionSourceState.pid = Process.INVALID_PID;
}
if ((mBuilderFieldsSet & 0x4) == 0) {
- mAttributionSourceState.attributionTag = null;
+ mAttributionSourceState.packageName = null;
}
if ((mBuilderFieldsSet & 0x8) == 0) {
- mAttributionSourceState.renouncedPermissions = null;
+ mAttributionSourceState.attributionTag = null;
}
if ((mBuilderFieldsSet & 0x10) == 0) {
+ mAttributionSourceState.renouncedPermissions = null;
+ }
+ if ((mBuilderFieldsSet & 0x20) == 0) {
mAttributionSourceState.next = null;
}
diff --git a/core/java/android/content/PermissionChecker.java b/core/java/android/content/PermissionChecker.java
index 8d3452ec2c60..0e3217d7071d 100644
--- a/core/java/android/content/PermissionChecker.java
+++ b/core/java/android/content/PermissionChecker.java
@@ -152,7 +152,7 @@ public final class PermissionChecker {
@NonNull String permission, int pid, int uid, @Nullable String packageName,
@Nullable String attributionTag, @Nullable String message, boolean startDataDelivery) {
return checkPermissionForDataDelivery(context, permission, pid, new AttributionSource(uid,
- packageName, attributionTag), message, startDataDelivery);
+ pid, packageName, attributionTag), message, startDataDelivery);
}
/**
diff --git a/core/java/android/content/pm/OWNERS b/core/java/android/content/pm/OWNERS
index 087e61d70576..f9d3222ada2a 100644
--- a/core/java/android/content/pm/OWNERS
+++ b/core/java/android/content/pm/OWNERS
@@ -11,3 +11,4 @@ per-file *Shortcut* = file:/core/java/android/content/pm/SHORTCUT_OWNERS
per-file AppSearchPerson.java = file:/core/java/android/content/pm/SHORTCUT_OWNERS
per-file *Launcher* = file:/core/java/android/content/pm/LAUNCHER_OWNERS
per-file UserInfo* = file:/MULTIUSER_OWNERS
+per-file *UserProperties* = file:/MULTIUSER_OWNERS
diff --git a/lowpan/java/android/net/lowpan/LowpanIdentity.aidl b/core/java/android/content/pm/UserProperties.aidl
index fcef98f5c496..4d37067958b3 100644
--- a/lowpan/java/android/net/lowpan/LowpanIdentity.aidl
+++ b/core/java/android/content/pm/UserProperties.aidl
@@ -1,19 +1,19 @@
-/**
- * Copyright (C) 2017 The Android Open Source Project
+/*
+ * 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
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
- * limitations under the License.
+ * limitations under the License
*/
-package android.net.lowpan;
+package android.content.pm;
-parcelable LowpanIdentity cpp_header "android/net/lowpan/LowpanIdentity.h";
+parcelable UserProperties;
diff --git a/core/java/android/content/pm/UserProperties.java b/core/java/android/content/pm/UserProperties.java
new file mode 100644
index 000000000000..1a82e4d78b11
--- /dev/null
+++ b/core/java/android/content/pm/UserProperties.java
@@ -0,0 +1,356 @@
+/*
+ * 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.content.pm;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Slog;
+import android.util.TypedXmlPullParser;
+import android.util.TypedXmlSerializer;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Class holding the properties of a user that derive mostly from its user type.
+ */
+public final class UserProperties implements Parcelable {
+ private static final String LOG_TAG = UserProperties.class.getSimpleName();
+
+ // Attribute strings for reading/writing properties to/from XML.
+ private static final String ATTR_SHOW_IN_LAUNCHER = "showInLauncher";
+ private static final String ATTR_START_WITH_PARENT = "startWithParent";
+
+ /** Index values of each property (to indicate whether they are present in this object). */
+ @IntDef(prefix = "INDEX_", value = {
+ INDEX_SHOW_IN_LAUNCHER,
+ INDEX_START_WITH_PARENT,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ private @interface PropertyIndex {
+ }
+ private static final int INDEX_SHOW_IN_LAUNCHER = 0;
+ private static final int INDEX_START_WITH_PARENT = 1;
+ /** A bit set, mapping each PropertyIndex to whether it is present (1) or absent (0). */
+ private long mPropertiesPresent = 0;
+
+
+ /**
+ * Possible values for whether or how to show this user in the Launcher.
+ * @hide
+ */
+ @IntDef(prefix = "SHOW_IN_LAUNCHER_", value = {
+ SHOW_IN_LAUNCHER_WITH_PARENT,
+ SHOW_IN_LAUNCHER_SEPARATE,
+ SHOW_IN_LAUNCHER_NO,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ShowInLauncher {
+ }
+ /**
+ * Suggests that the launcher should show this user's apps in the main tab.
+ * That is, either this user is a full user, so its apps should be presented accordingly, or, if
+ * this user is a profile, then its apps should be shown alongside its parent's apps.
+ */
+ public static final int SHOW_IN_LAUNCHER_WITH_PARENT = 0;
+ /**
+ * Suggests that the launcher should show this user's apps, but separately from the apps of this
+ * user's parent.
+ */
+ public static final int SHOW_IN_LAUNCHER_SEPARATE = 1;
+ /**
+ * Suggests that the launcher should not show this user.
+ */
+ public static final int SHOW_IN_LAUNCHER_NO = 2;
+
+ /**
+ * Reference to the default user properties for this user's user type.
+ * <li>If non-null, then any absent property will use the default property from here instead.
+ * <li>If null, then any absent property indicates that the caller lacks permission to see it,
+ * so attempting to get that property will trigger a SecurityException.
+ */
+ private final @Nullable UserProperties mDefaultProperties;
+
+ /**
+ * Creates a UserProperties (intended for the SystemServer) that stores a reference to the given
+ * default properties, which it uses for any property not subsequently set.
+ * @hide
+ */
+ public UserProperties(@NonNull UserProperties defaultProperties) {
+ mDefaultProperties = defaultProperties;
+ mPropertiesPresent = 0;
+ }
+
+ /**
+ * Copies the given UserProperties, excluding any information that doesn't satisfy the specified
+ * permissions.
+ * Can only be used on the original version (one that won't throw on permission errors).
+ * Note that, internally, this does not perform an exact copy.
+ * @hide
+ */
+ public UserProperties(UserProperties orig,
+ boolean exposeAllFields,
+ boolean hasManagePermission,
+ boolean hasQueryPermission) {
+
+ if (orig.mDefaultProperties == null) {
+ throw new IllegalArgumentException("Attempting to copy a non-original UserProperties.");
+ }
+
+ this.mDefaultProperties = null;
+
+ // NOTE: Copy each property using getters to ensure default values are copied if needed.
+ if (exposeAllFields) {
+ setStartWithParent(orig.getStartWithParent());
+ }
+ if (hasManagePermission) {
+ // Add any items that require this permission.
+ }
+ if (hasQueryPermission) {
+ // Add any items that require this permission.
+ }
+ // Add any items that require no permissions at all.
+ setShowInLauncher(orig.getShowInLauncher());
+ }
+
+ /**
+ * Indicates that the given property is being stored explicitly in this object.
+ * If false, it means that either
+ * <li>the default property for the user type should be used instead (for SystemServer callers)
+ * <li>the caller lacks permission to see this property (for all other callers)
+ */
+ private boolean isPresent(@PropertyIndex long index) {
+ return (mPropertiesPresent & (1L << index)) != 0;
+ }
+
+ /** Indicates that the given property is henceforth being explicitly stored in this object. */
+ private void setPresent(@PropertyIndex long index) {
+ mPropertiesPresent |= (1L << index);
+ }
+
+ /** @hide Returns the internal mPropertiesPresent value. Only for testing purposes. */
+ @VisibleForTesting
+ public long getPropertiesPresent() {
+ return mPropertiesPresent;
+ }
+
+ /**
+ * Returns whether, and how, a user should be shown in the Launcher.
+ * This is generally inapplicable for non-profile users.
+ *
+ * Possible return values include
+ * {@link #SHOW_IN_LAUNCHER_WITH_PARENT}},
+ * {@link #SHOW_IN_LAUNCHER_SEPARATE},
+ * and {@link #SHOW_IN_LAUNCHER_NO}.
+ *
+ * @return whether, and how, a profile should be shown in the Launcher.
+ */
+ public @ShowInLauncher int getShowInLauncher() {
+ if (isPresent(INDEX_SHOW_IN_LAUNCHER)) return mShowInLauncher;
+ if (mDefaultProperties != null) return mDefaultProperties.mShowInLauncher;
+ throw new SecurityException("You don't have permission to query showInLauncher");
+ }
+ /** @hide */
+ public void setShowInLauncher(@ShowInLauncher int val) {
+ this.mShowInLauncher = val;
+ setPresent(INDEX_SHOW_IN_LAUNCHER);
+ }
+ private @ShowInLauncher int mShowInLauncher;
+
+ /**
+ * Returns whether a profile should be started when its parent starts (unless in quiet mode).
+ * This only applies for users that have parents (i.e. for profiles).
+ * @hide
+ */
+ public boolean getStartWithParent() {
+ if (isPresent(INDEX_START_WITH_PARENT)) return mStartWithParent;
+ if (mDefaultProperties != null) return mDefaultProperties.mStartWithParent;
+ throw new SecurityException("You don't have permission to query startWithParent");
+ }
+ /** @hide */
+ public void setStartWithParent(boolean val) {
+ this.mStartWithParent = val;
+ setPresent(INDEX_START_WITH_PARENT);
+ }
+ private boolean mStartWithParent;
+
+ @Override
+ public String toString() {
+ // Please print in increasing order of PropertyIndex.
+ return "UserProperties{"
+ + "mPropertiesPresent=" + Long.toBinaryString(mPropertiesPresent)
+ + ", mShowInLauncher=" + getShowInLauncher()
+ + ", mStartWithParent=" + getStartWithParent()
+ + "}";
+ }
+
+ /**
+ * Print the UserProperties to the given PrintWriter.
+ * @hide
+ */
+ public void println(PrintWriter pw, String prefix) {
+ // Please print in increasing order of PropertyIndex.
+ pw.println(prefix + "UserProperties:");
+ pw.println(prefix + " mPropertiesPresent=" + Long.toBinaryString(mPropertiesPresent));
+ pw.println(prefix + " mShowInLauncher=" + getShowInLauncher());
+ pw.println(prefix + " mStartWithParent=" + getStartWithParent());
+ }
+
+ /**
+ * Reads in a UserProperties from an xml file, for use by the SystemServer.
+ *
+ * The serializer should already be inside a tag from which to read the user properties.
+ *
+ * @param defaultUserPropertiesReference the default UserProperties to use for this user type.
+ * @see #writeToXml
+ * @hide
+ */
+ public UserProperties(
+ TypedXmlPullParser parser,
+ @NonNull UserProperties defaultUserPropertiesReference)
+ throws IOException, XmlPullParserException {
+
+ this(defaultUserPropertiesReference);
+ updateFromXml(parser);
+ }
+
+ /**
+ * Parses the given xml file and updates this UserProperties with its data.
+ * I.e., if a piece of data is present in the xml, it will overwrite whatever was
+ * previously stored in this UserProperties.
+ * @hide
+ */
+ public void updateFromXml(TypedXmlPullParser parser)
+ throws IOException, XmlPullParserException {
+
+ final int attributeCount = parser.getAttributeCount();
+ for (int i = 0; i < attributeCount; i++) {
+ final String attributeName = parser.getAttributeName(i);
+ switch(attributeName) {
+ case ATTR_SHOW_IN_LAUNCHER:
+ setShowInLauncher(parser.getAttributeInt(i));
+ break;
+ case ATTR_START_WITH_PARENT:
+ setStartWithParent(parser.getAttributeBoolean(i));
+ break;
+ default:
+ Slog.w(LOG_TAG, "Skipping unknown property " + attributeName);
+ }
+ }
+ }
+
+ /**
+ * Writes the UserProperties, as used by the SystemServer, to the xml file.
+ *
+ * The serializer should already be inside a tag in which to write the user properties.
+ *
+ * @see #UserProperties(TypedXmlPullParser, UserProperties)
+ * @hide
+ */
+ public void writeToXml(TypedXmlSerializer serializer)
+ throws IOException, XmlPullParserException {
+
+ if (isPresent(INDEX_SHOW_IN_LAUNCHER)) {
+ serializer.attributeInt(null, ATTR_SHOW_IN_LAUNCHER, mShowInLauncher);
+ }
+ if (isPresent(INDEX_START_WITH_PARENT)) {
+ serializer.attributeBoolean(null, ATTR_START_WITH_PARENT, mStartWithParent);
+ }
+ }
+
+ // For use only with an object that has already had any permission-lacking fields stripped out.
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int parcelableFlags) {
+ dest.writeLong(mPropertiesPresent);
+ dest.writeInt(mShowInLauncher);
+ dest.writeBoolean(mStartWithParent);
+ }
+
+ /**
+ * Reads a UserProperties object from the parcel.
+ * Not suitable for the canonical SystemServer version since it lacks mDefaultProperties.
+ */
+ private UserProperties(@NonNull Parcel source) {
+ mDefaultProperties = null;
+
+ mPropertiesPresent = source.readLong();
+ mShowInLauncher = source.readInt();
+ mStartWithParent = source.readBoolean();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<UserProperties> CREATOR
+ = new Parcelable.Creator<UserProperties>() {
+ public UserProperties createFromParcel(Parcel source) {
+ return new UserProperties(source);
+ }
+ public UserProperties[] newArray(int size) {
+ return new UserProperties[size];
+ }
+ };
+
+ /**
+ * Builder for the SystemServer's {@link UserProperties}; see that class for documentation.
+ * Intended for building default values (and so all properties are present in the built object).
+ * @hide
+ */
+ public static final class Builder {
+ // UserProperties fields and their default values.
+ private @ShowInLauncher int mShowInLauncher = SHOW_IN_LAUNCHER_WITH_PARENT;
+ private boolean mStartWithParent = false;
+
+ public Builder setShowInLauncher(@ShowInLauncher int showInLauncher) {
+ mShowInLauncher = showInLauncher;
+ return this;
+ }
+
+ public Builder setStartWithParent(boolean startWithParent) {
+ mStartWithParent = startWithParent;
+ return this;
+ }
+
+ /** Builds a UserProperties object with *all* values populated. */
+ public UserProperties build() {
+ return new UserProperties(
+ mShowInLauncher,
+ mStartWithParent);
+ }
+ } // end Builder
+
+ /** Creates a UserProperties with the given properties. Intended for building default values. */
+ private UserProperties(
+ @ShowInLauncher int showInLauncher,
+ boolean startWithParent) {
+
+ mDefaultProperties = null;
+ setShowInLauncher(showInLauncher);
+ setStartWithParent(startWithParent);
+ }
+}
diff --git a/core/java/android/hardware/HardwareBuffer.java b/core/java/android/hardware/HardwareBuffer.java
index 1e4c95013205..11892fea20f2 100644
--- a/core/java/android/hardware/HardwareBuffer.java
+++ b/core/java/android/hardware/HardwareBuffer.java
@@ -188,9 +188,6 @@ public final class HardwareBuffer implements Parcelable, AutoCloseable {
public static HardwareBuffer create(
@IntRange(from = 1) int width, @IntRange(from = 1) int height,
@Format int format, @IntRange(from = 1) int layers, @Usage long usage) {
- if (!HardwareBuffer.isSupportedFormat(format)) {
- throw new IllegalArgumentException("Invalid pixel format " + format);
- }
if (width <= 0) {
throw new IllegalArgumentException("Invalid width " + width);
}
@@ -226,9 +223,6 @@ public final class HardwareBuffer implements Parcelable, AutoCloseable {
*/
public static boolean isSupported(@IntRange(from = 1) int width, @IntRange(from = 1) int height,
@Format int format, @IntRange(from = 1) int layers, @Usage long usage) {
- if (!HardwareBuffer.isSupportedFormat(format)) {
- throw new IllegalArgumentException("Invalid pixel format " + format);
- }
if (width <= 0) {
throw new IllegalArgumentException("Invalid width " + width);
}
@@ -286,10 +280,7 @@ public final class HardwareBuffer implements Parcelable, AutoCloseable {
* Returns the width of this buffer in pixels.
*/
public int getWidth() {
- if (isClosed()) {
- throw new IllegalStateException("This HardwareBuffer has been closed and its width "
- + "cannot be obtained.");
- }
+ checkClosed("width");
return nGetWidth(mNativeObject);
}
@@ -297,10 +288,7 @@ public final class HardwareBuffer implements Parcelable, AutoCloseable {
* Returns the height of this buffer in pixels.
*/
public int getHeight() {
- if (isClosed()) {
- throw new IllegalStateException("This HardwareBuffer has been closed and its height "
- + "cannot be obtained.");
- }
+ checkClosed("height");
return nGetHeight(mNativeObject);
}
@@ -309,10 +297,7 @@ public final class HardwareBuffer implements Parcelable, AutoCloseable {
*/
@Format
public int getFormat() {
- if (isClosed()) {
- throw new IllegalStateException("This HardwareBuffer has been closed and its format "
- + "cannot be obtained.");
- }
+ checkClosed("format");
return nGetFormat(mNativeObject);
}
@@ -320,10 +305,7 @@ public final class HardwareBuffer implements Parcelable, AutoCloseable {
* Returns the number of layers in this buffer.
*/
public int getLayers() {
- if (isClosed()) {
- throw new IllegalStateException("This HardwareBuffer has been closed and its layer "
- + "count cannot be obtained.");
- }
+ checkClosed("layer count");
return nGetLayers(mNativeObject);
}
@@ -331,11 +313,24 @@ public final class HardwareBuffer implements Parcelable, AutoCloseable {
* Returns the usage flags of the usage hints set on this buffer.
*/
public long getUsage() {
+ checkClosed("usage");
+ return nGetUsage(mNativeObject);
+ }
+
+ /**
+ * Returns the system-wide unique id for this buffer
+ *
+ */
+ public long getId() {
+ checkClosed("id");
+ return nGetId(mNativeObject);
+ }
+
+ private void checkClosed(String name) {
if (isClosed()) {
- throw new IllegalStateException("This HardwareBuffer has been closed and its usage "
- + "cannot be obtained.");
+ throw new IllegalStateException("This HardwareBuffer has been closed and its "
+ + name + " cannot be obtained.");
}
- return nGetUsage(mNativeObject);
}
/**
@@ -407,36 +402,6 @@ public final class HardwareBuffer implements Parcelable, AutoCloseable {
}
};
- /**
- * Validates whether a particular format is supported by HardwareBuffer.
- *
- * @param format The format to validate.
- *
- * @return True if <code>format</code> is a supported format. false otherwise.
- * See {@link #create(int, int, int, int, long)}.
- */
- private static boolean isSupportedFormat(@Format int format) {
- switch(format) {
- case RGBA_8888:
- case RGBA_FP16:
- case RGBA_1010102:
- case RGBX_8888:
- case RGB_565:
- case RGB_888:
- case BLOB:
- case YCBCR_420_888:
- case D_16:
- case D_24:
- case DS_24UI8:
- case D_FP32:
- case DS_FP32UI8:
- case S_UI8:
- case YCBCR_P010:
- return true;
- }
- return false;
- }
-
private static native long nCreateHardwareBuffer(int width, int height, int format, int layers,
long usage);
private static native long nCreateFromGraphicBuffer(GraphicBuffer graphicBuffer);
@@ -457,4 +422,6 @@ public final class HardwareBuffer implements Parcelable, AutoCloseable {
long usage);
@CriticalNative
private static native long nEstimateSize(long nativeObject);
+ @CriticalNative
+ private static native long nGetId(long nativeObject);
}
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index a9d665c8b8a5..621eab558337 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -1963,7 +1963,6 @@ public class SoundTrigger {
}
private static Object mServiceLock = new Object();
- private static ISoundTriggerMiddlewareService mService;
/**
* Translate an exception thrown from interaction with the underlying service to an error code.
@@ -2217,20 +2216,12 @@ public class SoundTrigger {
binder =
ServiceManager.getServiceOrThrow(
Context.SOUND_TRIGGER_MIDDLEWARE_SERVICE);
- binder.linkToDeath(() -> {
- synchronized (mServiceLock) {
- mService = null;
- }
- }, 0);
- mService = ISoundTriggerMiddlewareService.Stub.asInterface(binder);
- break;
+ return ISoundTriggerMiddlewareService.Stub.asInterface(binder);
} catch (Exception e) {
Log.e(TAG, "Failed to bind to soundtrigger service", e);
}
}
- return mService;
}
-
}
/**
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 09a52e452f9a..26600e256ca1 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -955,16 +955,7 @@ public abstract class BatteryStats {
public static final int NUM_WIFI_BATCHED_SCAN_BINS = 5;
- /**
- * Note that these must match the constants in android.os.PowerManager.
- * Also, if the user activity types change, the BatteryStatsImpl.VERSION must
- * also be bumped.
- */
- static final String[] USER_ACTIVITY_TYPES = {
- "other", "button", "touch", "accessibility", "attention"
- };
-
- public static final int NUM_USER_ACTIVITY_TYPES = USER_ACTIVITY_TYPES.length;
+ public static final int NUM_USER_ACTIVITY_TYPES = PowerManager.USER_ACTIVITY_EVENT_MAX + 1;
public abstract void noteUserActivityLocked(int type);
public abstract boolean hasUserActivity();
@@ -2326,11 +2317,6 @@ public abstract class BatteryStats {
public abstract void finishIteratingHistoryLocked();
/**
- * Return the base time offset for the battery history.
- */
- public abstract long getHistoryBaseTime();
-
- /**
* Returns the number of times the device has been started.
*/
public abstract int getStartCount();
@@ -6168,7 +6154,7 @@ public abstract class BatteryStats {
}
sb.append(val);
sb.append(" ");
- sb.append(Uid.USER_ACTIVITY_TYPES[i]);
+ sb.append(PowerManager.userActivityEventToString(i));
}
}
if (hasData) {
@@ -7615,8 +7601,6 @@ public abstract class BatteryStats {
CHECKIN_VERSION, getParcelVersion(), getStartPlatformVersion(),
getEndPlatformVersion());
- long now = getHistoryBaseTime() + SystemClock.elapsedRealtime();
-
if ((flags & (DUMP_INCLUDE_HISTORY | DUMP_HISTORY_ONLY)) != 0) {
if (startIteratingHistoryLocked()) {
try {
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index f69d6b065246..62ee408cffbf 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -23,6 +23,7 @@ import android.os.PersistableBundle;
import android.os.UserHandle;
import android.os.UserManager;
import android.content.pm.UserInfo;
+import android.content.pm.UserProperties;
import android.content.IntentSender;
import android.content.RestrictionEntry;
import android.graphics.Bitmap;
@@ -71,6 +72,7 @@ interface IUserManager {
boolean isUserOfType(int userId, in String userType);
@UnsupportedAppUsage
UserInfo getUserInfo(int userId);
+ UserProperties getUserPropertiesCopy(int userId);
String getUserAccount(int userId);
void setUserAccount(int userId, String accountName);
long getUserCreationTime(int userId);
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 01b75d104e5e..a3a3e3f80f3d 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -348,6 +348,44 @@ public final class PowerManager {
public static final int USER_ACTIVITY_EVENT_DEVICE_STATE = 6;
/**
+ * @hide
+ */
+ public static final int USER_ACTIVITY_EVENT_MAX = USER_ACTIVITY_EVENT_DEVICE_STATE;
+
+ /**
+ * @hide
+ */
+ @IntDef(prefix = { "USER_ACTIVITY_EVENT_" }, value = {
+ USER_ACTIVITY_EVENT_OTHER,
+ USER_ACTIVITY_EVENT_BUTTON,
+ USER_ACTIVITY_EVENT_TOUCH,
+ USER_ACTIVITY_EVENT_ACCESSIBILITY,
+ USER_ACTIVITY_EVENT_ATTENTION,
+ USER_ACTIVITY_EVENT_FACE_DOWN,
+ USER_ACTIVITY_EVENT_DEVICE_STATE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface UserActivityEvent{}
+
+ /**
+ *
+ * Convert the user activity event to a string for debugging purposes.
+ * @hide
+ */
+ public static String userActivityEventToString(@UserActivityEvent int userActivityEvent) {
+ switch (userActivityEvent) {
+ case USER_ACTIVITY_EVENT_OTHER: return "other";
+ case USER_ACTIVITY_EVENT_BUTTON: return "button";
+ case USER_ACTIVITY_EVENT_TOUCH: return "touch";
+ case USER_ACTIVITY_EVENT_ACCESSIBILITY: return "accessibility";
+ case USER_ACTIVITY_EVENT_ATTENTION: return "attention";
+ case USER_ACTIVITY_EVENT_FACE_DOWN: return "faceDown";
+ case USER_ACTIVITY_EVENT_DEVICE_STATE: return "deviceState";
+ default: return Integer.toString(userActivityEvent);
+ }
+ }
+
+ /**
* User activity flag: If already dimmed, extend the dim timeout
* but do not brighten. This flag is useful for keeping the screen on
* a little longer without causing a visible change such as when
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 14082f3388a0..095d53e837b2 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -364,6 +364,11 @@ public class Process {
public static final int LAST_APPLICATION_CACHE_GID = 29999;
/**
+ * An invalid PID value.
+ */
+ public static final int INVALID_PID = -1;
+
+ /**
* Standard priority of application threads.
* Use with {@link #setThreadPriority(int)} and
* {@link #setThreadPriority(int, int)}, <b>not</b> with the normal
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 16352b8fbeb3..ef04f641b0ee 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -51,6 +51,7 @@ import android.content.IntentFilter;
import android.content.IntentSender;
import android.content.pm.UserInfo;
import android.content.pm.UserInfo.UserInfoFlag;
+import android.content.pm.UserProperties;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
@@ -2786,7 +2787,7 @@ public class UserManager {
return isUserRunning(user.getIdentifier());
}
- /** {@hide} */
+ /** @hide */
@RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
public boolean isUserRunning(@UserIdInt int userId) {
@@ -2841,6 +2842,7 @@ public class UserManager {
/**
* @hide
*/
+ @TestApi
public static boolean isUsersOnSecondaryDisplaysEnabled() {
return SystemProperties.getBoolean("fw.users_on_secondary_displays",
Resources.getSystem()
@@ -2968,7 +2970,7 @@ public class UserManager {
}
};
- /** {@hide} */
+ /** @hide */
@UnsupportedAppUsage
@RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
@@ -2976,13 +2978,13 @@ public class UserManager {
return mIsUserUnlockedCache.query(userId);
}
- /** {@hide} */
+ /** @hide */
public void disableIsUserUnlockedCache() {
mIsUserUnlockedCache.disableLocal();
mIsUserUnlockingOrUnlockedCache.disableLocal();
}
- /** {@hide} */
+ /** @hide */
public static final void invalidateIsUserUnlockedCache() {
PropertyInvalidatedCache.invalidateCache(CACHE_KEY_IS_USER_UNLOCKED_PROPERTY);
}
@@ -3012,7 +3014,7 @@ public class UserManager {
return isUserUnlockingOrUnlocked(user.getIdentifier());
}
- /** {@hide} */
+ /** @hide */
@RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
public boolean isUserUnlockingOrUnlocked(@UserIdInt int userId) {
@@ -3083,6 +3085,30 @@ public class UserManager {
}
/**
+ * Returns a {@link UserProperties} object describing the properties of the given user.
+ *
+ * Note that the caller may not have permission to access all items; requesting any item for
+ * which permission is lacking will throw a {@link SecurityException}.
+ *
+ * <p> Requires
+ * {@code android.Manifest.permission#MANAGE_USERS},
+ * {@code android.Manifest.permission#QUERY_USERS}, or
+ * {@code android.Manifest.permission#INTERACT_ACROSS_USERS}
+ * permission, or else the caller must be in the same profile group as the caller.
+ *
+ * @param userHandle the user handle of the user whose information is being requested.
+ * @return a UserProperties object for a specific user.
+ * @throws IllegalArgumentException if {@code userHandle} doesn't correspond to an existing user
+ */
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.QUERY_USERS,
+ android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
+ public @NonNull UserProperties getUserProperties(@NonNull UserHandle userHandle) {
+ return mUserPropertiesCache.query(userHandle.getIdentifier());
+ }
+
+ /**
* @hide
*
* Returns who set a user restriction on a user.
@@ -5480,11 +5506,37 @@ public class UserManager {
}
};
- /** {@hide} */
+ /** @hide */
public static final void invalidateStaticUserProperties() {
PropertyInvalidatedCache.invalidateCache(CACHE_KEY_STATIC_USER_PROPERTIES);
}
+ /* Cache key for UserProperties object. */
+ private static final String CACHE_KEY_USER_PROPERTIES = "cache_key.user_properties";
+
+ // TODO: It would be better to somehow have this as static, so that it can work cross-context.
+ private final PropertyInvalidatedCache<Integer, UserProperties> mUserPropertiesCache =
+ new PropertyInvalidatedCache<Integer, UserProperties>(16, CACHE_KEY_USER_PROPERTIES) {
+ @Override
+ public UserProperties recompute(Integer userId) {
+ try {
+ // If the userId doesn't exist, this will throw rather than cache garbage.
+ return mService.getUserPropertiesCopy(userId);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+ @Override
+ public boolean bypass(Integer query) {
+ return query < 0;
+ }
+ };
+
+ /** @hide */
+ public static final void invalidateUserPropertiesCache() {
+ PropertyInvalidatedCache.invalidateCache(CACHE_KEY_USER_PROPERTIES);
+ }
+
/**
* @hide
* User that enforces a restriction.
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index 2483f99a07ec..b7adcb84102d 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -755,6 +755,13 @@ public final class DeviceConfig {
public static final String NAMESPACE_VENDOR_SYSTEM_NATIVE = "vendor_system_native";
/**
+ * Namespace for Vendor System Native Boot related features.
+ *
+ * @hide
+ */
+ public static final String NAMESPACE_VENDOR_SYSTEM_NATIVE_BOOT = "vendor_system_native_boot";
+
+ /**
* Namespace for memory safety related features (e.g. MTE)
*
* @hide
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index a7c72730a89f..e67b67d0d4f7 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -7822,6 +7822,16 @@ public final class Settings {
"accessibility_display_magnification_auto_update";
/**
+ * Accessibility Window Magnification Allow diagonal scrolling value. The value is boolean.
+ * 1 : on, 0 : off
+ *
+ * @hide
+ */
+ public static final String ACCESSIBILITY_ALLOW_DIAGONAL_SCROLLING =
+ "accessibility_allow_diagonal_scrolling";
+
+
+ /**
* Setting that specifies what mode the soft keyboard is in (default or hidden). Can be
* modified from an AccessibilityService using the SoftKeyboardController.
*
@@ -10831,6 +10841,23 @@ public final class Settings {
"accessibility_software_cursor_enabled";
/**
+ * Software Cursor settings that specifies whether trigger hints are enabled.
+ *
+ * @hide
+ */
+ public static final String ACCESSIBILITY_SOFTWARE_CURSOR_TRIGGER_HINTS_ENABLED =
+ "accessibility_software_cursor_trigger_hints_enabled";
+
+ /**
+ * Software Cursor settings that specifies whether triggers are shifted when the keyboard
+ * is shown.
+ *
+ * @hide
+ */
+ public static final String ACCESSIBILITY_SOFTWARE_CURSOR_KEYBOARD_SHIFT_ENABLED =
+ "accessibility_software_cursor_keyboard_shift_enabled";
+
+ /**
* Whether the Adaptive connectivity option is enabled.
*
* @hide
diff --git a/core/java/android/service/dreams/IDreamManager.aidl b/core/java/android/service/dreams/IDreamManager.aidl
index 3e0deeb556e9..53ae6576ef95 100644
--- a/core/java/android/service/dreams/IDreamManager.aidl
+++ b/core/java/android/service/dreams/IDreamManager.aidl
@@ -35,6 +35,8 @@ interface IDreamManager {
void testDream(int userId, in ComponentName componentName);
@UnsupportedAppUsage
boolean isDreaming();
+ @UnsupportedAppUsage
+ boolean isDreamingOrInPreview();
void finishSelf(in IBinder token, boolean immediate);
void startDozing(in IBinder token, int screenState, int screenBrightness);
void stopDozing(in IBinder token);
diff --git a/core/java/android/service/selectiontoolbar/DefaultSelectionToolbarRenderService.java b/core/java/android/service/selectiontoolbar/DefaultSelectionToolbarRenderService.java
index f028ed3e6b00..ad73a53cfd87 100644
--- a/core/java/android/service/selectiontoolbar/DefaultSelectionToolbarRenderService.java
+++ b/core/java/android/service/selectiontoolbar/DefaultSelectionToolbarRenderService.java
@@ -69,7 +69,7 @@ public final class DefaultSelectionToolbarRenderService extends SelectionToolbar
if (mToolbarCache.indexOfKey(callingUid) < 0) {
RemoteSelectionToolbar toolbar = new RemoteSelectionToolbar(this,
- widgetToken, showInfo.getHostInputToken(),
+ widgetToken, showInfo,
callbackWrapper, this::transferTouch);
mToolbarCache.put(callingUid, new Pair<>(widgetToken, toolbar));
}
diff --git a/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java b/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java
index d75fbc0c64e0..95bcda5f7c55 100644
--- a/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java
+++ b/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java
@@ -22,7 +22,6 @@ import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
-import android.content.res.TypedArray;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.AnimatedVectorDrawable;
@@ -162,15 +161,14 @@ final class RemoteSelectionToolbar {
private final Rect mTempContentRectForRoot = new Rect();
private final int[] mTempCoords = new int[2];
- RemoteSelectionToolbar(Context context, long selectionToolbarToken, IBinder hostInputToken,
+ RemoteSelectionToolbar(Context context, long selectionToolbarToken, ShowInfo showInfo,
SelectionToolbarRenderService.RemoteCallbackWrapper callbackWrapper,
SelectionToolbarRenderService.TransferTouchListener transferTouchListener) {
- mContext = applyDefaultTheme(context);
+ mContext = applyDefaultTheme(context, showInfo.isIsLightTheme());
mSelectionToolbarToken = selectionToolbarToken;
mCallbackWrapper = callbackWrapper;
mTransferTouchListener = transferTouchListener;
- mHostInputToken = hostInputToken;
-
+ mHostInputToken = showInfo.getHostInputToken();
mContentContainer = createContentContainer(mContext);
mMarginHorizontal = mContext.getResources()
.getDimensionPixelSize(R.dimen.floating_toolbar_horizontal_margin);
@@ -1359,12 +1357,9 @@ final class RemoteSelectionToolbar {
/**
* Returns a re-themed context with controlled look and feel for views.
*/
- private static Context applyDefaultTheme(Context originalContext) {
- TypedArray a = originalContext.obtainStyledAttributes(new int[]{R.attr.isLightTheme});
- boolean isLightTheme = a.getBoolean(0, true);
+ private static Context applyDefaultTheme(Context originalContext, boolean isLightTheme) {
int themeId =
isLightTheme ? R.style.Theme_DeviceDefault_Light : R.style.Theme_DeviceDefault;
- a.recycle();
return new ContextThemeWrapper(originalContext, themeId);
}
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 4fbf4b528fe9..bc8822cfb3e9 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -1153,8 +1153,8 @@ public abstract class WallpaperService extends Service {
mLayout.surfaceInsets.set(0, 0, 0, 0);
}
final int relayoutResult = mSession.relayout(mWindow, mLayout, mWidth, mHeight,
- View.VISIBLE, 0, mWinFrames, mMergedConfiguration, mSurfaceControl,
- mInsetsState, mTempControls, mSyncSeqIdBundle);
+ View.VISIBLE, 0, 0, 0, mWinFrames, mMergedConfiguration,
+ mSurfaceControl, mInsetsState, mTempControls, mSyncSeqIdBundle);
final int transformHint = SurfaceControl.rotationToBufferTransform(
(mDisplayInstallOrientation + mDisplay.getRotation()) % 4);
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index eae951f7b73d..b73ff901052f 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -115,6 +115,11 @@ public class FeatureFlagUtils {
public static final String SETTINGS_ACCESSIBILITY_SIMPLE_CURSOR =
"settings_accessibility_simple_cursor";
+ /**
+ * Enable new language and keyboard settings UI
+ * @hide
+ */
+ public static final String SETTINGS_NEW_KEYBOARD_UI = "settings_new_keyboard_ui";
private static final Map<String, String> DEFAULT_FLAGS;
@@ -148,6 +153,7 @@ public class FeatureFlagUtils {
DEFAULT_FLAGS.put(SETTINGS_GUEST_MODE_UX_CHANGES, "true");
DEFAULT_FLAGS.put(SETTINGS_ENABLE_CLEAR_CALLING, "false");
DEFAULT_FLAGS.put(SETTINGS_ACCESSIBILITY_SIMPLE_CURSOR, "false");
+ DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_UI, "false");
}
private static final Set<String> PERSISTENT_FLAGS;
@@ -161,6 +167,7 @@ public class FeatureFlagUtils {
PERSISTENT_FLAGS.add(SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME);
PERSISTENT_FLAGS.add(SETTINGS_HIDE_SECOND_LAYER_PAGE_NAVIGATE_UP_BUTTON_IN_TWO_PANE);
PERSISTENT_FLAGS.add(SETTINGS_AUTO_TEXT_WRAPPING);
+ PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_UI);
}
/**
diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java
index 52dc34298ae7..a6f63e859049 100644
--- a/core/java/android/view/AccessibilityInteractionController.java
+++ b/core/java/android/view/AccessibilityInteractionController.java
@@ -89,7 +89,9 @@ public final class AccessibilityInteractionController {
// Callbacks should have the same configuration of the flags below to allow satisfying a pending
// node request on prefetch
- private static final int FLAGS_AFFECTING_REPORTED_DATA = AccessibilityNodeInfo.FLAG_REPORT_MASK;
+ private static final int FLAGS_AFFECTING_REPORTED_DATA =
+ AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
+ | AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS;
private final ArrayList<AccessibilityNodeInfo> mTempAccessibilityNodeInfoList =
new ArrayList<AccessibilityNodeInfo>();
@@ -165,11 +167,6 @@ public final class AccessibilityInteractionController {
return (view != null) && (view.getWindowVisibility() == View.VISIBLE && view.isShown());
}
- private boolean isVisibleToAccessibilityService(View view) {
- return view != null && (!view.isAccessibilityDataPrivate()
- || mA11yManager.isRequestFromAccessibilityTool());
- }
-
public void findAccessibilityNodeInfoByAccessibilityIdClientThread(
long accessibilityNodeId, Region interactiveRegion, int interactionId,
IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
@@ -361,7 +358,7 @@ public final class AccessibilityInteractionController {
if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
return;
}
- setAccessibilityFetchFlags(flags);
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
requestedView = findViewByAccessibilityId(accessibilityViewId);
if (requestedView != null && isShown(requestedView)) {
requestedNode = populateAccessibilityNodeInfoForView(
@@ -374,7 +371,7 @@ public final class AccessibilityInteractionController {
mPrefetcher.prefetchAccessibilityNodeInfos(requestedView,
requestedNode == null ? null : new AccessibilityNodeInfo(requestedNode),
infos);
- resetAccessibilityFetchFlags();
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
}
}
} finally {
@@ -399,7 +396,7 @@ public final class AccessibilityInteractionController {
}
mPrefetcher.prefetchAccessibilityNodeInfos(requestedView,
requestedNode == null ? null : new AccessibilityNodeInfo(requestedNode), infos);
- resetAccessibilityFetchFlags();
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
updateInfosForViewPort(infos, spec, matrixValues, interactiveRegion);
final SatisfiedFindAccessibilityNodeByAccessibilityIdRequest satisfiedRequest =
getSatisfiedRequestInPrefetch(requestedNode == null ? null : requestedNode, infos,
@@ -481,7 +478,7 @@ public final class AccessibilityInteractionController {
|| viewId == null) {
return;
}
- setAccessibilityFetchFlags(flags);
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
final View root = findViewByAccessibilityId(accessibilityViewId);
if (root != null) {
final int resolvedViewId = root.getContext().getResources()
@@ -497,7 +494,7 @@ public final class AccessibilityInteractionController {
mAddNodeInfosForViewId.reset();
}
} finally {
- resetAccessibilityFetchFlags();
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
updateInfosForViewportAndReturnFindNodeResult(
infos, callback, interactionId, spec, matrixValues, interactiveRegion);
}
@@ -545,7 +542,7 @@ public final class AccessibilityInteractionController {
if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
return;
}
- setAccessibilityFetchFlags(flags);
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
final View root = findViewByAccessibilityId(accessibilityViewId);
if (root != null && isShown(root)) {
AccessibilityNodeProvider provider = root.getAccessibilityNodeProvider();
@@ -564,7 +561,7 @@ public final class AccessibilityInteractionController {
final int viewCount = foundViews.size();
for (int i = 0; i < viewCount; i++) {
View foundView = foundViews.get(i);
- if (isShown(foundView) && isVisibleToAccessibilityService(foundView)) {
+ if (isShown(foundView)) {
provider = foundView.getAccessibilityNodeProvider();
if (provider != null) {
List<AccessibilityNodeInfo> infosFromProvider =
@@ -582,7 +579,7 @@ public final class AccessibilityInteractionController {
}
}
} finally {
- resetAccessibilityFetchFlags();
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
updateInfosForViewportAndReturnFindNodeResult(
infos, callback, interactionId, spec, matrixValues, interactiveRegion);
}
@@ -630,7 +627,7 @@ public final class AccessibilityInteractionController {
if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
return;
}
- setAccessibilityFetchFlags(flags);
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
final View root = findViewByAccessibilityId(accessibilityViewId);
if (root != null && isShown(root)) {
switch (focusType) {
@@ -645,9 +642,6 @@ public final class AccessibilityInteractionController {
if (!isShown(host)) {
break;
}
- if (!isVisibleToAccessibilityService(host)) {
- break;
- }
// If the host has a provider ask this provider to search for the
// focus instead fetching all provider nodes to do the search here.
AccessibilityNodeProvider provider = host.getAccessibilityNodeProvider();
@@ -668,9 +662,6 @@ public final class AccessibilityInteractionController {
if (!isShown(target)) {
break;
}
- if (!isVisibleToAccessibilityService(target)) {
- break;
- }
AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider();
if (provider != null) {
focused = provider.findFocus(focusType);
@@ -684,7 +675,7 @@ public final class AccessibilityInteractionController {
}
}
} finally {
- resetAccessibilityFetchFlags();
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
updateInfoForViewportAndReturnFindNodeResult(
focused, callback, interactionId, spec, matrixValues, interactiveRegion);
}
@@ -731,7 +722,7 @@ public final class AccessibilityInteractionController {
if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
return;
}
- setAccessibilityFetchFlags(flags);
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
final View root = findViewByAccessibilityId(accessibilityViewId);
if (root != null && isShown(root)) {
View nextView = root.focusSearch(direction);
@@ -740,7 +731,7 @@ public final class AccessibilityInteractionController {
}
}
} finally {
- resetAccessibilityFetchFlags();
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
updateInfoForViewportAndReturnFindNodeResult(
next, callback, interactionId, spec, matrixValues, interactiveRegion);
}
@@ -787,9 +778,9 @@ public final class AccessibilityInteractionController {
mViewRootImpl.mStopped || mViewRootImpl.mPausedForTransition) {
return;
}
- setAccessibilityFetchFlags(flags);
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
final View target = findViewByAccessibilityId(accessibilityViewId);
- if (target != null && isShown(target) && isVisibleToAccessibilityService(target)) {
+ if (target != null && isShown(target)) {
mA11yManager.notifyPerformingAction(action);
if (action == R.id.accessibilityActionClickOnClickableSpan) {
// Handle this hidden action separately
@@ -808,7 +799,7 @@ public final class AccessibilityInteractionController {
}
} finally {
try {
- resetAccessibilityFetchFlags();
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
callback.setPerformAccessibilityActionResult(succeeded, interactionId);
} catch (RemoteException re) {
/* ignore - the other side will time out */
@@ -832,9 +823,9 @@ public final class AccessibilityInteractionController {
return;
}
try {
- setAccessibilityFetchFlags(
- AccessibilityNodeInfo.FLAG_SERVICE_REQUESTS_INCLUDE_NOT_IMPORTANT_VIEWS);
- final View root = getRootView();
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags =
+ AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
+ final View root = mViewRootImpl.mView;
if (root != null && isShown(root)) {
final View host = mViewRootImpl.mAccessibilityFocusedHost;
// If there is no accessibility focus host or it is not a descendant
@@ -858,7 +849,7 @@ public final class AccessibilityInteractionController {
}
}
} finally {
- resetAccessibilityFetchFlags();
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
}
}
@@ -878,7 +869,7 @@ public final class AccessibilityInteractionController {
|| mViewRootImpl.mStopped || mViewRootImpl.mPausedForTransition) {
return;
}
- final View root = getRootView();
+ final View root = mViewRootImpl.mView;
if (root != null && isShown(root)) {
// trigger ACTION_OUTSIDE to notify windows
final long now = SystemClock.uptimeMillis();
@@ -891,30 +882,12 @@ public final class AccessibilityInteractionController {
private View findViewByAccessibilityId(int accessibilityId) {
if (accessibilityId == AccessibilityNodeInfo.ROOT_ITEM_ID) {
- return getRootView();
+ return mViewRootImpl.mView;
} else {
return AccessibilityNodeIdManager.getInstance().findView(accessibilityId);
}
}
- private View getRootView() {
- if (!isVisibleToAccessibilityService(mViewRootImpl.mView)) {
- return null;
- }
- return mViewRootImpl.mView;
- }
-
- private void setAccessibilityFetchFlags(int flags) {
- mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
- mA11yManager.setRequestFromAccessibilityTool(
- (flags & AccessibilityNodeInfo.FLAG_SERVICE_IS_ACCESSIBILITY_TOOL) != 0);
- }
-
- private void resetAccessibilityFetchFlags() {
- mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
- mA11yManager.setRequestFromAccessibilityTool(false);
- }
-
// The boundInScreen includes magnification effect, so we need to normalize it before
// determine the visibility.
private void adjustIsVisibleToUserIfNeeded(AccessibilityNodeInfo info,
@@ -1733,7 +1706,7 @@ public final class AccessibilityInteractionController {
@Override
public boolean test(View view) {
- if (view.getId() == mViewId && isShown(view) && isVisibleToAccessibilityService(view)) {
+ if (view.getId() == mViewId && isShown(view)) {
mInfos.add(view.createAccessibilityNodeInfo());
}
return false;
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 3016473fa040..afcec6639dcf 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -75,41 +75,42 @@ interface IWindowSession {
* @param requestedWidth The width the window wants to be.
* @param requestedHeight The height the window wants to be.
* @param viewVisibility Window root view's visibility.
- * @param flags Request flags: {@link WindowManagerGlobal#RELAYOUT_INSETS_PENDING},
- * {@link WindowManagerGlobal#RELAYOUT_DEFER_SURFACE_DESTROY}.
- * @param outFrame Rect in which is placed the new position/size on
- * screen.
- * @param outContentInsets Rect in which is placed the offsets from
- * <var>outFrame</var> in which the content of the window should be
- * placed. This can be used to modify the window layout to ensure its
- * contents are visible to the user, taking into account system windows
- * like the status bar or a soft keyboard.
- * @param outVisibleInsets Rect in which is placed the offsets from
- * <var>outFrame</var> in which the window is actually completely visible
- * to the user. This can be used to temporarily scroll the window's
- * contents to make sure the user can see it. This is different than
- * <var>outContentInsets</var> in that these insets change transiently,
- * so complex relayout of the window should not happen based on them.
- * @param outOutsets Rect in which is placed the dead area of the screen that we would like to
- * treat as real display. Example of such area is a chin in some models of wearable devices.
- * @param outBackdropFrame Rect which is used draw the resizing background during a resize
- * operation.
+ * @param flags Request flags: {@link WindowManagerGlobal#RELAYOUT_INSETS_PENDING}.
+ * @param seq The calling sequence of {@link #relayout} and {@link #relayoutAsync}.
+ * @param lastSyncSeqId The last SyncSeqId that the client applied.
+ * @param outFrames The window frames used by the client side for layout.
* @param outMergedConfiguration New config container that holds global, override and merged
- * config for window, if it is now becoming visible and the merged configuration has changed
- * since it was last displayed.
- * @param outSurface Object in which is placed the new display surface.
+ * config for window, if it is now becoming visible and the merged
+ * config has changed since it was last displayed.
+ * @param outSurfaceControl Object in which is placed the new display surface.
* @param insetsState The current insets state in the system.
- *
- * @return int Result flags: {@link WindowManagerGlobal#RELAYOUT_SHOW_FOCUS},
- * {@link WindowManagerGlobal#RELAYOUT_FIRST_TIME}.
+ * @param activeControls Objects which allow controlling {@link InsetsSource}s.
+ * @param bundle A temporary object to obtain the latest SyncSeqId.
+ * @return int Result flags, defined in {@link WindowManagerGlobal}.
*/
int relayout(IWindow window, in WindowManager.LayoutParams attrs,
int requestedWidth, int requestedHeight, int viewVisibility,
- int flags, out ClientWindowFrames outFrames,
+ int flags, int seq, int lastSyncSeqId, out ClientWindowFrames outFrames,
out MergedConfiguration outMergedConfiguration, out SurfaceControl outSurfaceControl,
out InsetsState insetsState, out InsetsSourceControl[] activeControls,
out Bundle bundle);
+ /**
+ * Similar to {@link #relayout} but this is an oneway method which doesn't return anything.
+ *
+ * @param window The window being modified.
+ * @param attrs If non-null, new attributes to apply to the window.
+ * @param requestedWidth The width the window wants to be.
+ * @param requestedHeight The height the window wants to be.
+ * @param viewVisibility Window root view's visibility.
+ * @param flags Request flags: {@link WindowManagerGlobal#RELAYOUT_INSETS_PENDING}.
+ * @param seq The calling sequence of {@link #relayout} and {@link #relayoutAsync}.
+ * @param lastSyncSeqId The last SyncSeqId that the client applied.
+ */
+ oneway void relayoutAsync(IWindow window, in WindowManager.LayoutParams attrs,
+ int requestedWidth, int requestedHeight, int viewVisibility, int flags, int seq,
+ int lastSyncSeqId);
+
/*
* Notify the window manager that an application is relaunching and
* windows should be prepared for replacement.
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index a2cb1d5c6bd2..4a72a62b7a8d 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -314,6 +314,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
(int) (startValue.right + fraction * (endValue.right - startValue.right)),
(int) (startValue.bottom + fraction * (endValue.bottom - startValue.bottom)));
+ /** Logging listener. */
+ private WindowInsetsAnimationControlListener mLoggingListener;
+
/**
* The default implementation of listener, to be used by InsetsController and InsetsPolicy to
* animate insets.
@@ -330,6 +333,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
private final long mDurationMs;
private final boolean mDisable;
private final int mFloatingImeBottomInset;
+ private final WindowInsetsAnimationControlListener mLoggingListener;
private final ThreadLocal<AnimationHandler> mSfAnimationHandlerThreadLocal =
new ThreadLocal<AnimationHandler>() {
@@ -343,7 +347,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
public InternalAnimationControlListener(boolean show, boolean hasAnimationCallbacks,
@InsetsType int requestedTypes, @Behavior int behavior, boolean disable,
- int floatingImeBottomInset) {
+ int floatingImeBottomInset, WindowInsetsAnimationControlListener loggingListener) {
mShow = show;
mHasAnimationCallbacks = hasAnimationCallbacks;
mRequestedTypes = requestedTypes;
@@ -351,12 +355,16 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
mDurationMs = calculateDurationMs();
mDisable = disable;
mFloatingImeBottomInset = floatingImeBottomInset;
+ mLoggingListener = loggingListener;
}
@Override
public void onReady(WindowInsetsAnimationController controller, int types) {
mController = controller;
if (DEBUG) Log.d(TAG, "default animation onReady types: " + types);
+ if (mLoggingListener != null) {
+ mLoggingListener.onReady(controller, types);
+ }
if (mDisable) {
onAnimationFinish();
@@ -410,6 +418,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
public void onFinished(WindowInsetsAnimationController controller) {
if (DEBUG) Log.d(TAG, "InternalAnimationControlListener onFinished types:"
+ Type.toString(mRequestedTypes));
+ if (mLoggingListener != null) {
+ mLoggingListener.onFinished(controller);
+ }
}
@Override
@@ -420,6 +431,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
}
if (DEBUG) Log.d(TAG, "InternalAnimationControlListener onCancelled types:"
+ mRequestedTypes);
+ if (mLoggingListener != null) {
+ mLoggingListener.onCancelled(controller);
+ }
}
protected Interpolator getInsetsInterpolator() {
@@ -1147,6 +1161,13 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
updateRequestedVisibilities();
}
+ // TODO(b/242962223): Make this setter restrictive.
+ @Override
+ public void setSystemDrivenInsetsAnimationLoggingListener(
+ @Nullable WindowInsetsAnimationControlListener listener) {
+ mLoggingListener = listener;
+ }
+
/**
* @return Pair of (types ready to animate, IME ready to animate).
*/
@@ -1159,7 +1180,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
final InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i));
boolean show = animationType == ANIMATION_TYPE_SHOW
|| animationType == ANIMATION_TYPE_USER;
- boolean canRun = false;
+ boolean canRun = true;
if (show) {
// Show request
if (fromIme) {
@@ -1169,7 +1190,6 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
}
switch(consumer.requestShow(fromIme)) {
case ShowResult.SHOW_IMMEDIATELY:
- canRun = true;
break;
case ShowResult.IME_SHOW_DELAYED:
imeReady = false;
@@ -1180,6 +1200,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
+ fromIme);
// IME cannot be shown (since it didn't have focus), proceed
// with animation of other types.
+ canRun = false;
break;
}
} else {
@@ -1189,7 +1210,6 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
if (!fromIme) {
consumer.notifyHidden();
}
- canRun = true;
}
if (!canRun) {
if (WARN) Log.w(TAG, String.format(
@@ -1460,7 +1480,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
boolean hasAnimationCallbacks = mHost.hasAnimationCallbacks();
final InternalAnimationControlListener listener = new InternalAnimationControlListener(
show, hasAnimationCallbacks, types, mHost.getSystemBarsBehavior(),
- skipAnim || mAnimationsDisabled, mHost.dipToPx(FLOATING_IME_BOTTOM_INSET_DP));
+ skipAnim || mAnimationsDisabled, mHost.dipToPx(FLOATING_IME_BOTTOM_INSET_DP),
+ 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/PendingInsetsController.java b/core/java/android/view/PendingInsetsController.java
index c61baf6fb40c..3fe9110283a6 100644
--- a/core/java/android/view/PendingInsetsController.java
+++ b/core/java/android/view/PendingInsetsController.java
@@ -44,6 +44,7 @@ public class PendingInsetsController implements WindowInsetsController {
private ArrayList<OnControllableInsetsChangedListener> mControllableInsetsChangedListeners
= new ArrayList<>();
private int mCaptionInsetsHeight = 0;
+ private WindowInsetsAnimationControlListener mLoggingListener;
@Override
public void show(int types) {
@@ -176,6 +177,9 @@ public class PendingInsetsController implements WindowInsetsController {
controller.addOnControllableInsetsChangedListener(
mControllableInsetsChangedListeners.get(i));
}
+ if (mLoggingListener != null) {
+ controller.setSystemDrivenInsetsAnimationLoggingListener(mLoggingListener);
+ }
// Reset all state so it doesn't get applied twice just in case
mRequests.clear();
@@ -184,7 +188,7 @@ public class PendingInsetsController implements WindowInsetsController {
mAppearance = 0;
mAppearanceMask = 0;
mAnimationsDisabled = false;
-
+ mLoggingListener = null;
// After replaying, we forward everything directly to the replayed instance.
mReplayedInsetsController = controller;
}
@@ -198,6 +202,16 @@ public class PendingInsetsController implements WindowInsetsController {
}
@Override
+ public void setSystemDrivenInsetsAnimationLoggingListener(
+ @Nullable WindowInsetsAnimationControlListener listener) {
+ if (mReplayedInsetsController != null) {
+ mReplayedInsetsController.setSystemDrivenInsetsAnimationLoggingListener(listener);
+ } else {
+ mLoggingListener = listener;
+ }
+ }
+
+ @Override
public void controlWindowInsetsAnimation(@InsetsType int types, long durationMillis,
@Nullable Interpolator interpolator,
CancellationSignal cancellationSignal,
diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java
index 5721fa6dd11a..3acb0534e388 100644
--- a/core/java/android/view/SurfaceControlViewHost.java
+++ b/core/java/android/view/SurfaceControlViewHost.java
@@ -28,8 +28,8 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteException;
import android.util.Log;
-import android.window.WindowTokenClient;
import android.view.accessibility.IAccessibilityEmbeddedConnection;
+import android.window.WindowTokenClient;
import java.util.Objects;
@@ -271,14 +271,8 @@ public class SurfaceControlViewHost {
/** @hide */
public SurfaceControlViewHost(@NonNull Context c, @NonNull Display d,
@NonNull WindowlessWindowManager wwm) {
- this(c, d, wwm, false /* useSfChoreographer */);
- }
-
- /** @hide */
- public SurfaceControlViewHost(@NonNull Context c, @NonNull Display d,
- @NonNull WindowlessWindowManager wwm, boolean useSfChoreographer) {
mWm = wwm;
- mViewRoot = new ViewRootImpl(c, d, mWm, new WindowlessWindowLayout(), useSfChoreographer);
+ mViewRoot = new ViewRootImpl(c, d, mWm, new WindowlessWindowLayout());
addConfigCallback(c, d);
WindowManagerGlobal.getInstance().addWindowlessRoot(mViewRoot);
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 8f9c5fe2b87f..a66427843af0 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -723,9 +723,9 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
private void releaseSurfaces(boolean releaseSurfacePackage) {
mSurfaceAlpha = 1f;
- mSurface.destroy();
synchronized (mSurfaceControlLock) {
+ mSurface.destroy();
if (mBlastBufferQueue != null) {
mBlastBufferQueue.destroy();
mBlastBufferQueue = null;
@@ -774,99 +774,105 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
Transaction surfaceUpdateTransaction) {
boolean realSizeChanged = false;
- mDrawingStopped = !mVisible;
-
- if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
- + "Cur surface: " + mSurface);
+ mSurfaceLock.lock();
+ try {
+ mDrawingStopped = !mVisible;
- // If we are creating the surface control or the parent surface has not
- // changed, then set relative z. Otherwise allow the parent
- // SurfaceChangedCallback to update the relative z. This is needed so that
- // we do not change the relative z before the server is ready to swap the
- // parent surface.
- if (creating) {
- updateRelativeZ(surfaceUpdateTransaction);
- if (mSurfacePackage != null) {
- reparentSurfacePackage(surfaceUpdateTransaction, mSurfacePackage);
+ if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+ + "Cur surface: " + mSurface);
+
+ // If we are creating the surface control or the parent surface has not
+ // changed, then set relative z. Otherwise allow the parent
+ // SurfaceChangedCallback to update the relative z. This is needed so that
+ // we do not change the relative z before the server is ready to swap the
+ // parent surface.
+ if (creating) {
+ updateRelativeZ(surfaceUpdateTransaction);
+ if (mSurfacePackage != null) {
+ reparentSurfacePackage(surfaceUpdateTransaction, mSurfacePackage);
+ }
}
- }
- mParentSurfaceSequenceId = viewRoot.getSurfaceSequenceId();
-
- if (mViewVisibility) {
- surfaceUpdateTransaction.show(mSurfaceControl);
- } else {
- surfaceUpdateTransaction.hide(mSurfaceControl);
- }
+ mParentSurfaceSequenceId = viewRoot.getSurfaceSequenceId();
+ if (mViewVisibility) {
+ surfaceUpdateTransaction.show(mSurfaceControl);
+ } else {
+ surfaceUpdateTransaction.hide(mSurfaceControl);
+ }
- updateBackgroundVisibility(surfaceUpdateTransaction);
- updateBackgroundColor(surfaceUpdateTransaction);
- if (mUseAlpha) {
- float alpha = getFixedAlpha();
- surfaceUpdateTransaction.setAlpha(mSurfaceControl, alpha);
- mSurfaceAlpha = alpha;
- }
- surfaceUpdateTransaction.setCornerRadius(mSurfaceControl, mCornerRadius);
- if ((sizeChanged || hintChanged) && !creating) {
- setBufferSize(surfaceUpdateTransaction);
- }
- if (sizeChanged || creating || !isHardwareAccelerated()) {
- // Set a window crop when creating the surface or changing its size to
- // crop the buffer to the surface size since the buffer producer may
- // use SCALING_MODE_SCALE and submit a larger size than the surface
- // size.
- if (mClipSurfaceToBounds && mClipBounds != null) {
- surfaceUpdateTransaction.setWindowCrop(mSurfaceControl, mClipBounds);
- } else {
- surfaceUpdateTransaction.setWindowCrop(mSurfaceControl, mSurfaceWidth,
- mSurfaceHeight);
+ updateBackgroundVisibility(surfaceUpdateTransaction);
+ updateBackgroundColor(surfaceUpdateTransaction);
+ if (mUseAlpha) {
+ float alpha = getFixedAlpha();
+ surfaceUpdateTransaction.setAlpha(mSurfaceControl, alpha);
+ mSurfaceAlpha = alpha;
}
- surfaceUpdateTransaction.setDesintationFrame(mBlastSurfaceControl, mSurfaceWidth,
- mSurfaceHeight);
+ surfaceUpdateTransaction.setCornerRadius(mSurfaceControl, mCornerRadius);
+ if ((sizeChanged || hintChanged) && !creating) {
+ setBufferSize(surfaceUpdateTransaction);
+ }
+ if (sizeChanged || creating || !isHardwareAccelerated()) {
+
+ // Set a window crop when creating the surface or changing its size to
+ // crop the buffer to the surface size since the buffer producer may
+ // use SCALING_MODE_SCALE and submit a larger size than the surface
+ // size.
+ if (mClipSurfaceToBounds && mClipBounds != null) {
+ surfaceUpdateTransaction.setWindowCrop(mSurfaceControl, mClipBounds);
+ } else {
+ surfaceUpdateTransaction.setWindowCrop(mSurfaceControl, mSurfaceWidth,
+ mSurfaceHeight);
+ }
- if (isHardwareAccelerated()) {
- // This will consume the passed in transaction and the transaction will be
- // applied on a render worker thread.
- replacePositionUpdateListener(mSurfaceWidth, mSurfaceHeight);
- } else {
- onSetSurfacePositionAndScale(surfaceUpdateTransaction, mSurfaceControl,
- mScreenRect.left /*positionLeft*/,
- mScreenRect.top /*positionTop*/,
- mScreenRect.width() / (float) mSurfaceWidth /*postScaleX*/,
- mScreenRect.height() / (float) mSurfaceHeight /*postScaleY*/);
+ surfaceUpdateTransaction.setDesintationFrame(mBlastSurfaceControl, mSurfaceWidth,
+ mSurfaceHeight);
+
+ if (isHardwareAccelerated()) {
+ // This will consume the passed in transaction and the transaction will be
+ // applied on a render worker thread.
+ replacePositionUpdateListener(mSurfaceWidth, mSurfaceHeight);
+ } else {
+ onSetSurfacePositionAndScale(surfaceUpdateTransaction, mSurfaceControl,
+ mScreenRect.left /*positionLeft*/,
+ mScreenRect.top /*positionTop*/,
+ mScreenRect.width() / (float) mSurfaceWidth /*postScaleX*/,
+ mScreenRect.height() / (float) mSurfaceHeight /*postScaleY*/);
+ }
+ if (DEBUG_POSITION) {
+ Log.d(TAG, String.format(
+ "%d performSurfaceTransaction %s "
+ + "position = [%d, %d, %d, %d] surfaceSize = %dx%d",
+ System.identityHashCode(this),
+ isHardwareAccelerated() ? "RenderWorker" : "UI Thread",
+ mScreenRect.left, mScreenRect.top, mScreenRect.right,
+ mScreenRect.bottom, mSurfaceWidth, mSurfaceHeight));
+ }
}
- if (DEBUG_POSITION) {
- Log.d(TAG, String.format(
- "%d performSurfaceTransaction %s "
- + "position = [%d, %d, %d, %d] surfaceSize = %dx%d",
- System.identityHashCode(this),
- isHardwareAccelerated() ? "RenderWorker" : "UI Thread",
- mScreenRect.left, mScreenRect.top, mScreenRect.right,
- mScreenRect.bottom, mSurfaceWidth, mSurfaceHeight));
+ applyTransactionOnVriDraw(surfaceUpdateTransaction);
+ updateEmbeddedAccessibilityMatrix(false);
+
+ mSurfaceFrame.left = 0;
+ mSurfaceFrame.top = 0;
+ if (translator == null) {
+ mSurfaceFrame.right = mSurfaceWidth;
+ mSurfaceFrame.bottom = mSurfaceHeight;
+ } else {
+ float appInvertedScale = translator.applicationInvertedScale;
+ mSurfaceFrame.right = (int) (mSurfaceWidth * appInvertedScale + 0.5f);
+ mSurfaceFrame.bottom = (int) (mSurfaceHeight * appInvertedScale + 0.5f);
}
+ final int surfaceWidth = mSurfaceFrame.right;
+ final int surfaceHeight = mSurfaceFrame.bottom;
+ realSizeChanged = mLastSurfaceWidth != surfaceWidth
+ || mLastSurfaceHeight != surfaceHeight;
+ mLastSurfaceWidth = surfaceWidth;
+ mLastSurfaceHeight = surfaceHeight;
+ } finally {
+ mSurfaceLock.unlock();
}
- applyTransactionOnVriDraw(surfaceUpdateTransaction);
- updateEmbeddedAccessibilityMatrix(false);
- mSurfaceFrame.left = 0;
- mSurfaceFrame.top = 0;
- if (translator == null) {
- mSurfaceFrame.right = mSurfaceWidth;
- mSurfaceFrame.bottom = mSurfaceHeight;
- } else {
- float appInvertedScale = translator.applicationInvertedScale;
- mSurfaceFrame.right = (int) (mSurfaceWidth * appInvertedScale + 0.5f);
- mSurfaceFrame.bottom = (int) (mSurfaceHeight * appInvertedScale + 0.5f);
- }
- final int surfaceWidth = mSurfaceFrame.right;
- final int surfaceHeight = mSurfaceFrame.bottom;
- realSizeChanged = mLastSurfaceWidth != surfaceWidth
- || mLastSurfaceHeight != surfaceHeight;
- mLastSurfaceWidth = surfaceWidth;
- mLastSurfaceHeight = surfaceHeight;
-
return realSizeChanged;
}
@@ -1133,30 +1139,21 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
* Surface for compatibility reasons.
*/
private void copySurface(boolean surfaceControlCreated, boolean bufferSizeChanged) {
- // Some legacy applications use the underlying native {@link Surface} object
- // as a key to whether anything has changed. In these cases, updates to the
- // existing {@link Surface} will be ignored when the size changes.
- // Therefore, we must explicitly recreate the {@link Surface} in these
- // cases.
- boolean needsWorkaround = bufferSizeChanged &&
- getContext().getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.O;
- if (!surfaceControlCreated && !needsWorkaround) {
- return;
- }
- mSurfaceLock.lock();
- try {
- if (surfaceControlCreated) {
- mSurface.copyFrom(mBlastBufferQueue);
- }
-
- if (needsWorkaround) {
- if (mBlastBufferQueue != null) {
- mSurface.transferFrom(mBlastBufferQueue.createSurfaceWithHandle());
- }
- }
- } finally {
- mSurfaceLock.unlock();
- }
+ if (surfaceControlCreated) {
+ mSurface.copyFrom(mBlastBufferQueue);
+ }
+
+ if (bufferSizeChanged && getContext().getApplicationInfo().targetSdkVersion
+ < Build.VERSION_CODES.O) {
+ // Some legacy applications use the underlying native {@link Surface} object
+ // as a key to whether anything has changed. In these cases, updates to the
+ // existing {@link Surface} will be ignored when the size changes.
+ // Therefore, we must explicitly recreate the {@link Surface} in these
+ // cases.
+ if (mBlastBufferQueue != null) {
+ mSurface.transferFrom(mBlastBufferQueue.createSurfaceWithHandle());
+ }
+ }
}
private void setBufferSize(Transaction transaction) {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 84edb3a7bdee..48937770eddb 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -3085,45 +3085,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
static final int IMPORTANT_FOR_ACCESSIBILITY_DEFAULT = IMPORTANT_FOR_ACCESSIBILITY_AUTO;
/**
- * Automatically determine whether the view should only allow interactions from
- * {@link android.accessibilityservice.AccessibilityService}s with the
- * {@link android.accessibilityservice.AccessibilityServiceInfo#isAccessibilityTool} property
- * set to true.
- *
- * <p>
- * Accessibility interactions from services without {@code isAccessibilityTool} set to true are
- * disallowed for any of the following conditions:
- * <li>this view's window sets {@link WindowManager.LayoutParams#FLAG_SECURE}.</li>
- * <li>this view sets {@link #getFilterTouchesWhenObscured()}.</li>
- * <li>any parent of this view returns true from {@link #isAccessibilityDataPrivate()}.</li>
- * </p>
- */
- public static final int ACCESSIBILITY_DATA_PRIVATE_AUTO = 0x00000000;
-
- /**
- * Only allow interactions from {@link android.accessibilityservice.AccessibilityService}s
- * with the {@link android.accessibilityservice.AccessibilityServiceInfo#isAccessibilityTool}
- * property set to true.
- */
- public static final int ACCESSIBILITY_DATA_PRIVATE_YES = 0x00000001;
-
- /**
- * Allow interactions from all {@link android.accessibilityservice.AccessibilityService}s,
- * regardless of their
- * {@link android.accessibilityservice.AccessibilityServiceInfo#isAccessibilityTool} property.
- */
- public static final int ACCESSIBILITY_DATA_PRIVATE_NO = 0x00000002;
-
- /** @hide */
- @IntDef(prefix = { "ACCESSIBILITY_DATA_PRIVATE_" }, value = {
- ACCESSIBILITY_DATA_PRIVATE_AUTO,
- ACCESSIBILITY_DATA_PRIVATE_YES,
- ACCESSIBILITY_DATA_PRIVATE_NO,
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface AccessibilityDataPrivate {}
-
- /**
* Mask for obtaining the bits which specify how to determine
* whether a view is important for accessibility.
*/
@@ -4566,14 +4527,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
private CharSequence mAccessibilityPaneTitle;
/**
- * Describes whether this view should only allow interactions from
- * {@link android.accessibilityservice.AccessibilityService}s with the
- * {@link android.accessibilityservice.AccessibilityServiceInfo#isAccessibilityTool} property
- * set to true.
- */
- private int mAccessibilityDataPrivate = ACCESSIBILITY_DATA_PRIVATE_AUTO;
-
- /**
* Specifies the id of a view for which this view serves as a label for
* accessibility purposes.
*/
@@ -5966,10 +5919,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
setImportantForAccessibility(a.getInt(attr,
IMPORTANT_FOR_ACCESSIBILITY_DEFAULT));
break;
- case R.styleable.View_accessibilityDataPrivate:
- setAccessibilityDataPrivate(a.getInt(attr,
- ACCESSIBILITY_DATA_PRIVATE_AUTO));
- break;
case R.styleable.View_accessibilityLiveRegion:
setAccessibilityLiveRegion(a.getInt(attr, ACCESSIBILITY_LIVE_REGION_DEFAULT));
break;
@@ -8569,11 +8518,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* is responsible for handling this call.
* </p>
* <p>
- * If this view sets {@link #isAccessibilityDataPrivate()} then this view should only append
- * sensitive information to an event that also sets
- * {@link AccessibilityEvent#isAccessibilityDataPrivate()}.
- * </p>
- * <p>
* <em>Note:</em> Accessibility events of certain types are not dispatched for
* populating the event text via this method. For details refer to {@link AccessibilityEvent}.
* </p>
@@ -10475,7 +10419,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
if ((mAttachInfo.mAccessibilityFetchFlags
- & AccessibilityNodeInfo.FLAG_SERVICE_REQUESTS_REPORT_VIEW_IDS) != 0
+ & AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS) != 0
&& Resources.resourceHasPackage(mID)) {
try {
String viewId = getResources().getResourceName(mID);
@@ -14458,75 +14402,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
@UnsupportedAppUsage
public boolean includeForAccessibility() {
if (mAttachInfo != null) {
- if (isAccessibilityDataPrivate() && !AccessibilityManager.getInstance(
- mContext).isRequestFromAccessibilityTool()) {
- return false;
- }
-
return (mAttachInfo.mAccessibilityFetchFlags
- & AccessibilityNodeInfo.FLAG_SERVICE_REQUESTS_INCLUDE_NOT_IMPORTANT_VIEWS) != 0
+ & AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) != 0
|| isImportantForAccessibility();
}
return false;
}
/**
- * Whether this view should restrict accessibility service access only to services that have the
- * {@link android.accessibilityservice.AccessibilityServiceInfo#isAccessibilityTool} property
- * set to true.
- *
- * <p>
- * See default behavior provided by {@link #ACCESSIBILITY_DATA_PRIVATE_AUTO}. Otherwise,
- * returns true for {@link #ACCESSIBILITY_DATA_PRIVATE_YES} or false for {@link
- * #ACCESSIBILITY_DATA_PRIVATE_NO}.
- * </p>
- *
- * @return True if this view should restrict accessibility service access to services that have
- * the isAccessibilityTool property.
- */
- @ViewDebug.ExportedProperty(category = "accessibility")
- public boolean isAccessibilityDataPrivate() {
- if (mAccessibilityDataPrivate == ACCESSIBILITY_DATA_PRIVATE_YES) {
- return true;
- }
- if (mAccessibilityDataPrivate == ACCESSIBILITY_DATA_PRIVATE_NO) {
- return false;
- }
-
- // Views inside FLAG_SECURE windows default to accessibilityDataPrivate.
- if (mAttachInfo != null && mAttachInfo.mWindowSecure) {
- return true;
- }
- // Views that set filterTouchesWhenObscured default to accessibilityDataPrivate.
- if (getFilterTouchesWhenObscured()) {
- return true;
- }
-
- // Descendants of an accessibilityDataPrivate View are also accessibilityDataPrivate.
- ViewParent parent = mParent;
- while (parent instanceof View) {
- if (((View) parent).isAccessibilityDataPrivate()) {
- return true;
- }
- parent = parent.getParent();
- }
-
- // Otherwise, default to not accessibilityDataPrivate.
- return false;
- }
-
- /**
- * Specifies whether this view should only allow interactions from
- * {@link android.accessibilityservice.AccessibilityService}s with the
- * {@link android.accessibilityservice.AccessibilityServiceInfo#isAccessibilityTool} property
- * set to true.
- */
- public void setAccessibilityDataPrivate(
- @AccessibilityDataPrivate int accessibilityDataPrivate) {
- mAccessibilityDataPrivate = accessibilityDataPrivate;
- }
-
- /**
* Returns whether the View is considered actionable from
* accessibility perspective. Such view are important for
* accessibility.
@@ -30213,11 +30096,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
int mWindowVisibility;
/**
- * Indicates whether the view's window sets {@link WindowManager.LayoutParams#FLAG_SECURE}.
- */
- boolean mWindowSecure;
-
- /**
* Indicates the time at which drawing started to occur.
*/
@UnsupportedAppUsage
@@ -30394,8 +30272,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
/**
* Flags related to accessibility processing.
*
- * @see AccessibilityNodeInfo#FLAG_SERVICE_REQUESTS_INCLUDE_NOT_IMPORTANT_VIEWS
- * @see AccessibilityNodeInfo#FLAG_SERVICE_REQUESTS_REPORT_VIEW_IDS
+ * @see AccessibilityNodeInfo#FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
+ * @see AccessibilityNodeInfo#FLAG_REPORT_VIEW_IDS
*/
int mAccessibilityFetchFlags;
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index f79f0d49959d..9091b7955616 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -75,6 +75,7 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INSET_PARENT_
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT;
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;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL;
import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
@@ -707,6 +708,8 @@ public final class ViewRootImpl implements ViewParent,
final Rect mPendingBackDropFrame = new Rect();
boolean mPendingAlwaysConsumeSystemBars;
+ private int mRelayoutSeq;
+ private final Rect mWinFrameInScreen = new Rect();
private final InsetsState mTempInsets = new InsetsState();
private final InsetsSourceControl[] mTempControls = new InsetsSourceControl[SIZE];
private final WindowConfiguration mTempWinConfig = new WindowConfiguration();
@@ -907,17 +910,11 @@ public final class ViewRootImpl implements ViewParent,
private String mTag = TAG;
public ViewRootImpl(Context context, Display display) {
- this(context, display, WindowManagerGlobal.getWindowSession(), new WindowLayout(),
- false /* useSfChoreographer */);
+ this(context, display, WindowManagerGlobal.getWindowSession(), new WindowLayout());
}
public ViewRootImpl(@UiContext Context context, Display display, IWindowSession session,
WindowLayout windowLayout) {
- this(context, display, session, windowLayout, false /* useSfChoreographer */);
- }
-
- public ViewRootImpl(@UiContext Context context, Display display, IWindowSession session,
- WindowLayout windowLayout, boolean useSfChoreographer) {
mContext = context;
mWindowSession = session;
mWindowLayout = windowLayout;
@@ -949,8 +946,7 @@ public final class ViewRootImpl implements ViewParent,
mNoncompatDensity = context.getResources().getDisplayMetrics().noncompatDensityDpi;
mFallbackEventHandler = new PhoneFallbackEventHandler(context);
// TODO(b/222696368): remove getSfInstance usage and use vsyncId for transactions
- mChoreographer = useSfChoreographer
- ? Choreographer.getSfInstance() : Choreographer.getInstance();
+ mChoreographer = Choreographer.getInstance();
mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
mInsetsController = new InsetsController(new ViewRootInsetsControllerHost(this));
mHandwritingInitiator = new HandwritingInitiator(
@@ -2839,7 +2835,6 @@ public final class ViewRootImpl implements ViewParent,
// However, windows are now always 32 bits by default, so choose 32 bits
mAttachInfo.mUse32BitDrawingCache = true;
mAttachInfo.mWindowVisibility = viewVisibility;
- mAttachInfo.mWindowSecure = (lp.flags & WindowManager.LayoutParams.FLAG_SECURE) != 0;
mAttachInfo.mRecomputeGlobalAttributes = false;
mLastConfigurationFromResources.setTo(config);
mLastSystemUiVisibility = mAttachInfo.mSystemUiVisibility;
@@ -3364,20 +3359,6 @@ public final class ViewRootImpl implements ViewParent,
}
}
} else {
- // If a relayout isn't going to happen, we still need to check if this window can draw
- // when mCheckIfCanDraw is set. This is because it means we had a sync in the past, but
- // have not been told by WMS that the sync is complete and that we can continue to draw
- if (mCheckIfCanDraw) {
- try {
- cancelDraw = mWindowSession.cancelDraw(mWindow);
- cancelReason = "wm_sync";
- if (DEBUG_BLAST) {
- Log.d(mTag, "cancelDraw returned " + cancelDraw);
- }
- } catch (RemoteException e) {
- }
- }
-
// Not the first pass and no window/insets/visibility change but the window
// may have moved and we need check that and if so to update the left and right
// in the attach info. We translate only the window frame since on window move
@@ -3386,6 +3367,20 @@ public final class ViewRootImpl implements ViewParent,
maybeHandleWindowMove(frame);
}
+ if (!mRelayoutRequested && mCheckIfCanDraw) {
+ // We had a sync previously, but we didn't call IWindowSession#relayout in this
+ // traversal. So we don't know if the sync is complete that we can continue to draw.
+ // Here invokes cancelDraw to obtain the information.
+ try {
+ cancelDraw = mWindowSession.cancelDraw(mWindow);
+ cancelReason = "wm_sync";
+ if (DEBUG_BLAST) {
+ Log.d(mTag, "cancelDraw returned " + cancelDraw);
+ }
+ } catch (RemoteException e) {
+ }
+ }
+
if (surfaceSizeChanged || surfaceReplaced || surfaceCreated || windowAttributesChanged) {
// If the surface has been replaced, there's a chance the bounds layer is not parented
// to the new layer. When updating bounds layer, also reparent to the main VRI
@@ -6403,6 +6398,12 @@ public final class ViewRootImpl implements ViewParent,
// Make sure the fallback event policy sees all keys that will be
// delivered to the view hierarchy.
mFallbackEventHandler.preDispatchKeyEvent(event);
+
+ // Reset last tracked MotionEvent click toolType.
+ if (event.getAction() == KeyEvent.ACTION_DOWN) {
+ mLastClickToolType = MotionEvent.TOOL_TYPE_UNKNOWN;
+ }
+
return FORWARD;
}
@@ -8151,7 +8152,43 @@ public final class ViewRootImpl implements ViewParent,
private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
boolean insetsPending) throws RemoteException {
- mRelayoutRequested = true;
+ final WindowConfiguration winConfigFromAm = getConfiguration().windowConfiguration;
+ final WindowConfiguration winConfigFromWm =
+ mLastReportedMergedConfiguration.getGlobalConfiguration().windowConfiguration;
+ final WindowConfiguration winConfig = getCompatWindowConfiguration();
+ final int measuredWidth = mView.getMeasuredWidth();
+ final int measuredHeight = mView.getMeasuredHeight();
+ final boolean relayoutAsync;
+ if (LOCAL_LAYOUT && !mFirst && viewVisibility == mViewVisibility
+ && mWindowAttributes.type != TYPE_APPLICATION_STARTING
+ && mSyncSeqId <= mLastSyncSeqId
+ && winConfigFromAm.diff(winConfigFromWm, false /* compareUndefined */) == 0) {
+ final InsetsState state = mInsetsController.getState();
+ final Rect displayCutoutSafe = mTempRect;
+ state.getDisplayCutoutSafe(displayCutoutSafe);
+ mWindowLayout.computeFrames(mWindowAttributes.forRotation(winConfig.getRotation()),
+ state, displayCutoutSafe, winConfig.getBounds(), winConfig.getWindowingMode(),
+ measuredWidth, measuredHeight, mInsetsController.getRequestedVisibilities(),
+ 1f /* compatScale */, mTmpFrames);
+ mWinFrameInScreen.set(mTmpFrames.frame);
+ if (mTranslator != null) {
+ mTranslator.translateRectInAppWindowToScreen(mWinFrameInScreen);
+ }
+
+ // If the position and the size of the frame are both changed, it will trigger a BLAST
+ // sync, and we still need to call relayout to obtain the syncSeqId. Otherwise, we just
+ // need to send attributes via relayoutAsync.
+ final Rect oldFrame = mWinFrame;
+ final Rect newFrame = mTmpFrames.frame;
+ final boolean positionChanged =
+ newFrame.top != oldFrame.top || newFrame.left != oldFrame.left;
+ final boolean sizeChanged =
+ newFrame.width() != oldFrame.width() || newFrame.height() != oldFrame.height();
+ relayoutAsync = !positionChanged || !sizeChanged;
+ } else {
+ relayoutAsync = false;
+ }
+
float appScale = mAttachInfo.mApplicationScale;
boolean restore = false;
if (params != null && mTranslator != null) {
@@ -8173,26 +8210,47 @@ public final class ViewRootImpl implements ViewParent,
}
}
- final int requestedWidth = (int) (mView.getMeasuredWidth() * appScale + 0.5f);
- final int requestedHeight = (int) (mView.getMeasuredHeight() * appScale + 0.5f);
+ final int requestedWidth = (int) (measuredWidth * appScale + 0.5f);
+ final int requestedHeight = (int) (measuredHeight * appScale + 0.5f);
+ int relayoutResult = 0;
+ mRelayoutSeq++;
+ if (relayoutAsync) {
+ mWindowSession.relayoutAsync(mWindow, params,
+ requestedWidth, requestedHeight, viewVisibility,
+ insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, mRelayoutSeq,
+ mLastSyncSeqId);
+ } else {
+ relayoutResult = mWindowSession.relayout(mWindow, params,
+ requestedWidth, requestedHeight, viewVisibility,
+ insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, mRelayoutSeq,
+ mLastSyncSeqId, mTmpFrames, mPendingMergedConfiguration, mSurfaceControl,
+ mTempInsets, mTempControls, mRelayoutBundle);
+ mRelayoutRequested = true;
+ final int maybeSyncSeqId = mRelayoutBundle.getInt("seqid");
+ if (maybeSyncSeqId > 0) {
+ mSyncSeqId = maybeSyncSeqId;
+ }
+ mWinFrameInScreen.set(mTmpFrames.frame);
+ if (mTranslator != null) {
+ mTranslator.translateRectInScreenToAppWindow(mTmpFrames.frame);
+ mTranslator.translateRectInScreenToAppWindow(mTmpFrames.displayFrame);
+ mTranslator.translateRectInScreenToAppWindow(mTmpFrames.attachedFrame);
+ mTranslator.translateInsetsStateInScreenToAppWindow(mTempInsets);
+ mTranslator.translateSourceControlsInScreenToAppWindow(mTempControls);
+ }
+ mInvSizeCompatScale = 1f / mTmpFrames.sizeCompatScale;
+ mInsetsController.onStateChanged(mTempInsets);
+ mInsetsController.onControlsChanged(mTempControls);
- int relayoutResult = mWindowSession.relayout(mWindow, params,
- requestedWidth, requestedHeight, viewVisibility,
- insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0,
- mTmpFrames, mPendingMergedConfiguration, mSurfaceControl, mTempInsets,
- mTempControls, mRelayoutBundle);
- final int maybeSyncSeqId = mRelayoutBundle.getInt("seqid");
- if (maybeSyncSeqId > 0) {
- mSyncSeqId = maybeSyncSeqId;
+ mPendingAlwaysConsumeSystemBars =
+ (relayoutResult & RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS) != 0;
}
- mInvSizeCompatScale = 1f / mTmpFrames.sizeCompatScale;
final int transformHint = SurfaceControl.rotationToBufferTransform(
(mDisplayInstallOrientation + mDisplay.getRotation()) % 4);
- final WindowConfiguration winConfig = getCompatWindowConfiguration();
WindowLayout.computeSurfaceSize(mWindowAttributes, winConfig.getMaxBounds(), requestedWidth,
- requestedHeight, mTmpFrames.frame, mPendingDragResizing, mSurfaceSize);
+ requestedHeight, mWinFrameInScreen, mPendingDragResizing, mSurfaceSize);
final boolean transformHintChanged = transformHint != mLastTransformHint;
final boolean sizeChanged = !mLastSurfaceSize.equals(mSurfaceSize);
@@ -8239,23 +8297,11 @@ public final class ViewRootImpl implements ViewParent,
destroySurface();
}
- mPendingAlwaysConsumeSystemBars =
- (relayoutResult & RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS) != 0;
-
if (restore) {
params.restore();
}
- if (mTranslator != null) {
- mTranslator.translateRectInScreenToAppWindow(mTmpFrames.frame);
- mTranslator.translateRectInScreenToAppWindow(mTmpFrames.displayFrame);
- mTranslator.translateRectInScreenToAppWindow(mTmpFrames.attachedFrame);
- mTranslator.translateInsetsStateInScreenToAppWindow(mTempInsets);
- mTranslator.translateSourceControlsInScreenToAppWindow(mTempControls);
- }
setFrame(mTmpFrames.frame);
- mInsetsController.onStateChanged(mTempInsets);
- mInsetsController.onControlsChanged(mTempControls);
return relayoutResult;
}
diff --git a/core/java/android/view/WindowInsetsController.java b/core/java/android/view/WindowInsetsController.java
index 227b9f402bba..63f9e13214ff 100644
--- a/core/java/android/view/WindowInsetsController.java
+++ b/core/java/android/view/WindowInsetsController.java
@@ -201,6 +201,21 @@ public interface WindowInsetsController {
@NonNull WindowInsetsAnimationControlListener listener);
/**
+ * Lets the application add non-controllable listener object that can be called back
+ * when animation is invoked by the system by host calling methods such as {@link #show} or
+ * {@link #hide}.
+ *
+ * The listener is supposed to be used for logging only, using the control or
+ * relying on the timing of the callback in any other way is not supported.
+ *
+ * @param listener The {@link WindowInsetsAnimationControlListener} that gets called when
+ * the animation is driven by the system and not the host
+ * @hide
+ */
+ void setSystemDrivenInsetsAnimationLoggingListener(
+ @Nullable WindowInsetsAnimationControlListener listener);
+
+ /**
* Controls the appearance of system bars.
* <p>
* For example, the following statement adds {@link #APPEARANCE_LIGHT_STATUS_BARS}:
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index fe8d64f0152f..a49caafc63f1 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -1054,6 +1054,14 @@ public interface WindowManager extends ViewManager {
}
}
+ /**
+ * Ensure scales are between 0 and 20.
+ * @hide
+ */
+ static float fixScale(float scale) {
+ return Math.max(Math.min(scale, 20), 0);
+ }
+
public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable {
/**
* X position for this window. With the default gravity it is ignored.
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index d55c838c3e53..1ec17d00e99a 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -286,10 +286,11 @@ public class WindowlessWindowManager implements IWindowSession {
@Override
public int relayout(IWindow window, WindowManager.LayoutParams inAttrs,
- int requestedWidth, int requestedHeight, int viewFlags, int flags,
- ClientWindowFrames outFrames, MergedConfiguration mergedConfiguration,
- SurfaceControl outSurfaceControl, InsetsState outInsetsState,
- InsetsSourceControl[] outActiveControls, Bundle outSyncSeqIdBundle) {
+ int requestedWidth, int requestedHeight, int viewFlags, int flags, int seq,
+ int lastSyncSeqId, ClientWindowFrames outFrames,
+ MergedConfiguration outMergedConfiguration, SurfaceControl outSurfaceControl,
+ InsetsState outInsetsState, InsetsSourceControl[] outActiveControls,
+ Bundle outSyncSeqIdBundle) {
final State state;
synchronized (this) {
state = mStateForWindow.get(window.asBinder());
@@ -309,15 +310,23 @@ public class WindowlessWindowManager implements IWindowSession {
if (viewFlags == View.VISIBLE) {
t.setOpaque(sc, isOpaque(attrs)).show(sc).apply();
- outSurfaceControl.copyFrom(sc, "WindowlessWindowManager.relayout");
+ if (outSurfaceControl != null) {
+ outSurfaceControl.copyFrom(sc, "WindowlessWindowManager.relayout");
+ }
} else {
t.hide(sc).apply();
- outSurfaceControl.release();
+ if (outSurfaceControl != null) {
+ outSurfaceControl.release();
+ }
+ }
+ if (outFrames != null) {
+ outFrames.frame.set(0, 0, attrs.width, attrs.height);
+ outFrames.displayFrame.set(outFrames.frame);
}
- outFrames.frame.set(0, 0, attrs.width, attrs.height);
- outFrames.displayFrame.set(outFrames.frame);
- mergedConfiguration.setConfiguration(mConfiguration, mConfiguration);
+ if (outMergedConfiguration != null) {
+ outMergedConfiguration.setConfiguration(mConfiguration, mConfiguration);
+ }
if ((attrChanges & WindowManager.LayoutParams.FLAGS_CHANGED) != 0
&& state.mInputChannelToken != null) {
@@ -335,7 +344,7 @@ public class WindowlessWindowManager implements IWindowSession {
}
}
- if (mInsetsState != null) {
+ if (outInsetsState != null && mInsetsState != null) {
outInsetsState.set(mInsetsState);
}
@@ -343,6 +352,16 @@ public class WindowlessWindowManager implements IWindowSession {
}
@Override
+ public void relayoutAsync(IWindow window, WindowManager.LayoutParams inAttrs,
+ int requestedWidth, int requestedHeight, int viewFlags, int flags, int seq,
+ int lastSyncSeqId) {
+ relayout(window, inAttrs, requestedWidth, requestedHeight, viewFlags, flags, seq,
+ lastSyncSeqId, null /* outFrames */, null /* outMergedConfiguration */,
+ null /* outSurfaceControl */, null /* outInsetsState */,
+ null /* outActiveControls */, null /* outSyncSeqIdBundle */);
+ }
+
+ @Override
public void prepareToReplaceWindows(android.os.IBinder appToken, boolean childrenOnly) {
}
diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java
index 2db0dcbce45e..cd0dd1df1249 100644
--- a/core/java/android/view/accessibility/AccessibilityEvent.java
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -46,17 +46,15 @@ import java.util.List;
* </p>
* <p>
* The main purpose of an accessibility event is to communicate changes in the UI to an
- * {@link android.accessibilityservice.AccessibilityService}. If needed, the service may then
- * inspect the user interface by examining the View hierarchy through the event's
- * {@link #getSource() source}, as represented by a tree of {@link AccessibilityNodeInfo}s (snapshot
- * of a View state) which can be used for exploring the window content. Note that the privilege for
- * accessing an event's source, thus the window content, has to be explicitly requested. For more
+ * {@link android.accessibilityservice.AccessibilityService}. The service may then inspect,
+ * if needed the user interface by examining the View hierarchy, as represented by a tree of
+ * {@link AccessibilityNodeInfo}s (snapshot of a View state)
+ * which can be used for exploring the window content. Note that the privilege for accessing
+ * an event's source, thus the window content, has to be explicitly requested. For more
* details refer to {@link android.accessibilityservice.AccessibilityService}. If an
* accessibility service has not requested to retrieve the window content the event will
- * not contain reference to its source. <strong>Note: </strong> for events of type
- * {@link #TYPE_NOTIFICATION_STATE_CHANGED} the source is never available, and Views that set
- * {@link android.view.View#isAccessibilityDataPrivate()} may not populate all event properties on
- * events sent from higher up in the view hierarchy.
+ * not contain reference to its source. Also for events of type
+ * {@link #TYPE_NOTIFICATION_STATE_CHANGED} the source is never available.
* </p>
* <p>
* This class represents various semantically different accessibility event
@@ -1094,47 +1092,6 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
}
/**
- * Whether the event should only be delivered to an
- * {@link android.accessibilityservice.AccessibilityService} with the
- * {@link android.accessibilityservice.AccessibilityServiceInfo#isAccessibilityTool} property
- * set to true.
- *
- * <p>
- * Initial value matches the {@link android.view.View#isAccessibilityDataPrivate} property from
- * the event's source node, if present, or false by default.
- * </p>
- *
- * @return True if the event should be delivered only to isAccessibilityTool services, false
- * otherwise.
- * @see #setAccessibilityDataPrivate
- */
- @Override
- public boolean isAccessibilityDataPrivate() {
- return super.isAccessibilityDataPrivate();
- }
-
- /**
- * Sets whether the event should only be delivered to an
- * {@link android.accessibilityservice.AccessibilityService} with the
- * {@link android.accessibilityservice.AccessibilityServiceInfo#isAccessibilityTool} property
- * set to true.
- *
- * <p>
- * This will be set automatically based on the event's source (if present). If creating and
- * sending an event directly through {@link AccessibilityManager} (where an event may have
- * no source) then this method must be called explicitly if you want non-default behavior.
- * </p>
- *
- * @param accessibilityDataPrivate True if the event should be delivered only to
- * isAccessibilityTool services, false otherwise.
- * @throws IllegalStateException If called from an AccessibilityService.
- */
- @Override
- public void setAccessibilityDataPrivate(boolean accessibilityDataPrivate) {
- super.setAccessibilityDataPrivate(accessibilityDataPrivate);
- }
-
- /**
* Gets the bit mask of the speech state signaled by a {@link #TYPE_SPEECH_STATE_CHANGE} event
*
* @see #SPEECH_STATE_SPEAKING_START
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index e89f836aaac1..9e3195aec8a6 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -276,8 +276,6 @@ public final class AccessibilityManager {
private final ArrayMap<AudioDescriptionRequestedChangeListener, Executor>
mAudioDescriptionRequestedChangeListeners = new ArrayMap<>();
- private boolean mRequestFromAccessibilityTool;
-
/**
* Map from a view's accessibility id to the list of request preparers set for that view
*/
@@ -985,39 +983,6 @@ public final class AccessibilityManager {
}
/**
- * Whether the current accessibility request comes from an
- * {@link AccessibilityService} with the {@link AccessibilityServiceInfo#isAccessibilityTool}
- * property set to true.
- *
- * <p>
- * You can use this method inside {@link AccessibilityNodeProvider} to decide how to populate
- * your nodes.
- * </p>
- *
- * <p>
- * <strong>Note:</strong> The return value is valid only when an {@link AccessibilityNodeInfo}
- * request is in progress, can change from one request to another, and has no meaning when a
- * request is not in progress.
- * </p>
- *
- * @return True if the current request is from a tool that sets isAccessibilityTool.
- */
- public boolean isRequestFromAccessibilityTool() {
- return mRequestFromAccessibilityTool;
- }
-
- /**
- * Specifies whether the current accessibility request comes from an
- * {@link AccessibilityService} with the {@link AccessibilityServiceInfo#isAccessibilityTool}
- * property set to true.
- *
- * @hide
- */
- public void setRequestFromAccessibilityTool(boolean requestFromAccessibilityTool) {
- mRequestFromAccessibilityTool = requestFromAccessibilityTool;
- }
-
- /**
* Registers a {@link AccessibilityRequestPreparer}.
*/
public void addAccessibilityRequestPreparer(AccessibilityRequestPreparer preparer) {
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 15718c4af26f..953f2615b539 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -217,29 +217,14 @@ public class AccessibilityNodeInfo implements Parcelable {
@Retention(RetentionPolicy.SOURCE)
public @interface PrefetchingStrategy {}
- /**
- * @see AccessibilityServiceInfo#FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
- * @hide
- */
- public static final int FLAG_SERVICE_REQUESTS_INCLUDE_NOT_IMPORTANT_VIEWS = 0x00000080;
-
- /**
- * @see AccessibilityServiceInfo#FLAG_REPORT_VIEW_IDS
- * @hide
- */
- public static final int FLAG_SERVICE_REQUESTS_REPORT_VIEW_IDS = 0x00000100;
+ /** @hide */
+ public static final int FLAG_INCLUDE_NOT_IMPORTANT_VIEWS = 0x00000080;
- /**
- * @see AccessibilityServiceInfo#isAccessibilityTool()
- * @hide
- */
- public static final int FLAG_SERVICE_IS_ACCESSIBILITY_TOOL = 0x00000200;
+ /** @hide */
+ public static final int FLAG_REPORT_VIEW_IDS = 0x00000100;
/** @hide */
- public static final int FLAG_REPORT_MASK =
- FLAG_SERVICE_REQUESTS_INCLUDE_NOT_IMPORTANT_VIEWS
- | FLAG_SERVICE_REQUESTS_REPORT_VIEW_IDS
- | FLAG_SERVICE_IS_ACCESSIBILITY_TOOL;
+ public static final int FLAG_REPORT_MASK = 0x00000180;
// Actions.
diff --git a/core/java/android/view/accessibility/AccessibilityRecord.java b/core/java/android/view/accessibility/AccessibilityRecord.java
index 789c740bbba2..036316e15cb9 100644
--- a/core/java/android/view/accessibility/AccessibilityRecord.java
+++ b/core/java/android/view/accessibility/AccessibilityRecord.java
@@ -72,7 +72,6 @@ public class AccessibilityRecord {
private static final int PROPERTY_FULL_SCREEN = 0x00000080;
private static final int PROPERTY_SCROLLABLE = 0x00000100;
private static final int PROPERTY_IMPORTANT_FOR_ACCESSIBILITY = 0x00000200;
- private static final int PROPERTY_ACCESSIBILITY_DATA_PRIVATE = 0x00000400;
private static final int GET_SOURCE_PREFETCH_FLAGS =
AccessibilityNodeInfo.FLAG_PREFETCH_ANCESTORS
@@ -160,8 +159,6 @@ public class AccessibilityRecord {
important = root.isImportantForAccessibility();
rootViewId = root.getAccessibilityViewId();
mSourceWindowId = root.getAccessibilityWindowId();
- setBooleanProperty(PROPERTY_ACCESSIBILITY_DATA_PRIVATE,
- root.isAccessibilityDataPrivate());
}
setBooleanProperty(PROPERTY_IMPORTANT_FOR_ACCESSIBILITY, important);
mSourceNodeId = AccessibilityNodeInfo.makeNodeId(rootViewId, virtualDescendantId);
@@ -391,23 +388,6 @@ public class AccessibilityRecord {
}
/**
- * @see AccessibilityEvent#isAccessibilityDataPrivate
- * @hide
- */
- boolean isAccessibilityDataPrivate() {
- return getBooleanProperty(PROPERTY_ACCESSIBILITY_DATA_PRIVATE);
- }
-
- /**
- * @see AccessibilityEvent#setAccessibilityDataPrivate
- * @hide
- */
- void setAccessibilityDataPrivate(boolean accessibilityDataPrivate) {
- enforceNotSealed();
- setBooleanProperty(PROPERTY_ACCESSIBILITY_DATA_PRIVATE, accessibilityDataPrivate);
- }
-
- /**
* Gets the number of items that can be visited.
*
* @return The number of items.
@@ -961,8 +941,6 @@ public class AccessibilityRecord {
appendUnless(false, PROPERTY_CHECKED, builder);
appendUnless(false, PROPERTY_FULL_SCREEN, builder);
appendUnless(false, PROPERTY_SCROLLABLE, builder);
- appendUnless(false, PROPERTY_IMPORTANT_FOR_ACCESSIBILITY, builder);
- appendUnless(false, PROPERTY_ACCESSIBILITY_DATA_PRIVATE, builder);
append(builder, "BeforeText", mBeforeText);
append(builder, "FromIndex", mFromIndex);
@@ -996,8 +974,6 @@ public class AccessibilityRecord {
case PROPERTY_SCROLLABLE: return "Scrollable";
case PROPERTY_IMPORTANT_FOR_ACCESSIBILITY:
return "ImportantForAccessibility";
- case PROPERTY_ACCESSIBILITY_DATA_PRIVATE:
- return "AccessibilityDataPrivate";
default: return Integer.toHexString(prop);
}
}
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 0c03d109f649..6049613f41e7 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -18,7 +18,6 @@ package android.view.inputmethod;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
-import static android.view.inputmethod.InputConnection.CURSOR_UPDATE_FILTER_EDITOR_BOUNDS;
import static android.view.inputmethod.InputConnection.CURSOR_UPDATE_IMMEDIATE;
import static android.view.inputmethod.InputConnection.CURSOR_UPDATE_MONITOR;
import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.DISPLAY_ID;
@@ -92,6 +91,7 @@ import android.view.InputEventSender;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewRootImpl;
+import android.view.WindowInsets;
import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillManager;
@@ -2086,15 +2086,6 @@ public final class InputMethodManager {
Log.w(TAG, "Ignoring startStylusHandwriting: View's window does not have focus.");
return;
}
- if (mServedInputConnection != null && getDelegate().hasActiveConnection(view)) {
- // TODO (b/210039666): optimize CURSOR_UPDATE_IMMEDIATE.
- // TODO (b/210039666): Pipe IME displayId from InputBindResult and use it here.
- // instead of mDisplayId.
- mServedInputConnection.requestCursorUpdatesFromImm(
- CURSOR_UPDATE_IMMEDIATE | CURSOR_UPDATE_MONITOR,
- CURSOR_UPDATE_FILTER_EDITOR_BOUNDS,
- mDisplayId);
- }
mServiceInvoker.startStylusHandwriting(mClient);
// TODO(b/210039666): do we need any extra work for supporting non-native
@@ -2160,8 +2151,9 @@ public final class InputMethodManager {
null /* icProto */);
synchronized (mH) {
final View view = getServedViewLocked();
- if (mImeInsetsConsumer != null && view != null) {
- if (mImeInsetsConsumer.isRequestedVisible()) {
+ if (view != null) {
+ final WindowInsets rootInsets = view.getRootWindowInsets();
+ if (rootInsets != null && rootInsets.isVisible(WindowInsets.Type.ime())) {
hideSoftInputFromWindow(view.getWindowToken(), hideFlags, null,
SoftInputShowHideReason.HIDE_TOGGLE_SOFT_INPUT);
} else {
@@ -2341,9 +2333,6 @@ public final class InputMethodManager {
editorInfo.packageName = view.getContext().getOpPackageName();
editorInfo.autofillId = view.getAutofillId();
editorInfo.fieldId = view.getId();
- synchronized (mH) {
- editorInfo.setInitialToolType(mCurRootView.getLastClickToolType());
- }
InputConnection ic = view.onCreateInputConnection(editorInfo);
if (DEBUG) Log.v(TAG, "Starting input: editorInfo=" + editorInfo + " ic=" + ic);
@@ -2383,6 +2372,8 @@ public final class InputMethodManager {
startInputFlags |= StartInputFlags.INITIAL_CONNECTION;
}
+ editorInfo.setInitialToolType(mCurRootView.getLastClickToolType());
+
// Hook 'em up and let 'er rip.
mCurrentEditorInfo = editorInfo.createCopyInternal();
// Store the previously served connection so that we can determine whether it is safe
diff --git a/core/java/android/view/selectiontoolbar/ShowInfo.java b/core/java/android/view/selectiontoolbar/ShowInfo.java
index d9adef2c920b..28b4480d4967 100644
--- a/core/java/android/view/selectiontoolbar/ShowInfo.java
+++ b/core/java/android/view/selectiontoolbar/ShowInfo.java
@@ -75,6 +75,11 @@ public final class ShowInfo implements Parcelable {
@NonNull
private final IBinder mHostInputToken;
+ /**
+ * If the host application uses light theme.
+ */
+ private final boolean mIsLightTheme;
+
// Code below generated by codegen v1.0.23.
@@ -109,6 +114,8 @@ public final class ShowInfo implements Parcelable {
* @param hostInputToken
* The host application's input token, this allows the remote render service to transfer
* the touch focus to the host application.
+ * @param isLightTheme
+ * If the host application uses light theme.
*/
@DataClass.Generated.Member
public ShowInfo(
@@ -118,7 +125,8 @@ public final class ShowInfo implements Parcelable {
@NonNull Rect contentRect,
int suggestedWidth,
@NonNull Rect viewPortOnScreen,
- @NonNull IBinder hostInputToken) {
+ @NonNull IBinder hostInputToken,
+ boolean isLightTheme) {
this.mWidgetToken = widgetToken;
this.mLayoutRequired = layoutRequired;
this.mMenuItems = menuItems;
@@ -134,6 +142,7 @@ public final class ShowInfo implements Parcelable {
this.mHostInputToken = hostInputToken;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mHostInputToken);
+ this.mIsLightTheme = isLightTheme;
// onConstructed(); // You can define this method to get a callback
}
@@ -196,6 +205,14 @@ public final class ShowInfo implements Parcelable {
return mHostInputToken;
}
+ /**
+ * If the host application uses light theme.
+ */
+ @DataClass.Generated.Member
+ public boolean isIsLightTheme() {
+ return mIsLightTheme;
+ }
+
@Override
@DataClass.Generated.Member
public String toString() {
@@ -209,7 +226,8 @@ public final class ShowInfo implements Parcelable {
"contentRect = " + mContentRect + ", " +
"suggestedWidth = " + mSuggestedWidth + ", " +
"viewPortOnScreen = " + mViewPortOnScreen + ", " +
- "hostInputToken = " + mHostInputToken +
+ "hostInputToken = " + mHostInputToken + ", " +
+ "isLightTheme = " + mIsLightTheme +
" }";
}
@@ -232,7 +250,8 @@ public final class ShowInfo implements Parcelable {
&& java.util.Objects.equals(mContentRect, that.mContentRect)
&& mSuggestedWidth == that.mSuggestedWidth
&& java.util.Objects.equals(mViewPortOnScreen, that.mViewPortOnScreen)
- && java.util.Objects.equals(mHostInputToken, that.mHostInputToken);
+ && java.util.Objects.equals(mHostInputToken, that.mHostInputToken)
+ && mIsLightTheme == that.mIsLightTheme;
}
@Override
@@ -249,6 +268,7 @@ public final class ShowInfo implements Parcelable {
_hash = 31 * _hash + mSuggestedWidth;
_hash = 31 * _hash + java.util.Objects.hashCode(mViewPortOnScreen);
_hash = 31 * _hash + java.util.Objects.hashCode(mHostInputToken);
+ _hash = 31 * _hash + Boolean.hashCode(mIsLightTheme);
return _hash;
}
@@ -258,9 +278,10 @@ public final class ShowInfo implements Parcelable {
// You can override field parcelling by defining methods like:
// void parcelFieldName(Parcel dest, int flags) { ... }
- byte flg = 0;
+ int flg = 0;
if (mLayoutRequired) flg |= 0x2;
- dest.writeByte(flg);
+ if (mIsLightTheme) flg |= 0x80;
+ dest.writeInt(flg);
dest.writeLong(mWidgetToken);
dest.writeParcelableList(mMenuItems, flags);
dest.writeTypedObject(mContentRect, flags);
@@ -280,8 +301,9 @@ public final class ShowInfo implements Parcelable {
// You can override field unparcelling by defining methods like:
// static FieldType unparcelFieldName(Parcel in) { ... }
- byte flg = in.readByte();
+ int flg = in.readInt();
boolean layoutRequired = (flg & 0x2) != 0;
+ boolean isLightTheme = (flg & 0x80) != 0;
long widgetToken = in.readLong();
List<ToolbarMenuItem> menuItems = new java.util.ArrayList<>();
in.readParcelableList(menuItems, ToolbarMenuItem.class.getClassLoader(), android.view.selectiontoolbar.ToolbarMenuItem.class);
@@ -305,6 +327,7 @@ public final class ShowInfo implements Parcelable {
this.mHostInputToken = hostInputToken;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mHostInputToken);
+ this.mIsLightTheme = isLightTheme;
// onConstructed(); // You can define this method to get a callback
}
@@ -324,10 +347,10 @@ public final class ShowInfo implements Parcelable {
};
@DataClass.Generated(
- time = 1643186262604L,
+ time = 1645108384245L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/view/selectiontoolbar/ShowInfo.java",
- inputSignatures = "private final long mWidgetToken\nprivate final boolean mLayoutRequired\nprivate final @android.annotation.NonNull java.util.List<android.view.selectiontoolbar.ToolbarMenuItem> mMenuItems\nprivate final @android.annotation.NonNull android.graphics.Rect mContentRect\nprivate final int mSuggestedWidth\nprivate final @android.annotation.NonNull android.graphics.Rect mViewPortOnScreen\nprivate final @android.annotation.NonNull android.os.IBinder mHostInputToken\nclass ShowInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true)")
+ inputSignatures = "private final long mWidgetToken\nprivate final boolean mLayoutRequired\nprivate final @android.annotation.NonNull java.util.List<android.view.selectiontoolbar.ToolbarMenuItem> mMenuItems\nprivate final @android.annotation.NonNull android.graphics.Rect mContentRect\nprivate final int mSuggestedWidth\nprivate final @android.annotation.NonNull android.graphics.Rect mViewPortOnScreen\nprivate final @android.annotation.NonNull android.os.IBinder mHostInputToken\nprivate final boolean mIsLightTheme\nclass ShowInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/view/translation/TranslationManager.java b/core/java/android/view/translation/TranslationManager.java
index 55c0726f259a..5aad823c374e 100644
--- a/core/java/android/view/translation/TranslationManager.java
+++ b/core/java/android/view/translation/TranslationManager.java
@@ -40,11 +40,11 @@ import android.util.Pair;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.SyncResultReceiver;
+import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
-import java.util.Random;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeoutException;
@@ -92,7 +92,8 @@ public final class TranslationManager {
private final Map<Consumer<TranslationCapability>, IRemoteCallback> mCapabilityCallbacks =
new ArrayMap<>();
- private static final Random ID_GENERATOR = new Random();
+ // TODO(b/158778794): make the session ids truly globally unique across processes
+ private static final SecureRandom ID_GENERATOR = new SecureRandom();
private final Object mLock = new Object();
@NonNull
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 3f87ec2beae5..1c7c5829d2bc 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -4887,20 +4887,28 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
/**
- * Set the line break style for text wrapping.
+ * Sets the line-break style for text wrapping.
*
- * The line break style to indicates the line break strategies can be used when
- * calculating the text wrapping. The line break style affects rule-based breaking. It
- * specifies the strictness of line-breaking rules.
- * There are several types for the line break style:
- * {@link LineBreakConfig#LINE_BREAK_STYLE_LOOSE},
- * {@link LineBreakConfig#LINE_BREAK_STYLE_NORMAL} and
- * {@link LineBreakConfig#LINE_BREAK_STYLE_STRICT}. The default values of the line break style
- * is {@link LineBreakConfig#LINE_BREAK_STYLE_NONE}, indicating no breaking rule is specified.
- * See <a href="https://www.w3.org/TR/css-text-3/#line-break-property">
- * the line-break property</a>
+ * <p>Line-break style specifies the line-break strategies that can be used
+ * for text wrapping. The line-break style affects rule-based line breaking
+ * by specifying the strictness of line-breaking rules.</p>
*
- * @param lineBreakStyle the line break style for the text.
+ * <p>The following are types of line-break styles:</p>
+ * <ul>
+ * <li>{@link LineBreakConfig#LINE_BREAK_STYLE_LOOSE}</li>
+ * <li>{@link LineBreakConfig#LINE_BREAK_STYLE_NORMAL}</li>
+ * <li>{@link LineBreakConfig#LINE_BREAK_STYLE_STRICT}</li>
+ * </ul>
+ *
+ * <p>The default line-break style is
+ * {@link LineBreakConfig#LINE_BREAK_STYLE_NONE}, which specifies that no
+ * line-breaking rules are used.</p>
+ *
+ * <p>See the
+ * <a href="https://www.w3.org/TR/css-text-3/#line-break-property" class="external">
+ * line-break property</a> for more information.</p>
+ *
+ * @param lineBreakStyle The line break style for the text.
*/
public void setLineBreakStyle(@LineBreakConfig.LineBreakStyle int lineBreakStyle) {
if (mLineBreakStyle != lineBreakStyle) {
@@ -4914,17 +4922,22 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
/**
- * Set the line break word style for text wrapping.
+ * Sets the line-break word style for text wrapping.
*
- * The line break word style affects dictionary-based breaking and provide phrase-based
- * breaking opportunities. The type for the line break word style is
- * {@link LineBreakConfig#LINE_BREAK_WORD_STYLE_PHRASE}. The default values of the line break
- * word style is {@link LineBreakConfig#LINE_BREAK_WORD_STYLE_NONE}, indicating no breaking rule
- * is specified.
- * See <a href="https://www.w3.org/TR/css-text-3/#word-break-property">
- * the word-break property</a>
+ * <p>The line-break word style affects dictionary-based line breaking by
+ * providing phrase-based line-breaking opportunities. Use
+ * {@link LineBreakConfig#LINE_BREAK_WORD_STYLE_PHRASE} to specify
+ * phrase-based line breaking.</p>
*
- * @param lineBreakWordStyle the line break word style for the tet
+ * <p>The default line-break word style is
+ * {@link LineBreakConfig#LINE_BREAK_WORD_STYLE_NONE}, which specifies that
+ * no line-breaking word style is used.</p>
+ *
+ * <p>See the
+ * <a href="https://www.w3.org/TR/css-text-3/#word-break-property" class="external">
+ * word-break property</a> for more information.</p>
+ *
+ * @param lineBreakWordStyle The line break word style for the text.
*/
public void setLineBreakWordStyle(@LineBreakConfig.LineBreakWordStyle int lineBreakWordStyle) {
mUserSpeficiedLineBreakwordStyle = true;
@@ -4939,18 +4952,18 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
/**
- * Get the current line break style for text wrapping.
+ * Gets the current line-break style for text wrapping.
*
- * @return the current line break style to be used for text wrapping.
+ * @return The line-break style to be used for text wrapping.
*/
public @LineBreakConfig.LineBreakStyle int getLineBreakStyle() {
return mLineBreakStyle;
}
/**
- * Get the current line word break style for text wrapping.
+ * Gets the current line-break word style for text wrapping.
*
- * @return the current line break word style to be used for text wrapping.
+ * @return The line-break word style to be used for text wrapping.
*/
public @LineBreakConfig.LineBreakWordStyle int getLineBreakWordStyle() {
return mLineBreakWordStyle;
@@ -12076,13 +12089,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) {
super.onPopulateAccessibilityEventInternal(event);
- if (this.isAccessibilityDataPrivate() && !event.isAccessibilityDataPrivate()) {
- // This view's accessibility data is private, but another view that generated this event
- // is not, so don't append this view's text to the event in order to prevent sharing
- // this view's contents with non-accessibility-tool services.
- return;
- }
-
final CharSequence text = getTextForAccessibility();
if (!TextUtils.isEmpty(text)) {
event.getText().add(text);
diff --git a/core/java/android/window/TaskFragmentOrganizer.java b/core/java/android/window/TaskFragmentOrganizer.java
index e567cedb43cc..cd15df84debd 100644
--- a/core/java/android/window/TaskFragmentOrganizer.java
+++ b/core/java/android/window/TaskFragmentOrganizer.java
@@ -16,7 +16,7 @@
package android.window;
-import static android.window.TaskFragmentTransaction.TYPE_ACTIVITY_REPARENT_TO_TASK;
+import static android.window.TaskFragmentTransaction.TYPE_ACTIVITY_REPARENTED_TO_TASK;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_APPEARED;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_ERROR;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_INFO_CHANGED;
@@ -26,16 +26,15 @@ import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_VANISHED
import android.annotation.CallSuper;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.TestApi;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
-import android.util.SparseArray;
import android.view.RemoteAnimationDefinition;
-import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
@@ -74,12 +73,6 @@ public class TaskFragmentOrganizer extends WindowOrganizer {
*/
private final Executor mExecutor;
- // TODO(b/240519866): doing so to keep CTS compatibility. Remove in the next release.
- /** Map from Task id to client tokens of TaskFragments in the Task. */
- private final SparseArray<List<IBinder>> mTaskIdToFragmentTokens = new SparseArray<>();
- /** Map from Task id to Task configuration. */
- private final SparseArray<Configuration> mTaskIdToConfigurations = new SparseArray<>();
-
public TaskFragmentOrganizer(@NonNull Executor executor) {
mExecutor = executor;
}
@@ -147,26 +140,35 @@ public class TaskFragmentOrganizer extends WindowOrganizer {
}
}
- /** Called when a TaskFragment is created and organized by this organizer. */
- public void onTaskFragmentAppeared(@NonNull TaskFragmentInfo taskFragmentInfo) {}
-
- /** Called when the status of an organized TaskFragment is changed. */
- public void onTaskFragmentInfoChanged(@NonNull TaskFragmentInfo taskFragmentInfo) {}
+ /**
+ * Called when a TaskFragment is created and organized by this organizer.
+ *
+ * @param wct The {@link WindowContainerTransaction} to make any changes with if needed. No
+ * need to call {@link #applyTransaction} as it will be applied by the caller.
+ * @param taskFragmentInfo Info of the TaskFragment that is created.
+ */
+ public void onTaskFragmentAppeared(@NonNull WindowContainerTransaction wct,
+ @NonNull TaskFragmentInfo taskFragmentInfo) {}
- /** Called when an organized TaskFragment is removed. */
- public void onTaskFragmentVanished(@NonNull TaskFragmentInfo taskFragmentInfo) {}
+ /**
+ * Called when the status of an organized TaskFragment is changed.
+ *
+ * @param wct The {@link WindowContainerTransaction} to make any changes with if needed. No
+ * need to call {@link #applyTransaction} as it will be applied by the caller.
+ * @param taskFragmentInfo Info of the TaskFragment that is changed.
+ */
+ public void onTaskFragmentInfoChanged(@NonNull WindowContainerTransaction wct,
+ @NonNull TaskFragmentInfo taskFragmentInfo) {}
/**
- * Called when the parent leaf Task of organized TaskFragments is changed.
- * When the leaf Task is changed, the organizer may want to update the TaskFragments in one
- * transaction.
+ * Called when an organized TaskFragment is removed.
*
- * For case like screen size change, it will trigger onTaskFragmentParentInfoChanged with new
- * Task bounds, but may not trigger onTaskFragmentInfoChanged because there can be an override
- * bounds.
+ * @param wct The {@link WindowContainerTransaction} to make any changes with if needed. No
+ * need to call {@link #applyTransaction} as it will be applied by the caller.
+ * @param taskFragmentInfo Info of the TaskFragment that is removed.
*/
- public void onTaskFragmentParentInfoChanged(
- @NonNull IBinder fragmentToken, @NonNull Configuration parentConfig) {}
+ public void onTaskFragmentVanished(@NonNull WindowContainerTransaction wct,
+ @NonNull TaskFragmentInfo taskFragmentInfo) {}
/**
* Called when the parent leaf Task of organized TaskFragments is changed.
@@ -176,23 +178,21 @@ public class TaskFragmentOrganizer extends WindowOrganizer {
* For case like screen size change, it will trigger onTaskFragmentParentInfoChanged with new
* Task bounds, but may not trigger onTaskFragmentInfoChanged because there can be an override
* bounds.
- * @hide
+ *
+ * @param wct The {@link WindowContainerTransaction} to make any changes with if needed. No
+ * need to call {@link #applyTransaction} as it will be applied by the caller.
+ * @param taskId Id of the parent Task that is changed.
+ * @param parentConfig Config of the parent Task.
*/
- public void onTaskFragmentParentInfoChanged(int taskId, @NonNull Configuration parentConfig) {
- // TODO(b/240519866): doing so to keep CTS compatibility. Remove in the next release.
- final List<IBinder> tokens = mTaskIdToFragmentTokens.get(taskId);
- if (tokens == null || tokens.isEmpty()) {
- return;
- }
- for (int i = tokens.size() - 1; i >= 0; i--) {
- onTaskFragmentParentInfoChanged(tokens.get(i), parentConfig);
- }
- }
+ public void onTaskFragmentParentInfoChanged(@NonNull WindowContainerTransaction wct, int taskId,
+ @NonNull Configuration parentConfig) {}
/**
* Called when the {@link WindowContainerTransaction} created with
* {@link WindowContainerTransaction#setErrorCallbackToken(IBinder)} failed on the server side.
*
+ * @param wct The {@link WindowContainerTransaction} to make any changes with if needed. No
+ * need to call {@link #applyTransaction} as it will be applied by the caller.
* @param errorCallbackToken token set in
* {@link WindowContainerTransaction#setErrorCallbackToken(IBinder)}
* @param taskFragmentInfo The {@link TaskFragmentInfo}. This could be {@code null} if no
@@ -201,16 +201,18 @@ public class TaskFragmentOrganizer extends WindowOrganizer {
* transaction operation.
* @param exception exception from the server side.
*/
- public void onTaskFragmentError(
+ public void onTaskFragmentError(@NonNull WindowContainerTransaction wct,
@NonNull IBinder errorCallbackToken, @Nullable TaskFragmentInfo taskFragmentInfo,
int opType, @NonNull Throwable exception) {}
/**
* Called when an Activity is reparented to the Task with organized TaskFragment. For example,
* when an Activity enters and then exits Picture-in-picture, it will be reparented back to its
- * orginial Task. In this case, we need to notify the organizer so that it can check if the
+ * original Task. In this case, we need to notify the organizer so that it can check if the
* Activity matches any split rule.
*
+ * @param wct The {@link WindowContainerTransaction} to make any changes with if needed. No
+ * need to call {@link #applyTransaction} as it will be applied by the caller.
* @param taskId The Task that the activity is reparented to.
* @param activityIntent The intent that the activity is original launched with.
* @param activityToken If the activity belongs to the same process as the organizer, this
@@ -218,61 +220,41 @@ public class TaskFragmentOrganizer extends WindowOrganizer {
* different process, the server will generate a temporary token that
* the organizer can use to reparent the activity through
* {@link WindowContainerTransaction} if needed.
- * @hide
*/
- public void onActivityReparentToTask(int taskId, @NonNull Intent activityIntent,
- @NonNull IBinder activityToken) {}
+ public void onActivityReparentedToTask(@NonNull WindowContainerTransaction wct,
+ int taskId, @NonNull Intent activityIntent, @NonNull IBinder activityToken) {}
/**
* Called when the transaction is ready so that the organizer can update the TaskFragments based
* on the changes in transaction.
+ * Note: {@link WindowOrganizer#applyTransaction} permission requirement is conditional for
+ * {@link TaskFragmentOrganizer}.
+ * @see com.android.server.wm.WindowOrganizerController#enforceTaskPermission
* @hide
*/
+ @SuppressLint("AndroidFrameworkRequiresPermission")
public void onTransactionReady(@NonNull TaskFragmentTransaction transaction) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
final List<TaskFragmentTransaction.Change> changes = transaction.getChanges();
for (TaskFragmentTransaction.Change change : changes) {
- // TODO(b/240519866): apply all changes in one WCT.
final int taskId = change.getTaskId();
switch (change.getType()) {
case TYPE_TASK_FRAGMENT_APPEARED:
- // TODO(b/240519866): doing so to keep CTS compatibility. Remove in the next
- // release.
- if (!mTaskIdToFragmentTokens.contains(taskId)) {
- mTaskIdToFragmentTokens.put(taskId, new ArrayList<>());
- }
- mTaskIdToFragmentTokens.get(taskId).add(change.getTaskFragmentToken());
- onTaskFragmentParentInfoChanged(change.getTaskFragmentToken(),
- mTaskIdToConfigurations.get(taskId));
-
- onTaskFragmentAppeared(change.getTaskFragmentInfo());
+ onTaskFragmentAppeared(wct, change.getTaskFragmentInfo());
break;
case TYPE_TASK_FRAGMENT_INFO_CHANGED:
- onTaskFragmentInfoChanged(change.getTaskFragmentInfo());
+ onTaskFragmentInfoChanged(wct, change.getTaskFragmentInfo());
break;
case TYPE_TASK_FRAGMENT_VANISHED:
- // TODO(b/240519866): doing so to keep CTS compatibility. Remove in the next
- // release.
- if (mTaskIdToFragmentTokens.contains(taskId)) {
- final List<IBinder> tokens = mTaskIdToFragmentTokens.get(taskId);
- tokens.remove(change.getTaskFragmentToken());
- if (tokens.isEmpty()) {
- mTaskIdToFragmentTokens.remove(taskId);
- mTaskIdToConfigurations.remove(taskId);
- }
- }
-
- onTaskFragmentVanished(change.getTaskFragmentInfo());
+ onTaskFragmentVanished(wct, change.getTaskFragmentInfo());
break;
case TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED:
- // TODO(b/240519866): doing so to keep CTS compatibility. Remove in the next
- // release.
- mTaskIdToConfigurations.put(taskId, change.getTaskConfiguration());
-
- onTaskFragmentParentInfoChanged(taskId, change.getTaskConfiguration());
+ onTaskFragmentParentInfoChanged(wct, taskId, change.getTaskConfiguration());
break;
case TYPE_TASK_FRAGMENT_ERROR:
final Bundle errorBundle = change.getErrorBundle();
onTaskFragmentError(
+ wct,
change.getErrorCallbackToken(),
errorBundle.getParcelable(
KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO, TaskFragmentInfo.class),
@@ -280,8 +262,9 @@ public class TaskFragmentOrganizer extends WindowOrganizer {
errorBundle.getSerializable(KEY_ERROR_CALLBACK_EXCEPTION,
java.lang.Throwable.class));
break;
- case TYPE_ACTIVITY_REPARENT_TO_TASK:
- onActivityReparentToTask(
+ case TYPE_ACTIVITY_REPARENTED_TO_TASK:
+ onActivityReparentedToTask(
+ wct,
change.getTaskId(),
change.getActivityIntent(),
change.getActivityToken());
@@ -291,6 +274,8 @@ public class TaskFragmentOrganizer extends WindowOrganizer {
"Unknown TaskFragmentEvent=" + change.getType());
}
}
+ // TODO(b/240519866): notify TaskFragmentOrganizerController that the transition is done.
+ applyTransaction(wct);
}
@Override
diff --git a/core/java/android/window/TaskFragmentTransaction.java b/core/java/android/window/TaskFragmentTransaction.java
index 755864f60f87..07e8e8c473c6 100644
--- a/core/java/android/window/TaskFragmentTransaction.java
+++ b/core/java/android/window/TaskFragmentTransaction.java
@@ -122,7 +122,7 @@ public final class TaskFragmentTransaction implements Parcelable {
* then exits Picture-in-picture, it will be reparented back to its original Task. In this case,
* we need to notify the organizer so that it can check if the Activity matches any split rule.
*/
- public static final int TYPE_ACTIVITY_REPARENT_TO_TASK = 6;
+ public static final int TYPE_ACTIVITY_REPARENTED_TO_TASK = 6;
@IntDef(prefix = { "TYPE_" }, value = {
TYPE_TASK_FRAGMENT_APPEARED,
@@ -130,7 +130,7 @@ public final class TaskFragmentTransaction implements Parcelable {
TYPE_TASK_FRAGMENT_VANISHED,
TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED,
TYPE_TASK_FRAGMENT_ERROR,
- TYPE_ACTIVITY_REPARENT_TO_TASK
+ TYPE_ACTIVITY_REPARENTED_TO_TASK
})
@Retention(RetentionPolicy.SOURCE)
@interface ChangeType {}
@@ -247,7 +247,7 @@ public final class TaskFragmentTransaction implements Parcelable {
/**
* Intent of the activity that is reparented to the Task for
- * {@link #TYPE_ACTIVITY_REPARENT_TO_TASK}.
+ * {@link #TYPE_ACTIVITY_REPARENTED_TO_TASK}.
*/
public Change setActivityIntent(@NonNull Intent intent) {
mActivityIntent = requireNonNull(intent);
@@ -255,7 +255,7 @@ public final class TaskFragmentTransaction implements Parcelable {
}
/**
- * Token of the reparent activity for {@link #TYPE_ACTIVITY_REPARENT_TO_TASK}.
+ * Token of the reparent activity for {@link #TYPE_ACTIVITY_REPARENTED_TO_TASK}.
* If the activity belongs to the same process as the organizer, this will be the actual
* activity token; if the activity belongs to a different process, the server will generate
* a temporary token that the organizer can use to reparent the activity through
diff --git a/core/java/com/android/internal/app/BilingualSuggestedLocaleAdapter.java b/core/java/com/android/internal/app/BilingualSuggestedLocaleAdapter.java
index d2cb30e2363e..833d88c9350b 100644
--- a/core/java/com/android/internal/app/BilingualSuggestedLocaleAdapter.java
+++ b/core/java/com/android/internal/app/BilingualSuggestedLocaleAdapter.java
@@ -20,6 +20,8 @@ import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.RelativeLayout;
import android.widget.TextView;
import com.android.internal.R;
@@ -36,11 +38,21 @@ public class BilingualSuggestedLocaleAdapter extends SuggestedLocaleAdapter {
private final Locale mSecondaryLocale;
private final int mSecondaryLocaleTextDir;
+ private final boolean mShowSelection;
+ private LocaleStore.LocaleInfo mSelectedLocaleInfo;
public BilingualSuggestedLocaleAdapter(
Set<LocaleStore.LocaleInfo> localeOptions,
boolean countryMode,
Locale secondaryLocale) {
+ this(localeOptions, countryMode, secondaryLocale, false);
+ }
+
+ public BilingualSuggestedLocaleAdapter(
+ Set<LocaleStore.LocaleInfo> localeOptions,
+ boolean countryMode,
+ Locale secondaryLocale,
+ boolean showLastSelected) {
super(localeOptions, countryMode);
mSecondaryLocale = secondaryLocale;
if (TextUtils.getLayoutDirectionFromLocale(secondaryLocale) == View.LAYOUT_DIRECTION_RTL) {
@@ -48,6 +60,7 @@ public class BilingualSuggestedLocaleAdapter extends SuggestedLocaleAdapter {
} else {
mSecondaryLocaleTextDir = View.TEXT_DIRECTION_LTR;
}
+ mShowSelection = showLastSelected;
}
@Override
@@ -90,11 +103,55 @@ public class BilingualSuggestedLocaleAdapter extends SuggestedLocaleAdapter {
}
LocaleStore.LocaleInfo item = (LocaleStore.LocaleInfo) getItem(position);
+ if (mShowSelection) {
+ setItemState(isSelectedLocaleInfo(item), convertView);
+ }
setLocaleToListItem(convertView, item);
}
return convertView;
}
+ /**
+ * Set locale info as selected. Selected info can be the only one. Passing null would result to
+ * nothing is selected.
+ */
+ public void setSelectedLocaleInfo(LocaleStore.LocaleInfo info) {
+ mSelectedLocaleInfo = info;
+ notifyDataSetChanged();
+ }
+
+ /** Return selected locale info. */
+ public LocaleStore.LocaleInfo getSelectedLocaleInfo() {
+ return mSelectedLocaleInfo;
+ }
+
+ private boolean isSelectedLocaleInfo(LocaleStore.LocaleInfo item) {
+ return item != null
+ && mSelectedLocaleInfo != null
+ && item.getId().equals(mSelectedLocaleInfo.getId());
+ }
+
+ private void setItemState(boolean selected, View itemView) {
+ RelativeLayout background = (RelativeLayout) itemView;
+ ImageView indicator = itemView.findViewById(R.id.indicator);
+ TextView textNative = itemView.findViewById(R.id.locale_native);
+ TextView textSecondary = itemView.findViewById(R.id.locale_secondary);
+
+ if (indicator == null || textNative == null || textSecondary == null) {
+ return;
+ }
+
+ textNative.setSelected(selected);
+ textSecondary.setSelected(selected);
+ if (selected) {
+ background.setBackgroundResource(R.drawable.language_picker_item_bg_selected);
+ indicator.setVisibility(View.VISIBLE);
+ } else {
+ background.setBackgroundResource(0);
+ indicator.setVisibility(View.GONE);
+ }
+ }
+
private void setHeaderText(
TextView textView, int languageStringResourceId, int regionStringResourceId) {
if (mCountryMode) {
@@ -114,7 +171,7 @@ public class BilingualSuggestedLocaleAdapter extends SuggestedLocaleAdapter {
textNative.setTextLocale(localeInfo.getLocale());
textNative.setContentDescription(localeInfo.getContentDescription(mCountryMode));
- TextView textSecondary = (TextView) itemView.findViewById(R.id.locale_secondary);
+ TextView textSecondary = itemView.findViewById(R.id.locale_secondary);
textSecondary.setText(localeInfo.getLocale().getDisplayLanguage(mSecondaryLocale));
textSecondary.setTextDirection(mSecondaryLocaleTextDir);
if (mCountryMode) {
diff --git a/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java b/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java
index 2bef10f1aee5..b63ce1b22cdb 100644
--- a/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java
+++ b/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java
@@ -968,27 +968,6 @@ public final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub
});
}
- /**
- * Dispatches {@link InputConnection#requestCursorUpdates(int)}.
- *
- * <p>This method is intended to be called only from {@link InputMethodManager}.</p>
- * @param cursorUpdateMode the mode for {@link InputConnection#requestCursorUpdates(int, int)}
- * @param cursorUpdateFilter the filter for
- * {@link InputConnection#requestCursorUpdates(int, int)}
- * @param imeDisplayId displayId on which IME is displayed.
- */
- @Dispatching(cancellable = true)
- public void requestCursorUpdatesFromImm(int cursorUpdateMode, int cursorUpdateFilter,
- int imeDisplayId) {
- final int currentSessionId = mCurrentSessionId.get();
- dispatchWithTracing("requestCursorUpdatesFromImm", () -> {
- if (currentSessionId != mCurrentSessionId.get()) {
- return; // cancelled
- }
- requestCursorUpdatesInternal(cursorUpdateMode, cursorUpdateFilter, imeDisplayId);
- });
- }
-
@Dispatching(cancellable = true)
@Override
public void requestCursorUpdates(InputConnectionCommandHeader header, int cursorUpdateMode,
diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java
index 962870e733f7..6909965edcd8 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistory.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistory.java
@@ -17,25 +17,35 @@
package com.android.internal.os;
import android.annotation.Nullable;
-import android.os.BatteryStats;
+import android.os.BatteryManager;
+import android.os.BatteryStats.HistoryItem;
+import android.os.BatteryStats.HistoryStepDetails;
+import android.os.BatteryStats.HistoryTag;
import android.os.Parcel;
+import android.os.ParcelFormatException;
+import android.os.Process;
import android.os.StatFs;
import android.os.SystemClock;
import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.Slog;
+import android.util.SparseArray;
+import android.util.TimeUtils;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ParseUtils;
import java.io.File;
+import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.Set;
-import java.util.function.Supplier;
+import java.util.concurrent.locks.ReentrantLock;
/**
* BatteryStatsHistory encapsulates battery history files.
@@ -56,57 +66,62 @@ import java.util.function.Supplier;
* All interfaces in BatteryStatsHistory should only be called by BatteryStatsImpl and protected by
* locks on BatteryStatsImpl object.
*/
-@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public class BatteryStatsHistory {
private static final boolean DEBUG = false;
private static final String TAG = "BatteryStatsHistory";
// Current on-disk Parcel version. Must be updated when the format of the parcelable changes
- public static final int VERSION = 208;
+ private static final int VERSION = 208;
- public static final String HISTORY_DIR = "battery-history";
- public static final String FILE_SUFFIX = ".bin";
+ private static final String HISTORY_DIR = "battery-history";
+ private static final String FILE_SUFFIX = ".bin";
private static final int MIN_FREE_SPACE = 100 * 1024 * 1024;
+
// Part of initial delta int that specifies the time delta.
- public static final int DELTA_TIME_MASK = 0x7ffff;
- public static final int DELTA_TIME_LONG = 0x7ffff; // The delta is a following long
- public static final int DELTA_TIME_INT = 0x7fffe; // The delta is a following int
- public static final int DELTA_TIME_ABS = 0x7fffd; // Following is an entire abs update.
+ static final int DELTA_TIME_MASK = 0x7ffff;
+ static final int DELTA_TIME_LONG = 0x7ffff; // The delta is a following long
+ static final int DELTA_TIME_INT = 0x7fffe; // The delta is a following int
+ static final int DELTA_TIME_ABS = 0x7fffd; // Following is an entire abs update.
// Flag in delta int: a new battery level int follows.
- public static final int DELTA_BATTERY_LEVEL_FLAG = 0x00080000;
+ static final int DELTA_BATTERY_LEVEL_FLAG = 0x00080000;
// Flag in delta int: a new full state and battery status int follows.
- public static final int DELTA_STATE_FLAG = 0x00100000;
+ static final int DELTA_STATE_FLAG = 0x00100000;
// Flag in delta int: a new full state2 int follows.
- public static final int DELTA_STATE2_FLAG = 0x00200000;
+ static final int DELTA_STATE2_FLAG = 0x00200000;
// Flag in delta int: contains a wakelock or wakeReason tag.
- public static final int DELTA_WAKELOCK_FLAG = 0x00400000;
+ static final int DELTA_WAKELOCK_FLAG = 0x00400000;
// Flag in delta int: contains an event description.
- public static final int DELTA_EVENT_FLAG = 0x00800000;
+ static final int DELTA_EVENT_FLAG = 0x00800000;
// Flag in delta int: contains the battery charge count in uAh.
- public static final int DELTA_BATTERY_CHARGE_FLAG = 0x01000000;
+ static final int DELTA_BATTERY_CHARGE_FLAG = 0x01000000;
// These upper bits are the frequently changing state bits.
- public static final int DELTA_STATE_MASK = 0xfe000000;
+ static final int DELTA_STATE_MASK = 0xfe000000;
// These are the pieces of battery state that are packed in to the upper bits of
// the state int that have been packed in to the first delta int. They must fit
// in STATE_BATTERY_MASK.
- public static final int STATE_BATTERY_MASK = 0xff000000;
- public static final int STATE_BATTERY_STATUS_MASK = 0x00000007;
- public static final int STATE_BATTERY_STATUS_SHIFT = 29;
- public static final int STATE_BATTERY_HEALTH_MASK = 0x00000007;
- public static final int STATE_BATTERY_HEALTH_SHIFT = 26;
- public static final int STATE_BATTERY_PLUG_MASK = 0x00000003;
- public static final int STATE_BATTERY_PLUG_SHIFT = 24;
+ static final int STATE_BATTERY_MASK = 0xff000000;
+ static final int STATE_BATTERY_STATUS_MASK = 0x00000007;
+ static final int STATE_BATTERY_STATUS_SHIFT = 29;
+ static final int STATE_BATTERY_HEALTH_MASK = 0x00000007;
+ static final int STATE_BATTERY_HEALTH_SHIFT = 26;
+ static final int STATE_BATTERY_PLUG_MASK = 0x00000003;
+ static final int STATE_BATTERY_PLUG_SHIFT = 24;
// We use the low bit of the battery state int to indicate that we have full details
// from a battery level change.
- public static final int BATTERY_DELTA_LEVEL_FLAG = 0x00000001;
+ static final int BATTERY_DELTA_LEVEL_FLAG = 0x00000001;
// Flag in history tag index: indicates that this is the first occurrence of this tag,
// therefore the tag value is written in the parcel
- public static final int TAG_FIRST_OCCURRENCE_FLAG = 0x8000;
+ static final int TAG_FIRST_OCCURRENCE_FLAG = 0x8000;
- @Nullable
- private final Supplier<Integer> mMaxHistoryFiles;
private final Parcel mHistoryBuffer;
+ private final File mSystemDir;
+ private final HistoryStepDetailsCalculator mStepDetailsCalculator;
private final File mHistoryDir;
+ private final Clock mClock;
+
+ private int mMaxHistoryFiles;
+ private int mMaxHistoryBufferSize;
+
/**
* The active history file that the history buffer is backed up into.
*/
@@ -144,19 +159,77 @@ public class BatteryStatsHistory {
*/
private int mParcelIndex = 0;
+ private final ReentrantLock mWriteLock = new ReentrantLock();
+
+ private final HistoryItem mHistoryCur = new HistoryItem();
+
+ private boolean mHaveBatteryLevel;
+ private boolean mRecordingHistory;
+
+ private static final int HISTORY_TAG_INDEX_LIMIT = 0x7ffe;
+ private static final int MAX_HISTORY_TAG_STRING_LENGTH = 1024;
+
+ private final HashMap<HistoryTag, Integer> mHistoryTagPool = new HashMap<>();
+ private SparseArray<HistoryTag> mHistoryTags;
+ private final HistoryItem mHistoryLastWritten = new HistoryItem();
+ private final HistoryItem mHistoryLastLastWritten = new HistoryItem();
+ private final HistoryItem mHistoryAddTmp = new HistoryItem();
+ private int mNextHistoryTagIdx = 0;
+ private int mNumHistoryTagChars = 0;
+ private int mHistoryBufferLastPos = -1;
+ private int mActiveHistoryStates = 0xffffffff;
+ private int mActiveHistoryStates2 = 0xffffffff;
+ private long mLastHistoryElapsedRealtimeMs = 0;
+ private long mTrackRunningHistoryElapsedRealtimeMs = 0;
+ private long mTrackRunningHistoryUptimeMs = 0;
+ private long mHistoryBaseTimeMs;
+
+ private byte mLastHistoryStepLevel = 0;
+
+ private BatteryStatsHistoryIterator mBatteryStatsHistoryIterator;
+
+ /**
+ * A delegate responsible for computing additional details for a step in battery history.
+ */
+ public interface HistoryStepDetailsCalculator {
+ /**
+ * Returns additional details for the current history step or null.
+ */
+ @Nullable
+ HistoryStepDetails getHistoryStepDetails();
+
+ /**
+ * Resets the calculator to get ready for a new battery session
+ */
+ void clear();
+ }
+
/**
* Constructor
*
- * @param historyBuffer The in-memory history buffer.
- * @param systemDir typically /data/system
- * @param maxHistoryFiles the largest number of history buffer files to keep
+ * @param systemDir typically /data/system
+ * @param maxHistoryFiles the largest number of history buffer files to keep
+ * @param maxHistoryBufferSize the most amount of RAM to used for buffering of history steps
*/
+ public BatteryStatsHistory(File systemDir, int maxHistoryFiles, int maxHistoryBufferSize,
+ HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock) {
+ this(Parcel.obtain(), systemDir, maxHistoryFiles, maxHistoryBufferSize,
+ stepDetailsCalculator, clock);
+ initHistoryBuffer();
+ }
+
+ @VisibleForTesting
public BatteryStatsHistory(Parcel historyBuffer, File systemDir,
- Supplier<Integer> maxHistoryFiles) {
+ int maxHistoryFiles, int maxHistoryBufferSize,
+ HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock) {
mHistoryBuffer = historyBuffer;
- mHistoryDir = new File(systemDir, HISTORY_DIR);
+ mSystemDir = systemDir;
mMaxHistoryFiles = maxHistoryFiles;
+ mMaxHistoryBufferSize = maxHistoryBufferSize;
+ mStepDetailsCalculator = stepDetailsCalculator;
+ mClock = clock;
+ mHistoryDir = new File(systemDir, HISTORY_DIR);
mHistoryDir.mkdirs();
if (!mHistoryDir.exists()) {
Slog.wtf(TAG, "HistoryDir does not exist:" + mHistoryDir.getPath());
@@ -192,19 +265,81 @@ public class BatteryStatsHistory {
}
}
+ public BatteryStatsHistory(HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock) {
+ mStepDetailsCalculator = stepDetailsCalculator;
+ mClock = clock;
+
+ mHistoryBuffer = Parcel.obtain();
+ mSystemDir = null;
+ mHistoryDir = null;
+ initHistoryBuffer();
+ }
+
/**
* Used when BatteryStatsImpl object is created from deserialization of a parcel,
- * such as Settings app or checkin file.
- * @param historyBuffer the history buffer
+ * such as a checkin file.
*/
- public BatteryStatsHistory(Parcel historyBuffer) {
- mHistoryDir = null;
+ private BatteryStatsHistory(Parcel historyBuffer,
+ HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock) {
mHistoryBuffer = historyBuffer;
- mMaxHistoryFiles = null;
+ mClock = clock;
+ mSystemDir = null;
+ mHistoryDir = null;
+ mStepDetailsCalculator = stepDetailsCalculator;
+ }
+
+ private void initHistoryBuffer() {
+ mHistoryBaseTimeMs = 0;
+ mLastHistoryElapsedRealtimeMs = 0;
+ mTrackRunningHistoryElapsedRealtimeMs = 0;
+ mTrackRunningHistoryUptimeMs = 0;
+
+ mHistoryBuffer.setDataSize(0);
+ mHistoryBuffer.setDataPosition(0);
+ mHistoryBuffer.setDataCapacity(mMaxHistoryBufferSize / 2);
+ mHistoryLastLastWritten.clear();
+ mHistoryLastWritten.clear();
+ mHistoryTagPool.clear();
+ mNextHistoryTagIdx = 0;
+ mNumHistoryTagChars = 0;
+ mHistoryBufferLastPos = -1;
+ mActiveHistoryStates = 0xffffffff;
+ mActiveHistoryStates2 = 0xffffffff;
+ if (mStepDetailsCalculator != null) {
+ mStepDetailsCalculator.clear();
+ }
+ }
+
+ /**
+ * Changes the maximum number of history files to be kept.
+ */
+ public void setMaxHistoryFiles(int maxHistoryFiles) {
+ mMaxHistoryFiles = maxHistoryFiles;
+ }
+
+ /**
+ * Changes the maximum size of the history buffer, in bytes.
+ */
+ public void setMaxHistoryBufferSize(int maxHistoryBufferSize) {
+ mMaxHistoryBufferSize = maxHistoryBufferSize;
}
- public File getHistoryDirectory() {
- return mHistoryDir;
+ /**
+ * Creates a read-only copy of the battery history. Does not copy the files stored
+ * in the system directory, so it is not safe while actively writing history.
+ */
+ public BatteryStatsHistory copy() {
+ // Make a copy of battery history to avoid concurrent modification.
+ Parcel historyBuffer = Parcel.obtain();
+ historyBuffer.appendFrom(mHistoryBuffer, 0, mHistoryBuffer.dataSize());
+ return new BatteryStatsHistory(historyBuffer, mSystemDir, 0, 0, null, null);
+ }
+
+ /**
+ * Returns true if this instance only supports reading history.
+ */
+ public boolean isReadOnly() {
+ return mActiveFile == null;
}
/**
@@ -221,12 +356,13 @@ public class BatteryStatsHistory {
/**
* Create history AtomicFile from file number.
+ *
* @param num file number.
* @return AtomicFile object.
*/
private AtomicFile getFile(int num) {
return new AtomicFile(
- new File(mHistoryDir, num + FILE_SUFFIX));
+ new File(mHistoryDir, num + FILE_SUFFIX));
}
/**
@@ -234,7 +370,7 @@ public class BatteryStatsHistory {
* create next history file.
*/
public void startNextFile() {
- if (mMaxHistoryFiles == null) {
+ if (mMaxHistoryFiles == 0) {
Slog.wtf(TAG, "mMaxHistoryFiles should not be zero when writing history");
return;
}
@@ -264,7 +400,7 @@ public class BatteryStatsHistory {
// if there are more history files than allowed, delete oldest history files.
// mMaxHistoryFiles comes from Constants.MAX_HISTORY_FILES and can be updated by GService
// config at run time.
- while (mFileNumbers.size() > mMaxHistoryFiles.get()) {
+ while (mFileNumbers.size() > mMaxHistoryFiles) {
int oldest = mFileNumbers.get(0);
getFile(oldest).delete();
mFileNumbers.remove(0);
@@ -272,36 +408,43 @@ public class BatteryStatsHistory {
}
/**
- * Delete all existing history files. Active history file start from number 0 again.
+ * Clear history buffer and delete all existing history files. Active history file start from
+ * number 0 again.
*/
- public void resetAllFiles() {
+ public void reset() {
+ if (DEBUG) Slog.i(TAG, "********** CLEARING HISTORY!");
for (Integer i : mFileNumbers) {
getFile(i).delete();
}
mFileNumbers.clear();
mFileNumbers.add(0);
setActiveFile(0);
+
+ initHistoryBuffer();
}
/**
* Start iterating history files and history buffer.
+ *
* @return always return true.
*/
- public boolean startIteratingHistory() {
+ public BatteryStatsHistoryIterator iterate() {
mRecordCount = 0;
mCurrentFileIndex = 0;
mCurrentParcel = null;
mCurrentParcelEnd = 0;
mParcelIndex = 0;
- return true;
+ mBatteryStatsHistoryIterator = new BatteryStatsHistoryIterator(this);
+ return mBatteryStatsHistoryIterator;
}
/**
* Finish iterating history files and history buffer.
*/
- public void finishIteratingHistory() {
+ void finishIteratingHistory() {
// setDataPosition so mHistoryBuffer Parcel can be written.
mHistoryBuffer.setDataPosition(mHistoryBuffer.dataSize());
+ mBatteryStatsHistoryIterator = null;
if (DEBUG) {
Slog.d(TAG, "Battery history records iterated: " + mRecordCount);
}
@@ -311,11 +454,12 @@ public class BatteryStatsHistory {
* When iterating history files and history buffer, always start from the lowest numbered
* history file, when reached the mActiveFile (highest numbered history file), do not read from
* mActiveFile, read from history buffer instead because the buffer has more updated data.
+ *
* @param out a history item.
* @return The parcel that has next record. null if finished all history files and history
- * buffer
+ * buffer
*/
- public Parcel getNextParcel(BatteryStats.HistoryItem out) {
+ public Parcel getNextParcel(HistoryItem out) {
if (mRecordCount == 0) {
// reset out if it is the first record.
out.clear();
@@ -323,8 +467,7 @@ public class BatteryStatsHistory {
++mRecordCount;
// First iterate through all records in current parcel.
- if (mCurrentParcel != null)
- {
+ if (mCurrentParcel != null) {
if (mCurrentParcel.dataPosition() < mCurrentParcelEnd) {
// There are more records in current parcel.
return mCurrentParcel;
@@ -389,7 +532,8 @@ public class BatteryStatsHistory {
/**
* Read history file into a parcel.
- * @param out the Parcel read into.
+ *
+ * @param out the Parcel read into.
* @param file the File to read from.
* @return true if success, false otherwise.
*/
@@ -402,8 +546,8 @@ public class BatteryStatsHistory {
Slog.d(TAG, "readFileToParcel:" + file.getBaseFile().getPath()
+ " duration ms:" + (SystemClock.uptimeMillis() - start));
}
- } catch(Exception e) {
- Slog.e(TAG, "Error reading file "+ file.getBaseFile().getPath(), e);
+ } catch (Exception e) {
+ Slog.e(TAG, "Error reading file " + file.getBaseFile().getPath(), e);
return false;
}
out.unmarshall(raw, 0, raw.length);
@@ -413,6 +557,7 @@ public class BatteryStatsHistory {
/**
* Skip the header part of history parcel.
+ *
* @param p history parcel to skip head.
* @return true if version match, false if not.
*/
@@ -428,18 +573,68 @@ public class BatteryStatsHistory {
}
/**
+ * Writes the battery history contents for persistence.
+ */
+ public void writeSummaryToParcel(Parcel out, boolean inclHistory) {
+ out.writeBoolean(inclHistory);
+ if (inclHistory) {
+ writeToParcel(out);
+ }
+
+ out.writeInt(mHistoryTagPool.size());
+ for (Map.Entry<HistoryTag, Integer> ent : mHistoryTagPool.entrySet()) {
+ HistoryTag tag = ent.getKey();
+ out.writeInt(ent.getValue());
+ out.writeString(tag.string);
+ out.writeInt(tag.uid);
+ }
+ }
+
+ /**
+ * Reads battery history contents from a persisted parcel.
+ */
+ public void readSummaryFromParcel(Parcel in) {
+ boolean inclHistory = in.readBoolean();
+ if (inclHistory) {
+ readFromParcel(in);
+ }
+
+ mHistoryTagPool.clear();
+ mNextHistoryTagIdx = 0;
+ mNumHistoryTagChars = 0;
+
+ int numTags = in.readInt();
+ for (int i = 0; i < numTags; i++) {
+ int idx = in.readInt();
+ String str = in.readString();
+ int uid = in.readInt();
+ HistoryTag tag = new HistoryTag();
+ tag.string = str;
+ tag.uid = uid;
+ tag.poolIdx = idx;
+ mHistoryTagPool.put(tag, idx);
+ if (idx >= mNextHistoryTagIdx) {
+ mNextHistoryTagIdx = idx + 1;
+ }
+ mNumHistoryTagChars += tag.string.length() + 1;
+ }
+ }
+
+ /**
* Read all history files and serialize into a big Parcel.
* Checkin file calls this method.
*
* @param out the output parcel
*/
public void writeToParcel(Parcel out) {
+ writeHistoryBuffer(out);
writeToParcel(out, false /* useBlobs */);
}
/**
* This is for Settings app, when Settings app receives big history parcel, it call
* this method to parse it into list of parcels.
+ *
* @param out the output parcel
*/
public void writeToBatteryUsageStatsParcel(Parcel out) {
@@ -450,13 +645,13 @@ public class BatteryStatsHistory {
private void writeToParcel(Parcel out, boolean useBlobs) {
final long start = SystemClock.uptimeMillis();
out.writeInt(mFileNumbers.size() - 1);
- for(int i = 0; i < mFileNumbers.size() - 1; i++) {
+ for (int i = 0; i < mFileNumbers.size() - 1; i++) {
AtomicFile file = getFile(mFileNumbers.get(i));
byte[] raw = new byte[0];
try {
raw = file.readFully();
- } catch(Exception e) {
- Slog.e(TAG, "Error reading file "+ file.getBaseFile().getPath(), e);
+ } catch (Exception e) {
+ Slog.e(TAG, "Error reading file " + file.getBaseFile().getPath(), e);
}
if (useBlobs) {
out.writeBlob(raw);
@@ -480,17 +675,55 @@ public class BatteryStatsHistory {
Parcel historyBuffer = Parcel.obtain();
historyBuffer.unmarshall(historyBlob, 0, historyBlob.length);
- BatteryStatsHistory history = new BatteryStatsHistory(historyBuffer);
+ BatteryStatsHistory history = new BatteryStatsHistory(historyBuffer, null,
+ Clock.SYSTEM_CLOCK);
history.readFromParcel(in, true /* useBlobs */);
return history;
}
/**
+ * Read history from a check-in file.
+ */
+ public boolean readSummary() {
+ if (mActiveFile == null) {
+ Slog.w(TAG, "readSummary: no history file associated with this instance");
+ return false;
+ }
+
+ Parcel parcel = Parcel.obtain();
+ try {
+ final long start = SystemClock.uptimeMillis();
+ if (mActiveFile.exists()) {
+ byte[] raw = mActiveFile.readFully();
+ if (raw.length > 0) {
+ parcel.unmarshall(raw, 0, raw.length);
+ parcel.setDataPosition(0);
+ readHistoryBuffer(parcel);
+ }
+ if (DEBUG) {
+ Slog.d(TAG, "read history file::"
+ + mActiveFile.getBaseFile().getPath()
+ + " bytes:" + raw.length + " took ms:" + (SystemClock.uptimeMillis()
+ - start));
+ }
+ }
+ } catch (Exception e) {
+ Slog.e(TAG, "Error reading battery history", e);
+ reset();
+ return false;
+ } finally {
+ parcel.recycle();
+ }
+ return true;
+ }
+
+ /**
* This is for the check-in file, which has all history files embedded.
*
* @param in the input parcel.
*/
public void readFromParcel(Parcel in) {
+ readHistoryBuffer(in);
readFromParcel(in, false /* useBlobs */);
}
@@ -498,7 +731,7 @@ public class BatteryStatsHistory {
final long start = SystemClock.uptimeMillis();
mHistoryParcels = new ArrayList<>();
final int count = in.readInt();
- for(int i = 0; i < count; i++) {
+ for (int i = 0; i < count; i++) {
byte[] temp = useBlobs ? in.readBlob() : in.createByteArray();
if (temp == null || temp.length == 0) {
continue;
@@ -521,10 +754,12 @@ public class BatteryStatsHistory {
return stats.getAvailableBytes() > MIN_FREE_SPACE;
}
+ @VisibleForTesting
public List<Integer> getFilesNumbers() {
return mFileNumbers;
}
+ @VisibleForTesting
public AtomicFile getActiveFile() {
return mActiveFile;
}
@@ -534,15 +769,972 @@ public class BatteryStatsHistory {
*/
public int getHistoryUsedSize() {
int ret = 0;
- for(int i = 0; i < mFileNumbers.size() - 1; i++) {
+ for (int i = 0; i < mFileNumbers.size() - 1; i++) {
ret += getFile(mFileNumbers.get(i)).getBaseFile().length();
}
ret += mHistoryBuffer.dataSize();
if (mHistoryParcels != null) {
- for(int i = 0; i < mHistoryParcels.size(); i++) {
+ for (int i = 0; i < mHistoryParcels.size(); i++) {
ret += mHistoryParcels.get(i).dataSize();
}
}
return ret;
}
+
+ /**
+ * Enables/disables recording of history. When disabled, all "record*" calls are a no-op.
+ */
+ public void setHistoryRecordingEnabled(boolean enabled) {
+ mRecordingHistory = enabled;
+ }
+
+ /**
+ * Returns true if history recording is enabled.
+ */
+ public boolean isRecordingHistory() {
+ return mRecordingHistory;
+ }
+
+ /**
+ * Forces history recording regardless of charging state.
+ */
+ @VisibleForTesting
+ public void forceRecordAllHistory() {
+ mHaveBatteryLevel = true;
+ mRecordingHistory = true;
+ }
+
+ /**
+ * Starts a history buffer by recording the current wall-clock time.
+ */
+ public void startRecordingHistory(final long elapsedRealtimeMs, final long uptimeMs,
+ boolean reset) {
+ mRecordingHistory = true;
+ mHistoryCur.currentTime = mClock.currentTimeMillis();
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs, mHistoryCur,
+ reset ? HistoryItem.CMD_RESET : HistoryItem.CMD_CURRENT_TIME);
+ mHistoryCur.currentTime = 0;
+ }
+
+ /**
+ * Prepares to continue recording after restoring previous history from persistent storage.
+ */
+ public void continueRecordingHistory() {
+ if (mHistoryBuffer.dataPosition() <= 0 && mFileNumbers.size() <= 1) {
+ return;
+ }
+
+ mRecordingHistory = true;
+ final long elapsedRealtimeMs = mClock.elapsedRealtime();
+ final long uptimeMs = mClock.uptimeMillis();
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs, mHistoryCur, HistoryItem.CMD_START);
+ startRecordingHistory(elapsedRealtimeMs, uptimeMs, false);
+ }
+
+ /**
+ * Notes the current battery state to be reflected in the next written history item.
+ */
+ public void setBatteryState(boolean charging, int status, int level, int chargeUah) {
+ mHaveBatteryLevel = true;
+ setChargingState(charging);
+ mHistoryCur.batteryStatus = (byte) status;
+ mHistoryCur.batteryLevel = (byte) level;
+ mHistoryCur.batteryChargeUah = chargeUah;
+ }
+
+ /**
+ * Notes the current battery state to be reflected in the next written history item.
+ */
+ public void setBatteryState(int status, int level, int health, int plugType, int temperature,
+ int voltageMv, int chargeUah) {
+ mHaveBatteryLevel = true;
+ mHistoryCur.batteryStatus = (byte) status;
+ mHistoryCur.batteryLevel = (byte) level;
+ mHistoryCur.batteryHealth = (byte) health;
+ mHistoryCur.batteryPlugType = (byte) plugType;
+ mHistoryCur.batteryTemperature = (short) temperature;
+ mHistoryCur.batteryVoltage = (char) voltageMv;
+ mHistoryCur.batteryChargeUah = chargeUah;
+ }
+
+ /**
+ * Notes the current power plugged-in state to be reflected in the next written history item.
+ */
+ public void setPluggedInState(boolean pluggedIn) {
+ if (pluggedIn) {
+ mHistoryCur.states |= HistoryItem.STATE_BATTERY_PLUGGED_FLAG;
+ } else {
+ mHistoryCur.states &= ~HistoryItem.STATE_BATTERY_PLUGGED_FLAG;
+ }
+ }
+
+ /**
+ * Notes the current battery charging state to be reflected in the next written history item.
+ */
+ public void setChargingState(boolean charging) {
+ if (charging) {
+ mHistoryCur.states2 |= HistoryItem.STATE2_CHARGING_FLAG;
+ } else {
+ mHistoryCur.states2 &= ~HistoryItem.STATE2_CHARGING_FLAG;
+ }
+ }
+
+ /**
+ * Records a history event with the given code, name and UID.
+ */
+ public void recordEvent(long elapsedRealtimeMs, long uptimeMs, int code, String name,
+ int uid) {
+ mHistoryCur.eventCode = code;
+ mHistoryCur.eventTag = mHistoryCur.localEventTag;
+ mHistoryCur.eventTag.string = name;
+ mHistoryCur.eventTag.uid = uid;
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+ }
+
+ /**
+ * Records a time change event.
+ */
+ public void recordCurrentTimeChange(long elapsedRealtimeMs, long uptimeMs, long currentTimeMs) {
+ if (!mRecordingHistory) {
+ return;
+ }
+
+ mHistoryCur.currentTime = currentTimeMs;
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs, mHistoryCur,
+ HistoryItem.CMD_CURRENT_TIME);
+ mHistoryCur.currentTime = 0;
+ }
+
+ /**
+ * Records a system shutdown event.
+ */
+ public void recordShutdownEvent(long elapsedRealtimeMs, long uptimeMs, long currentTimeMs) {
+ if (!mRecordingHistory) {
+ return;
+ }
+
+ mHistoryCur.currentTime = currentTimeMs;
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs, mHistoryCur, HistoryItem.CMD_SHUTDOWN);
+ mHistoryCur.currentTime = 0;
+ }
+
+ /**
+ * Records a battery state change event.
+ */
+ public void recordBatteryState(long elapsedRealtimeMs, long uptimeMs, int batteryLevel,
+ boolean isPlugged) {
+ mHistoryCur.batteryLevel = (byte) batteryLevel;
+ setPluggedInState(isPlugged);
+ if (DEBUG) {
+ Slog.v(TAG, "Battery unplugged to: "
+ + Integer.toHexString(mHistoryCur.states));
+ }
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+ }
+
+ /**
+ * Records a history item with the amount of charge consumed by WiFi. Used on certain devices
+ * equipped with on-device power metering.
+ */
+ public void recordWifiConsumedCharge(long elapsedRealtimeMs, long uptimeMs,
+ double monitoredRailChargeMah) {
+ mHistoryCur.wifiRailChargeMah += monitoredRailChargeMah;
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+ }
+
+ /**
+ * Records a wakelock start event.
+ */
+ public void recordWakelockStartEvent(long elapsedRealtimeMs, long uptimeMs, String historyName,
+ int uid) {
+ mHistoryCur.wakelockTag = mHistoryCur.localWakelockTag;
+ mHistoryCur.wakelockTag.string = historyName;
+ mHistoryCur.wakelockTag.uid = uid;
+ recordStateStartEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.STATE_WAKE_LOCK_FLAG);
+ }
+
+ /**
+ * Updates the previous history event with a wakelock name and UID.
+ */
+ public boolean maybeUpdateWakelockTag(long elapsedRealtimeMs, long uptimeMs, String historyName,
+ int uid) {
+ if (mHistoryLastWritten.cmd != HistoryItem.CMD_UPDATE) {
+ return false;
+ }
+ if (mHistoryLastWritten.wakelockTag != null) {
+ // We'll try to update the last tag.
+ mHistoryLastWritten.wakelockTag = null;
+ mHistoryCur.wakelockTag = mHistoryCur.localWakelockTag;
+ mHistoryCur.wakelockTag.string = historyName;
+ mHistoryCur.wakelockTag.uid = uid;
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+ }
+ return true;
+ }
+
+ /**
+ * Records an event when some state flag changes to true.
+ */
+ public void recordStateStartEvent(long elapsedRealtimeMs, long uptimeMs, int stateFlags) {
+ mHistoryCur.states |= stateFlags;
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+ }
+
+ /**
+ * Records an event when some state flag changes to false.
+ */
+ public void recordStateStopEvent(long elapsedRealtimeMs, long uptimeMs, int stateFlags) {
+ mHistoryCur.states &= ~stateFlags;
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+ }
+
+ /**
+ * Records an event when some state flags change to true and some to false.
+ */
+ public void recordStateChangeEvent(long elapsedRealtimeMs, long uptimeMs, int stateStartFlags,
+ int stateStopFlags) {
+ mHistoryCur.states = (mHistoryCur.states | stateStartFlags) & ~stateStopFlags;
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+ }
+
+ /**
+ * Records an event when some state2 flag changes to true.
+ */
+ public void recordState2StartEvent(long elapsedRealtimeMs, long uptimeMs, int stateFlags) {
+ mHistoryCur.states2 |= stateFlags;
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+ }
+
+ /**
+ * Records an event when some state2 flag changes to false.
+ */
+ public void recordState2StopEvent(long elapsedRealtimeMs, long uptimeMs, int stateFlags) {
+ mHistoryCur.states2 &= ~stateFlags;
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+ }
+
+ /**
+ * Records an wakeup event.
+ */
+ public void recordWakeupEvent(long elapsedRealtimeMs, long uptimeMs, String reason) {
+ mHistoryCur.wakeReasonTag = mHistoryCur.localWakeReasonTag;
+ mHistoryCur.wakeReasonTag.string = reason;
+ mHistoryCur.wakeReasonTag.uid = 0;
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+ }
+
+ /**
+ * Records a screen brightness change event.
+ */
+ public void recordScreenBrightnessEvent(long elapsedRealtimeMs, long uptimeMs,
+ int brightnessBin) {
+ mHistoryCur.states = (mHistoryCur.states & ~HistoryItem.STATE_BRIGHTNESS_MASK)
+ | (brightnessBin << HistoryItem.STATE_BRIGHTNESS_SHIFT);
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+ }
+
+ /**
+ * Records a GNSS signal level change event.
+ */
+ public void recordGpsSignalQualityEvent(long elapsedRealtimeMs, long uptimeMs,
+ int signalLevel) {
+ mHistoryCur.states2 = (mHistoryCur.states2 & ~HistoryItem.STATE2_GPS_SIGNAL_QUALITY_MASK)
+ | (signalLevel << HistoryItem.STATE2_GPS_SIGNAL_QUALITY_SHIFT);
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+ }
+
+ /**
+ * Records a device idle mode change event.
+ */
+ public void recordDeviceIdleEvent(long elapsedRealtimeMs, long uptimeMs, int mode) {
+ mHistoryCur.states2 = (mHistoryCur.states2 & ~HistoryItem.STATE2_DEVICE_IDLE_MASK)
+ | (mode << HistoryItem.STATE2_DEVICE_IDLE_SHIFT);
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+ }
+
+ /**
+ * Records a telephony state change event.
+ */
+ public void recordPhoneStateChangeEvent(long elapsedRealtimeMs, long uptimeMs, int addStateFlag,
+ int removeStateFlag, int state, int signalStrength) {
+ mHistoryCur.states = (mHistoryCur.states | addStateFlag) & ~removeStateFlag;
+ if (state != -1) {
+ mHistoryCur.states =
+ (mHistoryCur.states & ~HistoryItem.STATE_PHONE_STATE_MASK)
+ | (state << HistoryItem.STATE_PHONE_STATE_SHIFT);
+ }
+ if (signalStrength != -1) {
+ mHistoryCur.states =
+ (mHistoryCur.states & ~HistoryItem.STATE_PHONE_SIGNAL_STRENGTH_MASK)
+ | (signalStrength << HistoryItem.STATE_PHONE_SIGNAL_STRENGTH_SHIFT);
+ }
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+ }
+
+ /**
+ * Records a data connection type change event.
+ */
+ public void recordDataConnectionTypeChangeEvent(long elapsedRealtimeMs, long uptimeMs,
+ int dataConnectionType) {
+ mHistoryCur.states = (mHistoryCur.states & ~HistoryItem.STATE_DATA_CONNECTION_MASK)
+ | (dataConnectionType << HistoryItem.STATE_DATA_CONNECTION_SHIFT);
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+ }
+
+ /**
+ * Records a WiFi supplicant state change event.
+ */
+ public void recordWifiSupplicantStateChangeEvent(long elapsedRealtimeMs, long uptimeMs,
+ int supplState) {
+ mHistoryCur.states2 =
+ (mHistoryCur.states2 & ~HistoryItem.STATE2_WIFI_SUPPL_STATE_MASK)
+ | (supplState << HistoryItem.STATE2_WIFI_SUPPL_STATE_SHIFT);
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+ }
+
+ /**
+ * Records a WiFi signal strength change event.
+ */
+ public void recordWifiSignalStrengthChangeEvent(long elapsedRealtimeMs, long uptimeMs,
+ int strengthBin) {
+ mHistoryCur.states2 =
+ (mHistoryCur.states2 & ~HistoryItem.STATE2_WIFI_SIGNAL_STRENGTH_MASK)
+ | (strengthBin << HistoryItem.STATE2_WIFI_SIGNAL_STRENGTH_SHIFT);
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+ }
+
+ /**
+ * Writes the current history item to history.
+ */
+ public void writeHistoryItem(long elapsedRealtimeMs, long uptimeMs) {
+ if (mTrackRunningHistoryElapsedRealtimeMs != 0) {
+ final long diffElapsedMs = elapsedRealtimeMs - mTrackRunningHistoryElapsedRealtimeMs;
+ final long diffUptimeMs = uptimeMs - mTrackRunningHistoryUptimeMs;
+ if (diffUptimeMs < (diffElapsedMs - 20)) {
+ final long wakeElapsedTimeMs = elapsedRealtimeMs - (diffElapsedMs - diffUptimeMs);
+ mHistoryAddTmp.setTo(mHistoryLastWritten);
+ mHistoryAddTmp.wakelockTag = null;
+ mHistoryAddTmp.wakeReasonTag = null;
+ mHistoryAddTmp.eventCode = HistoryItem.EVENT_NONE;
+ mHistoryAddTmp.states &= ~HistoryItem.STATE_CPU_RUNNING_FLAG;
+ writeHistoryItem(wakeElapsedTimeMs, uptimeMs, mHistoryAddTmp);
+ }
+ }
+ mHistoryCur.states |= HistoryItem.STATE_CPU_RUNNING_FLAG;
+ mTrackRunningHistoryElapsedRealtimeMs = elapsedRealtimeMs;
+ mTrackRunningHistoryUptimeMs = uptimeMs;
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs, mHistoryCur);
+ }
+
+ private void writeHistoryItem(long elapsedRealtimeMs, long uptimeMs, HistoryItem cur) {
+ if (!mHaveBatteryLevel || !mRecordingHistory) {
+ return;
+ }
+
+ final long timeDiffMs = (mHistoryBaseTimeMs + elapsedRealtimeMs) - mHistoryLastWritten.time;
+ final int diffStates = mHistoryLastWritten.states ^ (cur.states & mActiveHistoryStates);
+ final int diffStates2 = mHistoryLastWritten.states2 ^ (cur.states2 & mActiveHistoryStates2);
+ final int lastDiffStates = mHistoryLastWritten.states ^ mHistoryLastLastWritten.states;
+ final int lastDiffStates2 = mHistoryLastWritten.states2 ^ mHistoryLastLastWritten.states2;
+ if (DEBUG) {
+ Slog.i(TAG, "ADD: tdelta=" + timeDiffMs + " diff="
+ + Integer.toHexString(diffStates) + " lastDiff="
+ + Integer.toHexString(lastDiffStates) + " diff2="
+ + Integer.toHexString(diffStates2) + " lastDiff2="
+ + Integer.toHexString(lastDiffStates2));
+ }
+ if (mHistoryBufferLastPos >= 0 && mHistoryLastWritten.cmd == HistoryItem.CMD_UPDATE
+ && timeDiffMs < 1000 && (diffStates & lastDiffStates) == 0
+ && (diffStates2 & lastDiffStates2) == 0
+ && (!mHistoryLastWritten.tagsFirstOccurrence && !cur.tagsFirstOccurrence)
+ && (mHistoryLastWritten.wakelockTag == null || cur.wakelockTag == null)
+ && (mHistoryLastWritten.wakeReasonTag == null || cur.wakeReasonTag == null)
+ && mHistoryLastWritten.stepDetails == null
+ && (mHistoryLastWritten.eventCode == HistoryItem.EVENT_NONE
+ || cur.eventCode == HistoryItem.EVENT_NONE)
+ && mHistoryLastWritten.batteryLevel == cur.batteryLevel
+ && mHistoryLastWritten.batteryStatus == cur.batteryStatus
+ && mHistoryLastWritten.batteryHealth == cur.batteryHealth
+ && mHistoryLastWritten.batteryPlugType == cur.batteryPlugType
+ && mHistoryLastWritten.batteryTemperature == cur.batteryTemperature
+ && mHistoryLastWritten.batteryVoltage == cur.batteryVoltage) {
+ // We can merge this new change in with the last one. Merging is
+ // allowed as long as only the states have changed, and within those states
+ // as long as no bit has changed both between now and the last entry, as
+ // well as the last entry and the one before it (so we capture any toggles).
+ if (DEBUG) Slog.i(TAG, "ADD: rewinding back to " + mHistoryBufferLastPos);
+ mHistoryBuffer.setDataSize(mHistoryBufferLastPos);
+ mHistoryBuffer.setDataPosition(mHistoryBufferLastPos);
+ mHistoryBufferLastPos = -1;
+ elapsedRealtimeMs = mHistoryLastWritten.time - mHistoryBaseTimeMs;
+ // If the last written history had a wakelock tag, we need to retain it.
+ // Note that the condition above made sure that we aren't in a case where
+ // both it and the current history item have a wakelock tag.
+ if (mHistoryLastWritten.wakelockTag != null) {
+ cur.wakelockTag = cur.localWakelockTag;
+ cur.wakelockTag.setTo(mHistoryLastWritten.wakelockTag);
+ }
+ // If the last written history had a wake reason tag, we need to retain it.
+ // Note that the condition above made sure that we aren't in a case where
+ // both it and the current history item have a wakelock tag.
+ if (mHistoryLastWritten.wakeReasonTag != null) {
+ cur.wakeReasonTag = cur.localWakeReasonTag;
+ cur.wakeReasonTag.setTo(mHistoryLastWritten.wakeReasonTag);
+ }
+ // If the last written history had an event, we need to retain it.
+ // Note that the condition above made sure that we aren't in a case where
+ // both it and the current history item have an event.
+ if (mHistoryLastWritten.eventCode != HistoryItem.EVENT_NONE) {
+ cur.eventCode = mHistoryLastWritten.eventCode;
+ cur.eventTag = cur.localEventTag;
+ cur.eventTag.setTo(mHistoryLastWritten.eventTag);
+ }
+ mHistoryLastWritten.setTo(mHistoryLastLastWritten);
+ }
+ final int dataSize = mHistoryBuffer.dataSize();
+
+ if (dataSize >= mMaxHistoryBufferSize) {
+ if (mMaxHistoryBufferSize == 0) {
+ Slog.wtf(TAG, "mMaxHistoryBufferSize should not be zero when writing history");
+ mMaxHistoryBufferSize = 1024;
+ }
+
+ //open a new history file.
+ final long start = SystemClock.uptimeMillis();
+ writeHistory();
+ if (DEBUG) {
+ Slog.d(TAG, "addHistoryBufferLocked writeHistory took ms:"
+ + (SystemClock.uptimeMillis() - start));
+ }
+ startNextFile();
+ mHistoryBuffer.setDataSize(0);
+ mHistoryBuffer.setDataPosition(0);
+ mHistoryBuffer.setDataCapacity(mMaxHistoryBufferSize / 2);
+ mHistoryBufferLastPos = -1;
+ mHistoryLastWritten.clear();
+ mHistoryLastLastWritten.clear();
+
+ // Mark every entry in the pool with a flag indicating that the tag
+ // has not yet been encountered while writing the current history buffer.
+ for (Map.Entry<HistoryTag, Integer> entry : mHistoryTagPool.entrySet()) {
+ entry.setValue(entry.getValue() | BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG);
+ }
+ // Make a copy of mHistoryCur.
+ HistoryItem copy = new HistoryItem();
+ copy.setTo(cur);
+ // startRecordingHistory will reset mHistoryCur.
+ startRecordingHistory(elapsedRealtimeMs, uptimeMs, false);
+ // Add the copy into history buffer.
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs, copy, HistoryItem.CMD_UPDATE);
+ return;
+ }
+
+ if (dataSize == 0) {
+ // The history is currently empty; we need it to start with a time stamp.
+ cur.currentTime = mClock.currentTimeMillis();
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs, cur, HistoryItem.CMD_RESET);
+ }
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs, cur, HistoryItem.CMD_UPDATE);
+ }
+
+ private void writeHistoryItem(long elapsedRealtimeMs,
+ @SuppressWarnings("UnusedVariable") long uptimeMs, HistoryItem cur, byte cmd) {
+ if (mBatteryStatsHistoryIterator != null) {
+ throw new IllegalStateException("Can't do this while iterating history!");
+ }
+ mHistoryBufferLastPos = mHistoryBuffer.dataPosition();
+ mHistoryLastLastWritten.setTo(mHistoryLastWritten);
+ final boolean hasTags = mHistoryLastWritten.tagsFirstOccurrence || cur.tagsFirstOccurrence;
+ mHistoryLastWritten.setTo(mHistoryBaseTimeMs + elapsedRealtimeMs, cmd, cur);
+ mHistoryLastWritten.tagsFirstOccurrence = hasTags;
+ mHistoryLastWritten.states &= mActiveHistoryStates;
+ mHistoryLastWritten.states2 &= mActiveHistoryStates2;
+ writeHistoryDelta(mHistoryBuffer, mHistoryLastWritten, mHistoryLastLastWritten);
+ mLastHistoryElapsedRealtimeMs = elapsedRealtimeMs;
+ cur.wakelockTag = null;
+ cur.wakeReasonTag = null;
+ cur.eventCode = HistoryItem.EVENT_NONE;
+ cur.eventTag = null;
+ cur.tagsFirstOccurrence = false;
+ if (DEBUG) {
+ Slog.i(TAG, "Writing history buffer: was " + mHistoryBufferLastPos
+ + " now " + mHistoryBuffer.dataPosition()
+ + " size is now " + mHistoryBuffer.dataSize());
+ }
+ }
+
+ /*
+ The history delta format uses flags to denote further data in subsequent ints in the parcel.
+
+ There is always the first token, which may contain the delta time, or an indicator of
+ the length of the time (int or long) following this token.
+
+ First token: always present,
+ 31 23 15 7 0
+ █M|L|K|J|I|H|G|F█E|D|C|B|A|T|T|T█T|T|T|T|T|T|T|T█T|T|T|T|T|T|T|T█
+
+ T: the delta time if it is <= 0x7fffd. Otherwise 0x7fffe indicates an int immediately
+ follows containing the time, and 0x7ffff indicates a long immediately follows with the
+ delta time.
+ A: battery level changed and an int follows with battery data.
+ B: state changed and an int follows with state change data.
+ C: state2 has changed and an int follows with state2 change data.
+ D: wakelock/wakereason has changed and an wakelock/wakereason struct follows.
+ E: event data has changed and an event struct follows.
+ F: battery charge in coulombs has changed and an int with the charge follows.
+ G: state flag denoting that the mobile radio was active.
+ H: state flag denoting that the wifi radio was active.
+ I: state flag denoting that a wifi scan occurred.
+ J: state flag denoting that a wifi full lock was held.
+ K: state flag denoting that the gps was on.
+ L: state flag denoting that a wakelock was held.
+ M: state flag denoting that the cpu was running.
+
+ Time int/long: if T in the first token is 0x7ffff or 0x7fffe, then an int or long follows
+ with the time delta.
+
+ Battery level int: if A in the first token is set,
+ 31 23 15 7 0
+ █L|L|L|L|L|L|L|T█T|T|T|T|T|T|T|T█T|V|V|V|V|V|V|V█V|V|V|V|V|V|V|D█
+
+ D: indicates that extra history details follow.
+ V: the battery voltage.
+ T: the battery temperature.
+ L: the battery level (out of 100).
+
+ State change int: if B in the first token is set,
+ 31 23 15 7 0
+ █S|S|S|H|H|H|P|P█F|E|D|C|B| | |A█ | | | | | | | █ | | | | | | | █
+
+ A: wifi multicast was on.
+ B: battery was plugged in.
+ C: screen was on.
+ D: phone was scanning for signal.
+ E: audio was on.
+ F: a sensor was active.
+
+ State2 change int: if C in the first token is set,
+ 31 23 15 7 0
+ █M|L|K|J|I|H|H|G█F|E|D|C| | | | █ | | | | | | | █ |B|B|B|A|A|A|A█
+
+ A: 4 bits indicating the wifi supplicant state: {@link BatteryStats#WIFI_SUPPL_STATE_NAMES}.
+ B: 3 bits indicating the wifi signal strength: 0, 1, 2, 3, 4.
+ C: a bluetooth scan was active.
+ D: the camera was active.
+ E: bluetooth was on.
+ F: a phone call was active.
+ G: the device was charging.
+ H: 2 bits indicating the device-idle (doze) state: off, light, full
+ I: the flashlight was on.
+ J: wifi was on.
+ K: wifi was running.
+ L: video was playing.
+ M: power save mode was on.
+
+ Wakelock/wakereason struct: if D in the first token is set,
+ Event struct: if E in the first token is set,
+ History step details struct: if D in the battery level int is set,
+
+ Battery charge int: if F in the first token is set, an int representing the battery charge
+ in coulombs follows.
+ */
+ /**
+ * Writes the delta between the previous and current history items into history buffer.
+ */
+ public void writeHistoryDelta(Parcel dest, HistoryItem cur, HistoryItem last) {
+ if (last == null || cur.cmd != HistoryItem.CMD_UPDATE) {
+ dest.writeInt(BatteryStatsHistory.DELTA_TIME_ABS);
+ cur.writeToParcel(dest, 0);
+ return;
+ }
+
+ final long deltaTime = cur.time - last.time;
+ final int lastBatteryLevelInt = buildBatteryLevelInt(last);
+ final int lastStateInt = buildStateInt(last);
+
+ int deltaTimeToken;
+ if (deltaTime < 0 || deltaTime > Integer.MAX_VALUE) {
+ deltaTimeToken = BatteryStatsHistory.DELTA_TIME_LONG;
+ } else if (deltaTime >= BatteryStatsHistory.DELTA_TIME_ABS) {
+ deltaTimeToken = BatteryStatsHistory.DELTA_TIME_INT;
+ } else {
+ deltaTimeToken = (int) deltaTime;
+ }
+ int firstToken = deltaTimeToken | (cur.states & BatteryStatsHistory.DELTA_STATE_MASK);
+ final int includeStepDetails = mLastHistoryStepLevel > cur.batteryLevel
+ ? BatteryStatsHistory.BATTERY_DELTA_LEVEL_FLAG : 0;
+ mLastHistoryStepLevel = cur.batteryLevel;
+ final int batteryLevelInt = buildBatteryLevelInt(cur) | includeStepDetails;
+ final boolean batteryLevelIntChanged = batteryLevelInt != lastBatteryLevelInt;
+ if (batteryLevelIntChanged) {
+ firstToken |= BatteryStatsHistory.DELTA_BATTERY_LEVEL_FLAG;
+ }
+ final int stateInt = buildStateInt(cur);
+ final boolean stateIntChanged = stateInt != lastStateInt;
+ if (stateIntChanged) {
+ firstToken |= BatteryStatsHistory.DELTA_STATE_FLAG;
+ }
+ final boolean state2IntChanged = cur.states2 != last.states2;
+ if (state2IntChanged) {
+ firstToken |= BatteryStatsHistory.DELTA_STATE2_FLAG;
+ }
+ if (cur.wakelockTag != null || cur.wakeReasonTag != null) {
+ firstToken |= BatteryStatsHistory.DELTA_WAKELOCK_FLAG;
+ }
+ if (cur.eventCode != HistoryItem.EVENT_NONE) {
+ firstToken |= BatteryStatsHistory.DELTA_EVENT_FLAG;
+ }
+
+ final boolean batteryChargeChanged = cur.batteryChargeUah != last.batteryChargeUah;
+ if (batteryChargeChanged) {
+ firstToken |= BatteryStatsHistory.DELTA_BATTERY_CHARGE_FLAG;
+ }
+ dest.writeInt(firstToken);
+ if (DEBUG) {
+ Slog.i(TAG, "WRITE DELTA: firstToken=0x" + Integer.toHexString(firstToken)
+ + " deltaTime=" + deltaTime);
+ }
+
+ if (deltaTimeToken >= BatteryStatsHistory.DELTA_TIME_INT) {
+ if (deltaTimeToken == BatteryStatsHistory.DELTA_TIME_INT) {
+ if (DEBUG) Slog.i(TAG, "WRITE DELTA: int deltaTime=" + (int) deltaTime);
+ dest.writeInt((int) deltaTime);
+ } else {
+ if (DEBUG) Slog.i(TAG, "WRITE DELTA: long deltaTime=" + deltaTime);
+ dest.writeLong(deltaTime);
+ }
+ }
+ if (batteryLevelIntChanged) {
+ dest.writeInt(batteryLevelInt);
+ if (DEBUG) {
+ Slog.i(TAG, "WRITE DELTA: batteryToken=0x"
+ + Integer.toHexString(batteryLevelInt)
+ + " batteryLevel=" + cur.batteryLevel
+ + " batteryTemp=" + cur.batteryTemperature
+ + " batteryVolt=" + (int) cur.batteryVoltage);
+ }
+ }
+ if (stateIntChanged) {
+ dest.writeInt(stateInt);
+ if (DEBUG) {
+ Slog.i(TAG, "WRITE DELTA: stateToken=0x"
+ + Integer.toHexString(stateInt)
+ + " batteryStatus=" + cur.batteryStatus
+ + " batteryHealth=" + cur.batteryHealth
+ + " batteryPlugType=" + cur.batteryPlugType
+ + " states=0x" + Integer.toHexString(cur.states));
+ }
+ }
+ if (state2IntChanged) {
+ dest.writeInt(cur.states2);
+ if (DEBUG) {
+ Slog.i(TAG, "WRITE DELTA: states2=0x"
+ + Integer.toHexString(cur.states2));
+ }
+ }
+ if (cur.wakelockTag != null || cur.wakeReasonTag != null) {
+ int wakeLockIndex;
+ int wakeReasonIndex;
+ if (cur.wakelockTag != null) {
+ wakeLockIndex = writeHistoryTag(cur.wakelockTag);
+ if (DEBUG) {
+ Slog.i(TAG, "WRITE DELTA: wakelockTag=#" + cur.wakelockTag.poolIdx
+ + " " + cur.wakelockTag.uid + ":" + cur.wakelockTag.string);
+ }
+ } else {
+ wakeLockIndex = 0xffff;
+ }
+ if (cur.wakeReasonTag != null) {
+ wakeReasonIndex = writeHistoryTag(cur.wakeReasonTag);
+ if (DEBUG) {
+ Slog.i(TAG, "WRITE DELTA: wakeReasonTag=#" + cur.wakeReasonTag.poolIdx
+ + " " + cur.wakeReasonTag.uid + ":" + cur.wakeReasonTag.string);
+ }
+ } else {
+ wakeReasonIndex = 0xffff;
+ }
+ dest.writeInt((wakeReasonIndex << 16) | wakeLockIndex);
+ if (cur.wakelockTag != null
+ && (wakeLockIndex & BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG) != 0) {
+ cur.wakelockTag.writeToParcel(dest, 0);
+ cur.tagsFirstOccurrence = true;
+ }
+ if (cur.wakeReasonTag != null
+ && (wakeReasonIndex & BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG) != 0) {
+ cur.wakeReasonTag.writeToParcel(dest, 0);
+ cur.tagsFirstOccurrence = true;
+ }
+ }
+ if (cur.eventCode != HistoryItem.EVENT_NONE) {
+ final int index = writeHistoryTag(cur.eventTag);
+ final int codeAndIndex = (cur.eventCode & 0xffff) | (index << 16);
+ dest.writeInt(codeAndIndex);
+ if ((index & BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG) != 0) {
+ cur.eventTag.writeToParcel(dest, 0);
+ cur.tagsFirstOccurrence = true;
+ }
+ if (DEBUG) {
+ Slog.i(TAG, "WRITE DELTA: event=" + cur.eventCode + " tag=#"
+ + cur.eventTag.poolIdx + " " + cur.eventTag.uid + ":"
+ + cur.eventTag.string);
+ }
+ }
+
+ cur.stepDetails = mStepDetailsCalculator.getHistoryStepDetails();
+ if (includeStepDetails != 0) {
+ cur.stepDetails.writeToParcel(dest);
+ }
+
+ if (batteryChargeChanged) {
+ if (DEBUG) Slog.i(TAG, "WRITE DELTA: batteryChargeUah=" + cur.batteryChargeUah);
+ dest.writeInt(cur.batteryChargeUah);
+ }
+ dest.writeDouble(cur.modemRailChargeMah);
+ dest.writeDouble(cur.wifiRailChargeMah);
+ }
+
+ private int buildBatteryLevelInt(HistoryItem h) {
+ return ((((int) h.batteryLevel) << 25) & 0xfe000000)
+ | ((((int) h.batteryTemperature) << 15) & 0x01ff8000)
+ | ((((int) h.batteryVoltage) << 1) & 0x00007ffe);
+ }
+
+ private int buildStateInt(HistoryItem h) {
+ int plugType = 0;
+ if ((h.batteryPlugType & BatteryManager.BATTERY_PLUGGED_AC) != 0) {
+ plugType = 1;
+ } else if ((h.batteryPlugType & BatteryManager.BATTERY_PLUGGED_USB) != 0) {
+ plugType = 2;
+ } else if ((h.batteryPlugType & BatteryManager.BATTERY_PLUGGED_WIRELESS) != 0) {
+ plugType = 3;
+ }
+ return ((h.batteryStatus & BatteryStatsHistory.STATE_BATTERY_STATUS_MASK)
+ << BatteryStatsHistory.STATE_BATTERY_STATUS_SHIFT)
+ | ((h.batteryHealth & BatteryStatsHistory.STATE_BATTERY_HEALTH_MASK)
+ << BatteryStatsHistory.STATE_BATTERY_HEALTH_SHIFT)
+ | ((plugType & BatteryStatsHistory.STATE_BATTERY_PLUG_MASK)
+ << BatteryStatsHistory.STATE_BATTERY_PLUG_SHIFT)
+ | (h.states & (~BatteryStatsHistory.STATE_BATTERY_MASK));
+ }
+
+ /**
+ * Returns the index for the specified tag. If this is the first time the tag is encountered
+ * while writing the current history buffer, the method returns
+ * <code>(index | TAG_FIRST_OCCURRENCE_FLAG)</code>
+ */
+ private int writeHistoryTag(HistoryTag tag) {
+ if (tag.string == null) {
+ Slog.wtfStack(TAG, "writeHistoryTag called with null name");
+ }
+
+ final int stringLength = tag.string.length();
+ if (stringLength > MAX_HISTORY_TAG_STRING_LENGTH) {
+ Slog.e(TAG, "Long battery history tag: " + tag.string);
+ tag.string = tag.string.substring(0, MAX_HISTORY_TAG_STRING_LENGTH);
+ }
+
+ Integer idxObj = mHistoryTagPool.get(tag);
+ int idx;
+ if (idxObj != null) {
+ idx = idxObj;
+ if ((idx & BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG) != 0) {
+ mHistoryTagPool.put(tag, idx & ~BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG);
+ }
+ return idx;
+ } else if (mNextHistoryTagIdx < HISTORY_TAG_INDEX_LIMIT) {
+ idx = mNextHistoryTagIdx;
+ HistoryTag key = new HistoryTag();
+ key.setTo(tag);
+ tag.poolIdx = idx;
+ mHistoryTagPool.put(key, idx);
+ mNextHistoryTagIdx++;
+
+ mNumHistoryTagChars += stringLength + 1;
+ if (mHistoryTags != null) {
+ mHistoryTags.put(idx, key);
+ }
+ return idx | BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG;
+ } else {
+ // Tag pool overflow: include the tag itself in the parcel
+ return HISTORY_TAG_INDEX_LIMIT | BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG;
+ }
+ }
+
+ /**
+ * Don't allow any more batching in to the current history event.
+ */
+ public void commitCurrentHistoryBatchLocked() {
+ mHistoryLastWritten.cmd = HistoryItem.CMD_NULL;
+ }
+
+ /**
+ * Saves the accumulated history buffer in the active file, see {@link #getActiveFile()} .
+ */
+ public void writeHistory() {
+ if (mActiveFile == null) {
+ Slog.w(TAG, "writeHistory: no history file associated with this instance");
+ return;
+ }
+
+ Parcel p = Parcel.obtain();
+ try {
+ final long start = SystemClock.uptimeMillis();
+ writeHistoryBuffer(p);
+ if (DEBUG) {
+ Slog.d(TAG, "writeHistoryBuffer duration ms:"
+ + (SystemClock.uptimeMillis() - start) + " bytes:" + p.dataSize());
+ }
+ writeParcelToFileLocked(p, mActiveFile);
+ } finally {
+ p.recycle();
+ }
+ }
+
+ /**
+ * Reads history buffer from a persisted Parcel.
+ */
+ public void readHistoryBuffer(Parcel in) throws ParcelFormatException {
+ final int version = in.readInt();
+ if (version != BatteryStatsHistory.VERSION) {
+ Slog.w("BatteryStats", "readHistoryBuffer: version got " + version
+ + ", expected " + BatteryStatsHistory.VERSION + "; erasing old stats");
+ return;
+ }
+
+ final long historyBaseTime = in.readLong();
+
+ mHistoryBuffer.setDataSize(0);
+ mHistoryBuffer.setDataPosition(0);
+
+ int bufSize = in.readInt();
+ int curPos = in.dataPosition();
+ if (bufSize >= (mMaxHistoryBufferSize * 100)) {
+ throw new ParcelFormatException(
+ "File corrupt: history data buffer too large " + bufSize);
+ } else if ((bufSize & ~3) != bufSize) {
+ throw new ParcelFormatException(
+ "File corrupt: history data buffer not aligned " + bufSize);
+ } else {
+ if (DEBUG) {
+ Slog.i(TAG, "***************** READING NEW HISTORY: " + bufSize
+ + " bytes at " + curPos);
+ }
+ mHistoryBuffer.appendFrom(in, curPos, bufSize);
+ in.setDataPosition(curPos + bufSize);
+ }
+
+ if (DEBUG) {
+ StringBuilder sb = new StringBuilder(128);
+ sb.append("****************** OLD mHistoryBaseTimeMs: ");
+ TimeUtils.formatDuration(mHistoryBaseTimeMs, sb);
+ Slog.i(TAG, sb.toString());
+ }
+ mHistoryBaseTimeMs = historyBaseTime;
+ if (DEBUG) {
+ StringBuilder sb = new StringBuilder(128);
+ sb.append("****************** NEW mHistoryBaseTimeMs: ");
+ TimeUtils.formatDuration(mHistoryBaseTimeMs, sb);
+ Slog.i(TAG, sb.toString());
+ }
+
+ // We are just arbitrarily going to insert 1 minute from the sample of
+ // the last run until samples in this run.
+ if (mHistoryBaseTimeMs > 0) {
+ long oldnow = mClock.elapsedRealtime();
+ mHistoryBaseTimeMs = mHistoryBaseTimeMs - oldnow + 1;
+ if (DEBUG) {
+ StringBuilder sb = new StringBuilder(128);
+ sb.append("****************** ADJUSTED mHistoryBaseTimeMs: ");
+ TimeUtils.formatDuration(mHistoryBaseTimeMs, sb);
+ Slog.i(TAG, sb.toString());
+ }
+ }
+ }
+
+ private void writeHistoryBuffer(Parcel out) {
+ if (DEBUG) {
+ StringBuilder sb = new StringBuilder(128);
+ sb.append("****************** WRITING mHistoryBaseTimeMs: ");
+ TimeUtils.formatDuration(mHistoryBaseTimeMs, sb);
+ sb.append(" mLastHistoryElapsedRealtimeMs: ");
+ TimeUtils.formatDuration(mLastHistoryElapsedRealtimeMs, sb);
+ Slog.i(TAG, sb.toString());
+ }
+ out.writeInt(BatteryStatsHistory.VERSION);
+ out.writeLong(mHistoryBaseTimeMs + mLastHistoryElapsedRealtimeMs);
+ out.writeInt(mHistoryBuffer.dataSize());
+ if (DEBUG) {
+ Slog.i(TAG, "***************** WRITING HISTORY: "
+ + mHistoryBuffer.dataSize() + " bytes at " + out.dataPosition());
+ }
+ out.appendFrom(mHistoryBuffer, 0, mHistoryBuffer.dataSize());
+ }
+
+ private void writeParcelToFileLocked(Parcel p, AtomicFile file) {
+ FileOutputStream fos = null;
+ mWriteLock.lock();
+ try {
+ final long startTimeMs = SystemClock.uptimeMillis();
+ fos = file.startWrite();
+ fos.write(p.marshall());
+ fos.flush();
+ file.finishWrite(fos);
+ if (DEBUG) {
+ Slog.d(TAG, "writeParcelToFileLocked file:" + file.getBaseFile().getPath()
+ + " duration ms:" + (SystemClock.uptimeMillis() - startTimeMs)
+ + " bytes:" + p.dataSize());
+ }
+ com.android.internal.logging.EventLogTags.writeCommitSysConfigFile(
+ "batterystats", SystemClock.uptimeMillis() - startTimeMs);
+ } catch (IOException e) {
+ Slog.w(TAG, "Error writing battery statistics", e);
+ file.failWrite(fos);
+ } finally {
+ mWriteLock.unlock();
+ }
+ }
+
+ /**
+ * Returns the total number of history tags in the tag pool.
+ */
+ public int getHistoryStringPoolSize() {
+ return mHistoryTagPool.size();
+ }
+
+ /**
+ * Returns the total number of bytes occupied by the history tag pool.
+ */
+ public int getHistoryStringPoolBytes() {
+ return mNumHistoryTagChars;
+ }
+
+ /**
+ * Returns the string held by the requested history tag.
+ */
+ public String getHistoryTagPoolString(int index) {
+ ensureHistoryTagArray();
+ HistoryTag historyTag = mHistoryTags.get(index);
+ return historyTag != null ? historyTag.string : null;
+ }
+
+ /**
+ * Returns the UID held by the requested history tag.
+ */
+ public int getHistoryTagPoolUid(int index) {
+ ensureHistoryTagArray();
+ HistoryTag historyTag = mHistoryTags.get(index);
+ return historyTag != null ? historyTag.uid : Process.INVALID_UID;
+ }
+
+ private void ensureHistoryTagArray() {
+ if (mHistoryTags != null) {
+ return;
+ }
+
+ mHistoryTags = new SparseArray<>(mHistoryTagPool.size());
+ for (Map.Entry<HistoryTag, Integer> entry : mHistoryTagPool.entrySet()) {
+ mHistoryTags.put(entry.getValue() & ~BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG,
+ entry.getKey());
+ }
+ }
}
diff --git a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
index de8b414c4b78..1bf878cb9119 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
@@ -36,7 +36,6 @@ public class BatteryStatsHistoryIterator {
public BatteryStatsHistoryIterator(@NonNull BatteryStatsHistory history) {
mBatteryStatsHistory = history;
- mBatteryStatsHistory.startIteratingHistory();
}
/**
@@ -231,4 +230,11 @@ public class BatteryStatsHistoryIterator {
out.batteryTemperature = (short) ((batteryLevelInt & 0x01ff8000) >>> 15);
out.batteryVoltage = (char) ((batteryLevelInt & 0x00007ffe) >>> 1);
}
+
+ /**
+ * Should be called when iteration is complete.
+ */
+ public void close() {
+ mBatteryStatsHistory.finishIteratingHistory();
+ }
}
diff --git a/core/java/com/android/internal/os/BinderCallsStats.java b/core/java/com/android/internal/os/BinderCallsStats.java
index fa6fa55cbde9..0a29fc5285a5 100644
--- a/core/java/com/android/internal/os/BinderCallsStats.java
+++ b/core/java/com/android/internal/os/BinderCallsStats.java
@@ -1169,15 +1169,7 @@ public class BinderCallsStats implements BinderInternal.Observer {
}
- /** @hide */
- public static void startForWifi(Context context) {
- new BinderCallsStats.SettingsObserver(
- context,
- new BinderCallsStats(
- new BinderCallsStats.Injector(),
- com.android.internal.os.BinderLatencyProto.Dims.WIFI));
- }
/**
* Settings observer for other processes (not system_server).
diff --git a/core/java/com/android/internal/policy/DecorContext.java b/core/java/com/android/internal/policy/DecorContext.java
index 5e34c15c42e2..134a91710c0b 100644
--- a/core/java/com/android/internal/policy/DecorContext.java
+++ b/core/java/com/android/internal/policy/DecorContext.java
@@ -137,4 +137,13 @@ public class DecorContext extends ContextThemeWrapper {
}
return false;
}
+
+ @Override
+ public boolean isConfigurationContext() {
+ Context context = mContext.get();
+ if (context != null) {
+ return context.isConfigurationContext();
+ }
+ return false;
+ }
}
diff --git a/core/java/com/android/internal/view/inline/InlineTooltipUi.java b/core/java/com/android/internal/view/inline/InlineTooltipUi.java
index 3eae89e350a0..836786d7c592 100644
--- a/core/java/com/android/internal/view/inline/InlineTooltipUi.java
+++ b/core/java/com/android/internal/view/inline/InlineTooltipUi.java
@@ -170,9 +170,9 @@ public final class InlineTooltipUi extends PopupWindow implements AutoCloseable
int delayTimeMs = mShowDelayConfigMs;
try {
- final float scale = Settings.Global.getFloat(
+ final float scale = WindowManager.fixScale(Settings.Global.getFloat(
anchor.getContext().getContentResolver(),
- Settings.Global.ANIMATOR_DURATION_SCALE);
+ Settings.Global.ANIMATOR_DURATION_SCALE));
delayTimeMs *= scale;
} catch (Settings.SettingNotFoundException e) {
// do nothing
diff --git a/core/java/com/android/internal/widget/LocalImageResolver.java b/core/java/com/android/internal/widget/LocalImageResolver.java
index b866723954b5..b11ea2961c17 100644
--- a/core/java/com/android/internal/widget/LocalImageResolver.java
+++ b/core/java/com/android/internal/widget/LocalImageResolver.java
@@ -25,6 +25,7 @@ import android.graphics.ImageDecoder;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.net.Uri;
+import android.text.TextUtils;
import android.util.Log;
import android.util.Size;
@@ -108,6 +109,12 @@ public class LocalImageResolver {
}
break;
case Icon.TYPE_RESOURCE:
+ if (!(TextUtils.isEmpty(icon.getResPackage())
+ || context.getPackageName().equals(icon.getResPackage()))) {
+ // We can't properly resolve icons from other packages here, so fall back.
+ return icon.loadDrawable(context);
+ }
+
Drawable result = resolveImage(icon.getResId(), context, maxWidth, maxHeight);
if (result != null) {
return tintDrawable(icon, result);
diff --git a/core/java/com/android/internal/widget/floatingtoolbar/RemoteFloatingToolbarPopup.java b/core/java/com/android/internal/widget/floatingtoolbar/RemoteFloatingToolbarPopup.java
index 8c2eb1044e01..8787c39458b9 100644
--- a/core/java/com/android/internal/widget/floatingtoolbar/RemoteFloatingToolbarPopup.java
+++ b/core/java/com/android/internal/widget/floatingtoolbar/RemoteFloatingToolbarPopup.java
@@ -23,6 +23,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UiThread;
import android.content.Context;
+import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
@@ -107,6 +108,7 @@ public final class RemoteFloatingToolbarPopup implements FloatingToolbarPopup {
private int mSuggestedWidth;
private final Rect mScreenViewPort = new Rect();
private boolean mWidthChanged = true;
+ private final boolean mIsLightTheme;
private final int[] mCoordsOnScreen = new int[2];
private final int[] mCoordsOnWindow = new int[2];
@@ -116,9 +118,17 @@ public final class RemoteFloatingToolbarPopup implements FloatingToolbarPopup {
mPopupWindow = createPopupWindow(context);
mSelectionToolbarManager = context.getSystemService(SelectionToolbarManager.class);
mSelectionToolbarCallback = new SelectionToolbarCallbackImpl(this);
+ mIsLightTheme = isLightTheme(context);
mFloatingToolbarToken = NO_TOOLBAR_ID;
}
+ private boolean isLightTheme(Context context) {
+ TypedArray a = context.obtainStyledAttributes(new int[]{R.attr.isLightTheme});
+ boolean isLightTheme = a.getBoolean(0, true);
+ a.recycle();
+ return isLightTheme;
+ }
+
@UiThread
@Override
public void show(List<MenuItem> menuItems,
@@ -155,7 +165,7 @@ public final class RemoteFloatingToolbarPopup implements FloatingToolbarPopup {
contentRect,
suggestWidth,
mScreenViewPort,
- mParent.getViewRootImpl().getInputToken());
+ mParent.getViewRootImpl().getInputToken(), mIsLightTheme);
if (DEBUG) {
Log.v(FloatingToolbar.FLOATING_TOOLBAR_TAG,
"RemoteFloatingToolbarPopup.show() for " + showInfo);
diff --git a/core/jni/OWNERS b/core/jni/OWNERS
index ff97ab007efe..671e63493323 100644
--- a/core/jni/OWNERS
+++ b/core/jni/OWNERS
@@ -68,7 +68,7 @@ per-file com_android_internal_net_* = file:/services/core/java/com/android/serve
### Graphics ###
per-file android_graphics_* = file:/graphics/java/android/graphics/OWNERS
-per-file android_hardware_HardwareBuffer.cpp = 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_os_GraphicsEnvironment.cpp = file:platform/frameworks/native:/opengl/OWNERS
diff --git a/core/jni/android_hardware_HardwareBuffer.cpp b/core/jni/android_hardware_HardwareBuffer.cpp
index f462523c6ce4..5fcc46ee1499 100644
--- a/core/jni/android_hardware_HardwareBuffer.cpp
+++ b/core/jni/android_hardware_HardwareBuffer.cpp
@@ -163,7 +163,7 @@ static jint android_hardware_HardwareBuffer_getLayers(JNIEnv* env,
static jlong android_hardware_HardwareBuffer_getUsage(JNIEnv* env,
jobject clazz, jlong nativeObject) {
GraphicBuffer* buffer = GraphicBufferWrapper_to_GraphicBuffer(nativeObject);
- return AHardwareBuffer_convertFromGrallocUsageBits(buffer->getUsage());
+ return static_cast<jlong>(AHardwareBuffer_convertFromGrallocUsageBits(buffer->getUsage()));
}
static jlong android_hardware_HardwareBuffer_estimateSize(jlong nativeObject) {
@@ -177,7 +177,12 @@ static jlong android_hardware_HardwareBuffer_estimateSize(jlong nativeObject) {
const uint32_t bufferStride =
buffer->getStride() > 0 ? buffer->getStride() : buffer->getWidth();
- return static_cast<jlong>(buffer->getHeight() * bufferStride * bpp);
+ return static_cast<jlong>(static_cast<uint64_t>(buffer->getHeight() * bufferStride * bpp));
+}
+
+static jlong android_hardware_HardwareBuffer_getId(jlong nativeObject) {
+ GraphicBuffer* buffer = GraphicBufferWrapper_to_GraphicBuffer(nativeObject);
+ return static_cast<jlong>(buffer->getId());
}
// ----------------------------------------------------------------------------
@@ -223,16 +228,6 @@ AHardwareBuffer* android_hardware_HardwareBuffer_getNativeHardwareBuffer(
}
}
-GraphicBuffer* android_hardware_HardwareBuffer_getNativeGraphicBuffer(
- JNIEnv* env, jobject hardwareBufferObj) {
- if (env->IsInstanceOf(hardwareBufferObj, gHardwareBufferClassInfo.clazz)) {
- return GraphicBufferWrapper_to_GraphicBuffer(
- env->GetLongField(hardwareBufferObj, gHardwareBufferClassInfo.mNativeObject));
- } else {
- return nullptr;
- }
-}
-
jobject android_hardware_HardwareBuffer_createFromAHardwareBuffer(
JNIEnv* env, AHardwareBuffer* hardwareBuffer) {
GraphicBuffer* buffer = AHardwareBuffer_to_GraphicBuffer(hardwareBuffer);
@@ -295,6 +290,7 @@ static const JNINativeMethod gMethods[] = {
// --------------- @CriticalNative ----------------------
{ "nEstimateSize", "(J)J", (void*) android_hardware_HardwareBuffer_estimateSize },
+ { "nGetId", "(J)J", (void*) android_hardware_HardwareBuffer_getId },
};
// clang-format on
diff --git a/core/jni/include/android_runtime/android_hardware_HardwareBuffer.h b/core/jni/include/android_runtime/android_hardware_HardwareBuffer.h
index dfd80359e169..964c28f0a49f 100644
--- a/core/jni/include/android_runtime/android_hardware_HardwareBuffer.h
+++ b/core/jni/include/android_runtime/android_hardware_HardwareBuffer.h
@@ -28,10 +28,6 @@ namespace android {
extern AHardwareBuffer* android_hardware_HardwareBuffer_getNativeHardwareBuffer(
JNIEnv* env, jobject hardwareBufferObj);
-/* Gets the underlying GraphicBuffer for a HardwareBuffer. */
-extern GraphicBuffer* android_hardware_HardwareBuffer_getNativeGraphicBuffer(
- JNIEnv* env, jobject hardwareBufferObj);
-
/* Returns a HardwareBuffer wrapper for the underlying AHardwareBuffer. */
extern jobject android_hardware_HardwareBuffer_createFromAHardwareBuffer(
JNIEnv* env, AHardwareBuffer* hardwareBuffer);
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index 322354b5ad60..789ceffefbe9 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -89,6 +89,14 @@ message SecureSettingsProto {
// Setting for accessibility magnification for following typing.
optional SettingProto accessibility_magnification_follow_typing_enabled = 43 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto accessibility_software_cursor_enabled = 44 [ (android.privacy).dest = DEST_AUTOMATIC ];
+
+ message SoftwareCursorSettings {
+ optional SettingProto trigger_hints_enabled = 1 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto keyboard_shift_enabled = 2 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ }
+
+ optional SoftwareCursorSettings accessibility_software_cursor_settings = 45 [ (android.privacy).dest = DEST_AUTOMATIC ];
+
}
optional Accessibility accessibility = 2;
diff --git a/core/proto/android/server/peopleservice.proto b/core/proto/android/server/peopleservice.proto
index c465233036c4..a96ec41d3708 100644
--- a/core/proto/android/server/peopleservice.proto
+++ b/core/proto/android/server/peopleservice.proto
@@ -62,7 +62,10 @@ message ConversationInfoProto {
// The timestamp of the last event in millis.
optional int64 last_event_timestamp = 9;
- // Next tag: 10
+ // The timestamp this conversation was created in millis.
+ optional int64 creation_timestamp = 10;
+
+ // Next tag: 11
}
// On disk data of events.
diff --git a/core/proto/android/server/vibrator/vibratormanagerservice.proto b/core/proto/android/server/vibrator/vibratormanagerservice.proto
index 2a625b027c17..25a1f68a0afe 100644
--- a/core/proto/android/server/vibrator/vibratormanagerservice.proto
+++ b/core/proto/android/server/vibrator/vibratormanagerservice.proto
@@ -86,7 +86,7 @@ message VibrationAttributesProto {
optional int32 flags = 3;
}
-// Next id: 8
+// Next Tag: 9
message VibrationProto {
option (.android.msg_privacy).dest = DEST_AUTOMATIC;
optional int64 start_time = 1;
@@ -94,11 +94,43 @@ message VibrationProto {
optional CombinedVibrationEffectProto effect = 3;
optional CombinedVibrationEffectProto original_effect = 4;
optional VibrationAttributesProto attributes = 5;
- optional int32 status = 6;
optional int64 duration_ms = 7;
+ optional Status status = 8;
+ reserved 6; // prev int32 status
+
+ // Also used by VibrationReported from frameworks/proto_logging/stats/atoms.proto.
+ // Next Tag: 26
+ enum Status {
+ UNKNOWN = 0;
+ RUNNING = 1;
+ FINISHED = 2;
+ FINISHED_UNEXPECTED = 3; // Didn't terminate in the usual way.
+ FORWARDED_TO_INPUT_DEVICES = 4;
+ CANCELLED_BINDER_DIED = 5;
+ CANCELLED_BY_SCREEN_OFF = 6;
+ CANCELLED_BY_SETTINGS_UPDATE = 7;
+ CANCELLED_BY_USER = 8;
+ CANCELLED_BY_UNKNOWN_REASON = 9;
+ CANCELLED_SUPERSEDED = 10;
+ IGNORED_ERROR_APP_OPS = 11;
+ IGNORED_ERROR_CANCELLING = 12;
+ IGNORED_ERROR_SCHEDULING = 13;
+ IGNORED_ERROR_TOKEN= 14;
+ IGNORED_APP_OPS = 15;
+ IGNORED_BACKGROUND = 16;
+ IGNORED_UNKNOWN_VIBRATION = 17;
+ IGNORED_UNSUPPORTED = 18;
+ IGNORED_FOR_EXTERNAL = 19;
+ IGNORED_FOR_HIGHER_IMPORTANCE = 20;
+ IGNORED_FOR_ONGOING = 21;
+ IGNORED_FOR_POWER = 22;
+ IGNORED_FOR_RINGER_MODE = 23;
+ IGNORED_FOR_SETTINGS = 24;
+ IGNORED_SUPERSEDED = 25;
+ }
}
-// Next id: 25
+// Next Tag: 25
message VibratorManagerServiceDumpProto {
option (.android.msg_privacy).dest = DEST_AUTOMATIC;
repeated int32 vibrator_ids = 1;
diff --git a/core/res/res/color/system_bar_background_semi_transparent.xml b/core/res/res/color/system_bar_background_semi_transparent.xml
new file mode 100644
index 000000000000..839d58ac4bff
--- /dev/null
+++ b/core/res/res/color/system_bar_background_semi_transparent.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:color="@color/system_neutral2_900" android:alpha="0.5" />
+</selector>
diff --git a/core/res/res/drawable/bilingual_language_item_selection_indicator.xml b/core/res/res/drawable/bilingual_language_item_selection_indicator.xml
new file mode 100644
index 000000000000..78f26cc1dce4
--- /dev/null
+++ b/core/res/res/drawable/bilingual_language_item_selection_indicator.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp" android:height="24dp"
+ android:viewportWidth="24" android:viewportHeight="24">
+ <path android:fillColor="@color/language_picker_item_selected_indicator"
+ android:pathData="M9.55,18l-5.7,-5.7 1.425,-1.425L9.55,15.15l9.175,-9.175L20.15,7.4z"/>
+</vector> \ No newline at end of file
diff --git a/core/res/res/drawable/language_picker_item_bg_selected.xml b/core/res/res/drawable/language_picker_item_bg_selected.xml
new file mode 100644
index 000000000000..ef9a8e78cd10
--- /dev/null
+++ b/core/res/res/drawable/language_picker_item_bg_selected.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <item
+ android:bottom="-5dp"
+ android:right="-5dp"
+ android:top="-5dp">
+ <shape android:shape="rectangle" >
+ <solid android:color="@color/language_picker_item_selected_bg" />
+
+ <stroke
+ android:width="2dp"
+ android:color="@color/language_picker_item_selected_stroke" />
+ </shape>
+ </item>
+
+</layer-list> \ No newline at end of file
diff --git a/core/res/res/drawable/language_picker_item_text_color2_selector.xml b/core/res/res/drawable/language_picker_item_text_color2_selector.xml
new file mode 100644
index 000000000000..624ae2924f95
--- /dev/null
+++ b/core/res/res/drawable/language_picker_item_text_color2_selector.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_selected="true"
+ android:color="@color/language_picker_item_text_color_secondary_selected" />
+ <item android:color="@color/language_picker_item_text_color_secondary" />
+</selector> \ No newline at end of file
diff --git a/core/res/res/drawable/language_picker_item_text_color_selector.xml b/core/res/res/drawable/language_picker_item_text_color_selector.xml
new file mode 100644
index 000000000000..8d419f8f3326
--- /dev/null
+++ b/core/res/res/drawable/language_picker_item_text_color_selector.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_selected="true"
+ android:color="@color/language_picker_item_text_color_selected" />
+ <item android:color="@color/language_picker_item_text_color" />
+</selector> \ No newline at end of file
diff --git a/core/res/res/layout/language_picker_bilingual_item.xml b/core/res/res/layout/language_picker_bilingual_item.xml
index f56dda9fd1db..def0cccf38ae 100644
--- a/core/res/res/layout/language_picker_bilingual_item.xml
+++ b/core/res/res/layout/language_picker_bilingual_item.xml
@@ -1,20 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout
+<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/frame"
+ android:id="@+id/language_item"
android:layout_height="wrap_content"
android:layout_width="match_parent"
- android:paddingTop="8dp"
- android:paddingBottom="8dp"
+ android:paddingTop="12dp"
+ android:paddingBottom="12dp"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
- android:orientation="vertical">
+ >
+
+<LinearLayout
+ android:id="@+id/frame"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:orientation="vertical"
+ >
<TextView
android:id="@+id/locale_native"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:textColor="@color/language_picker_item_text_color"
+ android:textColor="@drawable/language_picker_item_text_color_selector"
android:textSize="18sp"
android:textAppearance="?android:attr/textAppearanceListItem"
/>
@@ -23,9 +30,20 @@
android:id="@+id/locale_secondary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:textColor="@color/language_picker_item_text_color_secondary"
+ android:textColor="@drawable/language_picker_item_text_color2_selector"
android:textSize="16sp"
android:textAppearance="?android:attr/textAppearanceListItem"
/>
+</LinearLayout>
-</LinearLayout> \ No newline at end of file
+<ImageView
+ android:id="@+id/indicator"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentEnd="true"
+ android:adjustViewBounds="true"
+ android:layout_marginEnd="10dp"
+ android:layout_centerVertical="true"
+ android:src="@drawable/bilingual_language_item_selection_indicator"
+ android:visibility="gone" />
+</RelativeLayout> \ No newline at end of file
diff --git a/core/res/res/layout/side_fps_toast.xml b/core/res/res/layout/side_fps_toast.xml
index 58b8cc9438ae..96860b050393 100644
--- a/core/res/res/layout/side_fps_toast.xml
+++ b/core/res/res/layout/side_fps_toast.xml
@@ -20,13 +20,14 @@
android:layout_height="wrap_content"
android:minWidth="350dp"
android:layout_gravity="center"
- android:theme="?attr/alertDialogTheme">
+ android:background="@color/side_fps_toast_background">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/fp_power_button_enrollment_title"
android:singleLine="true"
android:ellipsize="end"
+ android:textColor="@color/side_fps_text_color"
android:paddingLeft="20dp"/>
<Space
android:layout_width="wrap_content"
@@ -39,5 +40,6 @@
android:text="@string/fp_power_button_enrollment_button_text"
android:paddingRight="20dp"
style="?android:attr/buttonBarNegativeButtonStyle"
+ android:textColor="@color/side_fps_button_color"
android:maxLines="1"/>
</LinearLayout> \ No newline at end of file
diff --git a/core/res/res/values-night/colors.xml b/core/res/res/values-night/colors.xml
index e2db49cfa657..d3f998fb70cf 100644
--- a/core/res/res/values-night/colors.xml
+++ b/core/res/res/values-night/colors.xml
@@ -41,4 +41,14 @@
<!-- Lily Language Picker language item view colors -->
<color name="language_picker_item_text_color">#F1F3F4</color>
<color name="language_picker_item_text_color_secondary">#BDC1C6</color>
+ <color name="language_picker_item_text_color_selected">#202124</color>
+ <color name="language_picker_item_text_color_secondary_selected">#202124</color>
+ <color name="language_picker_item_selected_indicator">#202124</color>
+ <color name="language_picker_item_selected_bg">#92B3F2</color>
+ <color name="language_picker_item_selected_stroke">#185ABC</color>
+
+ <!-- Color for side fps toast dark theme-->
+ <color name="side_fps_toast_background">#2E3132</color>
+ <color name="side_fps_text_color">#EFF1F2</color>
+ <color name="side_fps_button_color">#33B9DB</color>
</resources>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index d43a6c59a477..c7153fcf8609 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -5542,22 +5542,22 @@
ignores some hyphen character related typographic features, e.g. kerning. -->
<enum name="fullFast" value="4" />
</attr>
- <!-- Indicates the line break strategies can be used when calculating the text wrapping. -->
+ <!-- Specifies the line-break strategies for text wrapping. -->
<attr name="lineBreakStyle">
- <!-- No line break style specific. -->
+ <!-- No line-break rules are used for line breaking. -->
<enum name="none" value="0" />
- <!-- Use the least restrictive rule for line-breaking. -->
+ <!-- The least restrictive line-break rules are used for line breaking. -->
<enum name="loose" value="1" />
- <!-- Indicates breaking text with the most comment set of line-breaking rules. -->
+ <!-- The most common line-break rules are used for line breaking. -->
<enum name="normal" value="2" />
- <!-- Indicates breaking text with the most strictest line-breaking rules. -->
+ <!-- The most strict line-break rules are used for line breaking. -->
<enum name="strict" value="3" />
</attr>
- <!-- Specify the phrase-based line break can be used when calculating the text wrapping.-->
+ <!-- Specifies the line-break word strategies for text wrapping.-->
<attr name="lineBreakWordStyle">
- <!-- No line break word style specific. -->
+ <!-- No line-break word style is used for line breaking. -->
<enum name="none" value="0" />
- <!-- Specify the phrase based breaking. -->
+ <!-- Line breaking is based on phrases, which results in text wrapping only on meaningful words. -->
<enum name="phrase" value="1" />
</attr>
<!-- Specify the type of auto-size. Note that this feature is not supported by EditText,
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 062523ed5f23..b5c7ea6bf4c5 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -2175,6 +2175,11 @@
tag; often this is one of the {@link android.Manifest.permission standard
system permissions}. -->
<attr name="name" />
+ <!-- Optional: specify the minimum version of the Android OS for which the
+ application wishes to request the permission. When running on a version
+ of Android lower than the number given here, the permission will not
+ be requested. -->
+ <attr name="minSdkVersion" format="integer|string" />
<!-- Optional: specify the maximum version of the Android OS for which the
application wishes to request the permission. When running on a version
of Android higher than the number given here, the permission will not
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index faf48f3c3fdc..77d7c43cd3d2 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -211,9 +211,6 @@
<color name="Red_700">#ffc53929</color>
<color name="Red_800">#ffb93221</color>
- <!-- Status bar color for semi transparent mode. -->
- <color name="system_bar_background_semi_transparent">#66000000</color> <!-- 40% black -->
-
<color name="resize_shadow_start_color">#2a000000</color>
<color name="resize_shadow_end_color">#00000000</color>
@@ -454,4 +451,14 @@
<!-- Lily Language Picker language item view colors -->
<color name="language_picker_item_text_color">#202124</color>
<color name="language_picker_item_text_color_secondary">#5F6368</color>
+ <color name="language_picker_item_text_color_selected">#202124</color>
+ <color name="language_picker_item_text_color_secondary_selected">#5F6368</color>
+ <color name="language_picker_item_selected_indicator">#4285F4</color>
+ <color name="language_picker_item_selected_bg">#E8F0FE</color>
+ <color name="language_picker_item_selected_stroke">#4C8DF6</color>
+
+ <!-- Color for side fps toast light theme -->
+ <color name="side_fps_toast_background">#F7F9FA</color>
+ <color name="side_fps_text_color">#191C1D</color>
+ <color name="side_fps_button_color">#00677E</color>
</resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 9faf5e85c31f..9890614c010c 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2000,6 +2000,18 @@
are controlled together (aliasing is true) or not. -->
<bool name="config_alias_ring_notif_stream_types">true</bool>
+ <!-- The number of volume steps for the notification stream -->
+ <integer name="config_audio_notif_vol_steps">7</integer>
+
+ <!-- The default volume for the notification stream -->
+ <integer name="config_audio_notif_vol_default">5</integer>
+
+ <!-- The number of volume steps for the ring stream -->
+ <integer name="config_audio_ring_vol_steps">7</integer>
+
+ <!-- The default volume for the ring stream -->
+ <integer name="config_audio_ring_vol_default">5</integer>
+
<!-- Flag indicating whether platform level volume adjustments are enabled for remote sessions
on grouped devices. -->
<bool name="config_volumeAdjustmentForRemoteGroupSessions">true</bool>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index d3d049382fd0..b7da6ae31ce3 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -6321,6 +6321,8 @@ ul.</string>
<string name="vdm_camera_access_denied" product="default">Can’t access the phone’s camera from your <xliff:g id="device" example="Chromebook">%1$s</xliff:g></string>
<!-- Error message indicating the camera cannot be accessed when running on a virtual device. [CHAR LIMIT=NONE] -->
<string name="vdm_camera_access_denied" product="tablet">Can’t access the tablet’s camera from your <xliff:g id="device" example="Chromebook">%1$s</xliff:g></string>
+ <!-- Error message indicating the user cannot access secure content when running on a virtual device. [CHAR LIMIT=NONE] -->
+ <string name="vdm_secure_window">This can’t be accessed while streaming. Try on your phone instead.</string>
<!-- Title for preference of the system default locale. [CHAR LIMIT=50]-->
<string name="system_locale_title">System default</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 51712ff5e35f..0654fff661bd 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -97,10 +97,12 @@
<java-symbol type="id" name="icon" />
<java-symbol type="id" name="image" />
<java-symbol type="id" name="increment" />
+ <java-symbol type="id" name="indicator" />
<java-symbol type="id" name="internalEmpty" />
<java-symbol type="id" name="inputExtractAccessories" />
<java-symbol type="id" name="inputExtractAction" />
<java-symbol type="id" name="issued_on" />
+ <java-symbol type="id" name="language_item" />
<java-symbol type="id" name="left_icon" />
<java-symbol type="id" name="leftSpacer" />
<java-symbol type="id" name="line1" />
@@ -276,6 +278,10 @@
<java-symbol type="bool" name="action_bar_embed_tabs" />
<java-symbol type="bool" name="action_bar_expanded_action_views_exclusive" />
<java-symbol type="bool" name="config_alias_ring_notif_stream_types" />
+ <java-symbol type="integer" name="config_audio_notif_vol_default" />
+ <java-symbol type="integer" name="config_audio_notif_vol_steps" />
+ <java-symbol type="integer" name="config_audio_ring_vol_default" />
+ <java-symbol type="integer" name="config_audio_ring_vol_steps" />
<java-symbol type="bool" name="config_avoidGfxAccel" />
<java-symbol type="bool" name="config_bluetooth_address_validation" />
<java-symbol type="integer" name="config_chooser_max_targets_per_row" />
@@ -3122,6 +3128,12 @@
<java-symbol type="id" name="locale_search_menu" />
<java-symbol type="layout" name="language_picker_item" />
<java-symbol type="layout" name="language_picker_bilingual_item" />
+ <java-symbol type="color" name="language_picker_item_text_color" />
+ <java-symbol type="color" name="language_picker_item_text_color_selected" />
+ <java-symbol type="color" name="language_picker_item_text_color_secondary_selected" />
+ <java-symbol type="drawable" name="language_picker_item_text_color_selector" />
+ <java-symbol type="drawable" name="language_picker_item_text_color2_selector" />
+ <java-symbol type="drawable" name="language_picker_item_bg_selected" />
<java-symbol type="layout" name="language_picker_section_header" />
<java-symbol type="layout" name="language_picker_bilingual_section_header" />
<java-symbol type="menu" name="language_selection_list" />
@@ -4791,6 +4803,7 @@
<!-- For VirtualDeviceManager -->
<java-symbol type="string" name="vdm_camera_access_denied" />
+ <java-symbol type="string" name="vdm_secure_window" />
<java-symbol type="color" name="camera_privacy_light_day"/>
<java-symbol type="color" name="camera_privacy_light_night"/>
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index ed6a649021a1..cc68fcee9f32 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -236,6 +236,21 @@ public class InsetsControllerTest {
}
@Test
+ public void testSystemDrivenInsetsAnimationLoggingListener_onReady() {
+ prepareControls();
+ // only the original thread that created view hierarchy can touch its views
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ WindowInsetsAnimationControlListener loggingListener =
+ mock(WindowInsetsAnimationControlListener.class);
+ mController.setSystemDrivenInsetsAnimationLoggingListener(loggingListener);
+ mController.getSourceConsumer(ITYPE_IME).onWindowFocusGained(true);
+ // since there is no focused view, forcefully make IME visible.
+ mController.show(Type.ime(), true /* fromIme */);
+ verify(loggingListener).onReady(notNull(), anyInt());
+ });
+ }
+
+ @Test
public void testAnimationEndState() {
InsetsSourceControl[] controls = prepareControls();
InsetsSourceControl navBar = controls[0];
@@ -914,6 +929,23 @@ public class InsetsControllerTest {
});
}
+ @Test
+ public void testImeRequestedVisibleWhenImeNotControllable() {
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ // Simulate IME insets is not controllable
+ mController.onControlsChanged(new InsetsSourceControl[0]);
+ final InsetsSourceConsumer imeInsetsConsumer = mController.getSourceConsumer(ITYPE_IME);
+ assertNull(imeInsetsConsumer.getControl());
+
+ // Verify IME requested visibility should be updated to IME consumer from controller.
+ mController.show(ime());
+ assertTrue(imeInsetsConsumer.isRequestedVisible());
+
+ mController.hide(ime());
+ assertFalse(imeInsetsConsumer.isRequestedVisible());
+ });
+ }
+
private void waitUntilNextFrame() throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
Choreographer.getMainThreadInstance().postCallback(Choreographer.CALLBACK_COMMIT,
diff --git a/core/tests/coretests/src/android/view/PendingInsetsControllerTest.java b/core/tests/coretests/src/android/view/PendingInsetsControllerTest.java
index 03c8b1bcb475..690b35879388 100644
--- a/core/tests/coretests/src/android/view/PendingInsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/PendingInsetsControllerTest.java
@@ -213,6 +213,25 @@ public class PendingInsetsControllerTest {
}
@Test
+ public void testSystemDrivenInsetsAnimationLoggingListener() {
+ WindowInsetsAnimationControlListener listener =
+ mock(WindowInsetsAnimationControlListener.class);
+ mPendingInsetsController.setSystemDrivenInsetsAnimationLoggingListener(listener);
+ mPendingInsetsController.replayAndAttach(mReplayedController);
+ verify(mReplayedController).setSystemDrivenInsetsAnimationLoggingListener(eq(listener));
+ }
+
+ @Test
+ public void testSystemDrivenInsetsAnimationLoggingListener_direct() {
+ mPendingInsetsController.replayAndAttach(mReplayedController);
+ WindowInsetsAnimationControlListener listener =
+ mock(WindowInsetsAnimationControlListener.class);
+ mPendingInsetsController.setSystemDrivenInsetsAnimationLoggingListener(listener);
+ verify(mReplayedController).setSystemDrivenInsetsAnimationLoggingListener(
+ eq(listener));
+ }
+
+ @Test
public void testDetachReattach() {
mPendingInsetsController.show(systemBars());
mPendingInsetsController.setSystemBarsBehavior(BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
diff --git a/core/tests/coretests/src/com/android/internal/widget/LocalImageResolverTest.java b/core/tests/coretests/src/com/android/internal/widget/LocalImageResolverTest.java
index c63d18bfa531..0cee526651a6 100644
--- a/core/tests/coretests/src/com/android/internal/widget/LocalImageResolverTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/LocalImageResolverTest.java
@@ -270,4 +270,13 @@ public class LocalImageResolverTest {
assertThat(bd.getBitmap().getHeight()).isEqualTo(originalHeight);
}
+
+ @Test
+ public void resolveImage_iconWithOtherPackageResource_usesPackageContextDefinition()
+ throws IOException {
+ Icon icon = Icon.createWithResource("this_is_invalid", R.drawable.test32x24);
+ Drawable d = LocalImageResolver.resolveImage(icon, mContext);
+ // This drawable must not be loaded - if it was, the code ignored the package specification.
+ assertThat(d).isNull();
+ }
}
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index 857af11e4ca3..318cd32d19fe 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -277,7 +277,7 @@ public final class Bitmap implements Parcelable {
* @see #setHeight(int)
* @see #setConfig(Config)
*/
- public void reconfigure(int width, int height, Config config) {
+ public void reconfigure(int width, int height, @NonNull Config config) {
checkRecycled("Can't call reconfigure() on a recycled bitmap");
if (width <= 0 || height <= 0) {
throw new IllegalArgumentException("width and height must be > 0");
@@ -336,7 +336,7 @@ public final class Bitmap implements Parcelable {
* @see #setWidth(int)
* @see #setHeight(int)
*/
- public void setConfig(Config config) {
+ public void setConfig(@NonNull Config config) {
reconfigure(getWidth(), getHeight(), config);
}
@@ -590,7 +590,7 @@ public final class Bitmap implements Parcelable {
* in the buffer.</p>
* @throws IllegalStateException if the bitmap's config is {@link Config#HARDWARE}
*/
- public void copyPixelsToBuffer(Buffer dst) {
+ public void copyPixelsToBuffer(@NonNull Buffer dst) {
checkHardware("unable to copyPixelsToBuffer, "
+ "pixel access is not supported on Config#HARDWARE bitmaps");
int elements = dst.remaining();
@@ -632,7 +632,7 @@ public final class Bitmap implements Parcelable {
* first rewind the buffer.</p>
* @throws IllegalStateException if the bitmap's config is {@link Config#HARDWARE}
*/
- public void copyPixelsFromBuffer(Buffer src) {
+ public void copyPixelsFromBuffer(@NonNull Buffer src) {
checkRecycled("copyPixelsFromBuffer called on recycled bitmap");
checkHardware("unable to copyPixelsFromBuffer, Config#HARDWARE bitmaps are immutable");
@@ -686,7 +686,7 @@ public final class Bitmap implements Parcelable {
* @return the new bitmap, or null if the copy could not be made.
* @throws IllegalArgumentException if config is {@link Config#HARDWARE} and isMutable is true
*/
- public Bitmap copy(Config config, boolean isMutable) {
+ public Bitmap copy(@NonNull Config config, boolean isMutable) {
checkRecycled("Can't copy a recycled bitmap");
if (config == Config.HARDWARE && isMutable) {
throw new IllegalArgumentException("Hardware bitmaps are always immutable");
@@ -791,6 +791,7 @@ public final class Bitmap implements Parcelable {
* @return The new scaled bitmap or the source bitmap if no scaling is required.
* @throws IllegalArgumentException if width is <= 0, or height is <= 0
*/
+ @NonNull
public static Bitmap createScaledBitmap(@NonNull Bitmap src, int dstWidth, int dstHeight,
boolean filter) {
Matrix m = new Matrix();
@@ -810,6 +811,7 @@ public final class Bitmap implements Parcelable {
* be the same object as source, or a copy may have been made. It is
* initialized with the same density and color space as the original bitmap.
*/
+ @NonNull
public static Bitmap createBitmap(@NonNull Bitmap src) {
return createBitmap(src, 0, 0, src.getWidth(), src.getHeight());
}
@@ -830,6 +832,7 @@ public final class Bitmap implements Parcelable {
* outside of the dimensions of the source bitmap, or width is <= 0,
* or height is <= 0
*/
+ @NonNull
public static Bitmap createBitmap(@NonNull Bitmap source, int x, int y, int width, int height) {
return createBitmap(source, x, y, width, height, null, false);
}
@@ -865,6 +868,7 @@ public final class Bitmap implements Parcelable {
* outside of the dimensions of the source bitmap, or width is <= 0,
* or height is <= 0, or if the source bitmap has already been recycled
*/
+ @NonNull
public static Bitmap createBitmap(@NonNull Bitmap source, int x, int y, int width, int height,
@Nullable Matrix m, boolean filter) {
@@ -985,6 +989,7 @@ public final class Bitmap implements Parcelable {
* @throws IllegalArgumentException if the width or height are <= 0, or if
* Config is Config.HARDWARE, because hardware bitmaps are always immutable
*/
+ @NonNull
public static Bitmap createBitmap(int width, int height, @NonNull Config config) {
return createBitmap(width, height, config, true);
}
@@ -1003,6 +1008,7 @@ public final class Bitmap implements Parcelable {
* @throws IllegalArgumentException if the width or height are <= 0, or if
* Config is Config.HARDWARE, because hardware bitmaps are always immutable
*/
+ @NonNull
public static Bitmap createBitmap(@Nullable DisplayMetrics display, int width,
int height, @NonNull Config config) {
return createBitmap(display, width, height, config, true);
@@ -1023,6 +1029,7 @@ public final class Bitmap implements Parcelable {
* @throws IllegalArgumentException if the width or height are <= 0, or if
* Config is Config.HARDWARE, because hardware bitmaps are always immutable
*/
+ @NonNull
public static Bitmap createBitmap(int width, int height,
@NonNull Config config, boolean hasAlpha) {
return createBitmap(null, width, height, config, hasAlpha);
@@ -1050,6 +1057,7 @@ public final class Bitmap implements Parcelable {
* {@link ColorSpace.Rgb.TransferParameters ICC parametric curve}, or if
* the color space is null
*/
+ @NonNull
public static Bitmap createBitmap(int width, int height, @NonNull Config config,
boolean hasAlpha, @NonNull ColorSpace colorSpace) {
return createBitmap(null, width, height, config, hasAlpha, colorSpace);
@@ -1073,6 +1081,7 @@ public final class Bitmap implements Parcelable {
* @throws IllegalArgumentException if the width or height are <= 0, or if
* Config is Config.HARDWARE, because hardware bitmaps are always immutable
*/
+ @NonNull
public static Bitmap createBitmap(@Nullable DisplayMetrics display, int width, int height,
@NonNull Config config, boolean hasAlpha) {
return createBitmap(display, width, height, config, hasAlpha,
@@ -1105,6 +1114,7 @@ public final class Bitmap implements Parcelable {
* {@link ColorSpace.Rgb.TransferParameters ICC parametric curve}, or if
* the color space is null
*/
+ @NonNull
public static Bitmap createBitmap(@Nullable DisplayMetrics display, int width, int height,
@NonNull Config config, boolean hasAlpha, @NonNull ColorSpace colorSpace) {
if (width <= 0 || height <= 0) {
@@ -1152,6 +1162,7 @@ public final class Bitmap implements Parcelable {
* @throws IllegalArgumentException if the width or height are <= 0, or if
* the color array's length is less than the number of pixels.
*/
+ @NonNull
public static Bitmap createBitmap(@NonNull @ColorInt int[] colors, int offset, int stride,
int width, int height, @NonNull Config config) {
return createBitmap(null, colors, offset, stride, width, height, config);
@@ -1179,6 +1190,7 @@ public final class Bitmap implements Parcelable {
* @throws IllegalArgumentException if the width or height are <= 0, or if
* the color array's length is less than the number of pixels.
*/
+ @NonNull
public static Bitmap createBitmap(@NonNull DisplayMetrics display,
@NonNull @ColorInt int[] colors, int offset, int stride,
int width, int height, @NonNull Config config) {
@@ -1221,6 +1233,7 @@ public final class Bitmap implements Parcelable {
* @throws IllegalArgumentException if the width or height are <= 0, or if
* the color array's length is less than the number of pixels.
*/
+ @NonNull
public static Bitmap createBitmap(@NonNull @ColorInt int[] colors,
int width, int height, Config config) {
return createBitmap(null, colors, 0, width, width, height, config);
@@ -1245,6 +1258,7 @@ public final class Bitmap implements Parcelable {
* @throws IllegalArgumentException if the width or height are <= 0, or if
* the color array's length is less than the number of pixels.
*/
+ @NonNull
public static Bitmap createBitmap(@Nullable DisplayMetrics display,
@NonNull @ColorInt int colors[], int width, int height, @NonNull Config config) {
return createBitmap(display, colors, 0, width, width, height, config);
@@ -1262,7 +1276,8 @@ public final class Bitmap implements Parcelable {
* @return An immutable bitmap with a HARDWARE config whose contents are created
* from the recorded drawing commands in the Picture source.
*/
- public static @NonNull Bitmap createBitmap(@NonNull Picture source) {
+ @NonNull
+ public static Bitmap createBitmap(@NonNull Picture source) {
return createBitmap(source, source.getWidth(), source.getHeight(), Config.HARDWARE);
}
@@ -1283,7 +1298,8 @@ public final class Bitmap implements Parcelable {
*
* @return An immutable bitmap with a configuration specified by the config parameter
*/
- public static @NonNull Bitmap createBitmap(@NonNull Picture source, int width, int height,
+ @NonNull
+ public static Bitmap createBitmap(@NonNull Picture source, int width, int height,
@NonNull Config config) {
if (width <= 0 || height <= 0) {
throw new IllegalArgumentException("width & height must be > 0");
@@ -1330,6 +1346,7 @@ public final class Bitmap implements Parcelable {
* Returns an optional array of private data, used by the UI system for
* some bitmaps. Not intended to be called by applications.
*/
+ @Nullable
public byte[] getNinePatchChunk() {
return mNinePatchChunk;
}
@@ -1431,7 +1448,8 @@ public final class Bitmap implements Parcelable {
* @return true if successfully compressed to the specified stream.
*/
@WorkerThread
- public boolean compress(CompressFormat format, int quality, OutputStream stream) {
+ public boolean compress(@NonNull CompressFormat format, int quality,
+ @NonNull OutputStream stream) {
checkRecycled("Can't compress a recycled bitmap");
// do explicit check before calling the native method
if (stream == null) {
@@ -1548,7 +1566,7 @@ public final class Bitmap implements Parcelable {
* Convenience for calling {@link #getScaledWidth(int)} with the target
* density of the given {@link Canvas}.
*/
- public int getScaledWidth(Canvas canvas) {
+ public int getScaledWidth(@NonNull Canvas canvas) {
return scaleFromDensity(getWidth(), mDensity, canvas.mDensity);
}
@@ -1556,7 +1574,7 @@ public final class Bitmap implements Parcelable {
* Convenience for calling {@link #getScaledHeight(int)} with the target
* density of the given {@link Canvas}.
*/
- public int getScaledHeight(Canvas canvas) {
+ public int getScaledHeight(@NonNull Canvas canvas) {
return scaleFromDensity(getHeight(), mDensity, canvas.mDensity);
}
@@ -1564,7 +1582,7 @@ public final class Bitmap implements Parcelable {
* Convenience for calling {@link #getScaledWidth(int)} with the target
* density of the given {@link DisplayMetrics}.
*/
- public int getScaledWidth(DisplayMetrics metrics) {
+ public int getScaledWidth(@NonNull DisplayMetrics metrics) {
return scaleFromDensity(getWidth(), mDensity, metrics.densityDpi);
}
@@ -1572,7 +1590,7 @@ public final class Bitmap implements Parcelable {
* Convenience for calling {@link #getScaledHeight(int)} with the target
* density of the given {@link DisplayMetrics}.
*/
- public int getScaledHeight(DisplayMetrics metrics) {
+ public int getScaledHeight(@NonNull DisplayMetrics metrics) {
return scaleFromDensity(getHeight(), mDensity, metrics.densityDpi);
}
@@ -1682,6 +1700,7 @@ public final class Bitmap implements Parcelable {
* If the bitmap's internal config is in one of the public formats, return
* that config, otherwise return null.
*/
+ @NonNull
public final Config getConfig() {
if (mRecycled) {
Log.w(TAG, "Called getConfig() on a recycle()'d bitmap! This is undefined behavior!");
@@ -1967,7 +1986,7 @@ public final class Bitmap implements Parcelable {
* to receive the specified number of pixels.
* @throws IllegalStateException if the bitmap's config is {@link Config#HARDWARE}
*/
- public void getPixels(@ColorInt int[] pixels, int offset, int stride,
+ public void getPixels(@NonNull @ColorInt int[] pixels, int offset, int stride,
int x, int y, int width, int height) {
checkRecycled("Can't call getPixels() on a recycled bitmap");
checkHardware("unable to getPixels(), "
@@ -2084,7 +2103,7 @@ public final class Bitmap implements Parcelable {
* @throws ArrayIndexOutOfBoundsException if the pixels array is too small
* to receive the specified number of pixels.
*/
- public void setPixels(@ColorInt int[] pixels, int offset, int stride,
+ public void setPixels(@NonNull @ColorInt int[] pixels, int offset, int stride,
int x, int y, int width, int height) {
checkRecycled("Can't call setPixels() on a recycled bitmap");
if (!isMutable()) {
@@ -2098,7 +2117,7 @@ public final class Bitmap implements Parcelable {
x, y, width, height);
}
- public static final @android.annotation.NonNull Parcelable.Creator<Bitmap> CREATOR
+ public static final @NonNull Parcelable.Creator<Bitmap> CREATOR
= new Parcelable.Creator<Bitmap>() {
/**
* Rebuilds a bitmap previously stored with writeToParcel().
@@ -2134,7 +2153,7 @@ public final class Bitmap implements Parcelable {
* by the final pixel format
* @param p Parcel object to write the bitmap data into
*/
- public void writeToParcel(Parcel p, int flags) {
+ public void writeToParcel(@NonNull Parcel p, int flags) {
checkRecycled("Can't parcel a recycled bitmap");
noteHardwareBitmapSlowCall();
if (!nativeWriteToParcel(mNativePtr, mDensity, p)) {
@@ -2150,6 +2169,7 @@ public final class Bitmap implements Parcelable {
* @return new bitmap containing the alpha channel of the original bitmap.
*/
@CheckResult
+ @NonNull
public Bitmap extractAlpha() {
return extractAlpha(null, null);
}
@@ -2180,7 +2200,8 @@ public final class Bitmap implements Parcelable {
* paint that is passed to the draw call.
*/
@CheckResult
- public Bitmap extractAlpha(Paint paint, int[] offsetXY) {
+ @NonNull
+ public Bitmap extractAlpha(@Nullable Paint paint, int[] offsetXY) {
checkRecycled("Can't extractAlpha on a recycled bitmap");
long nativePaint = paint != null ? paint.getNativeInstance() : 0;
noteHardwareBitmapSlowCall();
@@ -2197,12 +2218,12 @@ public final class Bitmap implements Parcelable {
* and pixel data as this bitmap. If any of those differ, return false.
* If other is null, return false.
*/
- public boolean sameAs(Bitmap other) {
+ @WorkerThread
+ public boolean sameAs(@Nullable Bitmap other) {
+ StrictMode.noteSlowCall("sameAs compares pixel data, not expected to be fast");
checkRecycled("Can't call sameAs on a recycled bitmap!");
- noteHardwareBitmapSlowCall();
if (this == other) return true;
if (other == null) return false;
- other.noteHardwareBitmapSlowCall();
if (other.isRecycled()) {
throw new IllegalArgumentException("Can't compare to a recycled bitmap!");
}
@@ -2247,7 +2268,8 @@ public final class Bitmap implements Parcelable {
* @throws IllegalStateException if the bitmap's config is not {@link Config#HARDWARE}
* or if the bitmap has been recycled.
*/
- public @NonNull HardwareBuffer getHardwareBuffer() {
+ @NonNull
+ public HardwareBuffer getHardwareBuffer() {
checkRecycled("Can't getHardwareBuffer from a recycled bitmap");
HardwareBuffer hardwareBuffer = mHardwareBuffer == null ? null : mHardwareBuffer.get();
if (hardwareBuffer == null || hardwareBuffer.isClosed()) {
diff --git a/graphics/java/android/graphics/text/LineBreakConfig.java b/graphics/java/android/graphics/text/LineBreakConfig.java
index 7ad9aecaf6a3..48aecd61fc19 100644
--- a/graphics/java/android/graphics/text/LineBreakConfig.java
+++ b/graphics/java/android/graphics/text/LineBreakConfig.java
@@ -24,29 +24,32 @@ import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
/**
- * Indicates the strategies can be used when calculating the text wrapping.
+ * Specifies the line-break strategies for text wrapping.
*
- * See <a href="https://www.w3.org/TR/css-text-3/#line-break-property">the line-break property</a>
+ * <p>See the
+ * <a href="https://www.w3.org/TR/css-text-3/#line-break-property" class="external">
+ * line-break property</a> for more information.</p>
*/
public final class LineBreakConfig {
/**
- * No line break style specified.
+ * No line-break rules are used for line breaking.
*/
public static final int LINE_BREAK_STYLE_NONE = 0;
/**
- * Use the least restrictive rule for line-breaking. This is usually used for short lines.
+ * The least restrictive line-break rules are used for line breaking. This
+ * setting is typically used for short lines.
*/
public static final int LINE_BREAK_STYLE_LOOSE = 1;
/**
- * Indicate breaking text with the most comment set of line-breaking rules.
+ * The most common line-break rules are used for line breaking.
*/
public static final int LINE_BREAK_STYLE_NORMAL = 2;
/**
- * Indicates breaking text with the most strictest line-breaking rules.
+ * The most strict line-break rules are used for line breaking.
*/
public static final int LINE_BREAK_STYLE_STRICT = 3;
@@ -59,15 +62,17 @@ public final class LineBreakConfig {
public @interface LineBreakStyle {}
/**
- * No line break word style specified.
+ * No line-break word style is used for line breaking.
*/
public static final int LINE_BREAK_WORD_STYLE_NONE = 0;
/**
- * Indicates the line breaking is based on the phrased. This makes text wrapping only on
- * meaningful words. The support of the text wrapping word style varies depending on the
- * locales. If the locale does not support the phrase based text wrapping,
- * there will be no effect.
+ * Line breaking is based on phrases, which results in text wrapping only on
+ * meaningful words.
+ *
+ * <p>Support for this line-break word style depends on locale. If the
+ * current locale does not support phrase-based text wrapping, this setting
+ * has no effect.</p>
*/
public static final int LINE_BREAK_WORD_STYLE_PHRASE = 1;
@@ -79,7 +84,7 @@ public final class LineBreakConfig {
public @interface LineBreakWordStyle {}
/**
- * A builder for creating {@link LineBreakConfig}.
+ * A builder for creating a {@code LineBreakConfig} instance.
*/
public static final class Builder {
// The line break style for the LineBreakConfig.
@@ -95,16 +100,16 @@ public final class LineBreakConfig {
private boolean mAutoPhraseBreaking = false;
/**
- * Builder constructor with line break parameters.
+ * Builder constructor.
*/
public Builder() {
}
/**
- * Set the line break style.
+ * Sets the line-break style.
*
- * @param lineBreakStyle the new line break style.
- * @return this Builder
+ * @param lineBreakStyle The new line-break style.
+ * @return This {@code Builder}.
*/
public @NonNull Builder setLineBreakStyle(@LineBreakStyle int lineBreakStyle) {
mLineBreakStyle = lineBreakStyle;
@@ -112,10 +117,10 @@ public final class LineBreakConfig {
}
/**
- * Set the line break word style.
+ * Sets the line-break word style.
*
- * @param lineBreakWordStyle the new line break word style.
- * @return this Builder
+ * @param lineBreakWordStyle The new line-break word style.
+ * @return This {@code Builder}.
*/
public @NonNull Builder setLineBreakWordStyle(@LineBreakWordStyle int lineBreakWordStyle) {
mLineBreakWordStyle = lineBreakWordStyle;
@@ -123,7 +128,7 @@ public final class LineBreakConfig {
}
/**
- * Enable or disable the automation of {@link LINE_BREAK_WORD_STYLE_PHRASE}.
+ * Enables or disables the automation of {@link LINE_BREAK_WORD_STYLE_PHRASE}.
*
* @hide
*/
@@ -133,9 +138,9 @@ public final class LineBreakConfig {
}
/**
- * Build the {@link LineBreakConfig}
+ * Builds a {@link LineBreakConfig} instance.
*
- * @return the LineBreakConfig instance.
+ * @return The {@code LineBreakConfig} instance.
*/
public @NonNull LineBreakConfig build() {
return new LineBreakConfig(mLineBreakStyle, mLineBreakWordStyle, mAutoPhraseBreaking);
@@ -143,11 +148,12 @@ public final class LineBreakConfig {
}
/**
- * Create the LineBreakConfig instance.
+ * Creates a {@code LineBreakConfig} instance with the provided line break
+ * parameters.
*
- * @param lineBreakStyle the line break style for text wrapping.
- * @param lineBreakWordStyle the line break word style for text wrapping.
- * @return the {@link LineBreakConfig} instance.
+ * @param lineBreakStyle The line-break style for text wrapping.
+ * @param lineBreakWordStyle The line-break word style for text wrapping.
+ * @return The {@code LineBreakConfig} instance.
* @hide
*/
public static @NonNull LineBreakConfig getLineBreakConfig(@LineBreakStyle int lineBreakStyle,
@@ -185,8 +191,10 @@ public final class LineBreakConfig {
private final boolean mAutoPhraseBreaking;
/**
- * Constructor with the line break parameters.
- * Use the {@link LineBreakConfig.Builder} to create the LineBreakConfig instance.
+ * Constructor with line-break parameters.
+ *
+ * <p>Use {@link LineBreakConfig.Builder} to create the
+ * {@code LineBreakConfig} instance.</p>
*/
private LineBreakConfig(@LineBreakStyle int lineBreakStyle,
@LineBreakWordStyle int lineBreakWordStyle, boolean autoPhraseBreaking) {
@@ -196,18 +204,18 @@ public final class LineBreakConfig {
}
/**
- * Get the line break style.
+ * Gets the current line-break style.
*
- * @return The current line break style to be used for the text wrapping.
+ * @return The line-break style to be used for text wrapping.
*/
public @LineBreakStyle int getLineBreakStyle() {
return mLineBreakStyle;
}
/**
- * Get the line break word style.
+ * Gets the current line-break word style.
*
- * @return The current line break word style to be used for the text wrapping.
+ * @return The line-break word style to be used for text wrapping.
*/
public @LineBreakWordStyle int getLineBreakWordStyle() {
return mLineBreakWordStyle;
diff --git a/ktfmt_includes.txt b/ktfmt_includes.txt
new file mode 100644
index 000000000000..96da8c9c803b
--- /dev/null
+++ b/ktfmt_includes.txt
@@ -0,0 +1,9 @@
+packages/SystemUI/compose/
+packages/SystemUI/screenshot/
+packages/SystemUI/src/com/android/systemui/people/data
+packages/SystemUI/src/com/android/systemui/people/ui
+packages/SystemUI/src/com/android/systemui/keyguard/data
+packages/SystemUI/src/com/android/systemui/keyguard/dagger
+packages/SystemUI/src/com/android/systemui/keyguard/domain
+packages/SystemUI/src/com/android/systemui/keyguard/shared
+packages/SystemUI/src/com/android/systemui/keyguard/ui \ No newline at end of file
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
index d42fca244120..1335e5ea051f 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
@@ -51,28 +51,37 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
@VisibleForTesting
final Map<IBinder, TaskFragmentInfo> mFragmentInfos = new ArrayMap<>();
+ @NonNull
private final TaskFragmentCallback mCallback;
+
@VisibleForTesting
+ @Nullable
TaskFragmentAnimationController mAnimationController;
/**
* Callback that notifies the controller about changes to task fragments.
*/
interface TaskFragmentCallback {
- void onTaskFragmentAppeared(@NonNull TaskFragmentInfo taskFragmentInfo);
- void onTaskFragmentInfoChanged(@NonNull TaskFragmentInfo taskFragmentInfo);
- void onTaskFragmentVanished(@NonNull TaskFragmentInfo taskFragmentInfo);
- void onTaskFragmentParentInfoChanged(int taskId, @NonNull Configuration parentConfig);
- void onActivityReparentToTask(int taskId, @NonNull Intent activityIntent,
- @NonNull IBinder activityToken);
- void onTaskFragmentError(@Nullable TaskFragmentInfo taskFragmentInfo, int opType);
+ void onTaskFragmentAppeared(@NonNull WindowContainerTransaction wct,
+ @NonNull TaskFragmentInfo taskFragmentInfo);
+ void onTaskFragmentInfoChanged(@NonNull WindowContainerTransaction wct,
+ @NonNull TaskFragmentInfo taskFragmentInfo);
+ void onTaskFragmentVanished(@NonNull WindowContainerTransaction wct,
+ @NonNull TaskFragmentInfo taskFragmentInfo);
+ void onTaskFragmentParentInfoChanged(@NonNull WindowContainerTransaction wct,
+ int taskId, @NonNull Configuration parentConfig);
+ void onActivityReparentedToTask(@NonNull WindowContainerTransaction wct,
+ int taskId, @NonNull Intent activityIntent, @NonNull IBinder activityToken);
+ void onTaskFragmentError(@NonNull WindowContainerTransaction wct,
+ @Nullable TaskFragmentInfo taskFragmentInfo, int opType);
}
/**
* @param executor callbacks from WM Core are posted on this executor. It should be tied to the
* UI thread that all other calls into methods of this class are also on.
*/
- JetpackTaskFragmentOrganizer(@NonNull Executor executor, TaskFragmentCallback callback) {
+ JetpackTaskFragmentOrganizer(@NonNull Executor executor,
+ @NonNull TaskFragmentCallback callback) {
super(executor);
mCallback = callback;
}
@@ -147,41 +156,31 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
* @param wct WindowContainerTransaction in which the task fragment should be resized.
* @param fragmentToken token of an existing TaskFragment.
*/
- void expandTaskFragment(WindowContainerTransaction wct, IBinder fragmentToken) {
+ void expandTaskFragment(@NonNull WindowContainerTransaction wct,
+ @NonNull IBinder fragmentToken) {
resizeTaskFragment(wct, fragmentToken, new Rect());
setAdjacentTaskFragments(wct, fragmentToken, null /* secondary */, null /* splitRule */);
updateWindowingMode(wct, fragmentToken, WINDOWING_MODE_UNDEFINED);
}
/**
- * Expands an existing TaskFragment to fill parent.
- * @param fragmentToken token of an existing TaskFragment.
- */
- void expandTaskFragment(IBinder fragmentToken) {
- WindowContainerTransaction wct = new WindowContainerTransaction();
- expandTaskFragment(wct, fragmentToken);
- applyTransaction(wct);
- }
-
- /**
* Expands an Activity to fill parent by moving it to a new TaskFragment.
* @param fragmentToken token to create new TaskFragment with.
* @param activity activity to move to the fill-parent TaskFragment.
*/
- void expandActivity(IBinder fragmentToken, Activity activity) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
+ void expandActivity(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken,
+ @NonNull Activity activity) {
createTaskFragmentAndReparentActivity(
wct, fragmentToken, activity.getActivityToken(), new Rect(),
WINDOWING_MODE_UNDEFINED, activity);
- applyTransaction(wct);
}
/**
* @param ownerToken The token of the activity that creates this task fragment. It does not
* have to be a child of this task fragment, but must belong to the same task.
*/
- void createTaskFragment(WindowContainerTransaction wct, IBinder fragmentToken,
- IBinder ownerToken, @NonNull Rect bounds, @WindowingMode int windowingMode) {
+ void createTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken,
+ @NonNull IBinder ownerToken, @NonNull Rect bounds, @WindowingMode int windowingMode) {
final TaskFragmentCreationParams fragmentOptions =
createFragmentOptions(fragmentToken, ownerToken, bounds, windowingMode);
wct.createTaskFragment(fragmentOptions);
@@ -191,9 +190,9 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
* @param ownerToken The token of the activity that creates this task fragment. It does not
* have to be a child of this task fragment, but must belong to the same task.
*/
- private void createTaskFragmentAndReparentActivity(
- WindowContainerTransaction wct, IBinder fragmentToken, IBinder ownerToken,
- @NonNull Rect bounds, @WindowingMode int windowingMode, Activity activity) {
+ private void createTaskFragmentAndReparentActivity(@NonNull WindowContainerTransaction wct,
+ @NonNull IBinder fragmentToken, @NonNull IBinder ownerToken, @NonNull Rect bounds,
+ @WindowingMode int windowingMode, @NonNull Activity activity) {
createTaskFragment(wct, fragmentToken, ownerToken, bounds, windowingMode);
wct.reparentActivityToTaskFragment(fragmentToken, activity.getActivityToken());
}
@@ -202,9 +201,9 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
* @param ownerToken The token of the activity that creates this task fragment. It does not
* have to be a child of this task fragment, but must belong to the same task.
*/
- private void createTaskFragmentAndStartActivity(
- WindowContainerTransaction wct, IBinder fragmentToken, IBinder ownerToken,
- @NonNull Rect bounds, @WindowingMode int windowingMode, Intent activityIntent,
+ private void createTaskFragmentAndStartActivity(@NonNull WindowContainerTransaction wct,
+ @NonNull IBinder fragmentToken, @NonNull IBinder ownerToken, @NonNull Rect bounds,
+ @WindowingMode int windowingMode, @NonNull Intent activityIntent,
@Nullable Bundle activityOptions) {
createTaskFragment(wct, fragmentToken, ownerToken, bounds, windowingMode);
wct.startActivityInTaskFragment(fragmentToken, ownerToken, activityIntent, activityOptions);
@@ -225,8 +224,8 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
wct.setAdjacentTaskFragments(primary, secondary, adjacentParams);
}
- TaskFragmentCreationParams createFragmentOptions(IBinder fragmentToken, IBinder ownerToken,
- Rect bounds, @WindowingMode int windowingMode) {
+ TaskFragmentCreationParams createFragmentOptions(@NonNull IBinder fragmentToken,
+ @NonNull IBinder ownerToken, @NonNull Rect bounds, @WindowingMode int windowingMode) {
if (mFragmentInfos.containsKey(fragmentToken)) {
throw new IllegalArgumentException(
"There is an existing TaskFragment with fragmentToken=" + fragmentToken);
@@ -241,7 +240,7 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
.build();
}
- void resizeTaskFragment(WindowContainerTransaction wct, IBinder fragmentToken,
+ void resizeTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken,
@Nullable Rect bounds) {
if (!mFragmentInfos.containsKey(fragmentToken)) {
throw new IllegalArgumentException(
@@ -253,8 +252,8 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
wct.setBounds(mFragmentInfos.get(fragmentToken).getToken(), bounds);
}
- void updateWindowingMode(WindowContainerTransaction wct, IBinder fragmentToken,
- @WindowingMode int windowingMode) {
+ void updateWindowingMode(@NonNull WindowContainerTransaction wct,
+ @NonNull IBinder fragmentToken, @WindowingMode int windowingMode) {
if (!mFragmentInfos.containsKey(fragmentToken)) {
throw new IllegalArgumentException(
"Can't find an existing TaskFragment with fragmentToken=" + fragmentToken);
@@ -262,7 +261,8 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
wct.setWindowingMode(mFragmentInfos.get(fragmentToken).getToken(), windowingMode);
}
- void deleteTaskFragment(WindowContainerTransaction wct, IBinder fragmentToken) {
+ void deleteTaskFragment(@NonNull WindowContainerTransaction wct,
+ @NonNull IBinder fragmentToken) {
if (!mFragmentInfos.containsKey(fragmentToken)) {
throw new IllegalArgumentException(
"Can't find an existing TaskFragment with fragmentToken=" + fragmentToken);
@@ -271,60 +271,49 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
}
@Override
- public void onTaskFragmentAppeared(@NonNull TaskFragmentInfo taskFragmentInfo) {
+ public void onTaskFragmentAppeared(@NonNull WindowContainerTransaction wct,
+ @NonNull TaskFragmentInfo taskFragmentInfo) {
final IBinder fragmentToken = taskFragmentInfo.getFragmentToken();
mFragmentInfos.put(fragmentToken, taskFragmentInfo);
-
- if (mCallback != null) {
- mCallback.onTaskFragmentAppeared(taskFragmentInfo);
- }
+ mCallback.onTaskFragmentAppeared(wct, taskFragmentInfo);
}
@Override
- public void onTaskFragmentInfoChanged(@NonNull TaskFragmentInfo taskFragmentInfo) {
+ public void onTaskFragmentInfoChanged(@NonNull WindowContainerTransaction wct,
+ @NonNull TaskFragmentInfo taskFragmentInfo) {
final IBinder fragmentToken = taskFragmentInfo.getFragmentToken();
mFragmentInfos.put(fragmentToken, taskFragmentInfo);
-
- if (mCallback != null) {
- mCallback.onTaskFragmentInfoChanged(taskFragmentInfo);
- }
+ mCallback.onTaskFragmentInfoChanged(wct, taskFragmentInfo);
}
@Override
- public void onTaskFragmentVanished(@NonNull TaskFragmentInfo taskFragmentInfo) {
+ public void onTaskFragmentVanished(@NonNull WindowContainerTransaction wct,
+ @NonNull TaskFragmentInfo taskFragmentInfo) {
mFragmentInfos.remove(taskFragmentInfo.getFragmentToken());
-
- if (mCallback != null) {
- mCallback.onTaskFragmentVanished(taskFragmentInfo);
- }
+ mCallback.onTaskFragmentVanished(wct, taskFragmentInfo);
}
@Override
- public void onTaskFragmentParentInfoChanged(int taskId, @NonNull Configuration parentConfig) {
- if (mCallback != null) {
- mCallback.onTaskFragmentParentInfoChanged(taskId, parentConfig);
- }
+ public void onTaskFragmentParentInfoChanged(@NonNull WindowContainerTransaction wct,
+ int taskId, @NonNull Configuration parentConfig) {
+ mCallback.onTaskFragmentParentInfoChanged(wct, taskId, parentConfig);
}
@Override
- public void onActivityReparentToTask(int taskId, @NonNull Intent activityIntent,
- @NonNull IBinder activityToken) {
- if (mCallback != null) {
- mCallback.onActivityReparentToTask(taskId, activityIntent, activityToken);
- }
+ public void onActivityReparentedToTask(@NonNull WindowContainerTransaction wct,
+ int taskId, @NonNull Intent activityIntent, @NonNull IBinder activityToken) {
+ mCallback.onActivityReparentedToTask(wct, taskId, activityIntent, activityToken);
}
@Override
- public void onTaskFragmentError(@NonNull IBinder errorCallbackToken,
+ public void onTaskFragmentError(@NonNull WindowContainerTransaction wct,
+ @NonNull IBinder errorCallbackToken,
@Nullable TaskFragmentInfo taskFragmentInfo,
int opType, @NonNull Throwable exception) {
if (taskFragmentInfo != null) {
final IBinder fragmentToken = taskFragmentInfo.getFragmentToken();
mFragmentInfos.put(fragmentToken, taskFragmentInfo);
}
-
- if (mCallback != null) {
- mCallback.onTaskFragmentError(taskFragmentInfo, opType);
- }
+ mCallback.onTaskFragmentError(wct, taskFragmentInfo, opType);
}
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
index f09a91018bf0..c8ac0fc73ff9 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
@@ -16,17 +16,21 @@
package androidx.window.extensions.embedding;
-import android.annotation.NonNull;
import android.app.Activity;
import android.util.Pair;
import android.util.Size;
+import androidx.annotation.NonNull;
+
/**
* Client-side descriptor of a split that holds two containers.
*/
class SplitContainer {
+ @NonNull
private final TaskFragmentContainer mPrimaryContainer;
+ @NonNull
private final TaskFragmentContainer mSecondaryContainer;
+ @NonNull
private final SplitRule mSplitRule;
SplitContainer(@NonNull TaskFragmentContainer primaryContainer,
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index dad07394e3fb..0597809f8e36 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -145,35 +145,36 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
@Override
- public void onTaskFragmentAppeared(@NonNull TaskFragmentInfo taskFragmentInfo) {
+ public void onTaskFragmentAppeared(@NonNull WindowContainerTransaction wct,
+ @NonNull TaskFragmentInfo taskFragmentInfo) {
synchronized (mLock) {
TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken());
if (container == null) {
return;
}
- container.setInfo(taskFragmentInfo);
+ container.setInfo(wct, taskFragmentInfo);
if (container.isFinished()) {
- mPresenter.cleanupContainer(container, false /* shouldFinishDependent */);
+ mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
} else {
// Update with the latest Task configuration.
- mPresenter.updateContainer(container);
+ updateContainer(wct, container);
}
updateCallbackIfNecessary();
}
}
@Override
- public void onTaskFragmentInfoChanged(@NonNull TaskFragmentInfo taskFragmentInfo) {
+ public void onTaskFragmentInfoChanged(@NonNull WindowContainerTransaction wct,
+ @NonNull TaskFragmentInfo taskFragmentInfo) {
synchronized (mLock) {
TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken());
if (container == null) {
return;
}
- final WindowContainerTransaction wct = new WindowContainerTransaction();
final boolean wasInPip = isInPictureInPicture(container);
- container.setInfo(taskFragmentInfo);
+ container.setInfo(wct, taskFragmentInfo);
final boolean isInPip = isInPictureInPicture(container);
// Check if there are no running activities - consider the container empty if there are
// no non-finishing activities left.
@@ -183,15 +184,15 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
// Instead, the original split should be cleanup, and the dependent may be
// expanded to fullscreen.
cleanupForEnterPip(wct, container);
- mPresenter.cleanupContainer(container, false /* shouldFinishDependent */, wct);
+ mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
} else if (taskFragmentInfo.isTaskClearedForReuse()) {
// Do not finish the dependents if this TaskFragment was cleared due to
// launching activity in the Task.
- mPresenter.cleanupContainer(container, false /* shouldFinishDependent */, wct);
+ mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
} else if (!container.isWaitingActivityAppear()) {
// Do not finish the container before the expected activity appear until
// timeout.
- mPresenter.cleanupContainer(container, true /* shouldFinishDependent */, wct);
+ mPresenter.cleanupContainer(wct, container, true /* shouldFinishDependent */);
}
} else if (wasInPip && isInPip) {
// No update until exit PIP.
@@ -208,13 +209,13 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
// needed.
updateContainer(wct, container);
}
- mPresenter.applyTransaction(wct);
updateCallbackIfNecessary();
}
}
@Override
- public void onTaskFragmentVanished(@NonNull TaskFragmentInfo taskFragmentInfo) {
+ public void onTaskFragmentVanished(@NonNull WindowContainerTransaction wct,
+ @NonNull TaskFragmentInfo taskFragmentInfo) {
synchronized (mLock) {
final TaskFragmentContainer container = getContainer(
taskFragmentInfo.getFragmentToken());
@@ -225,9 +226,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
final TaskFragmentContainer newTopContainer = getTopActiveContainer(
container.getTaskId());
if (newTopContainer != null) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
updateContainer(wct, newTopContainer);
- mPresenter.applyTransaction(wct);
}
updateCallbackIfNecessary();
}
@@ -236,7 +235,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
@Override
- public void onTaskFragmentParentInfoChanged(int taskId, @NonNull Configuration parentConfig) {
+ public void onTaskFragmentParentInfoChanged(@NonNull WindowContainerTransaction wct,
+ int taskId, @NonNull Configuration parentConfig) {
synchronized (mLock) {
onTaskConfigurationChanged(taskId, parentConfig);
if (isInPictureInPicture(parentConfig)) {
@@ -256,7 +256,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
final TaskFragmentContainer container = containers.get(i);
// Wait until onTaskFragmentAppeared to update new container.
if (!container.isFinished() && !container.isWaitingActivityAppear()) {
- mPresenter.updateContainer(container);
+ updateContainer(wct, container);
}
}
updateCallbackIfNecessary();
@@ -264,7 +264,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
@Override
- public void onActivityReparentToTask(int taskId, @NonNull Intent activityIntent,
+ public void onActivityReparentedToTask(@NonNull WindowContainerTransaction wct,
+ int taskId, @NonNull Intent activityIntent,
@NonNull IBinder activityToken) {
synchronized (mLock) {
// If the activity belongs to the current app process, we treat it as a new activity
@@ -275,10 +276,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
// launching to top. We allow split as primary for activity reparent because the
// activity may be split as primary before it is reparented out. In that case, we
// want to show it as primary again when it is reparented back.
- if (!resolveActivityToContainer(activity, true /* isOnReparent */)) {
+ if (!resolveActivityToContainer(wct, activity, true /* isOnReparent */)) {
// When there is no embedding rule matched, try to place it in the top container
// like a normal launch.
- placeActivityInTopContainer(activity);
+ placeActivityInTopContainer(wct, activity);
}
updateCallbackIfNecessary();
return;
@@ -293,7 +294,6 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
// If the activity belongs to a different app process, we treat it as starting new
// intent, since both actions might result in a new activity that should appear in an
// organized TaskFragment.
- final WindowContainerTransaction wct = new WindowContainerTransaction();
TaskFragmentContainer targetContainer = resolveStartActivityIntent(wct, taskId,
activityIntent, null /* launchingActivity */);
if (targetContainer == null) {
@@ -306,14 +306,14 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
wct.reparentActivityToTaskFragment(targetContainer.getTaskFragmentToken(),
activityToken);
- mPresenter.applyTransaction(wct);
// Because the activity does not belong to the organizer process, we wait until
// onTaskFragmentAppeared to trigger updateCallbackIfNecessary().
}
}
@Override
- public void onTaskFragmentError(@Nullable TaskFragmentInfo taskFragmentInfo, int opType) {
+ public void onTaskFragmentError(@NonNull WindowContainerTransaction wct,
+ @Nullable TaskFragmentInfo taskFragmentInfo, int opType) {
synchronized (mLock) {
switch (opType) {
case HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT:
@@ -329,10 +329,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
// Update the latest taskFragmentInfo and perform necessary clean-up
- container.setInfo(taskFragmentInfo);
+ container.setInfo(wct, taskFragmentInfo);
container.clearPendingAppearedActivities();
if (container.isEmpty()) {
- mPresenter.cleanupContainer(container, false /* shouldFinishDependent */);
+ mPresenter.cleanupContainer(wct, container,
+ false /* shouldFinishDependent */);
}
break;
}
@@ -343,7 +344,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
}
- /** Called on receiving {@link #onTaskFragmentVanished(TaskFragmentInfo)} for cleanup. */
+ /** Called on receiving {@link #onTaskFragmentVanished} for cleanup. */
private void cleanupTaskFragment(@NonNull IBinder taskFragmentToken) {
for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
final TaskContainer taskContainer = mTaskContainers.valueAt(i);
@@ -422,10 +423,12 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
@VisibleForTesting
- void onActivityCreated(@NonNull Activity launchedActivity) {
+ @GuardedBy("mLock")
+ void onActivityCreated(@NonNull WindowContainerTransaction wct,
+ @NonNull Activity launchedActivity) {
// TODO(b/229680885): we don't support launching into primary yet because we want to always
// launch the new activity on top.
- resolveActivityToContainer(launchedActivity, false /* isOnReparent */);
+ resolveActivityToContainer(wct, launchedActivity, false /* isOnReparent */);
updateCallbackIfNecessary();
}
@@ -440,7 +443,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
*/
@VisibleForTesting
@GuardedBy("mLock")
- boolean resolveActivityToContainer(@NonNull Activity activity, boolean isOnReparent) {
+ boolean resolveActivityToContainer(@NonNull WindowContainerTransaction wct,
+ @NonNull Activity activity, boolean isOnReparent) {
if (isInPictureInPicture(activity) || activity.isFinishing()) {
// We don't embed activity when it is in PIP, or finishing. Return true since we don't
// want any extra handling.
@@ -472,12 +476,12 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
// 1. Whether the new launched activity should always expand.
if (shouldExpand(activity, null /* intent */)) {
- expandActivity(activity);
+ expandActivity(wct, activity);
return true;
}
// 2. Whether the new launched activity should launch a placeholder.
- if (launchPlaceholderIfNecessary(activity, !isOnReparent)) {
+ if (launchPlaceholderIfNecessary(wct, activity, !isOnReparent)) {
return true;
}
@@ -492,11 +496,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
// Can't find any activity below.
return false;
}
- if (putActivitiesIntoSplitIfNecessary(activityBelow, activity)) {
+ if (putActivitiesIntoSplitIfNecessary(wct, activityBelow, activity)) {
// Have split rule of [ activityBelow | launchedActivity ].
return true;
}
- if (isOnReparent && putActivitiesIntoSplitIfNecessary(activity, activityBelow)) {
+ if (isOnReparent && putActivitiesIntoSplitIfNecessary(wct, activity, activityBelow)) {
// Have split rule of [ launchedActivity | activityBelow].
return true;
}
@@ -519,19 +523,20 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
// Can't find the top activity on the other split TaskFragment.
return false;
}
- if (putActivitiesIntoSplitIfNecessary(otherTopActivity, activity)) {
+ if (putActivitiesIntoSplitIfNecessary(wct, otherTopActivity, activity)) {
// Have split rule of [ otherTopActivity | launchedActivity ].
return true;
}
// Have split rule of [ launchedActivity | otherTopActivity].
- return isOnReparent && putActivitiesIntoSplitIfNecessary(activity, otherTopActivity);
+ return isOnReparent && putActivitiesIntoSplitIfNecessary(wct, activity, otherTopActivity);
}
/**
* Places the given activity to the top most TaskFragment in the task if there is any.
*/
@VisibleForTesting
- void placeActivityInTopContainer(@NonNull Activity activity) {
+ void placeActivityInTopContainer(@NonNull WindowContainerTransaction wct,
+ @NonNull Activity activity) {
if (getContainerWithActivity(activity) != null) {
// The activity has already been put in a TaskFragment. This is likely to be done by
// the server when the activity is started.
@@ -547,20 +552,20 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
return;
}
targetContainer.addPendingAppearedActivity(activity);
- final WindowContainerTransaction wct = new WindowContainerTransaction();
wct.reparentActivityToTaskFragment(targetContainer.getTaskFragmentToken(),
activity.getActivityToken());
- mPresenter.applyTransaction(wct);
}
/**
* Starts an activity to side of the launchingActivity with the provided split config.
*/
- private void startActivityToSide(@NonNull Activity launchingActivity, @NonNull Intent intent,
+ @GuardedBy("mLock")
+ private void startActivityToSide(@NonNull WindowContainerTransaction wct,
+ @NonNull Activity launchingActivity, @NonNull Intent intent,
@Nullable Bundle options, @NonNull SplitRule sideRule,
@Nullable Consumer<Exception> failureCallback, boolean isPlaceholder) {
try {
- mPresenter.startActivityToSide(launchingActivity, intent, options, sideRule,
+ mPresenter.startActivityToSide(wct, launchingActivity, intent, options, sideRule,
isPlaceholder);
} catch (Exception e) {
if (failureCallback != null) {
@@ -573,15 +578,17 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* Expands the given activity by either expanding the TaskFragment it is currently in or putting
* it into a new expanded TaskFragment.
*/
- private void expandActivity(@NonNull Activity activity) {
+ @GuardedBy("mLock")
+ private void expandActivity(@NonNull WindowContainerTransaction wct,
+ @NonNull Activity activity) {
final TaskFragmentContainer container = getContainerWithActivity(activity);
if (shouldContainerBeExpanded(container)) {
// Make sure that the existing container is expanded.
- mPresenter.expandTaskFragment(container.getTaskFragmentToken());
+ mPresenter.expandTaskFragment(wct, container.getTaskFragmentToken());
} else {
// Put activity into a new expanded container.
final TaskFragmentContainer newContainer = newContainer(activity, getTaskId(activity));
- mPresenter.expandActivity(newContainer.getTaskFragmentToken(), activity);
+ mPresenter.expandActivity(wct, newContainer.getTaskFragmentToken(), activity);
}
}
@@ -667,8 +674,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* and returns {@code true}. Otherwise, returns {@code false}.
*/
@GuardedBy("mLock")
- private boolean putActivitiesIntoSplitIfNecessary(@NonNull Activity primaryActivity,
- @NonNull Activity secondaryActivity) {
+ private boolean putActivitiesIntoSplitIfNecessary(@NonNull WindowContainerTransaction wct,
+ @NonNull Activity primaryActivity, @NonNull Activity secondaryActivity) {
final SplitPairRule splitRule = getSplitRule(primaryActivity, secondaryActivity);
if (splitRule == null) {
return false;
@@ -686,23 +693,23 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
return true;
}
secondaryContainer.addPendingAppearedActivity(secondaryActivity);
- final WindowContainerTransaction wct = new WindowContainerTransaction();
if (mPresenter.expandSplitContainerIfNeeded(wct, splitContainer, primaryActivity,
secondaryActivity, null /* secondaryIntent */)
!= RESULT_EXPAND_FAILED_NO_TF_INFO) {
wct.reparentActivityToTaskFragment(
secondaryContainer.getTaskFragmentToken(),
secondaryActivity.getActivityToken());
- mPresenter.applyTransaction(wct);
return true;
}
}
// Create new split pair.
- mPresenter.createNewSplitContainer(primaryActivity, secondaryActivity, splitRule);
+ mPresenter.createNewSplitContainer(wct, primaryActivity, secondaryActivity, splitRule);
return true;
}
- private void onActivityConfigurationChanged(@NonNull Activity activity) {
+ @GuardedBy("mLock")
+ private void onActivityConfigurationChanged(@NonNull WindowContainerTransaction wct,
+ @NonNull Activity activity) {
if (activity.isFinishing()) {
// Do nothing if the activity is currently finishing.
return;
@@ -721,7 +728,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
// Check if activity requires a placeholder
- launchPlaceholderIfNecessary(activity, false /* isOnCreated */);
+ launchPlaceholderIfNecessary(wct, activity, false /* isOnCreated */);
}
@VisibleForTesting
@@ -741,7 +748,22 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* creation.
*/
void onTaskFragmentAppearEmptyTimeout(@NonNull TaskFragmentContainer container) {
- mPresenter.cleanupContainer(container, false /* shouldFinishDependent */);
+ synchronized (mLock) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ onTaskFragmentAppearEmptyTimeout(wct, container);
+ mPresenter.applyTransaction(wct);
+ }
+ }
+
+ /**
+ * Called when we have been waiting too long for the TaskFragment to become non-empty after
+ * creation.
+ */
+ void onTaskFragmentAppearEmptyTimeout(@NonNull WindowContainerTransaction wct,
+ @NonNull TaskFragmentContainer container) {
+ synchronized (mLock) {
+ mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
+ }
}
/**
@@ -971,6 +993,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
/** Cleanups all the dependencies when the TaskFragment is entering PIP. */
+ @GuardedBy("mLock")
private void cleanupForEnterPip(@NonNull WindowContainerTransaction wct,
@NonNull TaskFragmentContainer container) {
final TaskContainer taskContainer = container.getTaskContainer();
@@ -1084,9 +1107,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* Updates the presentation of the container. If the container is part of the split or should
* have a placeholder, it will also update the other part of the split.
*/
+ @GuardedBy("mLock")
void updateContainer(@NonNull WindowContainerTransaction wct,
@NonNull TaskFragmentContainer container) {
- if (launchPlaceholderIfNecessary(container)) {
+ if (launchPlaceholderIfNecessary(wct, container)) {
// Placeholder was launched, the positions will be updated when the activity is added
// to the secondary container.
return;
@@ -1111,7 +1135,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
// Skip position update - one or both containers are finished.
return;
}
- if (dismissPlaceholderIfNecessary(splitContainer)) {
+ if (dismissPlaceholderIfNecessary(wct, splitContainer)) {
// Placeholder was finished, the positions will be updated when its container is emptied
return;
}
@@ -1173,16 +1197,20 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
/**
* Checks if the container requires a placeholder and launches it if necessary.
*/
- private boolean launchPlaceholderIfNecessary(@NonNull TaskFragmentContainer container) {
+ @GuardedBy("mLock")
+ private boolean launchPlaceholderIfNecessary(@NonNull WindowContainerTransaction wct,
+ @NonNull TaskFragmentContainer container) {
final Activity topActivity = container.getTopNonFinishingActivity();
if (topActivity == null) {
return false;
}
- return launchPlaceholderIfNecessary(topActivity, false /* isOnCreated */);
+ return launchPlaceholderIfNecessary(wct, topActivity, false /* isOnCreated */);
}
- boolean launchPlaceholderIfNecessary(@NonNull Activity activity, boolean isOnCreated) {
+ @GuardedBy("mLock")
+ boolean launchPlaceholderIfNecessary(@NonNull WindowContainerTransaction wct,
+ @NonNull Activity activity, boolean isOnCreated) {
if (activity.isFinishing()) {
return false;
}
@@ -1216,7 +1244,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
// TODO(b/190433398): Handle failed request
final Bundle options = getPlaceholderOptions(activity, isOnCreated);
- startActivityToSide(activity, placeholderRule.getPlaceholderIntent(), options,
+ startActivityToSide(wct, activity, placeholderRule.getPlaceholderIntent(), options,
placeholderRule, null /* failureCallback */, true /* isPlaceholder */);
return true;
}
@@ -1243,7 +1271,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
@VisibleForTesting
- boolean dismissPlaceholderIfNecessary(@NonNull SplitContainer splitContainer) {
+ @GuardedBy("mLock")
+ boolean dismissPlaceholderIfNecessary(@NonNull WindowContainerTransaction wct,
+ @NonNull SplitContainer splitContainer) {
if (!splitContainer.isPlaceholderContainer()) {
return false;
}
@@ -1257,7 +1287,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
return false;
}
- mPresenter.cleanupContainer(splitContainer.getSecondaryContainer(),
+ mPresenter.cleanupContainer(wct, splitContainer.getSecondaryContainer(),
false /* shouldFinishDependent */);
return true;
}
@@ -1523,7 +1553,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
private final class LifecycleCallbacks extends EmptyLifecycleCallbacksAdapter {
@Override
- public void onActivityPreCreated(Activity activity, Bundle savedInstanceState) {
+ public void onActivityPreCreated(@NonNull Activity activity,
+ @Nullable Bundle savedInstanceState) {
synchronized (mLock) {
final IBinder activityToken = activity.getActivityToken();
final IBinder initialTaskFragmentToken = getInitialTaskFragmentToken(activity);
@@ -1552,25 +1583,30 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
@Override
- public void onActivityPostCreated(Activity activity, Bundle savedInstanceState) {
+ public void onActivityPostCreated(@NonNull Activity activity,
+ @Nullable Bundle savedInstanceState) {
// Calling after Activity#onCreate is complete to allow the app launch something
// first. In case of a configured placeholder activity we want to make sure
// that we don't launch it if an activity itself already requested something to be
// launched to side.
synchronized (mLock) {
- SplitController.this.onActivityCreated(activity);
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ SplitController.this.onActivityCreated(wct, activity);
+ mPresenter.applyTransaction(wct);
}
}
@Override
- public void onActivityConfigurationChanged(Activity activity) {
+ public void onActivityConfigurationChanged(@NonNull Activity activity) {
synchronized (mLock) {
- SplitController.this.onActivityConfigurationChanged(activity);
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ SplitController.this.onActivityConfigurationChanged(wct, activity);
+ mPresenter.applyTransaction(wct);
}
}
@Override
- public void onActivityPostDestroyed(Activity activity) {
+ public void onActivityPostDestroyed(@NonNull Activity activity) {
synchronized (mLock) {
SplitController.this.onActivityDestroyed(activity);
}
@@ -1582,7 +1618,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
private final Handler mHandler = new Handler(Looper.getMainLooper());
@Override
- public void execute(Runnable r) {
+ public void execute(@NonNull Runnable r) {
mHandler.post(r);
}
}
@@ -1662,7 +1698,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* If the two rules have the same presentation, we can reuse the same {@link SplitContainer} if
* there is any.
*/
- private static boolean canReuseContainer(SplitRule rule1, SplitRule rule2) {
+ private static boolean canReuseContainer(@NonNull SplitRule rule1, @NonNull SplitRule rule2) {
if (!isContainerReusableRule(rule1) || !isContainerReusableRule(rule2)) {
return false;
}
@@ -1670,7 +1706,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
/** Whether the two rules have the same presentation. */
- private static boolean haveSamePresentation(SplitPairRule rule1, SplitPairRule rule2) {
+ private static boolean haveSamePresentation(@NonNull SplitPairRule rule1,
+ @NonNull SplitPairRule rule2) {
// TODO(b/231655482): add util method to do the comparison in SplitPairRule.
return rule1.getSplitRatio() == rule2.getSplitRatio()
&& rule1.getLayoutDirection() == rule2.getLayoutDirection()
@@ -1684,7 +1721,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* Whether it is ok for other rule to reuse the {@link TaskFragmentContainer} of the given
* rule.
*/
- private static boolean isContainerReusableRule(SplitRule rule) {
+ private static boolean isContainerReusableRule(@NonNull SplitRule rule) {
// We don't expect to reuse the placeholder rule.
if (!(rule instanceof SplitPairRule)) {
return false;
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index a89847a30d20..2b069d72e46f 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -102,37 +102,18 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
private final SplitController mController;
- SplitPresenter(@NonNull Executor executor, SplitController controller) {
+ SplitPresenter(@NonNull Executor executor, @NonNull SplitController controller) {
super(executor, controller);
mController = controller;
registerOrganizer();
}
/**
- * Updates the presentation of the provided container.
- */
- void updateContainer(@NonNull TaskFragmentContainer container) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- mController.updateContainer(wct, container);
- applyTransaction(wct);
- }
-
- /**
* Deletes the specified container and all other associated and dependent containers in the same
* transaction.
*/
- void cleanupContainer(@NonNull TaskFragmentContainer container, boolean shouldFinishDependent) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- cleanupContainer(container, shouldFinishDependent, wct);
- applyTransaction(wct);
- }
-
- /**
- * Deletes the specified container and all other associated and dependent containers in the same
- * transaction.
- */
- void cleanupContainer(@NonNull TaskFragmentContainer container, boolean shouldFinishDependent,
- @NonNull WindowContainerTransaction wct) {
+ void cleanupContainer(@NonNull WindowContainerTransaction wct,
+ @NonNull TaskFragmentContainer container, boolean shouldFinishDependent) {
container.finish(shouldFinishDependent, this, wct, mController);
final TaskFragmentContainer newTopContainer = mController.getTopActiveContainer(
@@ -190,10 +171,9 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
* created and the activity will be re-parented to it.
* @param rule The split rule to be applied to the container.
*/
- void createNewSplitContainer(@NonNull Activity primaryActivity,
- @NonNull Activity secondaryActivity, @NonNull SplitPairRule rule) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
-
+ void createNewSplitContainer(@NonNull WindowContainerTransaction wct,
+ @NonNull Activity primaryActivity, @NonNull Activity secondaryActivity,
+ @NonNull SplitPairRule rule) {
final Rect parentBounds = getParentContainerBounds(primaryActivity);
final Pair<Size, Size> minDimensionsPair = getActivitiesMinDimensionsPair(primaryActivity,
secondaryActivity);
@@ -219,8 +199,6 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
minDimensionsPair);
mController.registerSplit(wct, primaryContainer, primaryActivity, secondaryContainer, rule);
-
- applyTransaction(wct);
}
/**
@@ -262,7 +240,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
* @param rule The split rule to be applied to the container.
* @param isPlaceholder Whether the launch is a placeholder.
*/
- void startActivityToSide(@NonNull Activity launchingActivity, @NonNull Intent activityIntent,
+ void startActivityToSide(@NonNull WindowContainerTransaction wct,
+ @NonNull Activity launchingActivity, @NonNull Intent activityIntent,
@Nullable Bundle activityOptions, @NonNull SplitRule rule, boolean isPlaceholder) {
final Rect parentBounds = getParentContainerBounds(launchingActivity);
final Pair<Size, Size> minDimensionsPair = getActivityIntentMinDimensionsPair(
@@ -284,7 +263,6 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
launchingActivity, taskId);
final int windowingMode = mController.getTaskContainer(taskId)
.getWindowingModeForSplitTaskFragment(primaryRectBounds);
- final WindowContainerTransaction wct = new WindowContainerTransaction();
mController.registerSplit(wct, primaryContainer, launchingActivity, secondaryContainer,
rule);
startActivityToSide(wct, primaryContainer.getTaskFragmentToken(), primaryRectBounds,
@@ -294,7 +272,6 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
// When placeholder is launched in split, we should keep the focus on the primary.
wct.requestFocusOnTaskFragment(primaryContainer.getTaskFragmentToken());
}
- applyTransaction(wct);
}
/**
@@ -502,14 +479,14 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
}
@NonNull
- static Pair<Size, Size> getActivitiesMinDimensionsPair(Activity primaryActivity,
- Activity secondaryActivity) {
+ static Pair<Size, Size> getActivitiesMinDimensionsPair(@NonNull Activity primaryActivity,
+ @NonNull Activity secondaryActivity) {
return new Pair<>(getMinDimensions(primaryActivity), getMinDimensions(secondaryActivity));
}
@NonNull
- static Pair<Size, Size> getActivityIntentMinDimensionsPair(Activity primaryActivity,
- Intent secondaryIntent) {
+ static Pair<Size, Size> getActivityIntentMinDimensionsPair(@NonNull Activity primaryActivity,
+ @NonNull Intent secondaryIntent) {
return new Pair<>(getMinDimensions(primaryActivity), getMinDimensions(secondaryIntent));
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
index 0ea5603b1f3d..77e26c07f304 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -21,8 +21,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.app.Activity;
import android.app.WindowConfiguration;
import android.app.WindowConfiguration.WindowingMode;
@@ -31,6 +29,9 @@ import android.os.IBinder;
import android.util.ArraySet;
import android.window.TaskFragmentInfo;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java
index f721341a3647..ee2e139bb0b2 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java
@@ -30,6 +30,8 @@ import android.view.RemoteAnimationAdapter;
import android.view.RemoteAnimationDefinition;
import android.window.TaskFragmentOrganizer;
+import androidx.annotation.NonNull;
+
import com.android.internal.annotations.VisibleForTesting;
/** Controls the TaskFragment remote animations. */
@@ -45,7 +47,7 @@ class TaskFragmentAnimationController {
/** Task Ids that we have registered for remote animation. */
private final ArraySet<Integer> mRegisterTasks = new ArraySet<>();
- TaskFragmentAnimationController(TaskFragmentOrganizer organizer) {
+ TaskFragmentAnimationController(@NonNull TaskFragmentOrganizer organizer) {
mOrganizer = organizer;
mDefinition = new RemoteAnimationDefinition();
final RemoteAnimationAdapter animationAdapter =
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
index c4f37091a491..8af2d9c6810b 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
@@ -112,6 +112,7 @@ class TaskFragmentAnimationRunner extends IRemoteAnimationRunner.Stub {
}
/** Creates the animator given the transition type and windows. */
+ @NonNull
private Animator createAnimator(@WindowManager.TransitionOldType int transit,
@NonNull RemoteAnimationTarget[] targets,
@NonNull IRemoteAnimationFinishedCallback finishedCallback) {
@@ -161,6 +162,7 @@ class TaskFragmentAnimationRunner extends IRemoteAnimationRunner.Stub {
}
/** List of {@link TaskFragmentAnimationAdapter} to handle animations on all window targets. */
+ @NonNull
private List<TaskFragmentAnimationAdapter> createAnimationAdapters(
@WindowManager.TransitionOldType int transit,
@NonNull RemoteAnimationTarget[] targets) {
@@ -180,12 +182,14 @@ class TaskFragmentAnimationRunner extends IRemoteAnimationRunner.Stub {
}
}
+ @NonNull
private List<TaskFragmentAnimationAdapter> createOpenAnimationAdapters(
@NonNull RemoteAnimationTarget[] targets) {
return createOpenCloseAnimationAdapters(targets, true /* isOpening */,
mAnimationSpec::loadOpenAnimation);
}
+ @NonNull
private List<TaskFragmentAnimationAdapter> createCloseAnimationAdapters(
@NonNull RemoteAnimationTarget[] targets) {
return createOpenCloseAnimationAdapters(targets, false /* isOpening */,
@@ -196,6 +200,7 @@ class TaskFragmentAnimationRunner extends IRemoteAnimationRunner.Stub {
* Creates {@link TaskFragmentAnimationAdapter} for OPEN and CLOSE types of transition.
* @param isOpening {@code true} for OPEN type, {@code false} for CLOSE type.
*/
+ @NonNull
private List<TaskFragmentAnimationAdapter> createOpenCloseAnimationAdapters(
@NonNull RemoteAnimationTarget[] targets, boolean isOpening,
@NonNull BiFunction<RemoteAnimationTarget, Rect, Animation> animationProvider) {
@@ -238,6 +243,7 @@ class TaskFragmentAnimationRunner extends IRemoteAnimationRunner.Stub {
return adapters;
}
+ @NonNull
private TaskFragmentAnimationAdapter createOpenCloseAnimationAdapter(
@NonNull RemoteAnimationTarget target,
@NonNull BiFunction<RemoteAnimationTarget, Rect, Animation> animationProvider,
@@ -259,6 +265,7 @@ class TaskFragmentAnimationRunner extends IRemoteAnimationRunner.Stub {
return new TaskFragmentAnimationAdapter(animation, target);
}
+ @NonNull
private List<TaskFragmentAnimationAdapter> createChangeAnimationAdapters(
@NonNull RemoteAnimationTarget[] targets) {
final List<TaskFragmentAnimationAdapter> adapters = new ArrayList<>();
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java
index 5cc496a225c2..97d42391b6c4 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java
@@ -26,6 +26,7 @@ import android.graphics.Rect;
import android.os.Handler;
import android.provider.Settings;
import android.view.RemoteAnimationTarget;
+import android.view.WindowManager;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.AnimationSet;
@@ -68,16 +69,14 @@ class TaskFragmentAnimationSpec {
// The transition animation should be adjusted based on the developer option.
final ContentResolver resolver = mContext.getContentResolver();
- mTransitionAnimationScaleSetting = Settings.Global.getFloat(resolver,
- Settings.Global.TRANSITION_ANIMATION_SCALE,
- mContext.getResources().getFloat(
- R.dimen.config_appTransitionAnimationDurationScaleDefault));
+ mTransitionAnimationScaleSetting = getTransitionAnimationScaleSetting();
resolver.registerContentObserver(
Settings.Global.getUriFor(Settings.Global.TRANSITION_ANIMATION_SCALE), false,
new SettingsObserver(handler));
}
/** For target that doesn't need to be animated. */
+ @NonNull
static Animation createNoopAnimation(@NonNull RemoteAnimationTarget target) {
// Noop but just keep the target showing/hiding.
final float alpha = target.mode == MODE_CLOSING ? 0f : 1f;
@@ -85,6 +84,7 @@ class TaskFragmentAnimationSpec {
}
/** Animation for target that is opening in a change transition. */
+ @NonNull
Animation createChangeBoundsOpenAnimation(@NonNull RemoteAnimationTarget target) {
final Rect bounds = target.localBounds;
// The target will be animated in from left or right depends on its position.
@@ -101,6 +101,7 @@ class TaskFragmentAnimationSpec {
}
/** Animation for target that is closing in a change transition. */
+ @NonNull
Animation createChangeBoundsCloseAnimation(@NonNull RemoteAnimationTarget target) {
final Rect bounds = target.localBounds;
// The target will be animated out to left or right depends on its position.
@@ -121,6 +122,7 @@ class TaskFragmentAnimationSpec {
* @return the return array always has two elements. The first one is for the start leash, and
* the second one is for the end leash.
*/
+ @NonNull
Animation[] createChangeBoundsChangeAnimations(@NonNull RemoteAnimationTarget target) {
// Both start bounds and end bounds are in screen coordinates. We will post translate
// to the local coordinates in TaskFragmentAnimationAdapter#onAnimationUpdate
@@ -177,6 +179,7 @@ class TaskFragmentAnimationSpec {
return new Animation[]{startSet, endSet};
}
+ @NonNull
Animation loadOpenAnimation(@NonNull RemoteAnimationTarget target,
@NonNull Rect wholeAnimationBounds) {
final boolean isEnter = target.mode != MODE_CLOSING;
@@ -198,6 +201,7 @@ class TaskFragmentAnimationSpec {
return animation;
}
+ @NonNull
Animation loadCloseAnimation(@NonNull RemoteAnimationTarget target,
@NonNull Rect wholeAnimationBounds) {
final boolean isEnter = target.mode != MODE_CLOSING;
@@ -217,6 +221,12 @@ class TaskFragmentAnimationSpec {
return animation;
}
+ private float getTransitionAnimationScaleSetting() {
+ return WindowManager.fixScale(Settings.Global.getFloat(mContext.getContentResolver(),
+ Settings.Global.TRANSITION_ANIMATION_SCALE, mContext.getResources().getFloat(
+ R.dimen.config_appTransitionAnimationDurationScaleDefault)));
+ }
+
private class SettingsObserver extends ContentObserver {
SettingsObserver(@NonNull Handler handler) {
super(handler);
@@ -224,9 +234,7 @@ class TaskFragmentAnimationSpec {
@Override
public void onChange(boolean selfChange) {
- mTransitionAnimationScaleSetting = Settings.Global.getFloat(
- mContext.getContentResolver(), Settings.Global.TRANSITION_ANIMATION_SCALE,
- mTransitionAnimationScaleSetting);
+ mTransitionAnimationScaleSetting = getTransitionAnimationScaleSetting();
}
}
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index 37f5b6dc399e..11c0db320646 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -18,8 +18,6 @@ package androidx.window.extensions.embedding;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.app.Activity;
import android.app.WindowConfiguration.WindowingMode;
import android.content.Intent;
@@ -30,6 +28,9 @@ import android.util.Size;
import android.window.TaskFragmentInfo;
import android.window.WindowContainerTransaction;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
@@ -175,6 +176,7 @@ class TaskFragmentContainer {
&& mInfo.getActivities().size() == collectNonFinishingActivities().size();
}
+ @NonNull
ActivityStack toActivityStack() {
return new ActivityStack(collectNonFinishingActivities(), isEmpty());
}
@@ -249,19 +251,22 @@ class TaskFragmentContainer {
return mInfo;
}
- void setInfo(@NonNull TaskFragmentInfo info) {
+ void setInfo(@NonNull WindowContainerTransaction wct, @NonNull TaskFragmentInfo info) {
if (!mIsFinished && mInfo == null && info.isEmpty()) {
// onTaskFragmentAppeared with empty info. We will remove the TaskFragment if no
// pending appeared intent/activities. Otherwise, wait and removing the TaskFragment if
// it is still empty after timeout.
- mAppearEmptyTimeout = () -> {
- mAppearEmptyTimeout = null;
- mController.onTaskFragmentAppearEmptyTimeout(this);
- };
if (mPendingAppearedIntent != null || !mPendingAppearedActivities.isEmpty()) {
+ mAppearEmptyTimeout = () -> {
+ mAppearEmptyTimeout = null;
+ // Call without the pass-in wct when timeout. We need to applyWct directly
+ // in this case.
+ mController.onTaskFragmentAppearEmptyTimeout(this);
+ };
mController.getHandler().postDelayed(mAppearEmptyTimeout, APPEAR_EMPTY_TIMEOUT_MS);
} else {
- mAppearEmptyTimeout.run();
+ mAppearEmptyTimeout = null;
+ mController.onTaskFragmentAppearEmptyTimeout(wct, this);
}
} else if (mAppearEmptyTimeout != null && !info.isEmpty()) {
mController.getHandler().removeCallbacks(mAppearEmptyTimeout);
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
index 4d2595275f20..21cf7a6272a7 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
@@ -56,6 +56,8 @@ import java.util.ArrayList;
* Build/Install/Run:
* atest WMJetpackUnitTests:JetpackTaskFragmentOrganizerTest
*/
+// Suppress GuardedBy warning on unit tests
+@SuppressWarnings("GuardedBy")
@Presubmit
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -119,7 +121,7 @@ public class JetpackTaskFragmentOrganizerTest {
new Intent(), taskContainer, mSplitController);
final TaskFragmentInfo info = createMockInfo(container);
mOrganizer.mFragmentInfos.put(container.getTaskFragmentToken(), info);
- container.setInfo(info);
+ container.setInfo(mTransaction, info);
mOrganizer.expandTaskFragment(mTransaction, container.getTaskFragmentToken());
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
index 4bc503369d0e..07758d24ad94 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
@@ -89,6 +89,8 @@ import java.util.List;
* Build/Install/Run:
* atest WMJetpackUnitTests:SplitControllerTest
*/
+// Suppress GuardedBy warning on unit tests
+@SuppressWarnings("GuardedBy")
@Presubmit
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -158,14 +160,14 @@ public class SplitControllerTest {
final TaskFragmentInfo info = mock(TaskFragmentInfo.class);
doReturn(new ArrayList<>()).when(info).getActivities();
doReturn(true).when(info).isEmpty();
- tf1.setInfo(info);
+ tf1.setInfo(mTransaction, info);
assertWithMessage("Must return tf because we are waiting for tf1 to become non-empty after"
+ " creation.")
.that(mSplitController.getTopActiveContainer(TASK_ID)).isEqualTo(tf1);
doReturn(false).when(info).isEmpty();
- tf1.setInfo(info);
+ tf1.setInfo(mTransaction, info);
assertWithMessage("Must return null because tf1 becomes empty.")
.that(mSplitController.getTopActiveContainer(TASK_ID)).isNull();
@@ -177,7 +179,7 @@ public class SplitControllerTest {
doReturn(tf.getTaskFragmentToken()).when(mInfo).getFragmentToken();
// The TaskFragment has been removed in the server, we only need to cleanup the reference.
- mSplitController.onTaskFragmentVanished(mInfo);
+ mSplitController.onTaskFragmentVanished(mTransaction, mInfo);
verify(mSplitPresenter, never()).deleteTaskFragment(any(), any());
verify(mSplitController).removeContainer(tf);
@@ -187,9 +189,10 @@ public class SplitControllerTest {
@Test
public void testOnTaskFragmentAppearEmptyTimeout() {
final TaskFragmentContainer tf = mSplitController.newContainer(mActivity, TASK_ID);
- mSplitController.onTaskFragmentAppearEmptyTimeout(tf);
+ mSplitController.onTaskFragmentAppearEmptyTimeout(mTransaction, tf);
- verify(mSplitPresenter).cleanupContainer(tf, false /* shouldFinishDependent */);
+ verify(mSplitPresenter).cleanupContainer(mTransaction, tf,
+ false /* shouldFinishDependent */);
}
@Test
@@ -229,8 +232,8 @@ public class SplitControllerTest {
spyOn(tf);
doReturn(mActivity).when(tf).getTopNonFinishingActivity();
doReturn(true).when(tf).isEmpty();
- doReturn(true).when(mSplitController).launchPlaceholderIfNecessary(mActivity,
- false /* isOnCreated */);
+ doReturn(true).when(mSplitController).launchPlaceholderIfNecessary(mTransaction,
+ mActivity, false /* isOnCreated */);
doNothing().when(mSplitPresenter).updateSplitContainer(any(), any(), any());
mSplitController.updateContainer(mTransaction, tf);
@@ -250,7 +253,7 @@ public class SplitControllerTest {
mSplitController.updateContainer(mTransaction, tf);
- verify(mSplitController, never()).dismissPlaceholderIfNecessary(any());
+ verify(mSplitController, never()).dismissPlaceholderIfNecessary(any(), any());
// Verify if tf is not in the top splitContainer,
final SplitContainer splitContainer = mock(SplitContainer.class);
@@ -264,7 +267,7 @@ public class SplitControllerTest {
mSplitController.updateContainer(mTransaction, tf);
- verify(mSplitController, never()).dismissPlaceholderIfNecessary(any());
+ verify(mSplitController, never()).dismissPlaceholderIfNecessary(any(), any());
// Verify if one or both containers in the top SplitContainer are finished,
// dismissPlaceholder() won't be called.
@@ -273,12 +276,12 @@ public class SplitControllerTest {
mSplitController.updateContainer(mTransaction, tf);
- verify(mSplitController, never()).dismissPlaceholderIfNecessary(any());
+ verify(mSplitController, never()).dismissPlaceholderIfNecessary(any(), any());
// Verify if placeholder should be dismissed, updateSplitContainer() won't be called.
doReturn(false).when(tf).isFinished();
doReturn(true).when(mSplitController)
- .dismissPlaceholderIfNecessary(splitContainer);
+ .dismissPlaceholderIfNecessary(mTransaction, splitContainer);
mSplitController.updateContainer(mTransaction, tf);
@@ -286,7 +289,7 @@ public class SplitControllerTest {
// Verify if the top active split is updated if both of its containers are not finished.
doReturn(false).when(mSplitController)
- .dismissPlaceholderIfNecessary(splitContainer);
+ .dismissPlaceholderIfNecessary(mTransaction, splitContainer);
mSplitController.updateContainer(mTransaction, tf);
@@ -315,34 +318,36 @@ public class SplitControllerTest {
@Test
public void testOnActivityCreated() {
- mSplitController.onActivityCreated(mActivity);
+ mSplitController.onActivityCreated(mTransaction, mActivity);
// Disallow to split as primary because we want the new launch to be always on top.
- verify(mSplitController).resolveActivityToContainer(mActivity, false /* isOnReparent */);
+ verify(mSplitController).resolveActivityToContainer(mTransaction, mActivity,
+ false /* isOnReparent */);
}
@Test
- public void testOnActivityReparentToTask_sameProcess() {
- mSplitController.onActivityReparentToTask(TASK_ID, new Intent(),
+ public void testOnActivityReparentedToTask_sameProcess() {
+ mSplitController.onActivityReparentedToTask(mTransaction, TASK_ID, new Intent(),
mActivity.getActivityToken());
// Treated as on activity created, but allow to split as primary.
- verify(mSplitController).resolveActivityToContainer(mActivity, true /* isOnReparent */);
+ verify(mSplitController).resolveActivityToContainer(mTransaction,
+ mActivity, true /* isOnReparent */);
// Try to place the activity to the top TaskFragment when there is no matched rule.
- verify(mSplitController).placeActivityInTopContainer(mActivity);
+ verify(mSplitController).placeActivityInTopContainer(mTransaction, mActivity);
}
@Test
- public void testOnActivityReparentToTask_diffProcess() {
+ public void testOnActivityReparentedToTask_diffProcess() {
// Create an empty TaskFragment to initialize for the Task.
mSplitController.newContainer(new Intent(), mActivity, TASK_ID);
final IBinder activityToken = new Binder();
final Intent intent = new Intent();
- mSplitController.onActivityReparentToTask(TASK_ID, intent, activityToken);
+ mSplitController.onActivityReparentedToTask(mTransaction, TASK_ID, intent, activityToken);
// Treated as starting new intent
- verify(mSplitController, never()).resolveActivityToContainer(any(), anyBoolean());
+ verify(mSplitController, never()).resolveActivityToContainer(any(), any(), anyBoolean());
verify(mSplitController).resolveStartActivityIntent(any(), eq(TASK_ID), eq(intent),
isNull());
}
@@ -504,26 +509,29 @@ public class SplitControllerTest {
@Test
public void testPlaceActivityInTopContainer() {
- mSplitController.placeActivityInTopContainer(mActivity);
+ mSplitController.placeActivityInTopContainer(mTransaction, mActivity);
- verify(mSplitPresenter, never()).applyTransaction(any());
+ verify(mTransaction, never()).reparentActivityToTaskFragment(any(), any());
- mSplitController.newContainer(new Intent(), mActivity, TASK_ID);
- mSplitController.placeActivityInTopContainer(mActivity);
+ // Place in the top container if there is no other rule matched.
+ final TaskFragmentContainer topContainer = mSplitController
+ .newContainer(new Intent(), mActivity, TASK_ID);
+ mSplitController.placeActivityInTopContainer(mTransaction, mActivity);
- verify(mSplitPresenter).applyTransaction(any());
+ verify(mTransaction).reparentActivityToTaskFragment(topContainer.getTaskFragmentToken(),
+ mActivity.getActivityToken());
// Not reparent if activity is in a TaskFragment.
- clearInvocations(mSplitPresenter);
+ clearInvocations(mTransaction);
mSplitController.newContainer(mActivity, TASK_ID);
- mSplitController.placeActivityInTopContainer(mActivity);
+ mSplitController.placeActivityInTopContainer(mTransaction, mActivity);
- verify(mSplitPresenter, never()).applyTransaction(any());
+ verify(mTransaction, never()).reparentActivityToTaskFragment(any(), any());
}
@Test
public void testResolveActivityToContainer_noRuleMatched() {
- final boolean result = mSplitController.resolveActivityToContainer(mActivity,
+ final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
false /* isOnReparent */);
assertFalse(result);
@@ -535,7 +543,7 @@ public class SplitControllerTest {
setupExpandRule(mActivity);
// When the activity is not in any TaskFragment, create a new expanded TaskFragment for it.
- final boolean result = mSplitController.resolveActivityToContainer(mActivity,
+ final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
false /* isOnReparent */);
final TaskFragmentContainer container = mSplitController.getContainerWithActivity(
mActivity);
@@ -543,7 +551,8 @@ public class SplitControllerTest {
assertTrue(result);
assertNotNull(container);
verify(mSplitController).newContainer(mActivity, TASK_ID);
- verify(mSplitPresenter).expandActivity(container.getTaskFragmentToken(), mActivity);
+ verify(mSplitPresenter).expandActivity(mTransaction, container.getTaskFragmentToken(),
+ mActivity);
}
@Test
@@ -552,11 +561,11 @@ public class SplitControllerTest {
// When the activity is not in any TaskFragment, create a new expanded TaskFragment for it.
final TaskFragmentContainer container = mSplitController.newContainer(mActivity, TASK_ID);
- final boolean result = mSplitController.resolveActivityToContainer(mActivity,
+ final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
false /* isOnReparent */);
assertTrue(result);
- verify(mSplitPresenter).expandTaskFragment(container.getTaskFragmentToken());
+ verify(mSplitPresenter).expandTaskFragment(mTransaction, container.getTaskFragmentToken());
}
@Test
@@ -566,14 +575,15 @@ public class SplitControllerTest {
// When the activity is not in any TaskFragment, create a new expanded TaskFragment for it.
final Activity activity = createMockActivity();
addSplitTaskFragments(activity, mActivity);
- final boolean result = mSplitController.resolveActivityToContainer(mActivity,
+ final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
false /* isOnReparent */);
final TaskFragmentContainer container = mSplitController.getContainerWithActivity(
mActivity);
assertTrue(result);
assertNotNull(container);
- verify(mSplitPresenter).expandActivity(container.getTaskFragmentToken(), mActivity);
+ verify(mSplitPresenter).expandActivity(mTransaction, container.getTaskFragmentToken(),
+ mActivity);
}
@Test
@@ -583,11 +593,11 @@ public class SplitControllerTest {
(SplitPlaceholderRule) mSplitController.getSplitRules().get(0);
// Launch placeholder if the activity is not in any TaskFragment.
- final boolean result = mSplitController.resolveActivityToContainer(mActivity,
+ final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
false /* isOnReparent */);
assertTrue(result);
- verify(mSplitPresenter).startActivityToSide(mActivity, PLACEHOLDER_INTENT,
+ verify(mSplitPresenter).startActivityToSide(mTransaction, mActivity, PLACEHOLDER_INTENT,
mSplitController.getPlaceholderOptions(mActivity, true /* isOnCreated */),
placeholderRule, true /* isPlaceholder */);
}
@@ -600,11 +610,11 @@ public class SplitControllerTest {
final Activity activity = createMockActivity();
mSplitController.newContainer(mActivity, TASK_ID);
mSplitController.newContainer(activity, TASK_ID);
- final boolean result = mSplitController.resolveActivityToContainer(mActivity,
+ final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
false /* isOnReparent */);
assertFalse(result);
- verify(mSplitPresenter, never()).startActivityToSide(any(), any(), any(), any(),
+ verify(mSplitPresenter, never()).startActivityToSide(any(), any(), any(), any(), any(),
anyBoolean());
}
@@ -616,11 +626,11 @@ public class SplitControllerTest {
// Launch placeholder if the activity is in the topmost expanded TaskFragment.
mSplitController.newContainer(mActivity, TASK_ID);
- final boolean result = mSplitController.resolveActivityToContainer(mActivity,
+ final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
false /* isOnReparent */);
assertTrue(result);
- verify(mSplitPresenter).startActivityToSide(mActivity, PLACEHOLDER_INTENT,
+ verify(mSplitPresenter).startActivityToSide(mTransaction, mActivity, PLACEHOLDER_INTENT,
mSplitController.getPlaceholderOptions(mActivity, true /* isOnCreated */),
placeholderRule, true /* isPlaceholder */);
}
@@ -632,11 +642,11 @@ public class SplitControllerTest {
// Don't launch placeholder if the activity is in primary split.
final Activity secondaryActivity = createMockActivity();
addSplitTaskFragments(mActivity, secondaryActivity);
- final boolean result = mSplitController.resolveActivityToContainer(mActivity,
+ final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
false /* isOnReparent */);
assertFalse(result);
- verify(mSplitPresenter, never()).startActivityToSide(any(), any(), any(), any(),
+ verify(mSplitPresenter, never()).startActivityToSide(any(), any(), any(), any(), any(),
anyBoolean());
}
@@ -649,11 +659,11 @@ public class SplitControllerTest {
// Launch placeholder if the activity is in secondary split.
final Activity primaryActivity = createMockActivity();
addSplitTaskFragments(primaryActivity, mActivity);
- final boolean result = mSplitController.resolveActivityToContainer(mActivity,
+ final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
false /* isOnReparent */);
assertTrue(result);
- verify(mSplitPresenter).startActivityToSide(mActivity, PLACEHOLDER_INTENT,
+ verify(mSplitPresenter).startActivityToSide(mTransaction, mActivity, PLACEHOLDER_INTENT,
mSplitController.getPlaceholderOptions(mActivity, true /* isOnCreated */),
placeholderRule, true /* isPlaceholder */);
}
@@ -676,7 +686,7 @@ public class SplitControllerTest {
secondaryContainer,
splitRule);
clearInvocations(mSplitController);
- final boolean result = mSplitController.resolveActivityToContainer(mActivity,
+ final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
false /* isOnReparent */);
assertTrue(result);
@@ -705,7 +715,7 @@ public class SplitControllerTest {
final Activity launchedActivity = createMockActivity();
primaryContainer.addPendingAppearedActivity(launchedActivity);
- assertFalse(mSplitController.resolveActivityToContainer(launchedActivity,
+ assertFalse(mSplitController.resolveActivityToContainer(mTransaction, launchedActivity,
false /* isOnReparent */));
}
@@ -717,7 +727,7 @@ public class SplitControllerTest {
// Activity is already in secondary split, no need to create new split.
addSplitTaskFragments(primaryActivity, mActivity);
clearInvocations(mSplitController);
- final boolean result = mSplitController.resolveActivityToContainer(mActivity,
+ final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
false /* isOnReparent */);
assertTrue(result);
@@ -735,7 +745,7 @@ public class SplitControllerTest {
addSplitTaskFragments(primaryActivity, secondaryActivity);
mSplitController.getContainerWithActivity(secondaryActivity)
.addPendingAppearedActivity(mActivity);
- final boolean result = mSplitController.resolveActivityToContainer(mActivity,
+ final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
false /* isOnReparent */);
assertFalse(result);
@@ -760,7 +770,7 @@ public class SplitControllerTest {
mActivity,
secondaryContainer,
placeholderRule);
- final boolean result = mSplitController.resolveActivityToContainer(mActivity,
+ final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
false /* isOnReparent */);
assertTrue(result);
@@ -774,7 +784,7 @@ public class SplitControllerTest {
final TaskFragmentContainer container = mSplitController.newContainer(activityBelow,
TASK_ID);
container.addPendingAppearedActivity(mActivity);
- final boolean result = mSplitController.resolveActivityToContainer(mActivity,
+ final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
false /* isOnReparent */);
assertTrue(result);
@@ -790,14 +800,15 @@ public class SplitControllerTest {
final TaskFragmentContainer container = mSplitController.newContainer(activityBelow,
TASK_ID);
container.addPendingAppearedActivity(mActivity);
- boolean result = mSplitController.resolveActivityToContainer(mActivity,
+ boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
false /* isOnReparent */);
assertFalse(result);
assertEquals(container, mSplitController.getContainerWithActivity(mActivity));
// Allow to split as primary.
- result = mSplitController.resolveActivityToContainer(mActivity, true /* isOnReparent */);
+ result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
+ true /* isOnReparent */);
assertTrue(result);
assertSplitPair(mActivity, activityBelow);
@@ -815,7 +826,7 @@ public class SplitControllerTest {
final TaskFragmentContainer secondaryContainer = mSplitController.getContainerWithActivity(
activityBelow);
secondaryContainer.addPendingAppearedActivity(mActivity);
- final boolean result = mSplitController.resolveActivityToContainer(mActivity,
+ final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
false /* isOnReparent */);
final TaskFragmentContainer container = mSplitController.getContainerWithActivity(
mActivity);
@@ -836,14 +847,15 @@ public class SplitControllerTest {
final TaskFragmentContainer primaryContainer = mSplitController.getContainerWithActivity(
primaryActivity);
primaryContainer.addPendingAppearedActivity(mActivity);
- boolean result = mSplitController.resolveActivityToContainer(mActivity,
+ boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
false /* isOnReparent */);
assertFalse(result);
assertEquals(primaryContainer, mSplitController.getContainerWithActivity(mActivity));
- result = mSplitController.resolveActivityToContainer(mActivity, true /* isOnReparent */);
+ result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
+ true /* isOnReparent */);
assertTrue(result);
assertSplitPair(mActivity, primaryActivity);
@@ -861,7 +873,7 @@ public class SplitControllerTest {
container.addPendingAppearedActivity(mActivity);
// Allow to split as primary.
- boolean result = mSplitController.resolveActivityToContainer(mActivity,
+ boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
true /* isOnReparent */);
assertTrue(result);
@@ -879,15 +891,13 @@ public class SplitControllerTest {
TASK_ID);
container.addPendingAppearedActivity(mActivity);
- boolean result = mSplitController.resolveActivityToContainer(mActivity,
+ boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
false /* isOnReparent */);
assertTrue(result);
assertSplitPair(activityBelow, mActivity, true /* matchParentBounds */);
}
- // Suppress GuardedBy warning on unit tests
- @SuppressWarnings("GuardedBy")
@Test
public void testResolveActivityToContainer_minDimensions_shouldExpandSplitContainer() {
final Activity primaryActivity = createMockActivity();
@@ -899,14 +909,14 @@ public class SplitControllerTest {
doReturn(secondaryActivity).when(mSplitController).findActivityBelow(eq(mActivity));
clearInvocations(mSplitPresenter);
- boolean result = mSplitController.resolveActivityToContainer(mActivity,
+ boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
false /* isOnReparent */);
assertTrue(result);
assertSplitPair(primaryActivity, mActivity, true /* matchParentBounds */);
assertEquals(mSplitController.getContainerWithActivity(secondaryActivity),
mSplitController.getContainerWithActivity(mActivity));
- verify(mSplitPresenter, never()).createNewSplitContainer(any(), any(), any());
+ verify(mSplitPresenter, never()).createNewSplitContainer(any(), any(), any(), any());
}
@Test
@@ -914,7 +924,7 @@ public class SplitControllerTest {
doReturn(new Binder()).when(mSplitController).getInitialTaskFragmentToken(mActivity);
// No need to handle when the new launched activity is in an unknown TaskFragment.
- assertTrue(mSplitController.resolveActivityToContainer(mActivity,
+ assertTrue(mSplitController.resolveActivityToContainer(mTransaction, mActivity,
false /* isOnReparent */));
}
@@ -993,7 +1003,7 @@ public class SplitControllerTest {
private void setupTaskFragmentInfo(@NonNull TaskFragmentContainer container,
@NonNull Activity activity) {
final TaskFragmentInfo info = createMockTaskFragmentInfo(container, activity);
- container.setInfo(info);
+ container.setInfo(mTransaction, info);
mSplitPresenter.mFragmentInfos.put(container.getTaskFragmentToken(), info);
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
index d79319666c01..3fdf8e5d4c4d 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
@@ -78,6 +78,8 @@ import org.mockito.MockitoAnnotations;
* Build/Install/Run:
* atest WMJetpackUnitTests:SplitPresenterTest
*/
+// Suppress GuardedBy warning on unit tests
+@SuppressWarnings("GuardedBy")
@Presubmit
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -226,8 +228,9 @@ public class SplitPresenterTest {
mTransaction, splitContainer, mActivity, secondaryActivity,
null /* secondaryIntent */));
- primaryTf.setInfo(createMockTaskFragmentInfo(primaryTf, mActivity));
- secondaryTf.setInfo(createMockTaskFragmentInfo(secondaryTf, secondaryActivity));
+ primaryTf.setInfo(mTransaction, createMockTaskFragmentInfo(primaryTf, mActivity));
+ secondaryTf.setInfo(mTransaction,
+ createMockTaskFragmentInfo(secondaryTf, secondaryActivity));
assertEquals(RESULT_EXPANDED, mPresenter.expandSplitContainerIfNeeded(mTransaction,
splitContainer, mActivity, secondaryActivity, null /* secondaryIntent */));
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
index 44c7e6c611de..6cbecff81be5 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
@@ -19,6 +19,8 @@ package androidx.window.extensions.embedding;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMockTaskFragmentInfo;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static org.junit.Assert.assertEquals;
@@ -36,7 +38,6 @@ import static org.mockito.Mockito.never;
import android.app.Activity;
import android.content.Intent;
import android.os.Binder;
-import android.os.Handler;
import android.os.IBinder;
import android.platform.test.annotations.Presubmit;
import android.window.TaskFragmentInfo;
@@ -62,25 +63,27 @@ import java.util.List;
* Build/Install/Run:
* atest WMJetpackUnitTests:TaskFragmentContainerTest
*/
+// Suppress GuardedBy warning on unit tests
+@SuppressWarnings("GuardedBy")
@Presubmit
@SmallTest
@RunWith(AndroidJUnit4.class)
public class TaskFragmentContainerTest {
@Mock
private SplitPresenter mPresenter;
- @Mock
private SplitController mController;
@Mock
private TaskFragmentInfo mInfo;
@Mock
- private Handler mHandler;
+ private WindowContainerTransaction mTransaction;
private Activity mActivity;
private Intent mIntent;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
- doReturn(mHandler).when(mController).getHandler();
+ mController = new SplitController();
+ spyOn(mController);
mActivity = createMockActivity();
mIntent = new Intent();
}
@@ -123,7 +126,7 @@ public class TaskFragmentContainerTest {
// Remove all references after the container has appeared in server.
doReturn(new ArrayList<>()).when(mInfo).getActivities();
- container.setInfo(mInfo);
+ container.setInfo(mTransaction, mInfo);
container.finish(true /* shouldFinishDependent */, mPresenter, wct, mController);
verify(mActivity, never()).finish();
@@ -137,7 +140,7 @@ public class TaskFragmentContainerTest {
final TaskFragmentContainer container0 = new TaskFragmentContainer(mActivity,
null /* pendingAppearedIntent */, taskContainer, mController);
final TaskFragmentInfo info = createMockTaskFragmentInfo(container0, mActivity);
- container0.setInfo(info);
+ container0.setInfo(mTransaction, info);
// Request to reparent the activity to a new TaskFragment.
final TaskFragmentContainer container1 = new TaskFragmentContainer(mActivity,
null /* pendingAppearedIntent */, taskContainer, mController);
@@ -163,7 +166,7 @@ public class TaskFragmentContainerTest {
final TaskFragmentInfo info0 = createMockTaskFragmentInfo(pendingActivityContainer,
mActivity);
- pendingActivityContainer.setInfo(info0);
+ pendingActivityContainer.setInfo(mTransaction, info0);
assertTrue(pendingActivityContainer.mPendingAppearedActivities.isEmpty());
@@ -175,7 +178,7 @@ public class TaskFragmentContainerTest {
final TaskFragmentInfo info1 = createMockTaskFragmentInfo(pendingIntentContainer,
mActivity);
- pendingIntentContainer.setInfo(info1);
+ pendingIntentContainer.setInfo(mTransaction, info1);
assertNull(pendingIntentContainer.getPendingAppearedIntent());
}
@@ -191,18 +194,19 @@ public class TaskFragmentContainerTest {
final TaskFragmentInfo info = mock(TaskFragmentInfo.class);
doReturn(new ArrayList<>()).when(info).getActivities();
doReturn(true).when(info).isEmpty();
- container.setInfo(info);
+ container.setInfo(mTransaction, info);
assertTrue(container.isWaitingActivityAppear());
doReturn(false).when(info).isEmpty();
- container.setInfo(info);
+ container.setInfo(mTransaction, info);
assertFalse(container.isWaitingActivityAppear());
}
@Test
public void testAppearEmptyTimeout() {
+ doNothing().when(mController).onTaskFragmentAppearEmptyTimeout(any(), any());
final TaskContainer taskContainer = new TaskContainer(TASK_ID);
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
mIntent, taskContainer, mController);
@@ -213,20 +217,20 @@ public class TaskFragmentContainerTest {
final TaskFragmentInfo info = mock(TaskFragmentInfo.class);
container.mInfo = null;
doReturn(true).when(info).isEmpty();
- container.setInfo(info);
+ container.setInfo(mTransaction, info);
assertNotNull(container.mAppearEmptyTimeout);
// Not set if it is not appeared empty.
doReturn(new ArrayList<>()).when(info).getActivities();
doReturn(false).when(info).isEmpty();
- container.setInfo(info);
+ container.setInfo(mTransaction, info);
assertNull(container.mAppearEmptyTimeout);
// Remove timeout after the container becomes non-empty.
doReturn(false).when(info).isEmpty();
- container.setInfo(info);
+ container.setInfo(mTransaction, info);
assertNull(container.mAppearEmptyTimeout);
@@ -234,7 +238,7 @@ public class TaskFragmentContainerTest {
container.mInfo = null;
container.setPendingAppearedIntent(mIntent);
doReturn(true).when(info).isEmpty();
- container.setInfo(info);
+ container.setInfo(mTransaction, info);
container.mAppearEmptyTimeout.run();
assertNull(container.mAppearEmptyTimeout);
@@ -260,7 +264,7 @@ public class TaskFragmentContainerTest {
final List<IBinder> runningActivities = Lists.newArrayList(activity0.getActivityToken(),
activity1.getActivityToken());
doReturn(runningActivities).when(mInfo).getActivities();
- container.setInfo(mInfo);
+ container.setInfo(mTransaction, mInfo);
activities = container.collectNonFinishingActivities();
assertEquals(3, activities.size());
@@ -295,7 +299,7 @@ public class TaskFragmentContainerTest {
final Activity activity = createMockActivity();
final List<IBinder> runningActivities = Lists.newArrayList(activity.getActivityToken());
doReturn(runningActivities).when(mInfo).getActivities();
- container.setInfo(mInfo);
+ container.setInfo(mTransaction, mInfo);
assertEquals(activity, container.getBottomMostActivity());
}
diff --git a/libs/WindowManager/Shell/res/animator/tv_pip_menu_action_button_animator.xml b/libs/WindowManager/Shell/res/animator/tv_window_menu_action_button_animator.xml
index 7475abac4695..7475abac4695 100644
--- a/libs/WindowManager/Shell/res/animator/tv_pip_menu_action_button_animator.xml
+++ b/libs/WindowManager/Shell/res/animator/tv_window_menu_action_button_animator.xml
diff --git a/libs/WindowManager/Shell/res/color/tv_pip_menu_close_icon.xml b/libs/WindowManager/Shell/res/color/tv_window_menu_close_icon.xml
index ce8640df0093..67467bbc72ae 100644
--- a/libs/WindowManager/Shell/res/color/tv_pip_menu_close_icon.xml
+++ b/libs/WindowManager/Shell/res/color/tv_window_menu_close_icon.xml
@@ -15,5 +15,5 @@
~ limitations under the License.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:color="@color/tv_pip_menu_icon_unfocused" />
+ <item android:color="@color/tv_window_menu_icon_unfocused" />
</selector> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/color/tv_pip_menu_icon_bg.xml b/libs/WindowManager/Shell/res/color/tv_window_menu_close_icon_bg.xml
index 4f5e63dac5c0..4182bfeefa1b 100644
--- a/libs/WindowManager/Shell/res/color/tv_pip_menu_icon_bg.xml
+++ b/libs/WindowManager/Shell/res/color/tv_window_menu_close_icon_bg.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2021 The Android Open Source Project
+ ~ 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.
@@ -16,6 +16,6 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_focused="true"
- android:color="@color/tv_pip_menu_icon_bg_focused" />
- <item android:color="@color/tv_pip_menu_icon_bg_unfocused" />
+ android:color="@color/tv_window_menu_close_icon_bg_focused" />
+ <item android:color="@color/tv_window_menu_close_icon_bg_unfocused" />
</selector> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/color/tv_pip_menu_icon.xml b/libs/WindowManager/Shell/res/color/tv_window_menu_icon.xml
index 275870450493..45205d2a7138 100644
--- a/libs/WindowManager/Shell/res/color/tv_pip_menu_icon.xml
+++ b/libs/WindowManager/Shell/res/color/tv_window_menu_icon.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2021 The Android Open Source Project
+ ~ 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.
@@ -16,8 +16,8 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_focused="true"
- android:color="@color/tv_pip_menu_icon_focused" />
+ android:color="@color/tv_window_menu_icon_focused" />
<item android:state_enabled="false"
- android:color="@color/tv_pip_menu_icon_disabled" />
- <item android:color="@color/tv_pip_menu_icon_unfocused" />
+ android:color="@color/tv_window_menu_icon_disabled" />
+ <item android:color="@color/tv_window_menu_icon_unfocused" />
</selector> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/color/tv_pip_menu_close_icon_bg.xml b/libs/WindowManager/Shell/res/color/tv_window_menu_icon_bg.xml
index 6cbf66f00df7..1bd26e1d6583 100644
--- a/libs/WindowManager/Shell/res/color/tv_pip_menu_close_icon_bg.xml
+++ b/libs/WindowManager/Shell/res/color/tv_window_menu_icon_bg.xml
@@ -16,6 +16,6 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_focused="true"
- android:color="@color/tv_pip_menu_close_icon_bg_focused" />
- <item android:color="@color/tv_pip_menu_close_icon_bg_unfocused" />
+ android:color="@color/tv_window_menu_icon_bg_focused" />
+ <item android:color="@color/tv_window_menu_icon_bg_unfocused" />
</selector> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/tv_pip_menu_border.xml b/libs/WindowManager/Shell/res/drawable/tv_pip_menu_border.xml
index 846fdb3e8a58..7085a2c72c86 100644
--- a/libs/WindowManager/Shell/res/drawable/tv_pip_menu_border.xml
+++ b/libs/WindowManager/Shell/res/drawable/tv_pip_menu_border.xml
@@ -15,7 +15,7 @@
~ limitations under the License.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android"
- android:exitFadeDuration="@integer/pip_menu_fade_animation_duration">
+ android:exitFadeDuration="@integer/tv_window_menu_fade_animation_duration">
<item android:state_activated="true">
<shape android:shape="rectangle">
<corners android:radius="@dimen/pip_menu_border_corner_radius" />
diff --git a/libs/WindowManager/Shell/res/drawable/tv_window_button_bg.xml b/libs/WindowManager/Shell/res/drawable/tv_window_button_bg.xml
new file mode 100644
index 000000000000..2dba37daf059
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/tv_window_button_bg.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <corners android:radius="@dimen/tv_window_menu_button_radius" />
+ <solid android:color="@color/tv_window_menu_icon_bg" />
+</shape> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
index 2d50d3f1392d..8533a5994d33 100644
--- a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
@@ -64,14 +64,14 @@
android:layout_width="@dimen/pip_menu_button_wrapper_margin"
android:layout_height="@dimen/pip_menu_button_wrapper_margin"/>
- <com.android.wm.shell.pip.tv.TvPipMenuActionButton
+ <com.android.wm.shell.common.TvWindowMenuActionButton
android:id="@+id/tv_pip_menu_fullscreen_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/pip_ic_fullscreen_white"
android:text="@string/pip_fullscreen" />
- <com.android.wm.shell.pip.tv.TvPipMenuActionButton
+ <com.android.wm.shell.common.TvWindowMenuActionButton
android:id="@+id/tv_pip_menu_close_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -80,14 +80,14 @@
<!-- More TvPipMenuActionButtons may be added here at runtime. -->
- <com.android.wm.shell.pip.tv.TvPipMenuActionButton
+ <com.android.wm.shell.common.TvWindowMenuActionButton
android:id="@+id/tv_pip_menu_move_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/pip_ic_move_white"
android:text="@string/pip_move" />
- <com.android.wm.shell.pip.tv.TvPipMenuActionButton
+ <com.android.wm.shell.common.TvWindowMenuActionButton
android:id="@+id/tv_pip_menu_expand_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -145,7 +145,7 @@
android:layout_margin="@dimen/pip_menu_outer_space_frame"
android:background="@drawable/tv_pip_menu_border"/>
- <com.android.wm.shell.pip.tv.TvPipMenuActionButton
+ <com.android.wm.shell.common.TvWindowMenuActionButton
android:id="@+id/tv_pip_menu_done_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
diff --git a/libs/WindowManager/Shell/res/layout/tv_pip_menu_action_button.xml b/libs/WindowManager/Shell/res/layout/tv_pip_menu_action_button.xml
deleted file mode 100644
index db96d8de4094..000000000000
--- a/libs/WindowManager/Shell/res/layout/tv_pip_menu_action_button.xml
+++ /dev/null
@@ -1,40 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2020 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<!-- Layout for TvPipMenuActionButton -->
-<FrameLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/button"
- android:layout_width="@dimen/pip_menu_button_size"
- android:layout_height="@dimen/pip_menu_button_size"
- android:padding="@dimen/pip_menu_button_margin"
- android:stateListAnimator="@animator/tv_pip_menu_action_button_animator"
- android:focusable="true">
-
- <View android:id="@+id/background"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_gravity="center"
- android:duplicateParentState="true"
- android:background="@drawable/tv_pip_button_bg"/>
-
- <ImageView android:id="@+id/icon"
- android:layout_width="@dimen/pip_menu_icon_size"
- android:layout_height="@dimen/pip_menu_icon_size"
- android:layout_gravity="center"
- android:duplicateParentState="true"
- android:tint="@color/tv_pip_menu_icon" />
-</FrameLayout> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/tv_window_menu_action_button.xml b/libs/WindowManager/Shell/res/layout/tv_window_menu_action_button.xml
new file mode 100644
index 000000000000..c4dbd39c729a
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/tv_window_menu_action_button.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<!-- Layout for TvWindowMenuActionButton -->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/button"
+ android:layout_width="@dimen/tv_window_menu_button_size"
+ android:layout_height="@dimen/tv_window_menu_button_size"
+ android:padding="@dimen/tv_window_menu_button_margin"
+ android:stateListAnimator="@animator/tv_window_menu_action_button_animator"
+ android:focusable="true">
+
+ <View android:id="@+id/background"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center"
+ android:duplicateParentState="true"
+ android:background="@drawable/tv_window_button_bg"/>
+
+ <ImageView android:id="@+id/icon"
+ android:layout_width="@dimen/tv_window_menu_icon_size"
+ android:layout_height="@dimen/tv_window_menu_icon_size"
+ android:layout_gravity="center"
+ android:duplicateParentState="true"
+ android:tint="@color/tv_window_menu_icon" />
+</FrameLayout> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/values-television/dimen.xml b/libs/WindowManager/Shell/res/values-television/dimen.xml
index 14e89f8b08df..376cc4faca6f 100644
--- a/libs/WindowManager/Shell/res/values-television/dimen.xml
+++ b/libs/WindowManager/Shell/res/values-television/dimen.xml
@@ -21,4 +21,7 @@
<!-- Padding between PIP and keep clear areas that caused it to move. -->
<dimen name="pip_keep_clear_area_padding">16dp</dimen>
+
+ <!-- The corner radius for PiP window. -->
+ <dimen name="pip_corner_radius">0dp</dimen>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml b/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
index 02e726fbc3bf..b45b9ec0c457 100644
--- a/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
+++ b/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
@@ -15,20 +15,20 @@
limitations under the License.
-->
<resources>
- <!-- The dimensions to user for picture-in-picture action buttons. -->
- <dimen name="pip_menu_button_size">48dp</dimen>
- <dimen name="pip_menu_button_radius">20dp</dimen>
- <dimen name="pip_menu_icon_size">20dp</dimen>
- <dimen name="pip_menu_button_margin">4dp</dimen>
- <dimen name="pip_menu_button_wrapper_margin">26dp</dimen>
- <dimen name="pip_menu_border_width">4dp</dimen>
- <integer name="pip_menu_fade_animation_duration">500</integer>
+ <!-- The dimensions to use for tv window menu action buttons. -->
+ <dimen name="tv_window_menu_button_size">48dp</dimen>
+ <dimen name="tv_window_menu_button_radius">20dp</dimen>
+ <dimen name="tv_window_menu_icon_size">20dp</dimen>
+ <dimen name="tv_window_menu_button_margin">4dp</dimen>
+ <integer name="tv_window_menu_fade_animation_duration">500</integer>
<!-- The pip menu front border corner radius is 2dp smaller than
the background corner radius to hide the background from
showing through. -->
<dimen name="pip_menu_border_corner_radius">4dp</dimen>
<dimen name="pip_menu_background_corner_radius">6dp</dimen>
+ <dimen name="pip_menu_border_width">4dp</dimen>
<dimen name="pip_menu_outer_space">24dp</dimen>
+ <dimen name="pip_menu_button_wrapper_margin">26dp</dimen>
<!-- outer space minus border width -->
<dimen name="pip_menu_outer_space_frame">20dp</dimen>
diff --git a/libs/WindowManager/Shell/res/values/colors_tv.xml b/libs/WindowManager/Shell/res/values/colors_tv.xml
index fa90fe36b545..3e71c1010278 100644
--- a/libs/WindowManager/Shell/res/values/colors_tv.xml
+++ b/libs/WindowManager/Shell/res/values/colors_tv.xml
@@ -15,13 +15,15 @@
~ limitations under the License.
-->
<resources>
- <color name="tv_pip_menu_icon_focused">#0E0E0F</color>
- <color name="tv_pip_menu_icon_unfocused">#F8F9FA</color>
- <color name="tv_pip_menu_icon_disabled">#80868B</color>
- <color name="tv_pip_menu_close_icon_bg_focused">#D93025</color>
- <color name="tv_pip_menu_close_icon_bg_unfocused">#D69F261F</color>
- <color name="tv_pip_menu_icon_bg_focused">#E8EAED</color>
- <color name="tv_pip_menu_icon_bg_unfocused">#990E0E0F</color>
+ <color name="tv_window_menu_icon_focused">#0E0E0F</color>
+ <color name="tv_window_menu_icon_unfocused">#F8F9FA</color>
+
+ <color name="tv_window_menu_icon_disabled">#80868B</color>
+ <color name="tv_window_menu_close_icon_bg_focused">#D93025</color>
+ <color name="tv_window_menu_close_icon_bg_unfocused">#D69F261F</color>
+ <color name="tv_window_menu_icon_bg_focused">#E8EAED</color>
+ <color name="tv_window_menu_icon_bg_unfocused">#990E0E0F</color>
+
<color name="tv_pip_menu_focus_border">#E8EAED</color>
<color name="tv_pip_menu_background">#1E232C</color>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java
index 764e650a807c..b085b73d78ce 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java
@@ -16,14 +16,20 @@
package com.android.wm.shell;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
+
+import android.app.WindowConfiguration;
import android.util.SparseArray;
import android.view.SurfaceControl;
import android.window.DisplayAreaAppearedInfo;
import android.window.DisplayAreaInfo;
import android.window.DisplayAreaOrganizer;
+import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull;
+import com.android.internal.protolog.common.ProtoLog;
+
import java.io.PrintWriter;
import java.util.List;
import java.util.concurrent.Executor;
@@ -102,10 +108,44 @@ public class RootDisplayAreaOrganizer extends DisplayAreaOrganizer {
mDisplayAreasInfo.put(displayId, displayAreaInfo);
}
+ /**
+ * Create a {@link WindowContainerTransaction} to update display windowing mode.
+ *
+ * @param displayId display id to update windowing mode for
+ * @param windowingMode target {@link WindowConfiguration.WindowingMode}
+ * @return {@link WindowContainerTransaction} with pending operation to set windowing mode
+ */
+ public WindowContainerTransaction prepareWindowingModeChange(int displayId,
+ @WindowConfiguration.WindowingMode int windowingMode) {
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ DisplayAreaInfo displayAreaInfo = mDisplayAreasInfo.get(displayId);
+ if (displayAreaInfo == null) {
+ ProtoLog.e(WM_SHELL_DESKTOP_MODE,
+ "unable to update windowing mode for display %d display not found", displayId);
+ return wct;
+ }
+
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE,
+ "setWindowingMode: displayId=%d current wmMode=%d new wmMode=%d", displayId,
+ displayAreaInfo.configuration.windowConfiguration.getWindowingMode(),
+ windowingMode);
+
+ wct.setWindowingMode(displayAreaInfo.token, windowingMode);
+ return wct;
+ }
+
public void dump(@NonNull PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
final String childPrefix = innerPrefix + " ";
pw.println(prefix + this);
+
+ for (int i = 0; i < mDisplayAreasInfo.size(); i++) {
+ int displayId = mDisplayAreasInfo.keyAt(i);
+ DisplayAreaInfo displayAreaInfo = mDisplayAreasInfo.get(displayId);
+ int windowingMode =
+ displayAreaInfo.configuration.windowConfiguration.getWindowingMode();
+ pw.println(innerPrefix + "# displayId=" + displayId + " wmMode=" + windowingMode);
+ }
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index 6ae0f9ba0485..d5d4935f0529 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -22,6 +22,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG;
import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
@@ -46,6 +47,7 @@ import android.window.StartingWindowInfo;
import android.window.StartingWindowRemovalInfo;
import android.window.TaskAppearedInfo;
import android.window.TaskOrganizer;
+import android.window.WindowContainerTransaction;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
@@ -690,6 +692,49 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
taskListener.reparentChildSurfaceToTask(taskId, sc, t);
}
+ /**
+ * Create a {@link WindowContainerTransaction} to clear task bounds.
+ *
+ * @param displayId display id for tasks that will have bounds cleared
+ * @return {@link WindowContainerTransaction} with pending operations to clear bounds
+ */
+ public WindowContainerTransaction prepareClearBoundsForTasks(int displayId) {
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE, "prepareClearBoundsForTasks: displayId=%d", displayId);
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ for (int i = 0; i < mTasks.size(); i++) {
+ RunningTaskInfo taskInfo = mTasks.valueAt(i).getTaskInfo();
+ if (taskInfo.displayId == displayId) {
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE, "clearing bounds for token=%s taskInfo=%s",
+ taskInfo.token, taskInfo);
+ wct.setBounds(taskInfo.token, null);
+ }
+ }
+ return wct;
+ }
+
+ /**
+ * Create a {@link WindowContainerTransaction} to clear task level freeform setting.
+ *
+ * @param displayId display id for tasks that will have windowing mode reset to {@link
+ * WindowConfiguration#WINDOWING_MODE_UNDEFINED}
+ * @return {@link WindowContainerTransaction} with pending operations to clear windowing mode
+ */
+ public WindowContainerTransaction prepareClearFreeformForTasks(int displayId) {
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE, "prepareClearFreeformForTasks: displayId=%d", displayId);
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ for (int i = 0; i < mTasks.size(); i++) {
+ RunningTaskInfo taskInfo = mTasks.valueAt(i).getTaskInfo();
+ if (taskInfo.displayId == displayId
+ && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE,
+ "clearing windowing mode for token=%s taskInfo=%s", taskInfo.token,
+ taskInfo);
+ wct.setWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED);
+ }
+ }
+ return wct;
+ }
+
private void logSizeCompatRestartButtonEventReported(@NonNull TaskAppearedInfo info,
int event) {
ActivityInfo topActivityInfo = info.getTaskInfo().topActivityInfo;
@@ -816,7 +861,14 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
final int key = mTasks.keyAt(i);
final TaskAppearedInfo info = mTasks.valueAt(i);
final TaskListener listener = getTaskListener(info.getTaskInfo());
- pw.println(innerPrefix + "#" + i + " task=" + key + " listener=" + listener);
+ final int windowingMode = info.getTaskInfo().getWindowingMode();
+ String pkg = "";
+ if (info.getTaskInfo().baseActivity != null) {
+ pkg = info.getTaskInfo().baseActivity.getPackageName();
+ }
+ Rect bounds = info.getTaskInfo().getConfiguration().windowConfiguration.getBounds();
+ pw.println(innerPrefix + "#" + i + " task=" + key + " listener=" + listener
+ + " wmMode=" + windowingMode + " pkg=" + pkg + " bounds=" + bounds);
}
pw.println();
@@ -826,6 +878,7 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
final TaskListener listener = mLaunchCookieToListener.valueAt(i);
pw.println(innerPrefix + "#" + i + " cookie=" + key + " listener=" + listener);
}
+
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 8771ceb71d98..de26b54971ca 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -1094,13 +1094,16 @@ public class BubbleController implements ConfigurationChangeListener {
}
void updateNotNotifyingEntry(Bubble b, BubbleEntry entry, boolean showInShade) {
+ boolean showInShadeBefore = b.showInShade();
boolean isBubbleSelected = Objects.equals(b, mBubbleData.getSelectedBubble());
boolean isBubbleExpandedAndSelected = isStackExpanded() && isBubbleSelected;
b.setEntry(entry);
boolean suppress = isBubbleExpandedAndSelected || !showInShade || !b.showInShade();
b.setSuppressNotification(suppress);
b.setShowDot(!isBubbleExpandedAndSelected);
- mImpl.mCachedState.updateBubbleSuppressedState(b);
+ if (showInShadeBefore != b.showInShade()) {
+ mImpl.mCachedState.updateBubbleSuppressedState(b);
+ }
}
@VisibleForTesting
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
index d5875c03ccd2..e270edb800bd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
@@ -221,8 +221,7 @@ public class SystemWindows {
}
final Display display = mDisplayController.getDisplay(mDisplayId);
SurfaceControlViewHost viewRoot =
- new SurfaceControlViewHost(
- view.getContext(), display, wwm, true /* useSfChoreographer */);
+ new SurfaceControlViewHost(view.getContext(), display, wwm);
attrs.flags |= FLAG_HARDWARE_ACCELERATED;
viewRoot.setView(view, attrs);
mViewRoots.put(view, viewRoot);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuActionButton.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java
index a09aab666a31..572e3335eb11 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuActionButton.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.pip.tv;
+package com.android.wm.shell.common;
import android.content.Context;
import android.content.res.TypedArray;
@@ -28,33 +28,32 @@ import android.widget.RelativeLayout;
import com.android.wm.shell.R;
/**
- * A View that represents Pip Menu action button, such as "Fullscreen" and "Close" as well custom
- * (provided by the application in Pip) and media buttons.
+ * A common action button for TV window menu layouts.
*/
-public class TvPipMenuActionButton extends RelativeLayout implements View.OnClickListener {
+public class TvWindowMenuActionButton extends RelativeLayout implements View.OnClickListener {
private final ImageView mIconImageView;
private final View mButtonBackgroundView;
private final View mButtonView;
private OnClickListener mOnClickListener;
- public TvPipMenuActionButton(Context context) {
+ public TvWindowMenuActionButton(Context context) {
this(context, null, 0, 0);
}
- public TvPipMenuActionButton(Context context, AttributeSet attrs) {
+ public TvWindowMenuActionButton(Context context, AttributeSet attrs) {
this(context, attrs, 0, 0);
}
- public TvPipMenuActionButton(Context context, AttributeSet attrs, int defStyleAttr) {
+ public TvWindowMenuActionButton(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
- public TvPipMenuActionButton(
+ public TvWindowMenuActionButton(
Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
final LayoutInflater inflater = (LayoutInflater) getContext()
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- inflater.inflate(R.layout.tv_pip_menu_action_button, this);
+ inflater.inflate(R.layout.tv_window_menu_action_button, this);
mIconImageView = findViewById(R.id.icon);
mButtonView = findViewById(R.id.button);
@@ -129,20 +128,27 @@ public class TvPipMenuActionButton extends RelativeLayout implements View.OnClic
return mButtonView.isEnabled();
}
- void setIsCustomCloseAction(boolean isCustomCloseAction) {
+ /**
+ * Marks this button as a custom close action button.
+ * This changes the style of the action button to highlight that this action finishes the
+ * Picture-in-Picture activity.
+ *
+ * @param isCustomCloseAction sets or unsets this button as a custom close action button.
+ */
+ public void setIsCustomCloseAction(boolean isCustomCloseAction) {
mIconImageView.setImageTintList(
getResources().getColorStateList(
- isCustomCloseAction ? R.color.tv_pip_menu_close_icon
- : R.color.tv_pip_menu_icon));
+ isCustomCloseAction ? R.color.tv_window_menu_close_icon
+ : R.color.tv_window_menu_icon));
mButtonBackgroundView.setBackgroundTintList(getResources()
- .getColorStateList(isCustomCloseAction ? R.color.tv_pip_menu_close_icon_bg
- : R.color.tv_pip_menu_icon_bg));
+ .getColorStateList(isCustomCloseAction ? R.color.tv_window_menu_close_icon_bg
+ : R.color.tv_window_menu_icon_bg));
}
@Override
public String toString() {
if (mButtonView.getContentDescription() == null) {
- return TvPipMenuActionButton.class.getSimpleName();
+ return TvWindowMenuActionButton.class.getSimpleName();
}
return mButtonView.getContentDescription().toString();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java
index 35a309a8352c..0cc545a7724a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java
@@ -20,7 +20,6 @@ import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
import static android.os.Process.THREAD_PRIORITY_DISPLAY;
import static android.os.Process.THREAD_PRIORITY_TOP_APP_BOOST;
-import android.animation.AnimationHandler;
import android.content.Context;
import android.os.Build;
import android.os.Handler;
@@ -31,11 +30,9 @@ import android.view.Choreographer;
import androidx.annotation.Nullable;
-import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
import com.android.wm.shell.R;
import com.android.wm.shell.common.HandlerExecutor;
import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.annotations.ChoreographerSfVsync;
import com.android.wm.shell.common.annotations.ExternalMainThread;
import com.android.wm.shell.common.annotations.ShellAnimationThread;
import com.android.wm.shell.common.annotations.ShellBackgroundThread;
@@ -195,30 +192,6 @@ public abstract class WMShellConcurrencyModule {
}
/**
- * Provide a Shell main-thread AnimationHandler. The AnimationHandler can be set on
- * {@link android.animation.ValueAnimator}s and will ensure that the animation will run on
- * the Shell main-thread with the SF vsync.
- */
- @WMSingleton
- @Provides
- @ChoreographerSfVsync
- public static AnimationHandler provideShellMainExecutorSfVsyncAnimationHandler(
- @ShellMainThread ShellExecutor mainExecutor) {
- try {
- AnimationHandler handler = new AnimationHandler();
- mainExecutor.executeBlocking(() -> {
- // This is called on the animation thread since it calls
- // Choreographer.getSfInstance() which returns a thread-local Choreographer instance
- // that uses the SF vsync
- handler.setProvider(new SfVsyncFrameCallbackProvider());
- });
- return handler;
- } catch (InterruptedException e) {
- throw new RuntimeException("Failed to initialize SfVsync animation handler in 1s", e);
- }
- }
-
- /**
* Provides a Shell background thread Handler for low priority background tasks.
*/
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 2ca9c3be8a69..2bcc134f9dd5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -27,6 +27,7 @@ import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.statusbar.IStatusBarService;
import com.android.launcher3.icons.IconProvider;
+import com.android.wm.shell.RootDisplayAreaOrganizer;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.TaskViewTransitions;
@@ -48,6 +49,8 @@ import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.annotations.ShellBackgroundThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.desktopmode.DesktopModeConstants;
+import com.android.wm.shell.desktopmode.DesktopModeController;
import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.freeform.FreeformComponents;
import com.android.wm.shell.freeform.FreeformTaskListener;
@@ -574,6 +577,27 @@ public abstract class WMShellModule {
}
//
+ // Desktop mode (optional feature)
+ //
+
+ @WMSingleton
+ @Provides
+ static Optional<DesktopModeController> provideDesktopModeController(
+ Context context, ShellInit shellInit,
+ ShellTaskOrganizer shellTaskOrganizer,
+ RootDisplayAreaOrganizer rootDisplayAreaOrganizer,
+ @ShellMainThread Handler mainHandler
+ ) {
+ if (DesktopModeConstants.IS_FEATURE_ENABLED) {
+ return Optional.of(new DesktopModeController(context, shellInit, shellTaskOrganizer,
+ rootDisplayAreaOrganizer,
+ mainHandler));
+ } else {
+ return Optional.empty();
+ }
+ }
+
+ //
// Misc
//
@@ -583,7 +607,8 @@ public abstract class WMShellModule {
@ShellCreateTriggerOverride
@Provides
static Object provideIndependentShellComponentsToCreate(
- SplitscreenPipMixedHandler splitscreenPipMixedHandler) {
+ SplitscreenPipMixedHandler splitscreenPipMixedHandler,
+ Optional<DesktopModeController> desktopModeController) {
return new Object();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/common/domain/model/Position.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeConstants.java
index f697c0ab3b22..e62a63a910e7 100644
--- a/packages/SystemUI/src/com/android/systemui/common/domain/model/Position.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeConstants.java
@@ -14,21 +14,18 @@
* limitations under the License.
*/
-package com.android.systemui.common.domain.model
+package com.android.wm.shell.desktopmode;
-import com.android.systemui.common.data.model.Position as DataLayerPosition
+import android.os.SystemProperties;
-/** Models a two-dimensional position */
-data class Position(
- val x: Int,
- val y: Int,
-) {
- companion object {
- fun DataLayerPosition.toDomainLayer(): Position {
- return Position(
- x = x,
- y = y,
- )
- }
- }
+/**
+ * Constants for desktop mode feature
+ */
+public class DesktopModeConstants {
+
+ /**
+ * Flag to indicate whether desktop mode is available on the device
+ */
+ public static final boolean IS_FEATURE_ENABLED = SystemProperties.getBoolean(
+ "persist.wm.debug.desktop_mode", false);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
new file mode 100644
index 000000000000..5849e163f0e2
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
@@ -0,0 +1,137 @@
+/*
+ * 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.wm.shell.desktopmode;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
+
+import android.content.Context;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.window.WindowContainerTransaction;
+
+import androidx.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.RootDisplayAreaOrganizer;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.sysui.ShellInit;
+
+/**
+ * Handles windowing changes when desktop mode system setting changes
+ */
+public class DesktopModeController {
+
+ private final Context mContext;
+ private final ShellTaskOrganizer mShellTaskOrganizer;
+ private final RootDisplayAreaOrganizer mRootDisplayAreaOrganizer;
+ private final SettingsObserver mSettingsObserver;
+
+ public DesktopModeController(Context context, ShellInit shellInit,
+ ShellTaskOrganizer shellTaskOrganizer,
+ RootDisplayAreaOrganizer rootDisplayAreaOrganizer,
+ @ShellMainThread Handler mainHandler) {
+ mContext = context;
+ mShellTaskOrganizer = shellTaskOrganizer;
+ mRootDisplayAreaOrganizer = rootDisplayAreaOrganizer;
+ mSettingsObserver = new SettingsObserver(mContext, mainHandler);
+ shellInit.addInitCallback(this::onInit, this);
+ }
+
+ private void onInit() {
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE, "Initialize DesktopModeController");
+ mSettingsObserver.observe();
+ }
+
+ @VisibleForTesting
+ void updateDesktopModeEnabled(boolean enabled) {
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE, "updateDesktopModeState: enabled=%s", enabled);
+
+ int displayId = mContext.getDisplayId();
+
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ // Reset freeform windowing mode that is set per task level (tasks should inherit
+ // container value)
+ wct.merge(mShellTaskOrganizer.prepareClearFreeformForTasks(displayId), true /* transfer */);
+ int targetWindowingMode;
+ if (enabled) {
+ targetWindowingMode = WINDOWING_MODE_FREEFORM;
+ } else {
+ targetWindowingMode = WINDOWING_MODE_FULLSCREEN;
+ // Clear any resized bounds
+ wct.merge(mShellTaskOrganizer.prepareClearBoundsForTasks(displayId),
+ true /* transfer */);
+ }
+ wct.merge(mRootDisplayAreaOrganizer.prepareWindowingModeChange(displayId,
+ targetWindowingMode), true /* transfer */);
+ mRootDisplayAreaOrganizer.applyTransaction(wct);
+ }
+
+ /**
+ * A {@link ContentObserver} for listening to changes to {@link Settings.System#DESKTOP_MODE}
+ */
+ private final class SettingsObserver extends ContentObserver {
+
+ private final Uri mDesktopModeSetting = Settings.System.getUriFor(
+ Settings.System.DESKTOP_MODE);
+
+ private final Context mContext;
+
+ SettingsObserver(Context context, Handler handler) {
+ super(handler);
+ mContext = context;
+ }
+
+ public void observe() {
+ // TODO(b/242867463): listen for setting change for all users
+ mContext.getContentResolver().registerContentObserver(mDesktopModeSetting,
+ false /* notifyForDescendants */, this /* observer */, UserHandle.USER_CURRENT);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, @Nullable Uri uri) {
+ if (mDesktopModeSetting.equals(uri)) {
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE, "Received update for desktop mode setting");
+ desktopModeSettingChanged();
+ }
+ }
+
+ private void desktopModeSettingChanged() {
+ boolean enabled = isDesktopModeEnabled();
+ updateDesktopModeEnabled(enabled);
+ }
+
+ private boolean isDesktopModeEnabled() {
+ try {
+ int result = Settings.System.getIntForUser(mContext.getContentResolver(),
+ Settings.System.DESKTOP_MODE, UserHandle.USER_CURRENT);
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE, "isDesktopModeEnabled=%s", result);
+ return result != 0;
+ } catch (Settings.SettingNotFoundException e) {
+ ProtoLog.e(WM_SHELL_DESKTOP_MODE, "Failed to read DESKTOP_MODE setting %s", e);
+ return false;
+ }
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
index ff3c0834cf62..497a6f696df8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
@@ -105,6 +105,10 @@ public class DragLayout extends LinearLayout {
MATCH_PARENT));
((LayoutParams) mDropZoneView1.getLayoutParams()).weight = 1;
((LayoutParams) mDropZoneView2.getLayoutParams()).weight = 1;
+ int orientation = getResources().getConfiguration().orientation;
+ setOrientation(orientation == Configuration.ORIENTATION_LANDSCAPE
+ ? LinearLayout.HORIZONTAL
+ : LinearLayout.VERTICAL);
updateContainerMargins(getResources().getConfiguration().orientation);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
index 0e32663955d3..7096a645ef85 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
@@ -111,9 +111,6 @@ public abstract class PipContentOverlay {
private final TaskSnapshot mSnapshot;
private final Rect mSourceRectHint;
- private float mTaskSnapshotScaleX;
- private float mTaskSnapshotScaleY;
-
public PipSnapshotOverlay(TaskSnapshot snapshot, Rect sourceRectHint) {
mSnapshot = snapshot;
mSourceRectHint = new Rect(sourceRectHint);
@@ -125,16 +122,16 @@ public abstract class PipContentOverlay {
@Override
public void attach(SurfaceControl.Transaction tx, SurfaceControl parentLeash) {
- mTaskSnapshotScaleX = (float) mSnapshot.getTaskSize().x
+ final float taskSnapshotScaleX = (float) mSnapshot.getTaskSize().x
/ mSnapshot.getHardwareBuffer().getWidth();
- mTaskSnapshotScaleY = (float) mSnapshot.getTaskSize().y
+ final float taskSnapshotScaleY = (float) mSnapshot.getTaskSize().y
/ mSnapshot.getHardwareBuffer().getHeight();
tx.show(mLeash);
tx.setLayer(mLeash, Integer.MAX_VALUE);
tx.setBuffer(mLeash, mSnapshot.getHardwareBuffer());
// Relocate the content to parentLeash's coordinates.
tx.setPosition(mLeash, -mSourceRectHint.left, -mSourceRectHint.top);
- tx.setScale(mLeash, mTaskSnapshotScaleX, mTaskSnapshotScaleY);
+ tx.setScale(mLeash, taskSnapshotScaleX, taskSnapshotScaleY);
tx.reparent(mLeash, parentLeash);
tx.apply();
}
@@ -146,20 +143,6 @@ public abstract class PipContentOverlay {
@Override
public void onAnimationEnd(SurfaceControl.Transaction atomicTx, Rect destinationBounds) {
- // Work around to make sure the snapshot overlay is aligned with PiP window before
- // the atomicTx is committed along with the final WindowContainerTransaction.
- final SurfaceControl.Transaction nonAtomicTx = new SurfaceControl.Transaction();
- final float scaleX = (float) destinationBounds.width()
- / mSourceRectHint.width();
- final float scaleY = (float) destinationBounds.height()
- / mSourceRectHint.height();
- final float scale = Math.max(
- scaleX * mTaskSnapshotScaleX, scaleY * mTaskSnapshotScaleY);
- nonAtomicTx.setScale(mLeash, scale, scale);
- nonAtomicTx.setPosition(mLeash,
- -scale * mSourceRectHint.left / mTaskSnapshotScaleX,
- -scale * mSourceRectHint.top / mTaskSnapshotScaleY);
- nonAtomicTx.apply();
atomicTx.remove(mLeash);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
index 504dc02eb570..a0a8f9fb2cde 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
@@ -31,8 +31,6 @@ import android.os.RemoteException;
import android.util.Size;
import android.view.MotionEvent;
import android.view.SurfaceControl;
-import android.view.SyncRtSurfaceTransactionApplier;
-import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams;
import android.view.WindowManagerGlobal;
import com.android.internal.protolog.common.ProtoLog;
@@ -42,6 +40,7 @@ import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipMediaController.ActionListener;
import com.android.wm.shell.pip.PipMenuController;
+import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
import com.android.wm.shell.pip.PipUiEventLogger;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -115,6 +114,10 @@ public class PhonePipMenuController implements PipMenuController {
private final ShellExecutor mMainExecutor;
private final Handler mMainHandler;
+ private final PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
+ mSurfaceControlTransactionFactory;
+ private final float[] mTmpTransform = new float[9];
+
private final ArrayList<Listener> mListeners = new ArrayList<>();
private final SystemWindows mSystemWindows;
private final Optional<SplitScreenController> mSplitScreenController;
@@ -124,7 +127,6 @@ public class PhonePipMenuController implements PipMenuController {
private RemoteAction mCloseAction;
private List<RemoteAction> mMediaActions;
- private SyncRtSurfaceTransactionApplier mApplier;
private int mMenuState;
private PipMenuView mPipMenuView;
@@ -150,6 +152,9 @@ public class PhonePipMenuController implements PipMenuController {
mMainHandler = mainHandler;
mSplitScreenController = splitScreenOptional;
mPipUiEventLogger = pipUiEventLogger;
+
+ mSurfaceControlTransactionFactory =
+ new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory();
}
public boolean isMenuVisible() {
@@ -194,7 +199,6 @@ public class PhonePipMenuController implements PipMenuController {
return;
}
- mApplier = null;
mSystemWindows.removeView(mPipMenuView);
mPipMenuView = null;
}
@@ -289,7 +293,7 @@ public class PhonePipMenuController implements PipMenuController {
willResizeMenu, withDelay, showResizeHandle, Debug.getCallers(5, " "));
}
- if (!maybeCreateSyncApplier()) {
+ if (!checkPipMenuState()) {
return;
}
@@ -312,7 +316,7 @@ public class PhonePipMenuController implements PipMenuController {
return;
}
- if (!maybeCreateSyncApplier()) {
+ if (!checkPipMenuState()) {
return;
}
@@ -328,18 +332,15 @@ public class PhonePipMenuController implements PipMenuController {
mTmpSourceRectF.set(mTmpSourceBounds);
mTmpDestinationRectF.set(destinationBounds);
mMoveTransform.setRectToRect(mTmpSourceRectF, mTmpDestinationRectF, Matrix.ScaleToFit.FILL);
- SurfaceControl surfaceControl = getSurfaceControl();
- SurfaceParams params = new SurfaceParams.Builder(surfaceControl)
- .withMatrix(mMoveTransform)
- .build();
+ final SurfaceControl surfaceControl = getSurfaceControl();
+ final SurfaceControl.Transaction menuTx =
+ mSurfaceControlTransactionFactory.getTransaction();
+ menuTx.setMatrix(surfaceControl, mMoveTransform, mTmpTransform);
if (pipLeash != null && t != null) {
- SurfaceParams pipParams = new SurfaceParams.Builder(pipLeash)
- .withMergeTransaction(t)
- .build();
- mApplier.scheduleApply(params, pipParams);
- } else {
- mApplier.scheduleApply(params);
+ // Merge the two transactions, vsyncId has been set on menuTx.
+ menuTx.merge(t);
}
+ menuTx.apply();
}
/**
@@ -353,36 +354,29 @@ public class PhonePipMenuController implements PipMenuController {
return;
}
- if (!maybeCreateSyncApplier()) {
+ if (!checkPipMenuState()) {
return;
}
- SurfaceControl surfaceControl = getSurfaceControl();
- SurfaceParams params = new SurfaceParams.Builder(surfaceControl)
- .withWindowCrop(destinationBounds)
- .build();
+ final SurfaceControl surfaceControl = getSurfaceControl();
+ final SurfaceControl.Transaction menuTx =
+ mSurfaceControlTransactionFactory.getTransaction();
+ menuTx.setCrop(surfaceControl, destinationBounds);
if (pipLeash != null && t != null) {
- SurfaceParams pipParams = new SurfaceParams.Builder(pipLeash)
- .withMergeTransaction(t)
- .build();
- mApplier.scheduleApply(params, pipParams);
- } else {
- mApplier.scheduleApply(params);
+ // Merge the two transactions, vsyncId has been set on menuTx.
+ menuTx.merge(t);
}
+ menuTx.apply();
}
- private boolean maybeCreateSyncApplier() {
+ private boolean checkPipMenuState() {
if (mPipMenuView == null || mPipMenuView.getViewRootImpl() == null) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: Not going to move PiP, either menu or its parent is not created.", TAG);
return false;
}
- if (mApplier == null) {
- mApplier = new SyncRtSurfaceTransactionApplier(mPipMenuView);
- }
-
- return mApplier != null;
+ return true;
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java
index a0e22011b5d0..7619646804ad 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java
@@ -288,8 +288,10 @@ public class PipDismissTargetHandler implements ViewTreeObserver.OnPreDrawListen
if (mTargetViewContainer.getVisibility() != View.VISIBLE) {
mTargetViewContainer.getViewTreeObserver().addOnPreDrawListener(this);
- mTargetViewContainer.show();
}
+ // always invoke show, since the target might still be VISIBLE while playing hide animation,
+ // so we want to ensure it will show back again
+ mTargetViewContainer.show();
}
/** Animates the magnetic dismiss target out and then sets it to GONE. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java
index 0f3ff36601fb..8e3376f163c1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java
@@ -146,11 +146,8 @@ public class PipInputConsumer {
"%s: Failed to create input consumer, %s", TAG, e);
}
mMainExecutor.execute(() -> {
- // Choreographer.getSfInstance() must be called on the thread that the input event
- // receiver should be receiving events
- // TODO(b/222697646): remove getSfInstance usage and use vsyncId for transactions
mInputEventReceiver = new InputEventReceiver(inputChannel,
- Looper.myLooper(), Choreographer.getSfInstance());
+ Looper.myLooper(), Choreographer.getInstance());
if (mRegistrationListener != null) {
mRegistrationListener.onRegistrationChanged(true /* isRegistered */);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
index abf1a9500e6d..89d85e4b292d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
@@ -625,8 +625,7 @@ public class PipResizeGestureHandler {
class PipResizeInputEventReceiver extends BatchedInputEventReceiver {
PipResizeInputEventReceiver(InputChannel channel, Looper looper) {
- // TODO(b/222697646): remove getSfInstance usage and use vsyncId for transactions
- super(channel, looper, Choreographer.getSfInstance());
+ super(channel, looper, Choreographer.getInstance());
}
public void onInputEvent(InputEvent event) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
index 57d3a44ed2af..4d7c8465bcc2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
@@ -56,6 +56,7 @@ import androidx.annotation.Nullable;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
+import com.android.wm.shell.common.TvWindowMenuActionButton;
import com.android.wm.shell.pip.PipUtils;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
@@ -79,7 +80,7 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
private final LinearLayout mActionButtonsContainer;
private final View mMenuFrameView;
- private final List<TvPipMenuActionButton> mAdditionalButtons = new ArrayList<>();
+ private final List<TvWindowMenuActionButton> mAdditionalButtons = new ArrayList<>();
private final View mPipFrameView;
private final View mPipView;
private final TextView mEduTextView;
@@ -94,7 +95,7 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
private final ImageView mArrowRight;
private final ImageView mArrowDown;
private final ImageView mArrowLeft;
- private final TvPipMenuActionButton mA11yDoneButton;
+ private final TvWindowMenuActionButton mA11yDoneButton;
private final ScrollView mScrollView;
private final HorizontalScrollView mHorizontalScrollView;
@@ -104,8 +105,8 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
private boolean mMoveMenuIsVisible;
private boolean mButtonMenuIsVisible;
- private final TvPipMenuActionButton mExpandButton;
- private final TvPipMenuActionButton mCloseButton;
+ private final TvWindowMenuActionButton mExpandButton;
+ private final TvWindowMenuActionButton mCloseButton;
private boolean mSwitchingOrientation;
@@ -166,7 +167,7 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
mResizeAnimationDuration = context.getResources().getInteger(
R.integer.config_pipResizeAnimationDuration);
mPipMenuFadeAnimationDuration = context.getResources()
- .getInteger(R.integer.pip_menu_fade_animation_duration);
+ .getInteger(R.integer.tv_window_menu_fade_animation_duration);
mPipMenuOuterSpace = context.getResources()
.getDimensionPixelSize(R.dimen.pip_menu_outer_space);
@@ -568,7 +569,7 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
if (actionsNumber > buttonsNumber) {
// Add buttons until we have enough to display all the actions.
while (actionsNumber > buttonsNumber) {
- TvPipMenuActionButton button = new TvPipMenuActionButton(mContext);
+ TvWindowMenuActionButton button = new TvWindowMenuActionButton(mContext);
button.setOnClickListener(this);
mActionButtonsContainer.addView(button,
@@ -591,7 +592,7 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
// "Assign" actions to the buttons.
for (int index = 0; index < actionsNumber; index++) {
final RemoteAction action = actions.get(index);
- final TvPipMenuActionButton button = mAdditionalButtons.get(index);
+ final TvWindowMenuActionButton button = mAdditionalButtons.get(index);
// Remove action if it matches the custom close action.
if (PipUtils.remoteActionsMatch(action, closeAction)) {
@@ -607,7 +608,7 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
}
}
- private void setActionForButton(RemoteAction action, TvPipMenuActionButton button,
+ private void setActionForButton(RemoteAction action, TvWindowMenuActionButton button,
Handler mainHandler) {
button.setVisibility(View.VISIBLE); // Ensure the button is visible.
if (action.getContentDescription().length() > 0) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
index b2961518b66a..93c75299a64b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
@@ -46,6 +46,8 @@ public enum ShellProtoLogGroup implements IProtoLogGroup {
Consts.TAG_WM_SHELL),
WM_SHELL_SYSUI_EVENTS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
Consts.TAG_WM_SHELL),
+ WM_SHELL_DESKTOP_MODE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
+ Consts.TAG_WM_SHELL),
TEST_GROUP(true, true, false, "WindowManagerShellProtoLogTest");
private final boolean mEnabled;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java
index 681d9647d154..7fd03a9a306b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java
@@ -56,7 +56,7 @@ public class SplitScreenShellCommandHandler implements
return false;
}
final int taskId = new Integer(args[1]);
- final int sideStagePosition = args.length > 3
+ final int sideStagePosition = args.length > 2
? new Integer(args[2]) : SPLIT_POSITION_BOTTOM_OR_RIGHT;
mController.moveToSideStage(taskId, sideStagePosition);
return true;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 4bc8e913ec4e..7e83d2fa0a0b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -1374,21 +1374,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
}
} else if (isSideStage && hasChildren && !mMainStage.isActive()) {
- if (mFocusingTaskInfo != null && !isValidToEnterSplitScreen(mFocusingTaskInfo)) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- mSideStage.removeAllTasks(wct, true);
- wct.reorder(mRootTaskInfo.token, false /* onTop */);
- mTaskOrganizer.applyTransaction(wct);
- Slog.i(TAG, "cancel entering split screen, reason = "
- + exitReasonToString(EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW));
- } else {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- mSplitLayout.init();
- prepareEnterSplitScreen(wct);
- mSyncQueue.queue(wct);
- mSyncQueue.runInSync(t ->
- updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */));
- }
+ // TODO (b/238697912) : Add the validation to prevent entering non-recovered status
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ mSplitLayout.init();
+ prepareEnterSplitScreen(wct);
+ mSyncQueue.queue(wct);
+ mSyncQueue.runInSync(t ->
+ updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */));
}
if (mMainStageListener.mHasChildren && mSideStageListener.mHasChildren) {
mShouldUpdateRecents = true;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
index b70bde3a64ee..7b498e4f54ec 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
@@ -246,7 +246,7 @@ public class TaskSnapshotWindow {
window.setOuter(snapshotSurface);
try {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "TaskSnapshot#relayout");
- session.relayout(window, layoutParams, -1, -1, View.VISIBLE, 0,
+ session.relayout(window, layoutParams, -1, -1, View.VISIBLE, 0, 0, 0,
tmpFrames, tmpMergedConfiguration, surfaceControl, tmpInsetsState,
tmpControls, new Bundle());
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 08eb2c9b6bbe..6c659667a4a7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -64,6 +64,7 @@ import android.animation.ValueAnimator;
import android.annotation.ColorInt;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityManager;
import android.app.ActivityThread;
import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
@@ -203,14 +204,24 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
}
@VisibleForTesting
- static boolean isRotationSeamless(@NonNull TransitionInfo info,
- DisplayController displayController) {
+ static int getRotationAnimationHint(@NonNull TransitionInfo.Change displayChange,
+ @NonNull TransitionInfo info, @NonNull DisplayController displayController) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
- "Display is changing, check if it should be seamless.");
- boolean checkedDisplayLayout = false;
- boolean hasTask = false;
- boolean displayExplicitSeamless = false;
- for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ "Display is changing, resolve the animation hint.");
+ // The explicit request of display has the highest priority.
+ if (displayChange.getRotationAnimation() == ROTATION_ANIMATION_SEAMLESS) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ " display requests explicit seamless");
+ return ROTATION_ANIMATION_SEAMLESS;
+ }
+
+ boolean allTasksSeamless = false;
+ boolean rejectSeamless = false;
+ ActivityManager.RunningTaskInfo topTaskInfo = null;
+ int animationHint = ROTATION_ANIMATION_ROTATE;
+ // Traverse in top-to-bottom order so that the first task is top-most.
+ final int size = info.getChanges().size();
+ for (int i = 0; i < size; ++i) {
final TransitionInfo.Change change = info.getChanges().get(i);
// Only look at changing things. showing/hiding don't need to rotate.
@@ -223,95 +234,69 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
if ((change.getFlags() & FLAG_DISPLAY_HAS_ALERT_WINDOWS) != 0) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
" display has system alert windows, so not seamless.");
- return false;
+ rejectSeamless = true;
}
- displayExplicitSeamless =
- change.getRotationAnimation() == ROTATION_ANIMATION_SEAMLESS;
} else if ((change.getFlags() & FLAG_IS_WALLPAPER) != 0) {
if (change.getRotationAnimation() != ROTATION_ANIMATION_SEAMLESS) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
" wallpaper is participating but isn't seamless.");
- return false;
+ rejectSeamless = true;
}
} else if (change.getTaskInfo() != null) {
- hasTask = true;
+ final int anim = change.getRotationAnimation();
+ final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+ final boolean isTopTask = topTaskInfo == null;
+ if (isTopTask) {
+ topTaskInfo = taskInfo;
+ if (anim != ROTATION_ANIMATION_UNSPECIFIED
+ && anim != ROTATION_ANIMATION_SEAMLESS) {
+ animationHint = anim;
+ }
+ }
// We only enable seamless rotation if all the visible task windows requested it.
- if (change.getRotationAnimation() != ROTATION_ANIMATION_SEAMLESS) {
+ if (anim != ROTATION_ANIMATION_SEAMLESS) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
" task %s isn't requesting seamless, so not seamless.",
- change.getTaskInfo().taskId);
- return false;
- }
-
- // This is the only way to get display-id currently, so we will check display
- // capabilities here
- if (!checkedDisplayLayout) {
- // only need to check display once.
- checkedDisplayLayout = true;
- final DisplayLayout displayLayout = displayController.getDisplayLayout(
- change.getTaskInfo().displayId);
- // For the upside down rotation we don't rotate seamlessly as the navigation
- // bar moves position. Note most apps (using orientation:sensor or user as
- // opposed to fullSensor) will not enter the reverse portrait orientation, so
- // actually the orientation won't change at all.
- int upsideDownRotation = displayLayout.getUpsideDownRotation();
- if (change.getStartRotation() == upsideDownRotation
- || change.getEndRotation() == upsideDownRotation) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
- " rotation involves upside-down portrait, so not seamless.");
- return false;
- }
-
- // If the navigation bar can't change sides, then it will jump when we change
- // orientations and we don't rotate seamlessly - unless that is allowed, eg.
- // with gesture navigation where the navbar is low-profile enough that this
- // isn't very noticeable.
- if (!displayLayout.allowSeamlessRotationDespiteNavBarMoving()
- && (!(displayLayout.navigationBarCanMove()
- && (change.getStartAbsBounds().width()
- != change.getStartAbsBounds().height())))) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
- " nav bar changes sides, so not seamless.");
- return false;
- }
+ taskInfo.taskId);
+ allTasksSeamless = false;
+ } else if (isTopTask) {
+ allTasksSeamless = true;
}
}
}
- // ROTATION_ANIMATION_SEAMLESS can only be requested by task or display.
- if (hasTask || displayExplicitSeamless) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Rotation IS seamless.");
- return true;
+ if (!allTasksSeamless || rejectSeamless) {
+ return animationHint;
}
- return false;
- }
- /**
- * Gets the rotation animation for the topmost task. Assumes that seamless is checked
- * elsewhere, so it will default SEAMLESS to ROTATE.
- */
- private int getRotationAnimation(@NonNull TransitionInfo info) {
- // Traverse in top-to-bottom order so that the first task is top-most
- for (int i = 0; i < info.getChanges().size(); ++i) {
- final TransitionInfo.Change change = info.getChanges().get(i);
-
- // Only look at changing things. showing/hiding don't need to rotate.
- if (change.getMode() != TRANSIT_CHANGE) continue;
-
- // This container isn't rotating, so we can ignore it.
- if (change.getEndRotation() == change.getStartRotation()) continue;
+ // This is the only way to get display-id currently, so check display capabilities here.
+ final DisplayLayout displayLayout = displayController.getDisplayLayout(
+ topTaskInfo.displayId);
+ // For the upside down rotation we don't rotate seamlessly as the navigation bar moves
+ // position. Note most apps (using orientation:sensor or user as opposed to fullSensor)
+ // will not enter the reverse portrait orientation, so actually the orientation won't
+ // change at all.
+ final int upsideDownRotation = displayLayout.getUpsideDownRotation();
+ if (displayChange.getStartRotation() == upsideDownRotation
+ || displayChange.getEndRotation() == upsideDownRotation) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ " rotation involves upside-down portrait, so not seamless.");
+ return animationHint;
+ }
- if (change.getTaskInfo() != null) {
- final int anim = change.getRotationAnimation();
- if (anim == ROTATION_ANIMATION_UNSPECIFIED
- // Fallback animation for seamless should also be default.
- || anim == ROTATION_ANIMATION_SEAMLESS) {
- return ROTATION_ANIMATION_ROTATE;
- }
- return anim;
- }
+ // If the navigation bar can't change sides, then it will jump when we change orientations
+ // and we don't rotate seamlessly - unless that is allowed, e.g. with gesture navigation
+ // where the navbar is low-profile enough that this isn't very noticeable.
+ if (!displayLayout.allowSeamlessRotationDespiteNavBarMoving()
+ && (!(displayLayout.navigationBarCanMove()
+ && (displayChange.getStartAbsBounds().width()
+ != displayChange.getStartAbsBounds().height())))) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ " nav bar changes sides, so not seamless.");
+ return animationHint;
}
- return ROTATION_ANIMATION_ROTATE;
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Rotation IS seamless.");
+ return ROTATION_ANIMATION_SEAMLESS;
}
@Override
@@ -354,8 +339,8 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
if (change.getMode() == TRANSIT_CHANGE && (change.getFlags() & FLAG_IS_DISPLAY) != 0) {
if (info.getType() == TRANSIT_CHANGE) {
- isSeamlessDisplayChange = isRotationSeamless(info, mDisplayController);
- final int anim = getRotationAnimation(info);
+ final int anim = getRotationAnimationHint(change, info, mDisplayController);
+ isSeamlessDisplayChange = anim == ROTATION_ANIMATION_SEAMLESS;
if (!(isSeamlessDisplayChange || anim == ROTATION_ANIMATION_JUMPCUT)) {
startRotationAnimation(startTransaction, change, info, anim, animations,
onAnimFinish);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
index 45b69f17a861..6388ca13090e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
@@ -86,8 +86,6 @@ class ScreenRotationAnimation {
private final float[] mTmpFloats = new float[9];
/** The leash of the changing window container. */
private final SurfaceControl mSurfaceControl;
- private final Rect mStartBounds = new Rect();
- private final Rect mEndBounds = new Rect();
private final int mAnimHint;
private final int mStartWidth;
@@ -105,8 +103,7 @@ class ScreenRotationAnimation {
*/
private SurfaceControl mBackColorSurface;
/** The leash using to animate screenshot layer. */
- private SurfaceControl mAnimLeash;
- private Transaction mTransaction;
+ private final SurfaceControl mAnimLeash;
// The current active animation to move from the old to the new rotated
// state. Which animation is run here will depend on the old and new
@@ -134,9 +131,6 @@ class ScreenRotationAnimation {
mStartRotation = change.getStartRotation();
mEndRotation = change.getEndRotation();
- mStartBounds.set(change.getStartAbsBounds());
- mEndBounds.set(change.getEndAbsBounds());
-
mAnimLeash = new SurfaceControl.Builder(session)
.setParent(rootLeash)
.setEffectLayer()
@@ -169,6 +163,8 @@ class ScreenRotationAnimation {
t.setLayer(mAnimLeash, SCREEN_FREEZE_LAYER_BASE);
t.show(mAnimLeash);
+ // Crop the real content in case it contains a larger child layer, e.g. wallpaper.
+ t.setCrop(mSurfaceControl, new Rect(0, 0, mEndWidth, mEndHeight));
final ColorSpace colorSpace = screenshotBuffer.getColorSpace();
final HardwareBuffer hardwareBuffer = screenshotBuffer.getHardwareBuffer();
@@ -306,7 +302,6 @@ class ScreenRotationAnimation {
mRotateEnterAnimation.restrictDuration(MAX_ANIMATION_DURATION);
mRotateEnterAnimation.scaleCurrentDuration(animationScale);
- mTransaction = mTransactionPool.acquire();
if (customRotate) {
mRotateAlphaAnimation.initialize(mEndWidth, mEndHeight, mStartWidth, mStartHeight);
mRotateAlphaAnimation.restrictDuration(MAX_ANIMATION_DURATION);
@@ -386,22 +381,16 @@ class ScreenRotationAnimation {
}
public void kill() {
- Transaction t = mTransaction != null ? mTransaction : mTransactionPool.acquire();
+ final Transaction t = mTransactionPool.acquire();
if (mAnimLeash.isValid()) {
t.remove(mAnimLeash);
}
- if (mScreenshotLayer != null) {
- if (mScreenshotLayer.isValid()) {
- t.remove(mScreenshotLayer);
- }
- mScreenshotLayer = null;
+ if (mScreenshotLayer != null && mScreenshotLayer.isValid()) {
+ t.remove(mScreenshotLayer);
}
- if (mBackColorSurface != null) {
- if (mBackColorSurface.isValid()) {
- t.remove(mBackColorSurface);
- }
- mBackColorSurface = null;
+ if (mBackColorSurface != null && mBackColorSurface.isValid()) {
+ t.remove(mBackColorSurface);
}
t.apply();
mTransactionPool.release(t);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 9335438cea50..26d0ec637ccf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -23,6 +23,7 @@ import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static android.view.WindowManager.fixScale;
import static android.window.TransitionInfo.FLAG_IS_INPUT_METHOD;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
@@ -167,10 +168,7 @@ public class Transitions implements RemoteCallable<Transitions> {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: Remote");
ContentResolver resolver = mContext.getContentResolver();
- mTransitionAnimationScaleSetting = Settings.Global.getFloat(resolver,
- Settings.Global.TRANSITION_ANIMATION_SCALE,
- mContext.getResources().getFloat(
- R.dimen.config_appTransitionAnimationDurationScaleDefault));
+ mTransitionAnimationScaleSetting = getTransitionAnimationScaleSetting();
dispatchAnimScaleSetting(mTransitionAnimationScaleSetting);
resolver.registerContentObserver(
@@ -185,6 +183,12 @@ public class Transitions implements RemoteCallable<Transitions> {
}
}
+ private float getTransitionAnimationScaleSetting() {
+ return fixScale(Settings.Global.getFloat(mContext.getContentResolver(),
+ Settings.Global.TRANSITION_ANIMATION_SCALE, mContext.getResources().getFloat(
+ R.dimen.config_appTransitionAnimationDurationScaleDefault)));
+ }
+
public ShellTransitions asRemoteTransitions() {
return mImpl;
}
@@ -963,9 +967,7 @@ public class Transitions implements RemoteCallable<Transitions> {
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
- mTransitionAnimationScaleSetting = Settings.Global.getFloat(
- mContext.getContentResolver(), Settings.Global.TRANSITION_ANIMATION_SCALE,
- mTransitionAnimationScaleSetting);
+ mTransitionAnimationScaleSetting = getTransitionAnimationScaleSetting();
mMainExecutor.execute(() -> dispatchAnimScaleSetting(mTransitionAnimationScaleSetting));
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 98b5ee9f0cfb..dc3deb1a927c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -34,6 +34,7 @@ import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.desktopmode.DesktopModeConstants;
/**
* Defines visuals and behaviors of a window decoration of a caption bar and shadows. It works with
@@ -163,7 +164,13 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
View caption = mResult.mRootView.findViewById(R.id.caption);
caption.setOnTouchListener(mOnCaptionTouchListener);
View maximize = caption.findViewById(R.id.maximize_window);
- maximize.setOnClickListener(mOnCaptionButtonClickListener);
+ if (DesktopModeConstants.IS_FEATURE_ENABLED) {
+ // Hide maximize button when desktop mode is available
+ maximize.setVisibility(View.GONE);
+ } else {
+ maximize.setVisibility(View.VISIBLE);
+ maximize.setOnClickListener(mOnCaptionButtonClickListener);
+ }
View close = caption.findViewById(R.id.close_window);
close.setOnClickListener(mOnCaptionButtonClickListener);
View minimize = caption.findViewById(R.id.minimize_window);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 506a4c0f90f3..5e64a06e0326 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -248,7 +248,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
lp.setTrustedOverlay();
if (mViewHost == null) {
mViewHost = mSurfaceControlViewHostFactory.create(mDecorWindowContext, mDisplay,
- mCaptionWindowManager, true);
+ mCaptionWindowManager);
mViewHost.setView(outResult.mRootView, lp);
} else {
mViewHost.relayout(lp);
@@ -345,9 +345,8 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
}
interface SurfaceControlViewHostFactory {
- default SurfaceControlViewHost create(
- Context c, Display d, WindowlessWindowManager wmm, boolean useSfChoreographer) {
- return new SurfaceControlViewHost(c, d, wmm, useSfChoreographer);
+ default SurfaceControlViewHost create(Context c, Display d, WindowlessWindowManager wmm) {
+ return new SurfaceControlViewHost(c, d, wmm);
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt
index 1c587a2078cf..0a54b8c016fb 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt
@@ -49,7 +49,10 @@ abstract class BaseTest @JvmOverloads constructor(
) {
init {
testSpec.setIsTablet(
- WindowManagerStateHelper(instrumentation).currentState.wmState.isTablet
+ WindowManagerStateHelper(
+ instrumentation,
+ clearCacheAfterParsing = false
+ ).currentState.wmState.isTablet
)
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
index 9da51796a8c1..330c9c95e484 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
@@ -84,6 +84,14 @@ fun FlickerTestParameter.layerIsVisibleAtEnd(
}
}
+fun FlickerTestParameter.layerKeepVisible(
+ component: IComponentMatcher
+) {
+ assertLayers {
+ this.isVisible(component)
+ }
+}
+
fun FlickerTestParameter.splitAppLayerBoundsBecomesVisible(
component: IComponentMatcher,
splitLeftTop: Boolean
@@ -128,6 +136,15 @@ fun FlickerTestParameter.splitAppLayerBoundsIsVisibleAtEnd(
}
}
+fun FlickerTestParameter.splitAppLayerBoundsKeepVisible(
+ component: IComponentMatcher,
+ splitLeftTop: Boolean
+) {
+ assertLayers {
+ this.splitAppLayerBoundsSnapToDivider(component, splitLeftTop, endRotation)
+ }
+}
+
fun FlickerTestParameter.splitAppLayerBoundsChanges(
component: IComponentMatcher,
splitLeftTop: Boolean
@@ -190,6 +207,14 @@ fun FlickerTestParameter.appWindowIsVisibleAtEnd(
}
}
+fun FlickerTestParameter.appWindowKeepVisible(
+ component: IComponentMatcher
+) {
+ assertWm {
+ this.isAppWindowVisible(component)
+ }
+}
+
fun FlickerTestParameter.dockedStackDividerIsVisibleAtEnd() {
assertLayersEnd {
this.isVisible(DOCKED_STACK_DIVIDER_COMPONENT)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt
index 42b7b1162d93..a1226e682e05 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt
@@ -47,6 +47,7 @@ class SplitScreenHelper(
const val NOTIFICATION_SCROLLER = "notification_stack_scroller"
const val DIVIDER_BAR = "docked_divider_handle"
const val GESTURE_STEP_MS = 16L
+ const val LONG_PRESS_TIME_MS = 100L
private val notificationScrollerSelector: BySelector
get() = By.res(SYSTEM_UI_PACKAGE_NAME, NOTIFICATION_SCROLLER)
@@ -83,6 +84,13 @@ class SplitScreenHelper(
Components.SendNotificationActivity.COMPONENT.toFlickerComponent()
)
+ fun getIme(instrumentation: Instrumentation): SplitScreenHelper =
+ SplitScreenHelper(
+ instrumentation,
+ Components.ImeActivity.LABEL,
+ Components.ImeActivity.COMPONENT.toFlickerComponent()
+ )
+
fun waitForSplitComplete(
wmHelper: WindowManagerStateHelper,
primaryApp: IComponentMatcher,
@@ -207,6 +215,16 @@ class SplitScreenHelper(
}
}
+ fun longPress(
+ instrumentation: Instrumentation,
+ point: Point
+ ) {
+ val downTime = SystemClock.uptimeMillis()
+ touch(instrumentation, MotionEvent.ACTION_DOWN, downTime, downTime, TIMEOUT_MS, point)
+ SystemClock.sleep(LONG_PRESS_TIME_MS)
+ touch(instrumentation, MotionEvent.ACTION_UP, downTime, downTime, TIMEOUT_MS, point)
+ }
+
fun createShortcutOnHotseatIfNotExist(
tapl: LauncherInstrumentation,
appName: String
@@ -258,5 +276,33 @@ class SplitScreenHelper(
SystemClock.sleep(interval.toLong())
dividerBar.click()
}
+
+ fun copyContentFromLeftToRight(
+ instrumentation: Instrumentation,
+ device: UiDevice,
+ sourceApp: IComponentMatcher,
+ destinationApp: IComponentMatcher,
+ ) {
+ // Copy text from sourceApp
+ val textView = device.wait(Until.findObject(
+ By.res(sourceApp.packageNames.firstOrNull(), "SplitScreenTest")), TIMEOUT_MS)
+ longPress(instrumentation, textView.getVisibleCenter())
+
+ val copyBtn = device.wait(Until.findObject(By.text("Copy")), TIMEOUT_MS)
+ copyBtn.click()
+
+ // Paste text to destinationApp
+ val editText = device.wait(Until.findObject(
+ By.res(destinationApp.packageNames.firstOrNull(), "plain_text_input")), TIMEOUT_MS)
+ longPress(instrumentation, editText.getVisibleCenter())
+
+ val pasteBtn = device.wait(Until.findObject(By.text("Paste")), TIMEOUT_MS)
+ pasteBtn.click()
+
+ // Verify text
+ if (!textView.getText().contentEquals(editText.getText())) {
+ error("Fail to copy content in split")
+ }
+ }
}
}
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
new file mode 100644
index 000000000000..f69107eae638
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
@@ -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 com.android.wm.shell.flicker.splitscreen
+
+import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
+import android.view.WindowManagerPolicyConstants
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.annotation.Group1
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
+import com.android.wm.shell.flicker.appWindowKeepVisible
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import com.android.wm.shell.flicker.layerKeepVisible
+import com.android.wm.shell.flicker.splitAppLayerBoundsKeepVisible
+import org.junit.Assume
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test copy content from the left to the right side of the split-screen.
+ *
+ * To run this test: `atest WMShellFlickerTests:CopyContentInSplit`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group1
+class CopyContentInSplit(testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
+ protected val textEditApp = SplitScreenHelper.getIme(instrumentation)
+
+ // TODO(b/231399940): Remove this once we can use recent shortcut to enter split.
+ @Before
+ open fun before() {
+ Assume.assumeTrue(tapl.isTablet)
+ }
+
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ super.transition(this)
+ setup {
+ eachRun {
+ textEditApp.launchViaIntent(wmHelper)
+ // TODO(b/231399940): Use recent shortcut to enter split.
+ tapl.launchedAppState.taskbar
+ .openAllApps()
+ .getAppIcon(primaryApp.appName)
+ .dragToSplitscreen(primaryApp.`package`, textEditApp.`package`)
+ SplitScreenHelper.waitForSplitComplete(wmHelper, textEditApp, primaryApp)
+ }
+ }
+ transitions {
+ SplitScreenHelper.copyContentFromLeftToRight(
+ instrumentation, device, primaryApp, textEditApp)
+ }
+ }
+
+ @Presubmit
+ @Test
+ fun splitScreenDividerKeepVisible() = testSpec.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
+
+ @Presubmit
+ @Test
+ fun primaryAppLayerKeepVisible() = testSpec.layerKeepVisible(primaryApp)
+
+ @Presubmit
+ @Test
+ fun textEditAppLayerKeepVisible() = testSpec.layerKeepVisible(textEditApp)
+
+ @Presubmit
+ @Test
+ fun primaryAppBoundsKeepVisible() = testSpec.splitAppLayerBoundsKeepVisible(
+ primaryApp, splitLeftTop = true)
+
+ @Presubmit
+ @Test
+ fun textEditAppBoundsKeepVisible() = testSpec.splitAppLayerBoundsKeepVisible(
+ textEditApp, splitLeftTop = false)
+
+ @Presubmit
+ @Test
+ fun primaryAppWindowKeepVisible() = testSpec.appWindowKeepVisible(primaryApp)
+
+ @Presubmit
+ @Test
+ fun textEditAppWindowKeepVisible() = testSpec.appWindowKeepVisible(textEditApp)
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun entireScreenCovered() =
+ super.entireScreenCovered()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun navBarLayerIsVisibleAtStartAndEnd() =
+ super.navBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun navBarLayerPositionAtStartAndEnd() =
+ super.navBarLayerPositionAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun navBarWindowIsAlwaysVisible() =
+ super.navBarWindowIsAlwaysVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun statusBarLayerIsVisibleAtStartAndEnd() =
+ super.statusBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun statusBarLayerPositionAtStartAndEnd() =
+ super.statusBarLayerPositionAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun statusBarWindowIsAlwaysVisible() =
+ super.statusBarWindowIsAlwaysVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun taskBarLayerIsVisibleAtStartAndEnd() =
+ super.taskBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun taskBarWindowIsAlwaysVisible() =
+ super.taskBarWindowIsAlwaysVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTestParameter> {
+ return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
+ repetitions = SplitScreenHelper.TEST_REPETITIONS,
+ // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
+ supportedNavigationModes =
+ listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY))
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
index cf2dc39637df..0f4d98d69c00 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
@@ -27,7 +27,9 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group1
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
+import com.android.wm.shell.flicker.appWindowKeepVisible
import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import com.android.wm.shell.flicker.layerKeepVisible
import com.android.wm.shell.flicker.splitAppLayerBoundsChanges
import org.junit.Assume
import org.junit.Before
@@ -77,19 +79,11 @@ class DragDividerToResize (testSpec: FlickerTestParameter) : SplitScreenBase(tes
@Presubmit
@Test
- fun splitScreenDividerKeepVisible() {
- testSpec.assertLayers {
- this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
- }
- }
+ fun splitScreenDividerKeepVisible() = testSpec.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
@Presubmit
@Test
- fun primaryAppLayerKeepVisible() {
- testSpec.assertLayers {
- this.isVisible(primaryApp)
- }
- }
+ fun primaryAppLayerKeepVisible() = testSpec.layerKeepVisible(primaryApp)
@Presubmit
@Test
@@ -105,19 +99,11 @@ class DragDividerToResize (testSpec: FlickerTestParameter) : SplitScreenBase(tes
@Presubmit
@Test
- fun primaryAppWindowKeepVisible() {
- testSpec.assertWm {
- this.isAppWindowVisible(primaryApp)
- }
- }
+ fun primaryAppWindowKeepVisible() = testSpec.appWindowKeepVisible(primaryApp)
@Presubmit
@Test
- fun secondaryAppWindowKeepVisible() {
- testSpec.assertWm {
- this.isAppWindowVisible(secondaryApp)
- }
- }
+ fun secondaryAppWindowKeepVisible() = testSpec.appWindowKeepVisible(secondaryApp)
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
index 38279a3dfd17..bdfd9c7de32f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
@@ -29,6 +29,7 @@ import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
import com.android.wm.shell.flicker.helpers.SplitScreenHelper
import com.android.wm.shell.flicker.layerIsVisibleAtEnd
+import com.android.wm.shell.flicker.layerKeepVisible
import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
import org.junit.Assume
import org.junit.Before
@@ -80,11 +81,7 @@ class SwitchAppByDoubleTapDivider (testSpec: FlickerTestParameter) : SplitScreen
@Presubmit
@Test
- fun splitScreenDividerKeepVisible() {
- testSpec.assertLayers {
- this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
- }
- }
+ fun splitScreenDividerKeepVisible() = testSpec.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_splitscreen.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_splitscreen.xml
index 84789f5a6c02..642a08b5bbe0 100644
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_splitscreen.xml
+++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_splitscreen.xml
@@ -26,6 +26,7 @@
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center_vertical|center_horizontal"
+ android:textIsSelectable="true"
android:text="PrimaryActivity"
android:textAppearance="?android:attr/textAppearanceLarge"/>
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
index f865649b6bbc..b29c436d0d51 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
@@ -16,9 +16,11 @@
package com.android.wm.shell;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -30,6 +32,8 @@ import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIO
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeFalse;
@@ -38,9 +42,11 @@ import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.TaskInfo;
+import android.app.WindowConfiguration;
import android.content.LocusId;
import android.content.pm.ParceledListSlice;
import android.os.Binder;
@@ -53,6 +59,8 @@ import android.window.ITaskOrganizer;
import android.window.ITaskOrganizerController;
import android.window.TaskAppearedInfo;
import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+import android.window.WindowContainerTransaction.Change;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -628,6 +636,71 @@ public class ShellTaskOrganizerTests extends ShellTestCase {
verify(mTaskOrganizerController).restartTaskTopActivityProcessIfVisible(task1.token);
}
+ @Test
+ public void testPrepareClearBoundsForTasks() {
+ RunningTaskInfo task1 = createTaskInfo(1, WINDOWING_MODE_UNDEFINED);
+ task1.displayId = 1;
+ MockToken token1 = new MockToken();
+ task1.token = token1.token();
+ mOrganizer.onTaskAppeared(task1, null);
+
+ RunningTaskInfo task2 = createTaskInfo(2, WINDOWING_MODE_UNDEFINED);
+ task2.displayId = 1;
+ MockToken token2 = new MockToken();
+ task2.token = token2.token();
+ mOrganizer.onTaskAppeared(task2, null);
+
+ RunningTaskInfo otherDisplayTask = createTaskInfo(3, WINDOWING_MODE_UNDEFINED);
+ otherDisplayTask.displayId = 2;
+ MockToken otherDisplayToken = new MockToken();
+ otherDisplayTask.token = otherDisplayToken.token();
+ mOrganizer.onTaskAppeared(otherDisplayTask, null);
+
+ WindowContainerTransaction wct = mOrganizer.prepareClearBoundsForTasks(1);
+
+ assertEquals(wct.getChanges().size(), 2);
+ Change boundsChange1 = wct.getChanges().get(token1.binder());
+ assertNotNull(boundsChange1);
+ assertNotEquals(
+ (boundsChange1.getWindowSetMask() & WindowConfiguration.WINDOW_CONFIG_BOUNDS), 0);
+ assertTrue(boundsChange1.getConfiguration().windowConfiguration.getBounds().isEmpty());
+
+ Change boundsChange2 = wct.getChanges().get(token2.binder());
+ assertNotNull(boundsChange2);
+ assertNotEquals(
+ (boundsChange2.getWindowSetMask() & WindowConfiguration.WINDOW_CONFIG_BOUNDS), 0);
+ assertTrue(boundsChange2.getConfiguration().windowConfiguration.getBounds().isEmpty());
+ }
+
+ @Test
+ public void testPrepareClearFreeformForTasks() {
+ RunningTaskInfo task1 = createTaskInfo(1, WINDOWING_MODE_FREEFORM);
+ task1.displayId = 1;
+ MockToken token1 = new MockToken();
+ task1.token = token1.token();
+ mOrganizer.onTaskAppeared(task1, null);
+
+ RunningTaskInfo task2 = createTaskInfo(2, WINDOWING_MODE_MULTI_WINDOW);
+ task2.displayId = 1;
+ MockToken token2 = new MockToken();
+ task2.token = token2.token();
+ mOrganizer.onTaskAppeared(task2, null);
+
+ RunningTaskInfo otherDisplayTask = createTaskInfo(3, WINDOWING_MODE_FREEFORM);
+ otherDisplayTask.displayId = 2;
+ MockToken otherDisplayToken = new MockToken();
+ otherDisplayTask.token = otherDisplayToken.token();
+ mOrganizer.onTaskAppeared(otherDisplayTask, null);
+
+ WindowContainerTransaction wct = mOrganizer.prepareClearFreeformForTasks(1);
+
+ // Only task with freeform windowing mode and the right display should be updated
+ assertEquals(wct.getChanges().size(), 1);
+ Change wmModeChange1 = wct.getChanges().get(token1.binder());
+ assertNotNull(wmModeChange1);
+ assertEquals(wmModeChange1.getWindowingMode(), WINDOWING_MODE_UNDEFINED);
+ }
+
private static RunningTaskInfo createTaskInfo(int taskId, int windowingMode) {
RunningTaskInfo taskInfo = new RunningTaskInfo();
taskInfo.taskId = taskId;
@@ -635,4 +708,22 @@ public class ShellTaskOrganizerTests extends ShellTestCase {
return taskInfo;
}
+ private static class MockToken {
+ private final WindowContainerToken mToken;
+ private final IBinder mBinder;
+
+ MockToken() {
+ mToken = mock(WindowContainerToken.class);
+ mBinder = mock(IBinder.class);
+ when(mToken.asBinder()).thenReturn(mBinder);
+ }
+
+ WindowContainerToken token() {
+ return mToken;
+ }
+
+ IBinder binder() {
+ return mBinder;
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
new file mode 100644
index 000000000000..58f20da34943
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
@@ -0,0 +1,194 @@
+/*
+ * 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.wm.shell.desktopmode;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.WindowConfiguration;
+import android.os.Handler;
+import android.os.IBinder;
+import android.testing.AndroidTestingRunner;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+import android.window.WindowContainerTransaction.Change;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.RootDisplayAreaOrganizer;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.sysui.ShellInit;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class DesktopModeControllerTest extends ShellTestCase {
+
+ @Mock
+ private ShellTaskOrganizer mShellTaskOrganizer;
+ @Mock
+ private RootDisplayAreaOrganizer mRootDisplayAreaOrganizer;
+ @Mock
+ private ShellExecutor mTestExecutor;
+ @Mock
+ private Handler mMockHandler;
+
+ private DesktopModeController mController;
+ private ShellInit mShellInit;
+
+ @Before
+ public void setUp() {
+ mShellInit = Mockito.spy(new ShellInit(mTestExecutor));
+
+ mController = new DesktopModeController(mContext, mShellInit, mShellTaskOrganizer,
+ mRootDisplayAreaOrganizer, mMockHandler);
+
+ mShellInit.init();
+ }
+
+ @Test
+ public void instantiate_addInitCallback() {
+ verify(mShellInit, times(1)).addInitCallback(any(), any());
+ }
+
+ @Test
+ public void testDesktopModeEnabled_taskWmClearedDisplaySetToFreeform() {
+ // Create a fake WCT to simulate setting task windowing mode to undefined
+ WindowContainerTransaction taskWct = new WindowContainerTransaction();
+ MockToken taskMockToken = new MockToken();
+ taskWct.setWindowingMode(taskMockToken.token(), WINDOWING_MODE_UNDEFINED);
+ when(mShellTaskOrganizer.prepareClearFreeformForTasks(mContext.getDisplayId())).thenReturn(
+ taskWct);
+
+ // Create a fake WCT to simulate setting display windowing mode to freeform
+ WindowContainerTransaction displayWct = new WindowContainerTransaction();
+ MockToken displayMockToken = new MockToken();
+ displayWct.setWindowingMode(displayMockToken.token(), WINDOWING_MODE_FREEFORM);
+ when(mRootDisplayAreaOrganizer.prepareWindowingModeChange(mContext.getDisplayId(),
+ WINDOWING_MODE_FREEFORM)).thenReturn(displayWct);
+
+ // The test
+ mController.updateDesktopModeEnabled(true);
+
+ ArgumentCaptor<WindowContainerTransaction> arg = ArgumentCaptor.forClass(
+ WindowContainerTransaction.class);
+ verify(mRootDisplayAreaOrganizer).applyTransaction(arg.capture());
+
+ // WCT should have 2 changes - clear task wm mode and set display wm mode
+ WindowContainerTransaction wct = arg.getValue();
+ assertThat(wct.getChanges()).hasSize(2);
+
+ // Verify executed WCT has a change for setting task windowing mode to undefined
+ Change taskWmModeChange = wct.getChanges().get(taskMockToken.binder());
+ assertThat(taskWmModeChange).isNotNull();
+ assertThat(taskWmModeChange.getWindowingMode()).isEqualTo(WINDOWING_MODE_UNDEFINED);
+
+ // Verify executed WCT has a change for setting display windowing mode to freeform
+ Change displayWmModeChange = wct.getChanges().get(displayMockToken.binder());
+ assertThat(displayWmModeChange).isNotNull();
+ assertThat(displayWmModeChange.getWindowingMode()).isEqualTo(WINDOWING_MODE_FREEFORM);
+ }
+
+ @Test
+ public void testDesktopModeDisabled_taskWmAndBoundsClearedDisplaySetToFullscreen() {
+ // Create a fake WCT to simulate setting task windowing mode to undefined
+ WindowContainerTransaction taskWmWct = new WindowContainerTransaction();
+ MockToken taskWmMockToken = new MockToken();
+ taskWmWct.setWindowingMode(taskWmMockToken.token(), WINDOWING_MODE_UNDEFINED);
+ when(mShellTaskOrganizer.prepareClearFreeformForTasks(mContext.getDisplayId())).thenReturn(
+ taskWmWct);
+
+ // Create a fake WCT to simulate clearing task bounds
+ WindowContainerTransaction taskBoundsWct = new WindowContainerTransaction();
+ MockToken taskBoundsMockToken = new MockToken();
+ taskBoundsWct.setBounds(taskBoundsMockToken.token(), null);
+ when(mShellTaskOrganizer.prepareClearBoundsForTasks(mContext.getDisplayId())).thenReturn(
+ taskBoundsWct);
+
+ // Create a fake WCT to simulate setting display windowing mode to fullscreen
+ WindowContainerTransaction displayWct = new WindowContainerTransaction();
+ MockToken displayMockToken = new MockToken();
+ displayWct.setWindowingMode(displayMockToken.token(), WINDOWING_MODE_FULLSCREEN);
+ when(mRootDisplayAreaOrganizer.prepareWindowingModeChange(mContext.getDisplayId(),
+ WINDOWING_MODE_FULLSCREEN)).thenReturn(displayWct);
+
+ // The test
+ mController.updateDesktopModeEnabled(false);
+
+ ArgumentCaptor<WindowContainerTransaction> arg = ArgumentCaptor.forClass(
+ WindowContainerTransaction.class);
+ verify(mRootDisplayAreaOrganizer).applyTransaction(arg.capture());
+
+ // WCT should have 3 changes - clear task wm mode and bounds and set display wm mode
+ WindowContainerTransaction wct = arg.getValue();
+ assertThat(wct.getChanges()).hasSize(3);
+
+ // Verify executed WCT has a change for setting task windowing mode to undefined
+ Change taskWmModeChange = wct.getChanges().get(taskWmMockToken.binder());
+ assertThat(taskWmModeChange).isNotNull();
+ assertThat(taskWmModeChange.getWindowingMode()).isEqualTo(WINDOWING_MODE_UNDEFINED);
+
+ // Verify executed WCT has a change for clearing task bounds
+ Change taskBoundsChange = wct.getChanges().get(taskBoundsMockToken.binder());
+ assertThat(taskBoundsChange).isNotNull();
+ assertThat(taskBoundsChange.getWindowSetMask()
+ & WindowConfiguration.WINDOW_CONFIG_BOUNDS).isNotEqualTo(0);
+ assertThat(taskBoundsChange.getConfiguration().windowConfiguration.getBounds().isEmpty())
+ .isTrue();
+
+ // Verify executed WCT has a change for setting display windowing mode to fullscreen
+ Change displayWmModeChange = wct.getChanges().get(displayMockToken.binder());
+ assertThat(displayWmModeChange).isNotNull();
+ assertThat(displayWmModeChange.getWindowingMode()).isEqualTo(WINDOWING_MODE_FULLSCREEN);
+ }
+
+ private static class MockToken {
+ private final WindowContainerToken mToken;
+ private final IBinder mBinder;
+
+ MockToken() {
+ mToken = mock(WindowContainerToken.class);
+ mBinder = mock(IBinder.class);
+ when(mToken.asBinder()).thenReturn(mBinder);
+ }
+
+ WindowContainerToken token() {
+ return mToken;
+ }
+
+ IBinder binder() {
+ return mBinder;
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index b142039e6aa9..c6492bee040e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -22,6 +22,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED;
import static android.view.WindowManager.TRANSIT_CHANGE;
@@ -553,64 +554,77 @@ public class ShellTransitionTests extends ShellTestCase {
final @Surface.Rotation int upsideDown = displays
.getDisplayLayout(DEFAULT_DISPLAY).getUpsideDownRotation();
+ TransitionInfo.Change displayChange = new ChangeBuilder(TRANSIT_CHANGE)
+ .setFlags(FLAG_IS_DISPLAY).setRotate().build();
+ // Set non-square display so nav bar won't be allowed to move.
+ displayChange.getStartAbsBounds().set(0, 0, 1000, 2000);
final TransitionInfo normalDispRotate = new TransitionInfoBuilder(TRANSIT_CHANGE)
- .addChange(new ChangeBuilder(TRANSIT_CHANGE).setFlags(FLAG_IS_DISPLAY).setRotate()
- .build())
+ .addChange(displayChange)
.addChange(new ChangeBuilder(TRANSIT_CHANGE).setTask(taskInfo).setRotate().build())
.build();
- assertFalse(DefaultTransitionHandler.isRotationSeamless(normalDispRotate, displays));
+ assertEquals(ROTATION_ANIMATION_ROTATE, DefaultTransitionHandler.getRotationAnimationHint(
+ displayChange, normalDispRotate, displays));
// Seamless if all tasks are seamless
final TransitionInfo rotateSeamless = new TransitionInfoBuilder(TRANSIT_CHANGE)
- .addChange(new ChangeBuilder(TRANSIT_CHANGE).setFlags(FLAG_IS_DISPLAY).setRotate()
- .build())
+ .addChange(displayChange)
.addChange(new ChangeBuilder(TRANSIT_CHANGE).setTask(taskInfo)
.setRotate(ROTATION_ANIMATION_SEAMLESS).build())
.build();
- assertTrue(DefaultTransitionHandler.isRotationSeamless(rotateSeamless, displays));
+ assertEquals(ROTATION_ANIMATION_SEAMLESS, DefaultTransitionHandler.getRotationAnimationHint(
+ displayChange, rotateSeamless, displays));
// Not seamless if there is PiP (or any other non-seamless task)
final TransitionInfo pipDispRotate = new TransitionInfoBuilder(TRANSIT_CHANGE)
- .addChange(new ChangeBuilder(TRANSIT_CHANGE).setFlags(FLAG_IS_DISPLAY).setRotate()
- .build())
+ .addChange(displayChange)
.addChange(new ChangeBuilder(TRANSIT_CHANGE).setTask(taskInfo)
.setRotate(ROTATION_ANIMATION_SEAMLESS).build())
.addChange(new ChangeBuilder(TRANSIT_CHANGE).setTask(taskInfoPip)
.setRotate().build())
.build();
- assertFalse(DefaultTransitionHandler.isRotationSeamless(pipDispRotate, displays));
+ assertEquals(ROTATION_ANIMATION_ROTATE, DefaultTransitionHandler.getRotationAnimationHint(
+ displayChange, pipDispRotate, displays));
+
+ // Not seamless if there is no changed task.
+ final TransitionInfo noTask = new TransitionInfoBuilder(TRANSIT_CHANGE)
+ .addChange(displayChange)
+ .build();
+ assertEquals(ROTATION_ANIMATION_ROTATE, DefaultTransitionHandler.getRotationAnimationHint(
+ displayChange, noTask, displays));
// Not seamless if one of rotations is upside-down
+ displayChange = new ChangeBuilder(TRANSIT_CHANGE).setFlags(FLAG_IS_DISPLAY)
+ .setRotate(upsideDown, ROTATION_ANIMATION_UNSPECIFIED).build();
final TransitionInfo seamlessUpsideDown = new TransitionInfoBuilder(TRANSIT_CHANGE)
- .addChange(new ChangeBuilder(TRANSIT_CHANGE).setFlags(FLAG_IS_DISPLAY)
- .setRotate(upsideDown, ROTATION_ANIMATION_UNSPECIFIED).build())
+ .addChange(displayChange)
.addChange(new ChangeBuilder(TRANSIT_CHANGE).setTask(taskInfo)
.setRotate(upsideDown, ROTATION_ANIMATION_SEAMLESS).build())
.build();
- assertFalse(DefaultTransitionHandler.isRotationSeamless(seamlessUpsideDown, displays));
+ assertEquals(ROTATION_ANIMATION_ROTATE, DefaultTransitionHandler.getRotationAnimationHint(
+ displayChange, seamlessUpsideDown, displays));
// Not seamless if system alert windows
+ displayChange = new ChangeBuilder(TRANSIT_CHANGE)
+ .setFlags(FLAG_IS_DISPLAY | FLAG_DISPLAY_HAS_ALERT_WINDOWS).setRotate().build();
final TransitionInfo seamlessButAlert = new TransitionInfoBuilder(TRANSIT_CHANGE)
- .addChange(new ChangeBuilder(TRANSIT_CHANGE).setFlags(
- FLAG_IS_DISPLAY | FLAG_DISPLAY_HAS_ALERT_WINDOWS).setRotate().build())
+ .addChange(displayChange)
.addChange(new ChangeBuilder(TRANSIT_CHANGE).setTask(taskInfo)
.setRotate(ROTATION_ANIMATION_SEAMLESS).build())
.build();
- assertFalse(DefaultTransitionHandler.isRotationSeamless(seamlessButAlert, displays));
-
- // Not seamless if there is no changed task.
- final TransitionInfo noTask = new TransitionInfoBuilder(TRANSIT_CHANGE)
- .addChange(new ChangeBuilder(TRANSIT_CHANGE).setFlags(FLAG_IS_DISPLAY)
- .setRotate().build())
- .build();
- assertFalse(DefaultTransitionHandler.isRotationSeamless(noTask, displays));
+ assertEquals(ROTATION_ANIMATION_ROTATE, DefaultTransitionHandler.getRotationAnimationHint(
+ displayChange, seamlessButAlert, displays));
// Seamless if display is explicitly seamless.
+ displayChange = new ChangeBuilder(TRANSIT_CHANGE).setFlags(FLAG_IS_DISPLAY)
+ .setRotate(ROTATION_ANIMATION_SEAMLESS).build();
final TransitionInfo seamlessDisplay = new TransitionInfoBuilder(TRANSIT_CHANGE)
- .addChange(new ChangeBuilder(TRANSIT_CHANGE).setFlags(FLAG_IS_DISPLAY)
- .setRotate(ROTATION_ANIMATION_SEAMLESS).build())
+ .addChange(displayChange)
+ // The animation hint of task will be ignored.
+ .addChange(new ChangeBuilder(TRANSIT_CHANGE).setTask(taskInfo)
+ .setRotate(ROTATION_ANIMATION_ROTATE).build())
.build();
- assertTrue(DefaultTransitionHandler.isRotationSeamless(seamlessDisplay, displays));
+ assertEquals(ROTATION_ANIMATION_SEAMLESS, DefaultTransitionHandler.getRotationAnimationHint(
+ displayChange, seamlessDisplay, displays));
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index 226843eca64e..e11be31aa40e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -24,7 +24,6 @@ import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyBoolean;
import static org.mockito.Mockito.argThat;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
@@ -107,7 +106,7 @@ public class WindowDecorationTests extends ShellTestCase {
mMockSurfaceControlFinishT = createMockSurfaceControlTransaction();
doReturn(mMockSurfaceControlViewHost).when(mMockSurfaceControlViewHostFactory)
- .create(any(), any(), any(), anyBoolean());
+ .create(any(), any(), any());
}
@Test
@@ -148,8 +147,7 @@ public class WindowDecorationTests extends ShellTestCase {
verify(decorContainerSurfaceBuilder, never()).build();
verify(taskBackgroundSurfaceBuilder, never()).build();
- verify(mMockSurfaceControlViewHostFactory, never())
- .create(any(), any(), any(), anyBoolean());
+ verify(mMockSurfaceControlViewHostFactory, never()).create(any(), any(), any());
verify(mMockSurfaceControlFinishT).hide(taskSurface);
@@ -207,8 +205,7 @@ public class WindowDecorationTests extends ShellTestCase {
verify(mMockSurfaceControlStartT).setLayer(taskBackgroundSurface, -1);
verify(mMockSurfaceControlStartT).show(taskBackgroundSurface);
- verify(mMockSurfaceControlViewHostFactory)
- .create(any(), eq(defaultDisplay), any(), anyBoolean());
+ verify(mMockSurfaceControlViewHostFactory).create(any(), eq(defaultDisplay), any());
verify(mMockSurfaceControlViewHost)
.setView(same(mMockView),
argThat(lp -> lp.height == 64
@@ -326,8 +323,7 @@ public class WindowDecorationTests extends ShellTestCase {
verify(mMockDisplayController).removeDisplayWindowListener(same(listener));
assertThat(mRelayoutResult.mRootView).isSameInstanceAs(mMockView);
- verify(mMockSurfaceControlViewHostFactory)
- .create(any(), eq(mockDisplay), any(), anyBoolean());
+ verify(mMockSurfaceControlViewHostFactory).create(any(), eq(mockDisplay), any());
verify(mMockSurfaceControlViewHost).setView(same(mMockView), any());
}
diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp
index 2beb33abe782..9aa37872b8de 100755
--- a/libs/androidfw/ApkAssets.cpp
+++ b/libs/androidfw/ApkAssets.cpp
@@ -141,6 +141,9 @@ std::unique_ptr<ApkAssets> ApkAssets::LoadImpl(std::unique_ptr<Asset> resources_
return {};
}
loaded_arsc = LoadedArsc::Load(data, length, loaded_idmap.get(), property_flags);
+ } else if (loaded_idmap != nullptr &&
+ IsFabricatedOverlay(std::string(loaded_idmap->OverlayApkPath()))) {
+ loaded_arsc = LoadedArsc::Load(loaded_idmap.get());
} else {
loaded_arsc = LoadedArsc::CreateEmpty();
}
diff --git a/libs/androidfw/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp
index 35b6170fae5b..5b69cca2d747 100644
--- a/libs/androidfw/LoadedArsc.cpp
+++ b/libs/androidfw/LoadedArsc.cpp
@@ -820,6 +820,13 @@ bool LoadedArsc::LoadTable(const Chunk& chunk, const LoadedIdmap* loaded_idmap,
return true;
}
+bool LoadedArsc::LoadStringPool(const LoadedIdmap* loaded_idmap) {
+ if (loaded_idmap != nullptr) {
+ global_string_pool_ = util::make_unique<OverlayStringPool>(loaded_idmap);
+ }
+ return true;
+}
+
std::unique_ptr<LoadedArsc> LoadedArsc::Load(incfs::map_ptr<void> data,
const size_t length,
const LoadedIdmap* loaded_idmap,
@@ -855,6 +862,16 @@ std::unique_ptr<LoadedArsc> LoadedArsc::Load(incfs::map_ptr<void> data,
return loaded_arsc;
}
+std::unique_ptr<LoadedArsc> LoadedArsc::Load(const LoadedIdmap* loaded_idmap) {
+ ATRACE_NAME("LoadedArsc::Load");
+
+ // Not using make_unique because the constructor is private.
+ std::unique_ptr<LoadedArsc> loaded_arsc(new LoadedArsc());
+ loaded_arsc->LoadStringPool(loaded_idmap);
+ return loaded_arsc;
+}
+
+
std::unique_ptr<LoadedArsc> LoadedArsc::CreateEmpty() {
return std::unique_ptr<LoadedArsc>(new LoadedArsc());
}
diff --git a/libs/androidfw/include/androidfw/LoadedArsc.h b/libs/androidfw/include/androidfw/LoadedArsc.h
index b3d6a4dcb955..e45963950b04 100644
--- a/libs/androidfw/include/androidfw/LoadedArsc.h
+++ b/libs/androidfw/include/androidfw/LoadedArsc.h
@@ -314,6 +314,8 @@ class LoadedArsc {
const LoadedIdmap* loaded_idmap = nullptr,
package_property_t property_flags = 0U);
+ static std::unique_ptr<LoadedArsc> Load(const LoadedIdmap* loaded_idmap = nullptr);
+
// Create an empty LoadedArsc. This is used when an APK has no resources.arsc.
static std::unique_ptr<LoadedArsc> CreateEmpty();
@@ -338,6 +340,7 @@ class LoadedArsc {
LoadedArsc() = default;
bool LoadTable(
const Chunk& chunk, const LoadedIdmap* loaded_idmap, package_property_t property_flags);
+ bool LoadStringPool(const LoadedIdmap* loaded_idmap);
std::unique_ptr<ResStringPool> global_string_pool_ = util::make_unique<ResStringPool>();
std::vector<std::unique_ptr<const LoadedPackage>> packages_;
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index 3d66244646d5..8c614bcf7145 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -53,7 +53,7 @@ constexpr const uint32_t kFabricatedOverlayMagic = 0x4f525246; // FRRO (big endi
// The version should only be changed when a backwards-incompatible change must be made to the
// fabricated overlay file format. Old fabricated overlays must be migrated to the new file format
// to prevent losing fabricated overlay data.
-constexpr const uint32_t kFabricatedOverlayCurrentVersion = 1;
+constexpr const uint32_t kFabricatedOverlayCurrentVersion = 2;
// Returns whether or not the path represents a fabricated overlay.
bool IsFabricatedOverlay(const std::string& path);
diff --git a/libs/hwui/FrameInfoVisualizer.cpp b/libs/hwui/FrameInfoVisualizer.cpp
index 3a8e559f6d7e..687e4dd324d3 100644
--- a/libs/hwui/FrameInfoVisualizer.cpp
+++ b/libs/hwui/FrameInfoVisualizer.cpp
@@ -179,7 +179,7 @@ void FrameInfoVisualizer::initializeRects(const int baseline, const int width) {
void FrameInfoVisualizer::nextBarSegment(FrameInfoIndex start, FrameInfoIndex end) {
int fast_i = (mNumFastRects - 1) * 4;
int janky_i = (mNumJankyRects - 1) * 4;
- ;
+
for (size_t fi = 0; fi < mFrameSource.size(); fi++) {
if (mFrameSource[fi][FrameInfoIndex::Flags] & FrameInfoFlags::SkippedFrame) {
continue;
diff --git a/libs/hwui/hwui/Typeface.cpp b/libs/hwui/hwui/Typeface.cpp
index 5a9d2508230e..9a4bda2ee1df 100644
--- a/libs/hwui/hwui/Typeface.cpp
+++ b/libs/hwui/hwui/Typeface.cpp
@@ -127,7 +127,7 @@ Typeface* Typeface::createWithDifferentBaseWeight(Typeface* src, int weight) {
Typeface* Typeface::createFromFamilies(std::vector<std::shared_ptr<minikin::FontFamily>>&& families,
int weight, int italic) {
Typeface* result = new Typeface;
- result->fFontCollection.reset(new minikin::FontCollection(families));
+ result->fFontCollection = minikin::FontCollection::create(families);
if (weight == RESOLVE_BY_FONT_TABLE || italic == RESOLVE_BY_FONT_TABLE) {
int weightFromFont;
@@ -191,8 +191,8 @@ void Typeface::setRobotoTypefaceForTest() {
std::vector<std::shared_ptr<minikin::Font>> fonts;
fonts.push_back(minikin::Font::Builder(font).build());
- std::shared_ptr<minikin::FontCollection> collection = std::make_shared<minikin::FontCollection>(
- std::make_shared<minikin::FontFamily>(std::move(fonts)));
+ std::shared_ptr<minikin::FontCollection> collection =
+ minikin::FontCollection::create(minikin::FontFamily::create(std::move(fonts)));
Typeface* hwTypeface = new Typeface();
hwTypeface->fFontCollection = collection;
diff --git a/libs/hwui/jni/FontFamily.cpp b/libs/hwui/jni/FontFamily.cpp
index acc1b0424030..c146adac6b69 100644
--- a/libs/hwui/jni/FontFamily.cpp
+++ b/libs/hwui/jni/FontFamily.cpp
@@ -85,9 +85,9 @@ static jlong FontFamily_create(CRITICAL_JNI_PARAMS_COMMA jlong builderPtr) {
if (builder->fonts.empty()) {
return 0;
}
- std::shared_ptr<minikin::FontFamily> family = std::make_shared<minikin::FontFamily>(
- builder->langId, builder->variant, std::move(builder->fonts),
- true /* isCustomFallback */);
+ std::shared_ptr<minikin::FontFamily> family =
+ minikin::FontFamily::create(builder->langId, builder->variant,
+ std::move(builder->fonts), true /* isCustomFallback */);
if (family->getCoverage().length() == 0) {
return 0;
}
diff --git a/libs/hwui/jni/fonts/FontFamily.cpp b/libs/hwui/jni/fonts/FontFamily.cpp
index b68213549938..fbfc07e1f89d 100644
--- a/libs/hwui/jni/fonts/FontFamily.cpp
+++ b/libs/hwui/jni/fonts/FontFamily.cpp
@@ -66,9 +66,9 @@ static jlong FontFamily_Builder_build(JNIEnv* env, jobject clazz, jlong builderP
ScopedUtfChars str(env, langTags);
localeId = minikin::registerLocaleList(str.c_str());
}
- std::shared_ptr<minikin::FontFamily> family = std::make_shared<minikin::FontFamily>(
- localeId, static_cast<minikin::FamilyVariant>(variant), std::move(builder->fonts),
- isCustomFallback);
+ std::shared_ptr<minikin::FontFamily> family =
+ minikin::FontFamily::create(localeId, static_cast<minikin::FamilyVariant>(variant),
+ std::move(builder->fonts), isCustomFallback);
if (family->getCoverage().length() == 0) {
// No coverage means minikin rejected given font for some reasons.
jniThrowException(env, "java/lang/IllegalArgumentException",
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
index 2aca41e41905..62e42b8e1863 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
@@ -112,7 +112,7 @@ IRenderPipeline::DrawResult SkiaOpenGLPipeline::draw(
if (CC_UNLIKELY(Properties::showDirtyRegions ||
ProfileType::None != Properties::getProfileType())) {
SkCanvas* profileCanvas = surface->getCanvas();
- SkiaProfileRenderer profileRenderer(profileCanvas);
+ SkiaProfileRenderer profileRenderer(profileCanvas, frame.width(), frame.height());
profiler->draw(profileRenderer);
}
diff --git a/libs/hwui/pipeline/skia/SkiaProfileRenderer.cpp b/libs/hwui/pipeline/skia/SkiaProfileRenderer.cpp
index 492c39f1288c..81cfc5d93419 100644
--- a/libs/hwui/pipeline/skia/SkiaProfileRenderer.cpp
+++ b/libs/hwui/pipeline/skia/SkiaProfileRenderer.cpp
@@ -33,13 +33,5 @@ void SkiaProfileRenderer::drawRects(const float* rects, int count, const SkPaint
}
}
-uint32_t SkiaProfileRenderer::getViewportWidth() {
- return mCanvas->imageInfo().width();
-}
-
-uint32_t SkiaProfileRenderer::getViewportHeight() {
- return mCanvas->imageInfo().height();
-}
-
} /* namespace uirenderer */
} /* namespace android */
diff --git a/libs/hwui/pipeline/skia/SkiaProfileRenderer.h b/libs/hwui/pipeline/skia/SkiaProfileRenderer.h
index dc8420f4e01b..96d2a5e58139 100644
--- a/libs/hwui/pipeline/skia/SkiaProfileRenderer.h
+++ b/libs/hwui/pipeline/skia/SkiaProfileRenderer.h
@@ -23,18 +23,21 @@ namespace uirenderer {
class SkiaProfileRenderer : public IProfileRenderer {
public:
- explicit SkiaProfileRenderer(SkCanvas* canvas) : mCanvas(canvas) {}
+ explicit SkiaProfileRenderer(SkCanvas* canvas, uint32_t width, uint32_t height)
+ : mCanvas(canvas), mWidth(width), mHeight(height) {}
void drawRect(float left, float top, float right, float bottom, const SkPaint& paint) override;
void drawRects(const float* rects, int count, const SkPaint& paint) override;
- uint32_t getViewportWidth() override;
- uint32_t getViewportHeight() override;
+ uint32_t getViewportWidth() override { return mWidth; }
+ uint32_t getViewportHeight() override { return mHeight; }
virtual ~SkiaProfileRenderer() {}
private:
// Does not have ownership.
SkCanvas* mCanvas;
+ uint32_t mWidth;
+ uint32_t mHeight;
};
} /* namespace uirenderer */
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
index 905d46e58014..53a4c60cf8a8 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
@@ -88,7 +88,9 @@ IRenderPipeline::DrawResult SkiaVulkanPipeline::draw(
if (CC_UNLIKELY(Properties::showDirtyRegions ||
ProfileType::None != Properties::getProfileType())) {
SkCanvas* profileCanvas = backBuffer->getCanvas();
- SkiaProfileRenderer profileRenderer(profileCanvas);
+ SkAutoCanvasRestore saver(profileCanvas, true);
+ profileCanvas->concat(mVkSurface->getCurrentPreTransform());
+ SkiaProfileRenderer profileRenderer(profileCanvas, frame.width(), frame.height());
profiler->draw(profileRenderer);
}
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 976117b9bbd4..75d3ff7753cb 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -512,9 +512,19 @@ nsecs_t CanvasContext::draw() {
ATRACE_FORMAT("Drawing " RECT_STRING, SK_RECT_ARGS(dirty));
- const auto drawResult = mRenderPipeline->draw(frame, windowDirty, dirty, mLightGeometry,
- &mLayerUpdateQueue, mContentDrawBounds, mOpaque,
- mLightInfo, mRenderNodes, &(profiler()));
+ IRenderPipeline::DrawResult drawResult;
+ {
+ // FrameInfoVisualizer accesses the frame events, which cannot be mutated mid-draw
+ // or it can lead to memory corruption.
+ // This lock is overly broad, but it's the quickest fix since this mutex is otherwise
+ // not visible to IRenderPipeline much less FrameInfoVisualizer. And since this is
+ // the thread we're primarily concerned about being responsive, this being too broad
+ // shouldn't pose a performance issue.
+ std::scoped_lock lock(mFrameMetricsReporterMutex);
+ drawResult = mRenderPipeline->draw(frame, windowDirty, dirty, mLightGeometry,
+ &mLayerUpdateQueue, mContentDrawBounds, mOpaque,
+ mLightInfo, mRenderNodes, &(profiler()));
+ }
uint64_t frameCompleteNr = getFrameNumber();
@@ -754,11 +764,11 @@ void CanvasContext::onSurfaceStatsAvailable(void* context, int32_t surfaceContro
FrameInfo* frameInfo = instance->getFrameInfoFromLast4(frameNumber, surfaceControlId);
if (frameInfo != nullptr) {
+ std::scoped_lock lock(instance->mFrameMetricsReporterMutex);
frameInfo->set(FrameInfoIndex::FrameCompleted) = std::max(gpuCompleteTime,
frameInfo->get(FrameInfoIndex::SwapBuffersCompleted));
frameInfo->set(FrameInfoIndex::GpuCompleted) = std::max(
gpuCompleteTime, frameInfo->get(FrameInfoIndex::CommandSubmissionCompleted));
- std::scoped_lock lock(instance->mFrameMetricsReporterMutex);
instance->mJankTracker.finishFrame(*frameInfo, instance->mFrameMetricsReporter, frameNumber,
surfaceControlId);
}
diff --git a/libs/hwui/tests/unit/TypefaceTests.cpp b/libs/hwui/tests/unit/TypefaceTests.cpp
index 9295a938f397..25cc8ca0dafb 100644
--- a/libs/hwui/tests/unit/TypefaceTests.cpp
+++ b/libs/hwui/tests/unit/TypefaceTests.cpp
@@ -64,7 +64,7 @@ std::shared_ptr<minikin::FontFamily> buildFamily(const char* fileName) {
std::vector<minikin::FontVariation>());
std::vector<std::shared_ptr<minikin::Font>> fonts;
fonts.push_back(minikin::Font::Builder(font).build());
- return std::make_shared<minikin::FontFamily>(std::move(fonts));
+ return minikin::FontFamily::create(std::move(fonts));
}
std::vector<std::shared_ptr<minikin::FontFamily>> makeSingleFamlyVector(const char* fileName) {
diff --git a/location/java/android/location/util/identity/CallerIdentity.java b/location/java/android/location/util/identity/CallerIdentity.java
index ade0ea40e157..2f8d92b06f03 100644
--- a/location/java/android/location/util/identity/CallerIdentity.java
+++ b/location/java/android/location/util/identity/CallerIdentity.java
@@ -124,15 +124,22 @@ public final class CallerIdentity {
packageName, attributionTag, listenerId);
}
+ // in some tests these constants are loaded too early leading to an "incorrect" view of the
+ // current pid and uid. load lazily to prevent this problem in tests.
+ private static class Loader {
+ private static final int MY_UID = Process.myUid();
+ private static final int MY_PID = Process.myPid();
+ }
+
private final int mUid;
private final int mPid;
private final String mPackageName;
- private final @Nullable String mAttributionTag;
+ @Nullable private final String mAttributionTag;
- private final @Nullable String mListenerId;
+ @Nullable private final String mListenerId;
private CallerIdentity(int uid, int pid, String packageName,
@Nullable String attributionTag, @Nullable String listenerId) {
@@ -181,6 +188,24 @@ public final class CallerIdentity {
return mUid == Process.SYSTEM_UID;
}
+ /** Returns true if this identity represents the same user this code is running in. */
+ public boolean isMyUser() {
+ return UserHandle.getUserId(mUid) == UserHandle.getUserId(Loader.MY_UID);
+ }
+
+ /** Returns true if this identity represents the same uid this code is running in. */
+ public boolean isMyUid() {
+ return mUid == Loader.MY_UID;
+ }
+
+ /**
+ * Returns true if this identity represents the same process this code is running in. Returns
+ * false if the identity process is unknown.
+ */
+ public boolean isMyProcess() {
+ return mPid == Loader.MY_PID;
+ }
+
/**
* Adds this identity to the worksource supplied, or if not worksource is supplied, creates a
* new worksource representing this identity.
diff --git a/lowpan/java/Android.bp b/lowpan/java/Android.bp
deleted file mode 100644
index 58513d70042c..000000000000
--- a/lowpan/java/Android.bp
+++ /dev/null
@@ -1,17 +0,0 @@
-package {
- // See: http://go/android-license-faq
- // A large-scale-change added 'default_applicable_licenses' to import
- // all of the 'license_kinds' from "frameworks_base_license"
- // to get the below license kinds:
- // SPDX-license-identifier-Apache-2.0
- default_applicable_licenses: ["frameworks_base_license"],
-}
-
-filegroup {
- name: "framework-lowpan-sources",
- srcs: [
- "**/*.java",
- "**/*.aidl",
- ],
- visibility: ["//frameworks/base"],
-}
diff --git a/lowpan/java/android/net/lowpan/ILowpanInterface.aidl b/lowpan/java/android/net/lowpan/ILowpanInterface.aidl
deleted file mode 100644
index 603dc3cfe641..000000000000
--- a/lowpan/java/android/net/lowpan/ILowpanInterface.aidl
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-import android.net.IpPrefix;
-import android.net.lowpan.ILowpanEnergyScanCallback;
-import android.net.lowpan.ILowpanInterfaceListener;
-import android.net.lowpan.ILowpanNetScanCallback;
-import android.net.lowpan.LowpanBeaconInfo;
-import android.net.lowpan.LowpanChannelInfo;
-import android.net.lowpan.LowpanCredential;
-import android.net.lowpan.LowpanIdentity;
-import android.net.lowpan.LowpanProvision;
-
-/** {@hide} */
-interface ILowpanInterface {
-
- // These are here for the sake of C++ interface implementations.
-
- const String PERM_ACCESS_LOWPAN_STATE = "android.permission.ACCESS_LOWPAN_STATE";
- const String PERM_CHANGE_LOWPAN_STATE = "android.permission.CHANGE_LOWPAN_STATE";
- const String PERM_READ_LOWPAN_CREDENTIAL = "android.permission.READ_LOWPAN_CREDENTIAL";
-
- /**
- * Channel mask key.
- * Used for setting a channel mask when starting a scan.
- * Type: int[]
- * */
- const String KEY_CHANNEL_MASK = "android.net.lowpan.property.CHANNEL_MASK";
-
- /**
- * Max Transmit Power Key.
- * Used for setting the maximum transmit power when starting a network scan.
- * Type: Integer
- * */
- const String KEY_MAX_TX_POWER = "android.net.lowpan.property.MAX_TX_POWER";
-
- // Interface States
-
- const String STATE_OFFLINE = "offline";
- const String STATE_COMMISSIONING = "commissioning";
- const String STATE_ATTACHING = "attaching";
- const String STATE_ATTACHED = "attached";
- const String STATE_FAULT = "fault";
-
- // Device Roles
-
- const String ROLE_END_DEVICE = "end-device";
- const String ROLE_ROUTER = "router";
- const String ROLE_SLEEPY_END_DEVICE = "sleepy-end-device";
- const String ROLE_SLEEPY_ROUTER = "sleepy-router";
- const String ROLE_LEADER = "leader";
- const String ROLE_COORDINATOR = "coordinator";
- const String ROLE_DETACHED = "detached";
-
- const String NETWORK_TYPE_UNKNOWN = "unknown";
-
- /**
- * Network type for Thread 1.x networks.
- *
- * @see android.net.lowpan.LowpanIdentity#getType
- * @see #getLowpanIdentity
- */
- const String NETWORK_TYPE_THREAD_V1 = "org.threadgroup.thread.v1";
-
- // Service-Specific Error Code Constants
-
- const int ERROR_UNSPECIFIED = 1;
- const int ERROR_INVALID_ARGUMENT = 2;
- const int ERROR_DISABLED = 3;
- const int ERROR_WRONG_STATE = 4;
- const int ERROR_TIMEOUT = 5;
- const int ERROR_IO_FAILURE = 6;
- const int ERROR_NCP_PROBLEM = 7;
- const int ERROR_BUSY = 8;
- const int ERROR_ALREADY = 9;
- const int ERROR_CANCELED = 10;
- const int ERROR_FEATURE_NOT_SUPPORTED = 11;
- const int ERROR_JOIN_FAILED_UNKNOWN = 12;
- const int ERROR_JOIN_FAILED_AT_SCAN = 13;
- const int ERROR_JOIN_FAILED_AT_AUTH = 14;
- const int ERROR_FORM_FAILED_AT_SCAN = 15;
-
- // Methods
-
- @utf8InCpp String getName();
-
- @utf8InCpp String getNcpVersion();
- @utf8InCpp String getDriverVersion();
- LowpanChannelInfo[] getSupportedChannels();
- @utf8InCpp String[] getSupportedNetworkTypes();
- byte[] getMacAddress();
-
- boolean isEnabled();
- void setEnabled(boolean enabled);
-
- boolean isUp();
- boolean isCommissioned();
- boolean isConnected();
- @utf8InCpp String getState();
-
- @utf8InCpp String getRole();
- @utf8InCpp String getPartitionId();
- byte[] getExtendedAddress();
-
- LowpanIdentity getLowpanIdentity();
- LowpanCredential getLowpanCredential();
-
- @utf8InCpp String[] getLinkAddresses();
- IpPrefix[] getLinkNetworks();
-
- void join(in LowpanProvision provision);
- void form(in LowpanProvision provision);
- void attach(in LowpanProvision provision);
- void leave();
- void reset();
-
- void startCommissioningSession(in LowpanBeaconInfo beaconInfo);
- void closeCommissioningSession();
- oneway void sendToCommissioner(in byte[] packet);
-
- void beginLowPower();
- oneway void pollForData();
-
- oneway void onHostWake();
-
- void addListener(ILowpanInterfaceListener listener);
- oneway void removeListener(ILowpanInterfaceListener listener);
-
- void startNetScan(in Map properties, ILowpanNetScanCallback listener);
- oneway void stopNetScan();
-
- void startEnergyScan(in Map properties, ILowpanEnergyScanCallback listener);
- oneway void stopEnergyScan();
-
- void addOnMeshPrefix(in IpPrefix prefix, int flags);
- oneway void removeOnMeshPrefix(in IpPrefix prefix);
-
- void addExternalRoute(in IpPrefix prefix, int flags);
- oneway void removeExternalRoute(in IpPrefix prefix);
-}
diff --git a/lowpan/java/android/net/lowpan/ILowpanInterfaceListener.aidl b/lowpan/java/android/net/lowpan/ILowpanInterfaceListener.aidl
deleted file mode 100644
index 5e4049a0fab8..000000000000
--- a/lowpan/java/android/net/lowpan/ILowpanInterfaceListener.aidl
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-import android.net.IpPrefix;
-import android.net.lowpan.LowpanIdentity;
-
-/** {@hide} */
-interface ILowpanInterfaceListener {
- oneway void onEnabledChanged(boolean value);
-
- oneway void onConnectedChanged(boolean value);
-
- oneway void onUpChanged(boolean value);
-
- oneway void onRoleChanged(@utf8InCpp String value);
-
- oneway void onStateChanged(@utf8InCpp String value);
-
- oneway void onLowpanIdentityChanged(in LowpanIdentity value);
-
- oneway void onLinkNetworkAdded(in IpPrefix value);
-
- oneway void onLinkNetworkRemoved(in IpPrefix value);
-
- oneway void onLinkAddressAdded(@utf8InCpp String value);
-
- oneway void onLinkAddressRemoved(@utf8InCpp String value);
-
- oneway void onReceiveFromCommissioner(in byte[] packet);
-}
diff --git a/lowpan/java/android/net/lowpan/ILowpanManager.aidl b/lowpan/java/android/net/lowpan/ILowpanManager.aidl
deleted file mode 100644
index 326aa65e0fef..000000000000
--- a/lowpan/java/android/net/lowpan/ILowpanManager.aidl
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-import android.net.lowpan.ILowpanInterface;
-import android.net.lowpan.ILowpanManagerListener;
-
-/** {@hide} */
-interface ILowpanManager {
-
- /* Keep this in sync with Context.LOWPAN_SERVICE */
- const String LOWPAN_SERVICE_NAME = "lowpan";
-
- ILowpanInterface getInterface(@utf8InCpp String name);
-
- @utf8InCpp String[] getInterfaceList();
-
- void addListener(ILowpanManagerListener listener);
- void removeListener(ILowpanManagerListener listener);
-
- void addInterface(ILowpanInterface lowpan_interface);
- void removeInterface(ILowpanInterface lowpan_interface);
-}
diff --git a/lowpan/java/android/net/lowpan/InterfaceDisabledException.java b/lowpan/java/android/net/lowpan/InterfaceDisabledException.java
deleted file mode 100644
index e917d4521bae..000000000000
--- a/lowpan/java/android/net/lowpan/InterfaceDisabledException.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-/**
- * Exception indicating this operation requires the interface to be enabled.
- *
- * @see LowpanInterface
- * @hide
- */
-// @SystemApi
-public class InterfaceDisabledException extends LowpanException {
-
- public InterfaceDisabledException() {}
-
- public InterfaceDisabledException(String message) {
- super(message);
- }
-
- public InterfaceDisabledException(String message, Throwable cause) {
- super(message, cause);
- }
-
- protected InterfaceDisabledException(Exception cause) {
- super(cause);
- }
-}
diff --git a/lowpan/java/android/net/lowpan/JoinFailedAtAuthException.java b/lowpan/java/android/net/lowpan/JoinFailedAtAuthException.java
deleted file mode 100644
index 7aceb71e2a85..000000000000
--- a/lowpan/java/android/net/lowpan/JoinFailedAtAuthException.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-/**
- * Exception indicating the join operation was unable to find the given network.
- *
- * @see LowpanInterface
- * @hide
- */
-// @SystemApi
-public class JoinFailedAtAuthException extends JoinFailedException {
-
- public JoinFailedAtAuthException() {}
-
- public JoinFailedAtAuthException(String message) {
- super(message);
- }
-
- public JoinFailedAtAuthException(String message, Throwable cause) {
- super(message, cause);
- }
-
- public JoinFailedAtAuthException(Exception cause) {
- super(cause);
- }
-}
diff --git a/lowpan/java/android/net/lowpan/JoinFailedAtScanException.java b/lowpan/java/android/net/lowpan/JoinFailedAtScanException.java
deleted file mode 100644
index a4346f98d719..000000000000
--- a/lowpan/java/android/net/lowpan/JoinFailedAtScanException.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-/**
- * Exception indicating the join operation was unable to find the given network.
- *
- * @see LowpanInterface
- * @hide
- */
-// @SystemApi
-public class JoinFailedAtScanException extends JoinFailedException {
-
- public JoinFailedAtScanException() {}
-
- public JoinFailedAtScanException(String message) {
- super(message);
- }
-
- public JoinFailedAtScanException(String message, Throwable cause) {
- super(message, cause);
- }
-
- public JoinFailedAtScanException(Exception cause) {
- super(cause);
- }
-}
diff --git a/lowpan/java/android/net/lowpan/JoinFailedException.java b/lowpan/java/android/net/lowpan/JoinFailedException.java
deleted file mode 100644
index e51d3829f9f1..000000000000
--- a/lowpan/java/android/net/lowpan/JoinFailedException.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-/**
- * Exception indicating the join operation has failed.
- *
- * @see LowpanInterface
- * @hide
- */
-// @SystemApi
-public class JoinFailedException extends LowpanException {
-
- public JoinFailedException() {}
-
- public JoinFailedException(String message) {
- super(message);
- }
-
- public JoinFailedException(String message, Throwable cause) {
- super(message, cause);
- }
-
- protected JoinFailedException(Exception cause) {
- super(cause);
- }
-}
diff --git a/lowpan/java/android/net/lowpan/LowpanBeaconInfo.aidl b/lowpan/java/android/net/lowpan/LowpanBeaconInfo.aidl
deleted file mode 100644
index 9464fea02d18..000000000000
--- a/lowpan/java/android/net/lowpan/LowpanBeaconInfo.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/**
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-parcelable LowpanBeaconInfo cpp_header "android/net/lowpan/LowpanBeaconInfo.h";
diff --git a/lowpan/java/android/net/lowpan/LowpanBeaconInfo.java b/lowpan/java/android/net/lowpan/LowpanBeaconInfo.java
deleted file mode 100644
index 5d4a3a0cb9d6..000000000000
--- a/lowpan/java/android/net/lowpan/LowpanBeaconInfo.java
+++ /dev/null
@@ -1,234 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-import com.android.internal.util.HexDump;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Objects;
-import java.util.TreeSet;
-
-/**
- * Describes a LoWPAN Beacon
- *
- * @hide
- */
-// @SystemApi
-public class LowpanBeaconInfo implements Parcelable {
- public static final int UNKNOWN_RSSI = Integer.MAX_VALUE;
- public static final int UNKNOWN_LQI = 0;
-
- private LowpanIdentity mIdentity;
- private int mRssi = UNKNOWN_RSSI;
- private int mLqi = UNKNOWN_LQI;
- private byte[] mBeaconAddress = null;
- private final TreeSet<Integer> mFlags = new TreeSet<>();
-
- public static final int FLAG_CAN_ASSIST = 1;
-
- /** @hide */
- public static class Builder {
- final LowpanIdentity.Builder mIdentityBuilder = new LowpanIdentity.Builder();
- final LowpanBeaconInfo mBeaconInfo = new LowpanBeaconInfo();
-
- public Builder setLowpanIdentity(LowpanIdentity x) {
- mIdentityBuilder.setLowpanIdentity(x);
- return this;
- }
-
- public Builder setName(String x) {
- mIdentityBuilder.setName(x);
- return this;
- }
-
- public Builder setXpanid(byte x[]) {
- mIdentityBuilder.setXpanid(x);
- return this;
- }
-
- public Builder setPanid(int x) {
- mIdentityBuilder.setPanid(x);
- return this;
- }
-
- public Builder setChannel(int x) {
- mIdentityBuilder.setChannel(x);
- return this;
- }
-
- public Builder setType(String x) {
- mIdentityBuilder.setType(x);
- return this;
- }
-
- public Builder setRssi(int x) {
- mBeaconInfo.mRssi = x;
- return this;
- }
-
- public Builder setLqi(int x) {
- mBeaconInfo.mLqi = x;
- return this;
- }
-
- public Builder setBeaconAddress(byte x[]) {
- mBeaconInfo.mBeaconAddress = (x != null ? x.clone() : null);
- return this;
- }
-
- public Builder setFlag(int x) {
- mBeaconInfo.mFlags.add(x);
- return this;
- }
-
- public Builder setFlags(Collection<Integer> x) {
- mBeaconInfo.mFlags.addAll(x);
- return this;
- }
-
- public LowpanBeaconInfo build() {
- mBeaconInfo.mIdentity = mIdentityBuilder.build();
- if (mBeaconInfo.mBeaconAddress == null) {
- mBeaconInfo.mBeaconAddress = new byte[0];
- }
- return mBeaconInfo;
- }
- }
-
- private LowpanBeaconInfo() {}
-
- public LowpanIdentity getLowpanIdentity() {
- return mIdentity;
- }
-
- public int getRssi() {
- return mRssi;
- }
-
- public int getLqi() {
- return mLqi;
- }
-
- public byte[] getBeaconAddress() {
- return mBeaconAddress.clone();
- }
-
- public Collection<Integer> getFlags() {
- return (Collection<Integer>) mFlags.clone();
- }
-
- public boolean isFlagSet(int flag) {
- return mFlags.contains(flag);
- }
-
- @Override
- public String toString() {
- StringBuffer sb = new StringBuffer();
-
- sb.append(mIdentity.toString());
-
- if (mRssi != UNKNOWN_RSSI) {
- sb.append(", RSSI:").append(mRssi).append("dBm");
- }
-
- if (mLqi != UNKNOWN_LQI) {
- sb.append(", LQI:").append(mLqi);
- }
-
- if (mBeaconAddress.length > 0) {
- sb.append(", BeaconAddress:").append(HexDump.toHexString(mBeaconAddress));
- }
-
- for (Integer flag : mFlags) {
- switch (flag.intValue()) {
- case FLAG_CAN_ASSIST:
- sb.append(", CAN_ASSIST");
- break;
- default:
- sb.append(", FLAG_").append(Integer.toHexString(flag));
- break;
- }
- }
-
- return sb.toString();
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(mIdentity, mRssi, mLqi, Arrays.hashCode(mBeaconAddress), mFlags);
- }
-
- @Override
- public boolean equals(Object obj) {
- if (!(obj instanceof LowpanBeaconInfo)) {
- return false;
- }
- LowpanBeaconInfo rhs = (LowpanBeaconInfo) obj;
- return mIdentity.equals(rhs.mIdentity)
- && Arrays.equals(mBeaconAddress, rhs.mBeaconAddress)
- && mRssi == rhs.mRssi
- && mLqi == rhs.mLqi
- && mFlags.equals(rhs.mFlags);
- }
-
- /** Implement the Parcelable interface. */
- @Override
- public int describeContents() {
- return 0;
- }
-
- /** Implement the Parcelable interface. */
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- mIdentity.writeToParcel(dest, flags);
- dest.writeInt(mRssi);
- dest.writeInt(mLqi);
- dest.writeByteArray(mBeaconAddress);
-
- dest.writeInt(mFlags.size());
- for (Integer val : mFlags) {
- dest.writeInt(val);
- }
- }
-
- /** Implement the Parcelable interface. */
- public static final @android.annotation.NonNull Creator<LowpanBeaconInfo> CREATOR =
- new Creator<LowpanBeaconInfo>() {
- public LowpanBeaconInfo createFromParcel(Parcel in) {
- Builder builder = new Builder();
-
- builder.setLowpanIdentity(LowpanIdentity.CREATOR.createFromParcel(in));
-
- builder.setRssi(in.readInt());
- builder.setLqi(in.readInt());
-
- builder.setBeaconAddress(in.createByteArray());
-
- for (int i = in.readInt(); i > 0; i--) {
- builder.setFlag(in.readInt());
- }
-
- return builder.build();
- }
-
- public LowpanBeaconInfo[] newArray(int size) {
- return new LowpanBeaconInfo[size];
- }
- };
-}
diff --git a/lowpan/java/android/net/lowpan/LowpanChannelInfo.aidl b/lowpan/java/android/net/lowpan/LowpanChannelInfo.aidl
deleted file mode 100644
index 0676deb6c6df..000000000000
--- a/lowpan/java/android/net/lowpan/LowpanChannelInfo.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/**
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-parcelable LowpanChannelInfo cpp_header "android/net/lowpan/LowpanChannelInfo.h";
diff --git a/lowpan/java/android/net/lowpan/LowpanChannelInfo.java b/lowpan/java/android/net/lowpan/LowpanChannelInfo.java
deleted file mode 100644
index 12c98b61c49e..000000000000
--- a/lowpan/java/android/net/lowpan/LowpanChannelInfo.java
+++ /dev/null
@@ -1,216 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-import java.util.Objects;
-
-/**
- * Provides detailed information about a given channel.
- *
- * @hide
- */
-// @SystemApi
-public class LowpanChannelInfo implements Parcelable {
-
- public static final int UNKNOWN_POWER = Integer.MAX_VALUE;
- public static final float UNKNOWN_FREQUENCY = 0.0f;
- public static final float UNKNOWN_BANDWIDTH = 0.0f;
-
- private int mIndex = 0;
- private String mName = null;
- private float mSpectrumCenterFrequency = UNKNOWN_FREQUENCY;
- private float mSpectrumBandwidth = UNKNOWN_BANDWIDTH;
- private int mMaxTransmitPower = UNKNOWN_POWER;
- private boolean mIsMaskedByRegulatoryDomain = false;
-
- /** @hide */
- public static LowpanChannelInfo getChannelInfoForIeee802154Page0(int index) {
- LowpanChannelInfo info = new LowpanChannelInfo();
-
- if (index < 0) {
- info = null;
-
- } else if (index == 0) {
- info.mSpectrumCenterFrequency = 868300000.0f;
- info.mSpectrumBandwidth = 600000.0f;
-
- } else if (index < 11) {
- info.mSpectrumCenterFrequency = 906000000.0f - (2000000.0f * 1) + 2000000.0f * (index);
- info.mSpectrumBandwidth = 0; // Unknown
-
- } else if (index < 26) {
- info.mSpectrumCenterFrequency =
- 2405000000.0f - (5000000.0f * 11) + 5000000.0f * (index);
- info.mSpectrumBandwidth = 2000000.0f;
-
- } else {
- info = null;
- }
-
- info.mName = Integer.toString(index);
-
- return info;
- }
-
- private LowpanChannelInfo() {}
-
- private LowpanChannelInfo(int index, String name, float cf, float bw) {
- mIndex = index;
- mName = name;
- mSpectrumCenterFrequency = cf;
- mSpectrumBandwidth = bw;
- }
-
- public String getName() {
- return mName;
- }
-
- public int getIndex() {
- return mIndex;
- }
-
- public int getMaxTransmitPower() {
- return mMaxTransmitPower;
- }
-
- public boolean isMaskedByRegulatoryDomain() {
- return mIsMaskedByRegulatoryDomain;
- }
-
- public float getSpectrumCenterFrequency() {
- return mSpectrumCenterFrequency;
- }
-
- public float getSpectrumBandwidth() {
- return mSpectrumBandwidth;
- }
-
- @Override
- public String toString() {
- StringBuffer sb = new StringBuffer();
-
- sb.append("Channel ").append(mIndex);
-
- if (mName != null && !mName.equals(Integer.toString(mIndex))) {
- sb.append(" (").append(mName).append(")");
- }
-
- if (mSpectrumCenterFrequency > 0.0f) {
- if (mSpectrumCenterFrequency > 1000000000.0f) {
- sb.append(", SpectrumCenterFrequency: ")
- .append(mSpectrumCenterFrequency / 1000000000.0f)
- .append("GHz");
- } else if (mSpectrumCenterFrequency > 1000000.0f) {
- sb.append(", SpectrumCenterFrequency: ")
- .append(mSpectrumCenterFrequency / 1000000.0f)
- .append("MHz");
- } else {
- sb.append(", SpectrumCenterFrequency: ")
- .append(mSpectrumCenterFrequency / 1000.0f)
- .append("kHz");
- }
- }
-
- if (mSpectrumBandwidth > 0.0f) {
- if (mSpectrumBandwidth > 1000000000.0f) {
- sb.append(", SpectrumBandwidth: ")
- .append(mSpectrumBandwidth / 1000000000.0f)
- .append("GHz");
- } else if (mSpectrumBandwidth > 1000000.0f) {
- sb.append(", SpectrumBandwidth: ")
- .append(mSpectrumBandwidth / 1000000.0f)
- .append("MHz");
- } else {
- sb.append(", SpectrumBandwidth: ")
- .append(mSpectrumBandwidth / 1000.0f)
- .append("kHz");
- }
- }
-
- if (mMaxTransmitPower != UNKNOWN_POWER) {
- sb.append(", MaxTransmitPower: ").append(mMaxTransmitPower).append("dBm");
- }
-
- return sb.toString();
- }
-
- @Override
- public boolean equals(Object obj) {
- if (!(obj instanceof LowpanChannelInfo)) {
- return false;
- }
- LowpanChannelInfo rhs = (LowpanChannelInfo) obj;
- return Objects.equals(mName, rhs.mName)
- && mIndex == rhs.mIndex
- && mIsMaskedByRegulatoryDomain == rhs.mIsMaskedByRegulatoryDomain
- && mSpectrumCenterFrequency == rhs.mSpectrumCenterFrequency
- && mSpectrumBandwidth == rhs.mSpectrumBandwidth
- && mMaxTransmitPower == rhs.mMaxTransmitPower;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(
- mName,
- mIndex,
- mIsMaskedByRegulatoryDomain,
- mSpectrumCenterFrequency,
- mSpectrumBandwidth,
- mMaxTransmitPower);
- }
-
- /** Implement the Parcelable interface. */
- @Override
- public int describeContents() {
- return 0;
- }
-
- /** Implement the Parcelable interface. */
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(mIndex);
- dest.writeString(mName);
- dest.writeFloat(mSpectrumCenterFrequency);
- dest.writeFloat(mSpectrumBandwidth);
- dest.writeInt(mMaxTransmitPower);
- dest.writeBoolean(mIsMaskedByRegulatoryDomain);
- }
-
- /** Implement the Parcelable interface. */
- public static final @android.annotation.NonNull Creator<LowpanChannelInfo> CREATOR =
- new Creator<LowpanChannelInfo>() {
-
- public LowpanChannelInfo createFromParcel(Parcel in) {
- LowpanChannelInfo info = new LowpanChannelInfo();
-
- info.mIndex = in.readInt();
- info.mName = in.readString();
- info.mSpectrumCenterFrequency = in.readFloat();
- info.mSpectrumBandwidth = in.readFloat();
- info.mMaxTransmitPower = in.readInt();
- info.mIsMaskedByRegulatoryDomain = in.readBoolean();
-
- return info;
- }
-
- public LowpanChannelInfo[] newArray(int size) {
- return new LowpanChannelInfo[size];
- }
- };
-}
diff --git a/lowpan/java/android/net/lowpan/LowpanCommissioningSession.java b/lowpan/java/android/net/lowpan/LowpanCommissioningSession.java
deleted file mode 100644
index 8f75e8db6243..000000000000
--- a/lowpan/java/android/net/lowpan/LowpanCommissioningSession.java
+++ /dev/null
@@ -1,223 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.net.IpPrefix;
-import android.os.DeadObjectException;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.RemoteException;
-
-/**
- * Commissioning Session.
- *
- * <p>This class enables a device to learn the credential needed to join a network using a technique
- * called "in-band commissioning".
- *
- * @hide
- */
-// @SystemApi
-public class LowpanCommissioningSession {
-
- private final ILowpanInterface mBinder;
- private final LowpanBeaconInfo mBeaconInfo;
- private final ILowpanInterfaceListener mInternalCallback = new InternalCallback();
- private final Looper mLooper;
- private Handler mHandler;
- private Callback mCallback = null;
- private volatile boolean mIsClosed = false;
-
- /**
- * Callback base class for {@link LowpanCommissioningSession}
- *
- * @hide
- */
- // @SystemApi
- public abstract static class Callback {
- public void onReceiveFromCommissioner(@NonNull byte[] packet) {};
-
- public void onClosed() {};
- }
-
- private class InternalCallback extends ILowpanInterfaceListener.Stub {
- @Override
- public void onStateChanged(String value) {
- if (!mIsClosed) {
- switch (value) {
- case ILowpanInterface.STATE_OFFLINE:
- case ILowpanInterface.STATE_FAULT:
- synchronized (LowpanCommissioningSession.this) {
- lockedCleanup();
- }
- }
- }
- }
-
- @Override
- public void onReceiveFromCommissioner(byte[] packet) {
- mHandler.post(
- () -> {
- synchronized (LowpanCommissioningSession.this) {
- if (!mIsClosed && (mCallback != null)) {
- mCallback.onReceiveFromCommissioner(packet);
- }
- }
- });
- }
-
- // We ignore all other callbacks.
- @Override
- public void onEnabledChanged(boolean value) {}
-
- @Override
- public void onConnectedChanged(boolean value) {}
-
- @Override
- public void onUpChanged(boolean value) {}
-
- @Override
- public void onRoleChanged(String value) {}
-
- @Override
- public void onLowpanIdentityChanged(LowpanIdentity value) {}
-
- @Override
- public void onLinkNetworkAdded(IpPrefix value) {}
-
- @Override
- public void onLinkNetworkRemoved(IpPrefix value) {}
-
- @Override
- public void onLinkAddressAdded(String value) {}
-
- @Override
- public void onLinkAddressRemoved(String value) {}
- }
-
- LowpanCommissioningSession(
- ILowpanInterface binder, LowpanBeaconInfo beaconInfo, Looper looper) {
- mBinder = binder;
- mBeaconInfo = beaconInfo;
- mLooper = looper;
-
- if (mLooper != null) {
- mHandler = new Handler(mLooper);
- } else {
- mHandler = new Handler();
- }
-
- try {
- mBinder.addListener(mInternalCallback);
-
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
- }
- }
-
- private void lockedCleanup() {
- // Note: this method is only called from synchronized contexts.
-
- if (!mIsClosed) {
- try {
- mBinder.removeListener(mInternalCallback);
-
- } catch (DeadObjectException x) {
- /* We don't care if we receive a DOE at this point.
- * DOE is as good as success as far as we are concerned.
- */
-
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
- }
-
- if (mCallback != null) {
- mHandler.post(() -> mCallback.onClosed());
- }
- }
-
- mCallback = null;
- mIsClosed = true;
- }
-
- /** TODO: doc */
- @NonNull
- public LowpanBeaconInfo getBeaconInfo() {
- return mBeaconInfo;
- }
-
- /** TODO: doc */
- public void sendToCommissioner(@NonNull byte[] packet) {
- if (!mIsClosed) {
- try {
- mBinder.sendToCommissioner(packet);
-
- } catch (DeadObjectException x) {
- /* This method is a best-effort delivery.
- * We don't care if we receive a DOE at this point.
- */
-
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
- }
- }
- }
-
- /** TODO: doc */
- public synchronized void setCallback(@Nullable Callback cb, @Nullable Handler handler) {
- if (!mIsClosed) {
- /* This class can be created with or without a default looper.
- * Also, this method can be called with or without a specific
- * handler. If a handler is specified, it is to always be used.
- * Otherwise, if there was a Looper specified when this object
- * was created, we create a new handle based on that looper.
- * Otherwise we just create a default handler object. Since we
- * don't really know how the previous handler was created, we
- * end up always replacing it here. This isn't a huge problem
- * because this method should be called infrequently.
- */
- if (handler != null) {
- mHandler = handler;
- } else if (mLooper != null) {
- mHandler = new Handler(mLooper);
- } else {
- mHandler = new Handler();
- }
- mCallback = cb;
- }
- }
-
- /** TODO: doc */
- public synchronized void close() {
- if (!mIsClosed) {
- try {
- mBinder.closeCommissioningSession();
-
- lockedCleanup();
-
- } catch (DeadObjectException x) {
- /* We don't care if we receive a DOE at this point.
- * DOE is as good as success as far as we are concerned.
- */
-
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
- }
- }
- }
-}
diff --git a/lowpan/java/android/net/lowpan/LowpanCredential.aidl b/lowpan/java/android/net/lowpan/LowpanCredential.aidl
deleted file mode 100644
index af0c2d63474a..000000000000
--- a/lowpan/java/android/net/lowpan/LowpanCredential.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/**
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-parcelable LowpanCredential cpp_header "android/net/lowpan/LowpanCredential.h";
diff --git a/lowpan/java/android/net/lowpan/LowpanCredential.java b/lowpan/java/android/net/lowpan/LowpanCredential.java
deleted file mode 100644
index dcbb83128f84..000000000000
--- a/lowpan/java/android/net/lowpan/LowpanCredential.java
+++ /dev/null
@@ -1,172 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-import com.android.internal.util.HexDump;
-import java.util.Arrays;
-import java.util.Objects;
-
-/**
- * Describes a credential for a LoWPAN network.
- *
- * @hide
- */
-// @SystemApi
-public class LowpanCredential implements Parcelable {
-
- public static final int UNSPECIFIED_KEY_INDEX = 0;
-
- private byte[] mMasterKey = null;
- private int mMasterKeyIndex = UNSPECIFIED_KEY_INDEX;
-
- LowpanCredential() {}
-
- private LowpanCredential(byte[] masterKey, int keyIndex) {
- setMasterKey(masterKey, keyIndex);
- }
-
- private LowpanCredential(byte[] masterKey) {
- setMasterKey(masterKey);
- }
-
- public static LowpanCredential createMasterKey(byte[] masterKey) {
- return new LowpanCredential(masterKey);
- }
-
- public static LowpanCredential createMasterKey(byte[] masterKey, int keyIndex) {
- return new LowpanCredential(masterKey, keyIndex);
- }
-
- void setMasterKey(byte[] masterKey) {
- if (masterKey != null) {
- masterKey = masterKey.clone();
- }
- mMasterKey = masterKey;
- }
-
- void setMasterKeyIndex(int keyIndex) {
- mMasterKeyIndex = keyIndex;
- }
-
- void setMasterKey(byte[] masterKey, int keyIndex) {
- setMasterKey(masterKey);
- setMasterKeyIndex(keyIndex);
- }
-
- public byte[] getMasterKey() {
- if (mMasterKey != null) {
- return mMasterKey.clone();
- }
- return null;
- }
-
- public int getMasterKeyIndex() {
- return mMasterKeyIndex;
- }
-
- public boolean isMasterKey() {
- return mMasterKey != null;
- }
-
- public String toSensitiveString() {
- StringBuffer sb = new StringBuffer();
-
- sb.append("<LowpanCredential");
-
- if (isMasterKey()) {
- sb.append(" MasterKey:").append(HexDump.toHexString(mMasterKey));
- if (mMasterKeyIndex != UNSPECIFIED_KEY_INDEX) {
- sb.append(", Index:").append(mMasterKeyIndex);
- }
- } else {
- sb.append(" empty");
- }
-
- sb.append(">");
-
- return sb.toString();
- }
-
- @Override
- public String toString() {
- StringBuffer sb = new StringBuffer();
-
- sb.append("<LowpanCredential");
-
- if (isMasterKey()) {
- // We don't print out the contents of the key here,
- // we only do that in toSensitiveString.
- sb.append(" MasterKey");
- if (mMasterKeyIndex != UNSPECIFIED_KEY_INDEX) {
- sb.append(", Index:").append(mMasterKeyIndex);
- }
- } else {
- sb.append(" empty");
- }
-
- sb.append(">");
-
- return sb.toString();
- }
-
- @Override
- public boolean equals(Object obj) {
- if (!(obj instanceof LowpanCredential)) {
- return false;
- }
- LowpanCredential rhs = (LowpanCredential) obj;
- return Arrays.equals(mMasterKey, rhs.mMasterKey) && mMasterKeyIndex == rhs.mMasterKeyIndex;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(Arrays.hashCode(mMasterKey), mMasterKeyIndex);
- }
-
- /** Implement the Parcelable interface. */
- @Override
- public int describeContents() {
- return 0;
- }
-
- /** Implement the Parcelable interface. */
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeByteArray(mMasterKey);
- dest.writeInt(mMasterKeyIndex);
- }
-
- /** Implement the Parcelable interface. */
- public static final @android.annotation.NonNull Creator<LowpanCredential> CREATOR =
- new Creator<LowpanCredential>() {
-
- public LowpanCredential createFromParcel(Parcel in) {
- LowpanCredential credential = new LowpanCredential();
-
- credential.mMasterKey = in.createByteArray();
- credential.mMasterKeyIndex = in.readInt();
-
- return credential;
- }
-
- public LowpanCredential[] newArray(int size) {
- return new LowpanCredential[size];
- }
- };
-}
diff --git a/lowpan/java/android/net/lowpan/LowpanEnergyScanResult.java b/lowpan/java/android/net/lowpan/LowpanEnergyScanResult.java
deleted file mode 100644
index da87752008c9..000000000000
--- a/lowpan/java/android/net/lowpan/LowpanEnergyScanResult.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-/**
- * Describes the result from one channel of an energy scan.
- *
- * @hide
- */
-// @SystemApi
-public class LowpanEnergyScanResult {
- public static final int UNKNOWN = Integer.MAX_VALUE;
-
- private int mChannel = UNKNOWN;
- private int mMaxRssi = UNKNOWN;
-
- LowpanEnergyScanResult() {}
-
- public int getChannel() {
- return mChannel;
- }
-
- public int getMaxRssi() {
- return mMaxRssi;
- }
-
- void setChannel(int x) {
- mChannel = x;
- }
-
- void setMaxRssi(int x) {
- mMaxRssi = x;
- }
-
- @Override
- public String toString() {
- return "LowpanEnergyScanResult(channel: " + mChannel + ", maxRssi:" + mMaxRssi + ")";
- }
-}
diff --git a/lowpan/java/android/net/lowpan/LowpanException.java b/lowpan/java/android/net/lowpan/LowpanException.java
deleted file mode 100644
index 5dfce48d3f17..000000000000
--- a/lowpan/java/android/net/lowpan/LowpanException.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-import android.os.ServiceSpecificException;
-import android.util.AndroidException;
-
-/**
- * <code>LowpanException</code> is thrown if an action to a LoWPAN interface could not be performed
- * or a LoWPAN interface property could not be fetched or changed.
- *
- * @see LowpanInterface
- * @hide
- */
-// @SystemApi
-public class LowpanException extends AndroidException {
- public LowpanException() {}
-
- public LowpanException(String message) {
- super(message);
- }
-
- public LowpanException(String message, Throwable cause) {
- super(message, cause);
- }
-
- public LowpanException(Exception cause) {
- super(cause);
- }
-
- /* This method returns LowpanException so that the caller
- * can add "throw" before the invocation of this method.
- * This might seem superfluous, but it is actually to
- * help provide a hint to the java compiler that this
- * function will not return.
- */
- static LowpanException rethrowFromServiceSpecificException(ServiceSpecificException e)
- throws LowpanException {
- switch (e.errorCode) {
- case ILowpanInterface.ERROR_DISABLED:
- throw new InterfaceDisabledException(e);
-
- case ILowpanInterface.ERROR_WRONG_STATE:
- throw new WrongStateException(e);
-
- case ILowpanInterface.ERROR_CANCELED:
- throw new OperationCanceledException(e);
-
- case ILowpanInterface.ERROR_JOIN_FAILED_UNKNOWN:
- throw new JoinFailedException(e);
-
- case ILowpanInterface.ERROR_JOIN_FAILED_AT_SCAN:
- throw new JoinFailedAtScanException(e);
-
- case ILowpanInterface.ERROR_JOIN_FAILED_AT_AUTH:
- throw new JoinFailedAtAuthException(e);
-
- case ILowpanInterface.ERROR_FORM_FAILED_AT_SCAN:
- throw new NetworkAlreadyExistsException(e);
-
- case ILowpanInterface.ERROR_FEATURE_NOT_SUPPORTED:
- throw new LowpanException(
- e.getMessage() != null ? e.getMessage() : "Feature not supported", e);
-
- case ILowpanInterface.ERROR_NCP_PROBLEM:
- throw new LowpanRuntimeException(
- e.getMessage() != null ? e.getMessage() : "NCP problem", e);
-
- case ILowpanInterface.ERROR_INVALID_ARGUMENT:
- throw new LowpanRuntimeException(
- e.getMessage() != null ? e.getMessage() : "Invalid argument", e);
-
- case ILowpanInterface.ERROR_UNSPECIFIED:
- default:
- throw new LowpanRuntimeException(e);
- }
- }
-}
diff --git a/lowpan/java/android/net/lowpan/LowpanIdentity.java b/lowpan/java/android/net/lowpan/LowpanIdentity.java
deleted file mode 100644
index 1997bc4a4635..000000000000
--- a/lowpan/java/android/net/lowpan/LowpanIdentity.java
+++ /dev/null
@@ -1,255 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-import android.annotation.NonNull;
-import android.icu.text.StringPrep;
-import android.icu.text.StringPrepParseException;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.util.Log;
-import com.android.internal.util.HexDump;
-import java.nio.charset.StandardCharsets;
-import java.util.Arrays;
-import java.util.Objects;
-
-/**
- * Describes an instance of a LoWPAN network.
- *
- * @hide
- */
-// @SystemApi
-public class LowpanIdentity implements Parcelable {
- private static final String TAG = LowpanIdentity.class.getSimpleName();
-
- // Constants
- public static final int UNSPECIFIED_CHANNEL = -1;
- public static final int UNSPECIFIED_PANID = 0xFFFFFFFF;
- // Builder
-
- /** @hide */
- // @SystemApi
- public static class Builder {
- private static final StringPrep stringPrep =
- StringPrep.getInstance(StringPrep.RFC3920_RESOURCEPREP);
-
- final LowpanIdentity mIdentity = new LowpanIdentity();
-
- private static String escape(@NonNull byte[] bytes) {
- StringBuffer sb = new StringBuffer();
- for (byte b : bytes) {
- if (b >= 32 && b <= 126) {
- sb.append((char) b);
- } else {
- sb.append(String.format("\\0x%02x", b & 0xFF));
- }
- }
- return sb.toString();
- }
-
- public Builder setLowpanIdentity(@NonNull LowpanIdentity x) {
- Objects.requireNonNull(x);
- setRawName(x.getRawName());
- setXpanid(x.getXpanid());
- setPanid(x.getPanid());
- setChannel(x.getChannel());
- setType(x.getType());
- return this;
- }
-
- public Builder setName(@NonNull String name) {
- Objects.requireNonNull(name);
- try {
- mIdentity.mName = stringPrep.prepare(name, StringPrep.DEFAULT);
- mIdentity.mRawName = mIdentity.mName.getBytes(StandardCharsets.UTF_8);
- mIdentity.mIsNameValid = true;
- } catch (StringPrepParseException x) {
- Log.w(TAG, x.toString());
- setRawName(name.getBytes(StandardCharsets.UTF_8));
- }
- return this;
- }
-
- public Builder setRawName(@NonNull byte[] name) {
- Objects.requireNonNull(name);
- mIdentity.mRawName = name.clone();
- mIdentity.mName = new String(name, StandardCharsets.UTF_8);
- try {
- String nameCheck = stringPrep.prepare(mIdentity.mName, StringPrep.DEFAULT);
- mIdentity.mIsNameValid =
- Arrays.equals(nameCheck.getBytes(StandardCharsets.UTF_8), name);
- } catch (StringPrepParseException x) {
- Log.w(TAG, x.toString());
- mIdentity.mIsNameValid = false;
- }
-
- // Non-normal names must be rendered differently to avoid confusion.
- if (!mIdentity.mIsNameValid) {
- mIdentity.mName = "«" + escape(name) + "»";
- }
-
- return this;
- }
-
- public Builder setXpanid(byte x[]) {
- mIdentity.mXpanid = (x != null ? x.clone() : null);
- return this;
- }
-
- public Builder setPanid(int x) {
- mIdentity.mPanid = x;
- return this;
- }
-
- public Builder setType(@NonNull String x) {
- mIdentity.mType = x;
- return this;
- }
-
- public Builder setChannel(int x) {
- mIdentity.mChannel = x;
- return this;
- }
-
- public LowpanIdentity build() {
- return mIdentity;
- }
- }
-
- LowpanIdentity() {}
-
- // Instance Variables
-
- private String mName = "";
- private boolean mIsNameValid = true;
- private byte[] mRawName = new byte[0];
- private String mType = "";
- private byte[] mXpanid = new byte[0];
- private int mPanid = UNSPECIFIED_PANID;
- private int mChannel = UNSPECIFIED_CHANNEL;
-
- // Public Getters
-
- public String getName() {
- return mName;
- }
-
- public boolean isNameValid() {
- return mIsNameValid;
- }
-
- public byte[] getRawName() {
- return mRawName.clone();
- }
-
- public byte[] getXpanid() {
- return mXpanid.clone();
- }
-
- public int getPanid() {
- return mPanid;
- }
-
- public String getType() {
- return mType;
- }
-
- public int getChannel() {
- return mChannel;
- }
-
- @Override
- public String toString() {
- StringBuffer sb = new StringBuffer();
-
- sb.append("Name:").append(getName());
-
- if (mType.length() > 0) {
- sb.append(", Type:").append(mType);
- }
-
- if (mXpanid.length > 0) {
- sb.append(", XPANID:").append(HexDump.toHexString(mXpanid));
- }
-
- if (mPanid != UNSPECIFIED_PANID) {
- sb.append(", PANID:").append(String.format("0x%04X", mPanid));
- }
-
- if (mChannel != UNSPECIFIED_CHANNEL) {
- sb.append(", Channel:").append(mChannel);
- }
-
- return sb.toString();
- }
-
- @Override
- public boolean equals(Object obj) {
- if (!(obj instanceof LowpanIdentity)) {
- return false;
- }
- LowpanIdentity rhs = (LowpanIdentity) obj;
- return Arrays.equals(mRawName, rhs.mRawName)
- && Arrays.equals(mXpanid, rhs.mXpanid)
- && mType.equals(rhs.mType)
- && mPanid == rhs.mPanid
- && mChannel == rhs.mChannel;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(
- Arrays.hashCode(mRawName), mType, Arrays.hashCode(mXpanid), mPanid, mChannel);
- }
-
- /** Implement the Parcelable interface. */
- @Override
- public int describeContents() {
- return 0;
- }
-
- /** Implement the Parcelable interface. */
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeByteArray(mRawName);
- dest.writeString(mType);
- dest.writeByteArray(mXpanid);
- dest.writeInt(mPanid);
- dest.writeInt(mChannel);
- }
-
- /** Implement the Parcelable interface. */
- public static final @android.annotation.NonNull Creator<LowpanIdentity> CREATOR =
- new Creator<LowpanIdentity>() {
-
- public LowpanIdentity createFromParcel(Parcel in) {
- Builder builder = new Builder();
-
- builder.setRawName(in.createByteArray());
- builder.setType(in.readString());
- builder.setXpanid(in.createByteArray());
- builder.setPanid(in.readInt());
- builder.setChannel(in.readInt());
-
- return builder.build();
- }
-
- public LowpanIdentity[] newArray(int size) {
- return new LowpanIdentity[size];
- }
- };
-}
diff --git a/lowpan/java/android/net/lowpan/LowpanInterface.java b/lowpan/java/android/net/lowpan/LowpanInterface.java
deleted file mode 100644
index 57e91357b237..000000000000
--- a/lowpan/java/android/net/lowpan/LowpanInterface.java
+++ /dev/null
@@ -1,824 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.net.IpPrefix;
-import android.net.LinkAddress;
-import android.os.DeadObjectException;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.RemoteException;
-import android.os.ServiceSpecificException;
-import android.util.Log;
-import java.util.HashMap;
-
-/**
- * Class for managing a specific Low-power Wireless Personal Area Network (LoWPAN) interface.
- *
- * @hide
- */
-// @SystemApi
-public class LowpanInterface {
- private static final String TAG = LowpanInterface.class.getSimpleName();
-
- /** Detached role. The interface is not currently attached to a network. */
- public static final String ROLE_DETACHED = ILowpanInterface.ROLE_DETACHED;
-
- /** End-device role. End devices do not route traffic for other nodes. */
- public static final String ROLE_END_DEVICE = ILowpanInterface.ROLE_END_DEVICE;
-
- /** Router role. Routers help route traffic around the mesh network. */
- public static final String ROLE_ROUTER = ILowpanInterface.ROLE_ROUTER;
-
- /**
- * Sleepy End-Device role.
- *
- * <p>End devices with this role are nominally asleep, waking up periodically to check in with
- * their parent to see if there are packets destined for them. Such devices are capable of
- * extraordinarilly low power consumption, but packet latency can be on the order of dozens of
- * seconds(depending on how the node is configured).
- */
- public static final String ROLE_SLEEPY_END_DEVICE = ILowpanInterface.ROLE_SLEEPY_END_DEVICE;
-
- /**
- * Sleepy-router role.
- *
- * <p>Routers with this role are nominally asleep, waking up periodically to check in with other
- * routers and their children.
- */
- public static final String ROLE_SLEEPY_ROUTER = ILowpanInterface.ROLE_SLEEPY_ROUTER;
-
- /** TODO: doc */
- public static final String ROLE_LEADER = ILowpanInterface.ROLE_LEADER;
-
- /** TODO: doc */
- public static final String ROLE_COORDINATOR = ILowpanInterface.ROLE_COORDINATOR;
-
- /**
- * Offline state.
- *
- * <p>This is the initial state of the LoWPAN interface when the underlying driver starts. In
- * this state the NCP is idle and not connected to any network.
- *
- * <p>This state can be explicitly entered by calling {@link #reset()}, {@link #leave()}, or
- * <code>setUp(false)</code>, with the later two only working if we were not previously in the
- * {@link #STATE_FAULT} state.
- *
- * @see #getState()
- * @see #STATE_FAULT
- */
- public static final String STATE_OFFLINE = ILowpanInterface.STATE_OFFLINE;
-
- /**
- * Commissioning state.
- *
- * <p>The interface enters this state after a call to {@link #startCommissioningSession()}. This
- * state may only be entered directly from the {@link #STATE_OFFLINE} state.
- *
- * @see #startCommissioningSession()
- * @see #getState()
- * @hide
- */
- public static final String STATE_COMMISSIONING = ILowpanInterface.STATE_COMMISSIONING;
-
- /**
- * Attaching state.
- *
- * <p>The interface enters this state when it starts the process of trying to find other nodes
- * so that it can attach to any pre-existing network fragment, or when it is in the process of
- * calculating the optimal values for unspecified parameters when forming a new network.
- *
- * <p>The interface may stay in this state for a prolonged period of time (or may spontaneously
- * enter this state from {@link #STATE_ATTACHED}) if the underlying network technology is
- * heirarchical (like ZigBeeIP) or if the device role is that of an "end-device" ({@link
- * #ROLE_END_DEVICE} or {@link #ROLE_SLEEPY_END_DEVICE}). This is because such roles cannot
- * create their own network fragments.
- *
- * @see #STATE_ATTACHED
- * @see #getState()
- */
- public static final String STATE_ATTACHING = ILowpanInterface.STATE_ATTACHING;
-
- /**
- * Attached state.
- *
- * <p>The interface enters this state from {@link #STATE_ATTACHING} once it is actively
- * participating on a network fragment.
- *
- * @see #STATE_ATTACHING
- * @see #getState()
- */
- public static final String STATE_ATTACHED = ILowpanInterface.STATE_ATTACHED;
-
- /**
- * Fault state.
- *
- * <p>The interface will enter this state when the driver has detected some sort of problem from
- * which it was not immediately able to recover.
- *
- * <p>This state can be entered spontaneously from any other state. Calling {@link #reset} will
- * cause the device to return to the {@link #STATE_OFFLINE} state.
- *
- * @see #getState
- * @see #STATE_OFFLINE
- */
- public static final String STATE_FAULT = ILowpanInterface.STATE_FAULT;
-
- /**
- * Network type for Thread 1.x networks.
- *
- * @see android.net.lowpan.LowpanIdentity#getType
- * @see #getLowpanIdentity
- * @hide
- */
- public static final String NETWORK_TYPE_THREAD_V1 = ILowpanInterface.NETWORK_TYPE_THREAD_V1;
-
- public static final String EMPTY_PARTITION_ID = "";
-
- /**
- * Callback base class for LowpanInterface
- *
- * @hide
- */
- // @SystemApi
- public abstract static class Callback {
- public void onConnectedChanged(boolean value) {}
-
- public void onEnabledChanged(boolean value) {}
-
- public void onUpChanged(boolean value) {}
-
- public void onRoleChanged(@NonNull String value) {}
-
- public void onStateChanged(@NonNull String state) {}
-
- public void onLowpanIdentityChanged(@NonNull LowpanIdentity value) {}
-
- public void onLinkNetworkAdded(IpPrefix prefix) {}
-
- public void onLinkNetworkRemoved(IpPrefix prefix) {}
-
- public void onLinkAddressAdded(LinkAddress address) {}
-
- public void onLinkAddressRemoved(LinkAddress address) {}
- }
-
- private final ILowpanInterface mBinder;
- private final Looper mLooper;
- private final HashMap<Integer, ILowpanInterfaceListener> mListenerMap = new HashMap<>();
-
- /**
- * Create a new LowpanInterface instance. Applications will almost always want to use {@link
- * LowpanManager#getInterface LowpanManager.getInterface()} instead of this.
- *
- * @param context the application context
- * @param service the Binder interface
- * @param looper the Binder interface
- * @hide
- */
- public LowpanInterface(Context context, ILowpanInterface service, Looper looper) {
- /* We aren't currently using the context, but if we need
- * it later on we can easily add it to the class.
- */
-
- mBinder = service;
- mLooper = looper;
- }
-
- /**
- * Returns the ILowpanInterface object associated with this interface.
- *
- * @hide
- */
- public ILowpanInterface getService() {
- return mBinder;
- }
-
- // Public Actions
-
- /**
- * Form a new network with the given network information optional credential. Unspecified fields
- * in the network information will be filled in with reasonable values. If the network
- * credential is unspecified, one will be generated automatically.
- *
- * <p>This method will block until either the network was successfully formed or an error
- * prevents the network form being formed.
- *
- * <p>Upon success, the interface will be up and attached to the newly formed network.
- *
- * @see #join(LowpanProvision)
- */
- public void form(@NonNull LowpanProvision provision) throws LowpanException {
- try {
- mBinder.form(provision);
-
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
-
- } catch (ServiceSpecificException x) {
- throw LowpanException.rethrowFromServiceSpecificException(x);
- }
- }
-
- /**
- * Attempts to join a new network with the given network information. This method will block
- * until either the network was successfully joined or an error prevented the network from being
- * formed. Upon success, the interface will be up and attached to the newly joined network.
- *
- * <p>Note that “joining” is distinct from “attaching”: Joining requires at least one other peer
- * device to be present in order for the operation to complete successfully.
- */
- public void join(@NonNull LowpanProvision provision) throws LowpanException {
- try {
- mBinder.join(provision);
-
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
-
- } catch (ServiceSpecificException x) {
- throw LowpanException.rethrowFromServiceSpecificException(x);
- }
- }
-
- /**
- * Attaches to the network described by identity and credential. This is similar to {@link
- * #join}, except that (assuming the identity and credential are valid) it will always succeed
- * and provision the interface, even if there are no peers nearby.
- *
- * <p>This method will block execution until the operation has completed.
- */
- public void attach(@NonNull LowpanProvision provision) throws LowpanException {
- try {
- mBinder.attach(provision);
-
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
-
- } catch (ServiceSpecificException x) {
- throw LowpanException.rethrowFromServiceSpecificException(x);
- }
- }
-
- /**
- * Bring down the network interface and forget all non-volatile details about the current
- * network.
- *
- * <p>This method will block execution until the operation has completed.
- */
- public void leave() throws LowpanException {
- try {
- mBinder.leave();
-
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
-
- } catch (ServiceSpecificException x) {
- throw LowpanException.rethrowFromServiceSpecificException(x);
- }
- }
-
- /**
- * Start a new commissioning session. Will fail if the interface is attached to a network or if
- * the interface is disabled.
- */
- public @NonNull LowpanCommissioningSession startCommissioningSession(
- @NonNull LowpanBeaconInfo beaconInfo) throws LowpanException {
- try {
- mBinder.startCommissioningSession(beaconInfo);
-
- return new LowpanCommissioningSession(mBinder, beaconInfo, mLooper);
-
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
-
- } catch (ServiceSpecificException x) {
- throw LowpanException.rethrowFromServiceSpecificException(x);
- }
- }
-
- /**
- * Reset this network interface as if it has been power cycled. Will bring the network interface
- * down if it was previously up. Will not erase any non-volatile settings.
- *
- * <p>This method will block execution until the operation has completed.
- *
- * @hide
- */
- public void reset() throws LowpanException {
- try {
- mBinder.reset();
-
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
-
- } catch (ServiceSpecificException x) {
- throw LowpanException.rethrowFromServiceSpecificException(x);
- }
- }
-
- // Public Getters and Setters
-
- /** Returns the name of this network interface. */
- @NonNull
- public String getName() {
- try {
- return mBinder.getName();
-
- } catch (DeadObjectException x) {
- return "";
-
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
- }
- }
-
- /**
- * Indicates if the interface is enabled or disabled.
- *
- * @see #setEnabled
- * @see android.net.lowpan.LowpanException#LOWPAN_DISABLED
- */
- public boolean isEnabled() {
- try {
- return mBinder.isEnabled();
-
- } catch (DeadObjectException x) {
- return false;
-
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
- }
- }
-
- /**
- * Enables or disables the LoWPAN interface. When disabled, the interface is put into a
- * low-power state and all commands that require the NCP to be queried will fail with {@link
- * android.net.lowpan.LowpanException#LOWPAN_DISABLED}.
- *
- * @see #isEnabled
- * @see android.net.lowpan.LowpanException#LOWPAN_DISABLED
- * @hide
- */
- public void setEnabled(boolean enabled) throws LowpanException {
- try {
- mBinder.setEnabled(enabled);
-
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
-
- } catch (ServiceSpecificException x) {
- throw LowpanException.rethrowFromServiceSpecificException(x);
- }
- }
-
- /**
- * Indicates if the network interface is up or down.
- *
- * @hide
- */
- public boolean isUp() {
- try {
- return mBinder.isUp();
-
- } catch (DeadObjectException x) {
- return false;
-
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
- }
- }
-
- /**
- * Indicates if there is at least one peer in range.
- *
- * @return <code>true</code> if we have at least one other peer in range, <code>false</code>
- * otherwise.
- */
- public boolean isConnected() {
- try {
- return mBinder.isConnected();
-
- } catch (DeadObjectException x) {
- return false;
-
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
- }
- }
-
- /**
- * Indicates if this interface is currently commissioned onto an existing network. If the
- * interface is commissioned, the interface may be brought up using setUp().
- */
- public boolean isCommissioned() {
- try {
- return mBinder.isCommissioned();
-
- } catch (DeadObjectException x) {
- return false;
-
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
- }
- }
-
- /**
- * Get interface state
- *
- * <h3>State Diagram</h3>
- *
- * <img src="LowpanInterface-1.png" />
- *
- * @return The current state of the interface.
- * @see #STATE_OFFLINE
- * @see #STATE_COMMISSIONING
- * @see #STATE_ATTACHING
- * @see #STATE_ATTACHED
- * @see #STATE_FAULT
- */
- public String getState() {
- try {
- return mBinder.getState();
-
- } catch (DeadObjectException x) {
- return STATE_FAULT;
-
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
- }
- }
-
- /** Get network partition/fragment identifier. */
- public String getPartitionId() {
- try {
- return mBinder.getPartitionId();
-
- } catch (DeadObjectException x) {
- return EMPTY_PARTITION_ID;
-
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
- }
- }
-
- /** TODO: doc */
- public LowpanIdentity getLowpanIdentity() {
- try {
- return mBinder.getLowpanIdentity();
-
- } catch (DeadObjectException x) {
- return new LowpanIdentity();
-
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
- }
- }
-
- /** TODO: doc */
- @NonNull
- public String getRole() {
- try {
- return mBinder.getRole();
-
- } catch (DeadObjectException x) {
- return ROLE_DETACHED;
-
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
- }
- }
-
- /** TODO: doc */
- @Nullable
- public LowpanCredential getLowpanCredential() {
- try {
- return mBinder.getLowpanCredential();
-
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
- }
- }
-
- public @NonNull String[] getSupportedNetworkTypes() throws LowpanException {
- try {
- return mBinder.getSupportedNetworkTypes();
-
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
-
- } catch (ServiceSpecificException x) {
- throw LowpanException.rethrowFromServiceSpecificException(x);
- }
- }
-
- public @NonNull LowpanChannelInfo[] getSupportedChannels() throws LowpanException {
- try {
- return mBinder.getSupportedChannels();
-
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
-
- } catch (ServiceSpecificException x) {
- throw LowpanException.rethrowFromServiceSpecificException(x);
- }
- }
-
- // Listener Support
-
- /**
- * Registers a subclass of {@link LowpanInterface.Callback} to receive events.
- *
- * @param cb Subclass of {@link LowpanInterface.Callback} which will receive events.
- * @param handler If not <code>null</code>, events will be dispatched via the given handler
- * object. If <code>null</code>, the thread upon which events will be dispatched is
- * unspecified.
- * @see #registerCallback(Callback)
- * @see #unregisterCallback(Callback)
- */
- public void registerCallback(@NonNull Callback cb, @Nullable Handler handler) {
- ILowpanInterfaceListener.Stub listenerBinder =
- new ILowpanInterfaceListener.Stub() {
- private Handler mHandler;
-
- {
- if (handler != null) {
- mHandler = handler;
- } else if (mLooper != null) {
- mHandler = new Handler(mLooper);
- } else {
- mHandler = new Handler();
- }
- }
-
- @Override
- public void onEnabledChanged(boolean value) {
- mHandler.post(() -> cb.onEnabledChanged(value));
- }
-
- @Override
- public void onConnectedChanged(boolean value) {
- mHandler.post(() -> cb.onConnectedChanged(value));
- }
-
- @Override
- public void onUpChanged(boolean value) {
- mHandler.post(() -> cb.onUpChanged(value));
- }
-
- @Override
- public void onRoleChanged(String value) {
- mHandler.post(() -> cb.onRoleChanged(value));
- }
-
- @Override
- public void onStateChanged(String value) {
- mHandler.post(() -> cb.onStateChanged(value));
- }
-
- @Override
- public void onLowpanIdentityChanged(LowpanIdentity value) {
- mHandler.post(() -> cb.onLowpanIdentityChanged(value));
- }
-
- @Override
- public void onLinkNetworkAdded(IpPrefix value) {
- mHandler.post(() -> cb.onLinkNetworkAdded(value));
- }
-
- @Override
- public void onLinkNetworkRemoved(IpPrefix value) {
- mHandler.post(() -> cb.onLinkNetworkRemoved(value));
- }
-
- @Override
- public void onLinkAddressAdded(String value) {
- LinkAddress la;
- try {
- la = new LinkAddress(value);
- } catch (IllegalArgumentException x) {
- Log.e(
- TAG,
- "onLinkAddressAdded: Bad LinkAddress \"" + value + "\", " + x);
- return;
- }
- mHandler.post(() -> cb.onLinkAddressAdded(la));
- }
-
- @Override
- public void onLinkAddressRemoved(String value) {
- LinkAddress la;
- try {
- la = new LinkAddress(value);
- } catch (IllegalArgumentException x) {
- Log.e(
- TAG,
- "onLinkAddressRemoved: Bad LinkAddress \""
- + value
- + "\", "
- + x);
- return;
- }
- mHandler.post(() -> cb.onLinkAddressRemoved(la));
- }
-
- @Override
- public void onReceiveFromCommissioner(byte[] packet) {
- // This is only used by the LowpanCommissioningSession.
- }
- };
- try {
- mBinder.addListener(listenerBinder);
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
- }
-
- synchronized (mListenerMap) {
- mListenerMap.put(System.identityHashCode(cb), listenerBinder);
- }
- }
-
- /**
- * Registers a subclass of {@link LowpanInterface.Callback} to receive events.
- *
- * <p>The thread upon which events will be dispatched is unspecified.
- *
- * @param cb Subclass of {@link LowpanInterface.Callback} which will receive events.
- * @see #registerCallback(Callback, Handler)
- * @see #unregisterCallback(Callback)
- */
- public void registerCallback(Callback cb) {
- registerCallback(cb, null);
- }
-
- /**
- * Unregisters a previously registered callback class.
- *
- * @param cb Subclass of {@link LowpanInterface.Callback} which was previously registered to
- * receive events.
- * @see #registerCallback(Callback, Handler)
- * @see #registerCallback(Callback)
- */
- public void unregisterCallback(Callback cb) {
- int hashCode = System.identityHashCode(cb);
- synchronized (mListenerMap) {
- ILowpanInterfaceListener listenerBinder = mListenerMap.get(hashCode);
-
- if (listenerBinder != null) {
- mListenerMap.remove(hashCode);
-
- try {
- mBinder.removeListener(listenerBinder);
- } catch (DeadObjectException x) {
- // We ignore a dead object exception because that
- // pretty clearly means our callback isn't registered.
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
- }
- }
- }
- }
-
- // Active and Passive Scanning
-
- /**
- * Creates a new {@link android.net.lowpan.LowpanScanner} object for this interface.
- *
- * <p>This method allocates a new unique object for each call.
- *
- * @see android.net.lowpan.LowpanScanner
- */
- public @NonNull LowpanScanner createScanner() {
- return new LowpanScanner(mBinder);
- }
-
- // Route Management
-
- /**
- * Makes a copy of the internal list of LinkAddresses.
- *
- * @hide
- */
- public LinkAddress[] getLinkAddresses() throws LowpanException {
- try {
- String[] linkAddressStrings = mBinder.getLinkAddresses();
- LinkAddress[] ret = new LinkAddress[linkAddressStrings.length];
- int i = 0;
- for (String str : linkAddressStrings) {
- ret[i++] = new LinkAddress(str);
- }
- return ret;
-
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
-
- } catch (ServiceSpecificException x) {
- throw LowpanException.rethrowFromServiceSpecificException(x);
- }
- }
-
- /**
- * Makes a copy of the internal list of networks reachable on via this link.
- *
- * @hide
- */
- public IpPrefix[] getLinkNetworks() throws LowpanException {
- try {
- return mBinder.getLinkNetworks();
-
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
-
- } catch (ServiceSpecificException x) {
- throw LowpanException.rethrowFromServiceSpecificException(x);
- }
- }
-
- /**
- * Advertise the given IP prefix as an on-mesh prefix.
- *
- * @hide
- */
- public void addOnMeshPrefix(IpPrefix prefix, int flags) throws LowpanException {
- try {
- mBinder.addOnMeshPrefix(prefix, flags);
-
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
-
- } catch (ServiceSpecificException x) {
- throw LowpanException.rethrowFromServiceSpecificException(x);
- }
- }
-
- /**
- * Remove an IP prefix previously advertised by this device from the list of advertised on-mesh
- * prefixes.
- *
- * @hide
- */
- public void removeOnMeshPrefix(IpPrefix prefix) {
- try {
- mBinder.removeOnMeshPrefix(prefix);
-
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
-
- } catch (ServiceSpecificException x) {
- // Catch and ignore all service exceptions
- Log.e(TAG, x.toString());
- }
- }
-
- /**
- * Advertise this device to other devices on the mesh network as having a specific route to the
- * given network. This device will then receive forwarded traffic for that network.
- *
- * @hide
- */
- public void addExternalRoute(IpPrefix prefix, int flags) throws LowpanException {
- try {
- mBinder.addExternalRoute(prefix, flags);
-
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
-
- } catch (ServiceSpecificException x) {
- throw LowpanException.rethrowFromServiceSpecificException(x);
- }
- }
-
- /**
- * Revoke a previously advertised specific route to the given network.
- *
- * @hide
- */
- public void removeExternalRoute(IpPrefix prefix) {
- try {
- mBinder.removeExternalRoute(prefix);
-
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
-
- } catch (ServiceSpecificException x) {
- // Catch and ignore all service exceptions
- Log.e(TAG, x.toString());
- }
- }
-}
diff --git a/lowpan/java/android/net/lowpan/LowpanManager.java b/lowpan/java/android/net/lowpan/LowpanManager.java
deleted file mode 100644
index 33b35e6af7af..000000000000
--- a/lowpan/java/android/net/lowpan/LowpanManager.java
+++ /dev/null
@@ -1,335 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.os.BackgroundThread;
-
-import java.lang.ref.WeakReference;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.WeakHashMap;
-
-/**
- * Manager object for looking up LoWPAN interfaces.
- *
- * @hide
- */
-// @SystemApi
-public class LowpanManager {
- private static final String TAG = LowpanManager.class.getSimpleName();
-
- /** @hide */
- // @SystemApi
- public abstract static class Callback {
- public void onInterfaceAdded(LowpanInterface lowpanInterface) {}
-
- public void onInterfaceRemoved(LowpanInterface lowpanInterface) {}
- }
-
- private final Map<Integer, ILowpanManagerListener> mListenerMap = new HashMap<>();
- private final Map<String, LowpanInterface> mInterfaceCache = new HashMap<>();
-
- /* This is a WeakHashMap because we don't want to hold onto
- * a strong reference to ILowpanInterface, so that it can be
- * garbage collected if it isn't being used anymore. Since
- * the value class holds onto this specific ILowpanInterface,
- * we also need to have a weak reference to the value.
- * This design pattern allows us to skip removal of items
- * from this Map without leaking memory.
- */
- private final Map<IBinder, WeakReference<LowpanInterface>> mBinderCache =
- new WeakHashMap<>();
-
- private final ILowpanManager mService;
- private final Context mContext;
- private final Looper mLooper;
-
- // Static Methods
-
- public static LowpanManager from(Context context) {
- return (LowpanManager) context.getSystemService(Context.LOWPAN_SERVICE);
- }
-
- /** @hide */
- public static LowpanManager getManager() {
- IBinder binder = ServiceManager.getService(Context.LOWPAN_SERVICE);
-
- if (binder != null) {
- ILowpanManager service = ILowpanManager.Stub.asInterface(binder);
- return new LowpanManager(service);
- }
-
- return null;
- }
-
- // Constructors
-
- LowpanManager(ILowpanManager service) {
- mService = service;
- mContext = null;
- mLooper = null;
- }
-
- /**
- * Create a new LowpanManager instance. Applications will almost always want to use {@link
- * android.content.Context#getSystemService Context.getSystemService()} to retrieve the standard
- * {@link android.content.Context#LOWPAN_SERVICE Context.LOWPAN_SERVICE}.
- *
- * @param context the application context
- * @param service the Binder interface
- * @hide - hide this because it takes in a parameter of type ILowpanManager, which is a system
- * private class.
- */
- public LowpanManager(Context context, ILowpanManager service) {
- this(context, service, BackgroundThread.get().getLooper());
- }
-
- @VisibleForTesting
- public LowpanManager(Context context, ILowpanManager service, Looper looper) {
- mContext = context;
- mService = service;
- mLooper = looper;
- }
-
- /** @hide */
- @Nullable
- public LowpanInterface getInterfaceNoCreate(@NonNull ILowpanInterface ifaceService) {
- LowpanInterface iface = null;
-
- synchronized (mBinderCache) {
- if (mBinderCache.containsKey(ifaceService.asBinder())) {
- iface = mBinderCache.get(ifaceService.asBinder()).get();
- }
- }
-
- return iface;
- }
-
- /** @hide */
- @Nullable
- public LowpanInterface getInterface(@NonNull ILowpanInterface ifaceService) {
- LowpanInterface iface = null;
-
- try {
- synchronized (mBinderCache) {
- if (mBinderCache.containsKey(ifaceService.asBinder())) {
- iface = mBinderCache.get(ifaceService.asBinder()).get();
- }
-
- if (iface == null) {
- String ifaceName = ifaceService.getName();
-
- iface = new LowpanInterface(mContext, ifaceService, mLooper);
-
- synchronized (mInterfaceCache) {
- mInterfaceCache.put(iface.getName(), iface);
- }
-
- mBinderCache.put(ifaceService.asBinder(), new WeakReference(iface));
-
- /* Make sure we remove the object from the
- * interface cache if the associated service
- * dies.
- */
- ifaceService
- .asBinder()
- .linkToDeath(
- new IBinder.DeathRecipient() {
- @Override
- public void binderDied() {
- synchronized (mInterfaceCache) {
- LowpanInterface iface =
- mInterfaceCache.get(ifaceName);
-
- if ((iface != null)
- && (iface.getService() == ifaceService)) {
- mInterfaceCache.remove(ifaceName);
- }
- }
- }
- },
- 0);
- }
- }
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
- }
-
- return iface;
- }
-
- /**
- * Returns a reference to the requested LowpanInterface object. If the given interface doesn't
- * exist, or it is not a LoWPAN interface, returns null.
- */
- @Nullable
- public LowpanInterface getInterface(@NonNull String name) {
- LowpanInterface iface = null;
-
- try {
- /* This synchronized block covers both branches of the enclosed
- * if() statement in order to avoid a race condition. Two threads
- * calling getInterface() with the same name would race to create
- * the associated LowpanInterface object, creating two of them.
- * Having the whole block be synchronized avoids that race.
- */
- synchronized (mInterfaceCache) {
- if (mInterfaceCache.containsKey(name)) {
- iface = mInterfaceCache.get(name);
-
- } else {
- ILowpanInterface ifaceService = mService.getInterface(name);
-
- if (ifaceService != null) {
- iface = getInterface(ifaceService);
- }
- }
- }
- } catch (RemoteException x) {
- throw x.rethrowFromSystemServer();
- }
-
- return iface;
- }
-
- /**
- * Returns a reference to the first registered LowpanInterface object. If there are no LoWPAN
- * interfaces registered, returns null.
- */
- @Nullable
- public LowpanInterface getInterface() {
- String[] ifaceList = getInterfaceList();
- if (ifaceList.length > 0) {
- return getInterface(ifaceList[0]);
- }
- return null;
- }
-
- /**
- * Returns a string array containing the names of LoWPAN interfaces. This list may contain fewer
- * interfaces if the calling process does not have permissions to see individual interfaces.
- */
- @NonNull
- public String[] getInterfaceList() {
- try {
- return mService.getInterfaceList();
- } catch (RemoteException x) {
- throw x.rethrowFromSystemServer();
- }
- }
-
- /**
- * Registers a callback object to receive notifications when LoWPAN interfaces are added or
- * removed.
- *
- * @hide
- */
- public void registerCallback(@NonNull Callback cb, @Nullable Handler handler)
- throws LowpanException {
- ILowpanManagerListener.Stub listenerBinder =
- new ILowpanManagerListener.Stub() {
- private Handler mHandler;
-
- {
- if (handler != null) {
- mHandler = handler;
- } else if (mLooper != null) {
- mHandler = new Handler(mLooper);
- } else {
- mHandler = new Handler();
- }
- }
-
- @Override
- public void onInterfaceAdded(ILowpanInterface ifaceService) {
- Runnable runnable =
- () -> {
- LowpanInterface iface = getInterface(ifaceService);
-
- if (iface != null) {
- cb.onInterfaceAdded(iface);
- }
- };
-
- mHandler.post(runnable);
- }
-
- @Override
- public void onInterfaceRemoved(ILowpanInterface ifaceService) {
- Runnable runnable =
- () -> {
- LowpanInterface iface = getInterfaceNoCreate(ifaceService);
-
- if (iface != null) {
- cb.onInterfaceRemoved(iface);
- }
- };
-
- mHandler.post(runnable);
- }
- };
- try {
- mService.addListener(listenerBinder);
- } catch (RemoteException x) {
- throw x.rethrowFromSystemServer();
- }
-
- synchronized (mListenerMap) {
- mListenerMap.put(Integer.valueOf(System.identityHashCode(cb)), listenerBinder);
- }
- }
-
- /** @hide */
- public void registerCallback(@NonNull Callback cb) throws LowpanException {
- registerCallback(cb, null);
- }
-
- /**
- * Unregisters a previously registered {@link LowpanManager.Callback} object.
- *
- * @hide
- */
- public void unregisterCallback(@NonNull Callback cb) {
- Integer hashCode = Integer.valueOf(System.identityHashCode(cb));
- ILowpanManagerListener listenerBinder = null;
-
- synchronized (mListenerMap) {
- listenerBinder = mListenerMap.get(hashCode);
- mListenerMap.remove(hashCode);
- }
-
- if (listenerBinder != null) {
- try {
- mService.removeListener(listenerBinder);
- } catch (RemoteException x) {
- throw x.rethrowFromSystemServer();
- }
- } else {
- throw new RuntimeException("Attempt to unregister an unknown callback");
- }
- }
-}
diff --git a/lowpan/java/android/net/lowpan/LowpanProperties.java b/lowpan/java/android/net/lowpan/LowpanProperties.java
deleted file mode 100644
index cc45ff85f723..000000000000
--- a/lowpan/java/android/net/lowpan/LowpanProperties.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-/** {@hide} */
-public final class LowpanProperties {
-
- public static final LowpanProperty<int[]> KEY_CHANNEL_MASK =
- new LowpanStandardProperty("android.net.lowpan.property.CHANNEL_MASK", int[].class);
-
- public static final LowpanProperty<Integer> KEY_MAX_TX_POWER =
- new LowpanStandardProperty("android.net.lowpan.property.MAX_TX_POWER", Integer.class);
-
- /** @hide */
- private LowpanProperties() {}
-
- /** @hide */
- static final class LowpanStandardProperty<T> extends LowpanProperty<T> {
- private final String mName;
- private final Class<T> mType;
-
- LowpanStandardProperty(String name, Class<T> type) {
- mName = name;
- mType = type;
- }
-
- @Override
- public String getName() {
- return mName;
- }
-
- @Override
- public Class<T> getType() {
- return mType;
- }
-
- @Override
- public String toString() {
- return getName();
- }
- }
-}
diff --git a/lowpan/java/android/net/lowpan/LowpanProperty.java b/lowpan/java/android/net/lowpan/LowpanProperty.java
deleted file mode 100644
index 7f26986e9da0..000000000000
--- a/lowpan/java/android/net/lowpan/LowpanProperty.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-import java.util.Map;
-
-/** {@hide} */
-public abstract class LowpanProperty<T> {
- public abstract String getName();
-
- public abstract Class<T> getType();
-
- public void putInMap(Map map, T value) {
- map.put(getName(), value);
- }
-
- public T getFromMap(Map map) {
- return (T) map.get(getName());
- }
-}
diff --git a/lowpan/java/android/net/lowpan/LowpanProvision.aidl b/lowpan/java/android/net/lowpan/LowpanProvision.aidl
deleted file mode 100644
index 100e9dc8cc4a..000000000000
--- a/lowpan/java/android/net/lowpan/LowpanProvision.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/**
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-parcelable LowpanProvision cpp_header "android/net/lowpan/LowpanProvision.h";
diff --git a/lowpan/java/android/net/lowpan/LowpanProvision.java b/lowpan/java/android/net/lowpan/LowpanProvision.java
deleted file mode 100644
index 68c87092fbed..000000000000
--- a/lowpan/java/android/net/lowpan/LowpanProvision.java
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.os.Parcel;
-import android.os.Parcelable;
-import java.util.Objects;
-
-/**
- * Describes the information needed to describe a network
- *
- * @hide
- */
-// @SystemApi
-public class LowpanProvision implements Parcelable {
-
- // Builder
-
- /** @hide */
- // @SystemApi
- public static class Builder {
- private final LowpanProvision provision = new LowpanProvision();
-
- public Builder setLowpanIdentity(@NonNull LowpanIdentity identity) {
- provision.mIdentity = identity;
- return this;
- }
-
- public Builder setLowpanCredential(@NonNull LowpanCredential credential) {
- provision.mCredential = credential;
- return this;
- }
-
- public LowpanProvision build() {
- return provision;
- }
- }
-
- private LowpanProvision() {}
-
- // Instance Variables
-
- private LowpanIdentity mIdentity = new LowpanIdentity();
- private LowpanCredential mCredential = null;
-
- // Public Getters and Setters
-
- @NonNull
- public LowpanIdentity getLowpanIdentity() {
- return mIdentity;
- }
-
- @Nullable
- public LowpanCredential getLowpanCredential() {
- return mCredential;
- }
-
- @Override
- public String toString() {
- StringBuffer sb = new StringBuffer();
-
- sb.append("LowpanProvision { identity => ").append(mIdentity.toString());
-
- if (mCredential != null) {
- sb.append(", credential => ").append(mCredential.toString());
- }
-
- sb.append("}");
-
- return sb.toString();
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(mIdentity, mCredential);
- }
-
- @Override
- public boolean equals(Object obj) {
- if (!(obj instanceof LowpanProvision)) {
- return false;
- }
- LowpanProvision rhs = (LowpanProvision) obj;
-
- if (!mIdentity.equals(rhs.mIdentity)) {
- return false;
- }
-
- if (!Objects.equals(mCredential, rhs.mCredential)) {
- return false;
- }
-
- return true;
- }
-
- /** Implement the Parcelable interface. */
- @Override
- public int describeContents() {
- return 0;
- }
-
- /** Implement the Parcelable interface. */
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- mIdentity.writeToParcel(dest, flags);
- if (mCredential == null) {
- dest.writeBoolean(false);
- } else {
- dest.writeBoolean(true);
- mCredential.writeToParcel(dest, flags);
- }
- }
-
- /** Implement the Parcelable interface. */
- public static final @android.annotation.NonNull Creator<LowpanProvision> CREATOR =
- new Creator<LowpanProvision>() {
- public LowpanProvision createFromParcel(Parcel in) {
- Builder builder = new Builder();
-
- builder.setLowpanIdentity(LowpanIdentity.CREATOR.createFromParcel(in));
-
- if (in.readBoolean()) {
- builder.setLowpanCredential(LowpanCredential.CREATOR.createFromParcel(in));
- }
-
- return builder.build();
- }
-
- public LowpanProvision[] newArray(int size) {
- return new LowpanProvision[size];
- }
- };
-};
diff --git a/lowpan/java/android/net/lowpan/LowpanRuntimeException.java b/lowpan/java/android/net/lowpan/LowpanRuntimeException.java
deleted file mode 100644
index 71a5a1397a4e..000000000000
--- a/lowpan/java/android/net/lowpan/LowpanRuntimeException.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-import android.util.AndroidRuntimeException;
-
-/**
- * Generic runtime exception for LoWPAN operations.
- *
- * @hide
- */
-// @SystemApi
-public class LowpanRuntimeException extends AndroidRuntimeException {
-
- public LowpanRuntimeException() {}
-
- public LowpanRuntimeException(String message) {
- super(message);
- }
-
- public LowpanRuntimeException(String message, Throwable cause) {
- super(message, cause);
- }
-
- public LowpanRuntimeException(Exception cause) {
- super(cause);
- }
-}
diff --git a/lowpan/java/android/net/lowpan/LowpanScanner.java b/lowpan/java/android/net/lowpan/LowpanScanner.java
deleted file mode 100644
index 59156c429010..000000000000
--- a/lowpan/java/android/net/lowpan/LowpanScanner.java
+++ /dev/null
@@ -1,326 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.os.Handler;
-import android.os.RemoteException;
-import android.os.ServiceSpecificException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * LoWPAN Scanner
- *
- * <p>This class allows performing network (active) scans and energy (passive) scans.
- *
- * @see LowpanInterface
- * @hide
- */
-// @SystemApi
-public class LowpanScanner {
- private static final String TAG = LowpanScanner.class.getSimpleName();
-
- // Public Classes
-
- /**
- * Callback base class for LowpanScanner
- *
- * @hide
- */
- // @SystemApi
- public abstract static class Callback {
- public void onNetScanBeacon(LowpanBeaconInfo beacon) {}
-
- public void onEnergyScanResult(LowpanEnergyScanResult result) {}
-
- public void onScanFinished() {}
- }
-
- // Instance Variables
-
- private ILowpanInterface mBinder;
- private Callback mCallback = null;
- private Handler mHandler = null;
- private ArrayList<Integer> mChannelMask = null;
- private int mTxPower = Integer.MAX_VALUE;
-
- // Constructors/Accessors and Exception Glue
-
- LowpanScanner(@NonNull ILowpanInterface binder) {
- mBinder = binder;
- }
-
- /** Sets an instance of {@link LowpanScanner.Callback} to receive events. */
- public synchronized void setCallback(@Nullable Callback cb, @Nullable Handler handler) {
- mCallback = cb;
- mHandler = handler;
- }
-
- /** Sets an instance of {@link LowpanScanner.Callback} to receive events. */
- public void setCallback(@Nullable Callback cb) {
- setCallback(cb, null);
- }
-
- /**
- * Sets the channel mask to use when scanning.
- *
- * @param mask The channel mask to use when scanning. If <code>null</code>, any previously set
- * channel mask will be cleared and all channels not masked by the current regulatory zone
- * will be scanned.
- */
- public void setChannelMask(@Nullable Collection<Integer> mask) {
- if (mask == null) {
- mChannelMask = null;
- } else {
- if (mChannelMask == null) {
- mChannelMask = new ArrayList<>();
- } else {
- mChannelMask.clear();
- }
- mChannelMask.addAll(mask);
- }
- }
-
- /**
- * Gets the current channel mask.
- *
- * @return the current channel mask, or <code>null</code> if no channel mask is currently set.
- */
- public @Nullable Collection<Integer> getChannelMask() {
- return (Collection<Integer>) mChannelMask.clone();
- }
-
- /**
- * Adds a channel to the channel mask used for scanning.
- *
- * <p>If a channel mask was previously <code>null</code>, a new one is created containing only
- * this channel. May be called multiple times to add additional channels ot the channel mask.
- *
- * @see #setChannelMask
- * @see #getChannelMask
- * @see #getTxPower
- */
- public void addChannel(int channel) {
- if (mChannelMask == null) {
- mChannelMask = new ArrayList<>();
- }
- mChannelMask.add(Integer.valueOf(channel));
- }
-
- /**
- * Sets the maximum transmit power to be used for active scanning.
- *
- * <p>The actual transmit power used is the lesser of this value and the currently configured
- * maximum transmit power for the interface.
- *
- * @see #getTxPower
- */
- public void setTxPower(int txPower) {
- mTxPower = txPower;
- }
-
- /**
- * Gets the maximum transmit power used for active scanning.
- *
- * @see #setTxPower
- */
- public int getTxPower() {
- return mTxPower;
- }
-
- private Map<String, Object> createScanOptionMap() {
- Map<String, Object> map = new HashMap();
-
- if (mChannelMask != null) {
- LowpanProperties.KEY_CHANNEL_MASK.putInMap(
- map, mChannelMask.stream().mapToInt(i -> i).toArray());
- }
-
- if (mTxPower != Integer.MAX_VALUE) {
- LowpanProperties.KEY_MAX_TX_POWER.putInMap(map, Integer.valueOf(mTxPower));
- }
-
- return map;
- }
-
- /**
- * Start a network scan.
- *
- * <p>This method will return once the scan has started.
- *
- * @see #stopNetScan
- */
- public void startNetScan() throws LowpanException {
- Map<String, Object> map = createScanOptionMap();
-
- ILowpanNetScanCallback binderListener =
- new ILowpanNetScanCallback.Stub() {
- public void onNetScanBeacon(LowpanBeaconInfo beaconInfo) {
- Callback callback;
- Handler handler;
-
- synchronized (LowpanScanner.this) {
- callback = mCallback;
- handler = mHandler;
- }
-
- if (callback == null) {
- return;
- }
-
- Runnable runnable = () -> callback.onNetScanBeacon(beaconInfo);
-
- if (handler != null) {
- handler.post(runnable);
- } else {
- runnable.run();
- }
- }
-
- public void onNetScanFinished() {
- Callback callback;
- Handler handler;
-
- synchronized (LowpanScanner.this) {
- callback = mCallback;
- handler = mHandler;
- }
-
- if (callback == null) {
- return;
- }
-
- Runnable runnable = () -> callback.onScanFinished();
-
- if (handler != null) {
- handler.post(runnable);
- } else {
- runnable.run();
- }
- }
- };
-
- try {
- mBinder.startNetScan(map, binderListener);
-
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
-
- } catch (ServiceSpecificException x) {
- throw LowpanException.rethrowFromServiceSpecificException(x);
- }
- }
-
- /**
- * Stop a network scan currently in progress.
- *
- * @see #startNetScan
- */
- public void stopNetScan() {
- try {
- mBinder.stopNetScan();
-
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
- }
- }
-
- /**
- * Start an energy scan.
- *
- * <p>This method will return once the scan has started.
- *
- * @see #stopEnergyScan
- */
- public void startEnergyScan() throws LowpanException {
- Map<String, Object> map = createScanOptionMap();
-
- ILowpanEnergyScanCallback binderListener =
- new ILowpanEnergyScanCallback.Stub() {
- public void onEnergyScanResult(int channel, int rssi) {
- Callback callback = mCallback;
- Handler handler = mHandler;
-
- if (callback == null) {
- return;
- }
-
- Runnable runnable =
- () -> {
- if (callback != null) {
- LowpanEnergyScanResult result =
- new LowpanEnergyScanResult();
- result.setChannel(channel);
- result.setMaxRssi(rssi);
- callback.onEnergyScanResult(result);
- }
- };
-
- if (handler != null) {
- handler.post(runnable);
- } else {
- runnable.run();
- }
- }
-
- public void onEnergyScanFinished() {
- Callback callback = mCallback;
- Handler handler = mHandler;
-
- if (callback == null) {
- return;
- }
-
- Runnable runnable = () -> callback.onScanFinished();
-
- if (handler != null) {
- handler.post(runnable);
- } else {
- runnable.run();
- }
- }
- };
-
- try {
- mBinder.startEnergyScan(map, binderListener);
-
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
-
- } catch (ServiceSpecificException x) {
- throw LowpanException.rethrowFromServiceSpecificException(x);
- }
- }
-
- /**
- * Stop an energy scan currently in progress.
- *
- * @see #startEnergyScan
- */
- public void stopEnergyScan() {
- try {
- mBinder.stopEnergyScan();
-
- } catch (RemoteException x) {
- throw x.rethrowAsRuntimeException();
- }
- }
-}
diff --git a/lowpan/java/android/net/lowpan/NetworkAlreadyExistsException.java b/lowpan/java/android/net/lowpan/NetworkAlreadyExistsException.java
deleted file mode 100644
index 90ef498baaba..000000000000
--- a/lowpan/java/android/net/lowpan/NetworkAlreadyExistsException.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-/**
- * Exception indicating the form operation found a network nearby with the same identity.
- *
- * @see LowpanInterface
- * @hide
- */
-// @SystemApi
-public class NetworkAlreadyExistsException extends LowpanException {
-
- public NetworkAlreadyExistsException() {}
-
- public NetworkAlreadyExistsException(String message) {
- super(message, null);
- }
-
- public NetworkAlreadyExistsException(String message, Throwable cause) {
- super(message, cause);
- }
-
- public NetworkAlreadyExistsException(Exception cause) {
- super(cause);
- }
-}
diff --git a/lowpan/java/android/net/lowpan/OperationCanceledException.java b/lowpan/java/android/net/lowpan/OperationCanceledException.java
deleted file mode 100644
index fcafe3ae9b8f..000000000000
--- a/lowpan/java/android/net/lowpan/OperationCanceledException.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-/**
- * Exception indicating this operation was canceled by the driver before it could finish.
- *
- * @see LowpanInterface
- * @hide
- */
-// @SystemApi
-public class OperationCanceledException extends LowpanException {
-
- public OperationCanceledException() {}
-
- public OperationCanceledException(String message) {
- super(message);
- }
-
- public OperationCanceledException(String message, Throwable cause) {
- super(message, cause);
- }
-
- protected OperationCanceledException(Exception cause) {
- super(cause);
- }
-}
diff --git a/lowpan/java/android/net/lowpan/WrongStateException.java b/lowpan/java/android/net/lowpan/WrongStateException.java
deleted file mode 100644
index 35654194e32b..000000000000
--- a/lowpan/java/android/net/lowpan/WrongStateException.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-/**
- * Exception indicating the interface is the wrong state for an operation.
- *
- * @see LowpanInterface
- * @hide
- */
-// @SystemApi
-public class WrongStateException extends LowpanException {
-
- public WrongStateException() {}
-
- public WrongStateException(String message) {
- super(message);
- }
-
- public WrongStateException(String message, Throwable cause) {
- super(message, cause);
- }
-
- protected WrongStateException(Exception cause) {
- super(cause);
- }
-}
diff --git a/lowpan/java/android/net/lowpan/package.html b/lowpan/java/android/net/lowpan/package.html
deleted file mode 100644
index 342e32eefc1e..000000000000
--- a/lowpan/java/android/net/lowpan/package.html
+++ /dev/null
@@ -1,29 +0,0 @@
-<HTML>
-<BODY>
-<p>@SystemApi</p>
-<!-- @hide -->
-<p>Provides classes to manage Low-power Wireless Personal Area Network (LoWPAN) functionality on the device.
-Examples of such network technologies include <a href="http://threadgroup.org/">Thread</a> and
-<a href="http://www.zigbee.org/zigbee-for-developers/network-specifications/zigbeeip/">ZigBee IP</a>.</p>
-<p>The LoWPAN APIs provide a means by which applications can communicate
-with the lower-level wireless stack that provides LoWPAN network access.</p>
-
-<p>Some APIs may require the following user permissions:</p>
-<ul>
- <li>{@link android.Manifest.permission#ACCESS_LOWPAN_STATE}</li>
- <li>{@link android.Manifest.permission#CHANGE_LOWPAN_STATE}</li>
- <li>TBD</li>
-</ul>
-
-<p class="note"><strong>Note:</strong> Not all Android-powered devices provide LoWPAN functionality.
-If your application uses these APIs, declare so with a <a
-href="{@docRoot}guide/topics/manifest/uses-feature-element.html">{@code <uses-feature>}</a>
-element in the manifest file:</p>
-<pre>
-&lt;manifest ...>
- &lt;uses-feature android:name="android.hardware.lowpan" />
- ...
-&lt;/manifest>
-</pre>
-</BODY>
-</HTML>
diff --git a/lowpan/tests/Android.bp b/lowpan/tests/Android.bp
deleted file mode 100644
index 590892926ec1..000000000000
--- a/lowpan/tests/Android.bp
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright (C) 2017 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Make test APK
-// ============================================================
-package {
- // See: http://go/android-license-faq
- // A large-scale-change added 'default_applicable_licenses' to import
- // all of the 'license_kinds' from "frameworks_base_license"
- // to get the below license kinds:
- // SPDX-license-identifier-Apache-2.0
- default_applicable_licenses: ["frameworks_base_license"],
-}
-
-android_test {
- name: "FrameworksLowpanApiTests",
- srcs: ["**/*.java"],
- // Filter all src files to just java files
- jacoco: {
- include_filter: ["android.net.lowpan.*"],
- exclude_filter: [
- "android.net.lowpan.LowpanInterfaceTest*",
- "android.net.lowpan.LowpanManagerTest*",
- ],
- },
- static_libs: [
- "androidx.test.rules",
- "guava",
- "mockito-target-minus-junit4",
- "frameworks-base-testutils",
- ],
- libs: [
- "android.test.runner",
- "android.test.base",
- ],
- platform_apis: true,
- test_suites: ["device-tests"],
- certificate: "platform",
-}
diff --git a/lowpan/tests/AndroidManifest.xml b/lowpan/tests/AndroidManifest.xml
deleted file mode 100644
index 8e68fc74561e..000000000000
--- a/lowpan/tests/AndroidManifest.xml
+++ /dev/null
@@ -1,39 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!--
- ~ Copyright (C) 2017 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License
- -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="android.net.lowpan.test">
-
- <application>
- <uses-library android:name="android.test.runner" />
- <activity android:label="LowpanTestDummyLabel"
- android:name="LowpanTestDummyName"
- android:exported="true">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER"/>
- </intent-filter>
- </activity>
- </application>
-
- <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
- android:targetPackage="android.net.lowpan.test"
- android:label="Frameworks LoWPAN API Tests">
- </instrumentation>
-
-</manifest>
diff --git a/lowpan/tests/AndroidTest.xml b/lowpan/tests/AndroidTest.xml
deleted file mode 100644
index 978cc02d2f57..000000000000
--- a/lowpan/tests/AndroidTest.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<configuration description="Runs Frameworks LoWPAN API Tests.">
- <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
- <option name="test-file-name" value="FrameworksLowpanApiTests.apk" />
- </target_preparer>
-
- <option name="test-suite-tag" value="apct" />
- <option name="test-tag" value="FrameworksLowpanApiTests" />
- <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
- <option name="package" value="android.net.lowpan.test" />
- <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
- </test>
-</configuration>
diff --git a/lowpan/tests/README.md b/lowpan/tests/README.md
deleted file mode 100644
index cb5772ee48ab..000000000000
--- a/lowpan/tests/README.md
+++ /dev/null
@@ -1,50 +0,0 @@
-# LoWPAN Unit Tests
-This package contains unit tests for the android LoWPAN framework System APIs based on the
-[Android Testing Support Library](http://developer.android.com/tools/testing-support-library/index.html).
-The test cases are built using the [JUnit](http://junit.org/) and [Mockito](http://mockito.org/)
-libraries.
-
-## Running Tests
-The easiest way to run tests is simply run
-
-```
-frameworks/base/lowpan/tests/runtests.sh
-```
-
-`runtests.sh` will build the test project and all of its dependencies and push the APK to the
-connected device. It will then run the tests on the device.
-
-To pick up changes in framework/base, you will need to:
-1. rebuild the framework library 'make -j32'
-2. sync over the updated library to the device 'adb sync'
-3. restart framework on the device 'adb shell stop' then 'adb shell start'
-
-To enable syncing data to the device for first time after clean reflash:
-1. adb disable-verity
-2. adb reboot
-3. adb remount
-
-See below for a few example of options to limit which tests are run.
-See the
-[AndroidJUnitRunner Documentation](https://developer.android.com/reference/android/support/test/runner/AndroidJUnitRunner.html)
-for more details on the supported options.
-
-```
-runtests.sh -e package android.net.lowpan
-runtests.sh -e class android.net.lowpan.LowpanManagerTest
-```
-
-If you manually build and push the test APK to the device you can run tests using
-
-```
-adb shell am instrument -w 'android.net.wifi.test/androidx.test.runner.AndroidJUnitRunner'
-```
-
-## Adding Tests
-Tests can be added by adding classes to the src directory. JUnit4 style test cases can
-be written by simply annotating test methods with `org.junit.Test`.
-
-## Debugging Tests
-If you are trying to debug why tests are not doing what you expected, you can add android log
-statements and use logcat to view them. The beginning and end of every tests is automatically logged
-with the tag `TestRunner`.
diff --git a/lowpan/tests/runtests.sh b/lowpan/tests/runtests.sh
deleted file mode 100755
index 8267a7975c42..000000000000
--- a/lowpan/tests/runtests.sh
+++ /dev/null
@@ -1,24 +0,0 @@
-#!/usr/bin/env bash
-
-if [ -z $ANDROID_BUILD_TOP ]; then
- echo "You need to source and lunch before you can use this script"
- exit 1
-fi
-
-echo "Running tests"
-
-set -e # fail early
-
-echo "+ mmma -j32 $ANDROID_BUILD_TOP/frameworks/base/lowpan/tests"
-# NOTE Don't actually run the command above since this shell doesn't inherit functions from the
-# caller.
-make -j32 -C $ANDROID_BUILD_TOP -f build/core/main.mk MODULES-IN-frameworks-base-lowpan-tests
-
-set -x # print commands
-
-adb root
-adb wait-for-device
-
-adb install -r -g "$OUT/data/app/FrameworksLowpanApiTests/FrameworksLowpanApiTests.apk"
-
-adb shell am instrument -w "$@" 'android.net.lowpan.test/androidx.test.runner.AndroidJUnitRunner'
diff --git a/lowpan/tests/src/android/net/lowpan/LowpanInterfaceTest.java b/lowpan/tests/src/android/net/lowpan/LowpanInterfaceTest.java
deleted file mode 100644
index 86f9d0e3ca69..000000000000
--- a/lowpan/tests/src/android/net/lowpan/LowpanInterfaceTest.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-import static org.mockito.Mockito.*;
-
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.test.TestLooper;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.Map;
-
-/** Unit tests for android.net.lowpan.LowpanInterface. */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class LowpanInterfaceTest {
- private static final String TEST_PACKAGE_NAME = "TestPackage";
-
- @Mock Context mContext;
- @Mock ILowpanInterface mLowpanInterfaceService;
- @Mock IBinder mLowpanInterfaceBinder;
- @Mock ApplicationInfo mApplicationInfo;
- @Mock IBinder mAppBinder;
- @Mock LowpanInterface.Callback mLowpanInterfaceCallback;
-
- private Handler mHandler;
- private final TestLooper mTestLooper = new TestLooper();
- private ILowpanInterfaceListener mInterfaceListener;
- private LowpanInterface mLowpanInterface;
- private Map<String, Object> mPropertyMap;
-
- @Before
- public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
- when(mContext.getApplicationInfo()).thenReturn(mApplicationInfo);
- when(mContext.getOpPackageName()).thenReturn(TEST_PACKAGE_NAME);
- when(mLowpanInterfaceService.getName()).thenReturn("wpan0");
- when(mLowpanInterfaceService.asBinder()).thenReturn(mLowpanInterfaceBinder);
-
- mLowpanInterface =
- new LowpanInterface(mContext, mLowpanInterfaceService, mTestLooper.getLooper());
- }
-
- @Test
- public void testStateChangedCallback() throws Exception {
- // Register our callback
- mLowpanInterface.registerCallback(mLowpanInterfaceCallback);
-
- // Verify a listener was added
- verify(mLowpanInterfaceService)
- .addListener(
- argThat(
- listener -> {
- mInterfaceListener = listener;
- return listener instanceof ILowpanInterfaceListener;
- }));
-
- // Change some properties
- mInterfaceListener.onStateChanged(LowpanInterface.STATE_OFFLINE);
- mTestLooper.dispatchAll();
-
- // Verify that the property was changed
- verify(mLowpanInterfaceCallback)
- .onStateChanged(
- argThat(stateString -> stateString.equals(LowpanInterface.STATE_OFFLINE)));
- }
-}
diff --git a/lowpan/tests/src/android/net/lowpan/LowpanManagerTest.java b/lowpan/tests/src/android/net/lowpan/LowpanManagerTest.java
deleted file mode 100644
index 998e8a549540..000000000000
--- a/lowpan/tests/src/android/net/lowpan/LowpanManagerTest.java
+++ /dev/null
@@ -1,177 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.lowpan;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.*;
-
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.test.TestLooper;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-/** Unit tests for android.net.lowpan.LowpanManager. */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class LowpanManagerTest {
- private static final String TEST_PACKAGE_NAME = "TestPackage";
-
- @Mock Context mContext;
- @Mock ILowpanManager mLowpanService;
- @Mock ILowpanInterface mLowpanInterfaceService;
- @Mock IBinder mLowpanInterfaceBinder;
- @Mock ApplicationInfo mApplicationInfo;
- @Mock IBinder mAppBinder;
- @Mock LowpanManager.Callback mLowpanManagerCallback;
-
- private Handler mHandler;
- private final TestLooper mTestLooper = new TestLooper();
- private LowpanManager mLowpanManager;
-
- private ILowpanManagerListener mManagerListener;
- private LowpanInterface mLowpanInterface;
-
- @Before
- public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
- when(mContext.getApplicationInfo()).thenReturn(mApplicationInfo);
- when(mContext.getOpPackageName()).thenReturn(TEST_PACKAGE_NAME);
-
- mLowpanManager = new LowpanManager(mContext, mLowpanService, mTestLooper.getLooper());
- }
-
- @Test
- public void testGetEmptyInterfaceList() throws Exception {
- when(mLowpanService.getInterfaceList()).thenReturn(new String[0]);
- assertTrue(mLowpanManager.getInterfaceList().length == 0);
- assertTrue(mLowpanManager.getInterface() == null);
- }
-
- @Test
- public void testGetInterfaceList() throws Exception {
- when(mLowpanService.getInterfaceList()).thenReturn(new String[] {"wpan0"});
- when(mLowpanService.getInterface("wpan0")).thenReturn(mLowpanInterfaceService);
- when(mLowpanInterfaceService.getName()).thenReturn("wpan0");
- when(mLowpanInterfaceService.asBinder()).thenReturn(mLowpanInterfaceBinder);
- assertEquals(mLowpanManager.getInterfaceList().length, 1);
-
- LowpanInterface iface = mLowpanManager.getInterface();
- assertNotNull(iface);
- assertEquals(iface.getName(), "wpan0");
- }
-
- @Test
- public void testRegisterCallback() throws Exception {
- when(mLowpanInterfaceService.getName()).thenReturn("wpan0");
- when(mLowpanInterfaceService.asBinder()).thenReturn(mLowpanInterfaceBinder);
-
- // Register our callback
- mLowpanManager.registerCallback(mLowpanManagerCallback);
-
- // Verify a listener was added
- verify(mLowpanService)
- .addListener(
- argThat(
- listener -> {
- mManagerListener = listener;
- return listener instanceof ILowpanManagerListener;
- }));
-
- // Add an interface
- mManagerListener.onInterfaceAdded(mLowpanInterfaceService);
- mTestLooper.dispatchAll();
-
- // Verify that the interface was added
- verify(mLowpanManagerCallback)
- .onInterfaceAdded(
- argThat(
- iface -> {
- mLowpanInterface = iface;
- return iface instanceof LowpanInterface;
- }));
- verifyNoMoreInteractions(mLowpanManagerCallback);
-
- // This check causes the test to fail with a weird error, but I'm not sure why.
- assertEquals(mLowpanInterface.getService(), mLowpanInterfaceService);
-
- // Verify that calling getInterface on the LowpanManager object will yield the same
- // LowpanInterface object.
- when(mLowpanService.getInterfaceList()).thenReturn(new String[] {"wpan0"});
- when(mLowpanService.getInterface("wpan0")).thenReturn(mLowpanInterfaceService);
- assertEquals(mLowpanManager.getInterface(), mLowpanInterface);
-
- // Remove the service
- mManagerListener.onInterfaceRemoved(mLowpanInterfaceService);
- mTestLooper.dispatchAll();
-
- // Verify that the interface was removed
- verify(mLowpanManagerCallback).onInterfaceRemoved(mLowpanInterface);
- }
-
- @Test
- public void testUnregisterCallback() throws Exception {
- when(mLowpanInterfaceService.getName()).thenReturn("wpan0");
- when(mLowpanInterfaceService.asBinder()).thenReturn(mLowpanInterfaceBinder);
-
- // Register our callback
- mLowpanManager.registerCallback(mLowpanManagerCallback);
-
- // Verify a listener was added
- verify(mLowpanService)
- .addListener(
- argThat(
- listener -> {
- mManagerListener = listener;
- return listener instanceof ILowpanManagerListener;
- }));
-
- // Add an interface
- mManagerListener.onInterfaceAdded(mLowpanInterfaceService);
- mTestLooper.dispatchAll();
-
- // Verify that the interface was added
- verify(mLowpanManagerCallback)
- .onInterfaceAdded(
- argThat(
- iface -> {
- mLowpanInterface = iface;
- return iface instanceof LowpanInterface;
- }));
- verifyNoMoreInteractions(mLowpanManagerCallback);
-
- // Unregister our callback
- mLowpanManager.unregisterCallback(mLowpanManagerCallback);
-
- // Verify the listener was removed
- verify(mLowpanService).removeListener(mManagerListener);
-
- // Verify that the callback wasn't invoked.
- verifyNoMoreInteractions(mLowpanManagerCallback);
- }
-}
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 444366adf265..650f36059495 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -1299,8 +1299,8 @@ public class AudioSystem
/** @hide */ public static final String DEVICE_OUT_REMOTE_SUBMIX_NAME = "remote_submix";
/** @hide */ public static final String DEVICE_OUT_TELEPHONY_TX_NAME = "telephony_tx";
/** @hide */ public static final String DEVICE_OUT_LINE_NAME = "line";
- /** @hide */ public static final String DEVICE_OUT_HDMI_ARC_NAME = "hmdi_arc";
- /** @hide */ public static final String DEVICE_OUT_HDMI_EARC_NAME = "hmdi_earc";
+ /** @hide */ public static final String DEVICE_OUT_HDMI_ARC_NAME = "hdmi_arc";
+ /** @hide */ public static final String DEVICE_OUT_HDMI_EARC_NAME = "hdmi_earc";
/** @hide */ public static final String DEVICE_OUT_SPDIF_NAME = "spdif";
/** @hide */ public static final String DEVICE_OUT_FM_NAME = "fm_transmitter";
/** @hide */ public static final String DEVICE_OUT_AUX_LINE_NAME = "aux_line";
@@ -2294,10 +2294,10 @@ public class AudioSystem
public static int[] DEFAULT_STREAM_VOLUME = new int[] {
4, // STREAM_VOICE_CALL
7, // STREAM_SYSTEM
- 5, // STREAM_RING
+ 5, // STREAM_RING // configured in AudioService by config_audio_notif_vol_default
5, // STREAM_MUSIC
6, // STREAM_ALARM
- 5, // STREAM_NOTIFICATION
+ 5, // STREAM_NOTIFICATION // configured in AudioService by config_audio_ring_vol_default
7, // STREAM_BLUETOOTH_SCO
7, // STREAM_SYSTEM_ENFORCED
5, // STREAM_DTMF
diff --git a/media/java/android/media/BluetoothProfileConnectionInfo.java b/media/java/android/media/BluetoothProfileConnectionInfo.java
index c14884657ddd..f3a65a121768 100644
--- a/media/java/android/media/BluetoothProfileConnectionInfo.java
+++ b/media/java/android/media/BluetoothProfileConnectionInfo.java
@@ -126,6 +126,21 @@ public final class BluetoothProfileConnectionInfo implements Parcelable {
}
/**
+ * @hide
+ * Factory method for <code>BluetoothProfileConnectionInfo</code> for an LE output device
+ * @param suppressNoisyIntent if true the {@link AudioManager.ACTION_AUDIO_BECOMING_NOISY}
+ * intent will not be sent.
+ * @param volume the volume index of the device, -1 if unknown or to be ignored
+ * @return an instance of BluetoothProfileConnectionInfo for the BLE output device that reflects
+ * the given parameters
+ */
+ public static @NonNull BluetoothProfileConnectionInfo createLeAudioOutputInfo(
+ boolean suppressNoisyIntent, int volume) {
+ return new BluetoothProfileConnectionInfo(BluetoothProfile.LE_AUDIO, suppressNoisyIntent,
+ volume, /*isLeOutput*/ true);
+ }
+
+ /**
* @return The profile connection
*/
public int getProfile() {
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/BluetoothProfileConnectionInfoTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/BluetoothProfileConnectionInfoTest.java
new file mode 100644
index 000000000000..ae162b57b709
--- /dev/null
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/BluetoothProfileConnectionInfoTest.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.mediaframeworktest.unit;
+
+import static org.junit.Assert.assertEquals;
+
+import android.bluetooth.BluetoothProfile;
+import android.media.BluetoothProfileConnectionInfo;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class BluetoothProfileConnectionInfoTest {
+
+ @Test
+ public void testCoverageLeAudioOutputVolume() {
+ final boolean supprNoisy = false;
+ final int volume = 1;
+ final BluetoothProfileConnectionInfo info = BluetoothProfileConnectionInfo
+ .createLeAudioOutputInfo(supprNoisy, volume);
+ assertEquals(info.getProfile(), BluetoothProfile.LE_AUDIO);
+ assertEquals(info.isSuppressNoisyIntent(), supprNoisy);
+ assertEquals(info.isLeOutput(), true);
+ assertEquals(info.getVolume(), volume);
+ }
+
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java
index 98438cde7c68..b6b88375a0a2 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java
@@ -189,7 +189,8 @@ public class InstallStaging extends AlertActivity {
out.write(buffer, 0, bytesRead);
}
}
- } catch (IOException | SecurityException | IllegalStateException e) {
+ } catch (IOException | SecurityException | IllegalStateException
+ | IllegalArgumentException e) {
Log.w(LOG_TAG, "Error staging apk from content URI", e);
return false;
}
diff --git a/packages/SettingsLib/Spa/.idea/codeStyles/Project.xml b/packages/SettingsLib/Spa/.idea/codeStyles/Project.xml
new file mode 100644
index 000000000000..cb757d34dfb4
--- /dev/null
+++ b/packages/SettingsLib/Spa/.idea/codeStyles/Project.xml
@@ -0,0 +1,129 @@
+<component name="ProjectCodeStyleConfiguration">
+ <code_scheme name="Project" version="173">
+ <JetCodeStyleSettings>
+ <option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="2147483647" />
+ <option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="2147483647" />
+ </JetCodeStyleSettings>
+ <XML>
+ <option name="XML_KEEP_LINE_BREAKS" value="true" />
+ </XML>
+ <codeStyleSettings language="XML">
+ <option name="FORCE_REARRANGE_MODE" value="1" />
+ <indentOptions>
+ <option name="CONTINUATION_INDENT_SIZE" value="4" />
+ </indentOptions>
+ <arrangement>
+ <rules>
+ <section>
+ <rule>
+ <match>
+ <AND>
+ <NAME>xmlns:android</NAME>
+ <XML_ATTRIBUTE />
+ <XML_NAMESPACE>^$</XML_NAMESPACE>
+ </AND>
+ </match>
+ </rule>
+ </section>
+ <section>
+ <rule>
+ <match>
+ <AND>
+ <NAME>xmlns:.*</NAME>
+ <XML_ATTRIBUTE />
+ <XML_NAMESPACE>^$</XML_NAMESPACE>
+ </AND>
+ </match>
+ <order>BY_NAME</order>
+ </rule>
+ </section>
+ <section>
+ <rule>
+ <match>
+ <AND>
+ <NAME>.*:id</NAME>
+ <XML_ATTRIBUTE />
+ <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
+ </AND>
+ </match>
+ </rule>
+ </section>
+ <section>
+ <rule>
+ <match>
+ <AND>
+ <NAME>.*:name</NAME>
+ <XML_ATTRIBUTE />
+ <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
+ </AND>
+ </match>
+ </rule>
+ </section>
+ <section>
+ <rule>
+ <match>
+ <AND>
+ <NAME>name</NAME>
+ <XML_ATTRIBUTE />
+ <XML_NAMESPACE>^$</XML_NAMESPACE>
+ </AND>
+ </match>
+ </rule>
+ </section>
+ <section>
+ <rule>
+ <match>
+ <AND>
+ <NAME>style</NAME>
+ <XML_ATTRIBUTE />
+ <XML_NAMESPACE>^$</XML_NAMESPACE>
+ </AND>
+ </match>
+ </rule>
+ </section>
+ <section>
+ <rule>
+ <match>
+ <AND>
+ <NAME>.*</NAME>
+ <XML_ATTRIBUTE />
+ <XML_NAMESPACE>^$</XML_NAMESPACE>
+ </AND>
+ </match>
+ <order>BY_NAME</order>
+ </rule>
+ </section>
+ <section>
+ <rule>
+ <match>
+ <AND>
+ <NAME>.*</NAME>
+ <XML_ATTRIBUTE />
+ <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
+ </AND>
+ </match>
+ <order>ANDROID_ATTRIBUTE_ORDER</order>
+ </rule>
+ </section>
+ <section>
+ <rule>
+ <match>
+ <AND>
+ <NAME>.*</NAME>
+ <XML_ATTRIBUTE />
+ <XML_NAMESPACE>.*</XML_NAMESPACE>
+ </AND>
+ </match>
+ <order>BY_NAME</order>
+ </rule>
+ </section>
+ </rules>
+ </arrangement>
+ </codeStyleSettings>
+ <codeStyleSettings language="kotlin">
+ <indentOptions>
+ <option name="CONTINUATION_INDENT_SIZE" value="4" />
+ </indentOptions>
+ </codeStyleSettings>
+ </code_scheme>
+</component> \ No newline at end of file
diff --git a/packages/SettingsLib/Spa/.idea/codeStyles/codeStyleConfig.xml b/packages/SettingsLib/Spa/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 000000000000..0f7bc519db61
--- /dev/null
+++ b/packages/SettingsLib/Spa/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+<component name="ProjectCodeStyleConfiguration">
+ <state>
+ <option name="USE_PER_PROJECT_SETTINGS" value="true" />
+ </state>
+</component>
diff --git a/packages/SettingsLib/Spa/.idea/copyright/Apache_2.xml b/packages/SettingsLib/Spa/.idea/copyright/Apache_2.xml
new file mode 100644
index 000000000000..d1866d0a9df5
--- /dev/null
+++ b/packages/SettingsLib/Spa/.idea/copyright/Apache_2.xml
@@ -0,0 +1,10 @@
+<component name="CopyrightManager">
+ <copyright>
+ <option name="notice"
+ value="Copyright (C) &amp;#36;today.year The Android Open Source Project&#10;&#10;Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);&#10;you may not use this file except in compliance with the License.&#10;You may obtain a copy of the License at&#10;&#10; http://www.apache.org/licenses/LICENSE-2.0&#10;&#10;Unless required by applicable law or agreed to in writing, software&#10;distributed under the License is distributed on an &quot;AS IS&quot; BASIS,&#10;WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.&#10;See the License for the specific language governing permissions and&#10;limitations under the License."/>
+ <option name="keyword" value="Copyright"/>
+ <option name="allowReplaceKeyword" value=""/>
+ <option name="myName" value="Apache 2"/>
+ <option name="myLocal" value="true"/>
+ </copyright>
+</component>
diff --git a/packages/SettingsLib/Spa/.idea/copyright/profiles_settings.xml b/packages/SettingsLib/Spa/.idea/copyright/profiles_settings.xml
new file mode 100644
index 000000000000..b011125be924
--- /dev/null
+++ b/packages/SettingsLib/Spa/.idea/copyright/profiles_settings.xml
@@ -0,0 +1,8 @@
+<component name="CopyrightManager">
+ <settings default="Apache 2">
+ <LanguageOptions name="XML">
+ <option name="fileTypeOverride" value="3" />
+ <option name="prefixLines" value="false" />
+ </LanguageOptions>
+ </settings>
+</component>
diff --git a/packages/SettingsLib/Spa/.idea/vcs.xml b/packages/SettingsLib/Spa/.idea/vcs.xml
new file mode 100644
index 000000000000..f3aa34851daf
--- /dev/null
+++ b/packages/SettingsLib/Spa/.idea/vcs.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="CommitMessageInspectionProfile">
+ <profile version="1.0">
+ <inspection_tool class="BodyLimit" enabled="true" level="WARNING" enabled_by_default="true" />
+ <inspection_tool class="SubjectBodySeparation" enabled="true" level="WARNING" enabled_by_default="true" />
+ <inspection_tool class="SubjectLimit" enabled="true" level="WARNING" enabled_by_default="true">
+ <option name="RIGHT_MARGIN" value="50" />
+ </inspection_tool>
+ </profile>
+ </component>
+ <component name="VcsDirectoryMappings">
+ <mapping directory="$PROJECT_DIR$/../../.." vcs="Git" />
+ </component>
+</project> \ No newline at end of file
diff --git a/packages/SettingsLib/Spa/build.gradle b/packages/SettingsLib/Spa/build.gradle
index 8c97eca548b5..d38013679ad4 100644
--- a/packages/SettingsLib/Spa/build.gradle
+++ b/packages/SettingsLib/Spa/build.gradle
@@ -16,9 +16,9 @@
buildscript {
ext {
- minSdk_version = 31
- compose_version = '1.2.0-alpha04'
- compose_material3_version = '1.0.0-alpha06'
+ spa_min_sdk = 31
+ jetpack_compose_version = '1.2.0-alpha04'
+ jetpack_compose_material3_version = '1.0.0-alpha06'
}
}
plugins {
diff --git a/packages/SettingsLib/Spa/codelab/Android.bp b/packages/SettingsLib/Spa/gallery/Android.bp
index 8fbbf9aa03c6..bc083c9db6b0 100644
--- a/packages/SettingsLib/Spa/codelab/Android.bp
+++ b/packages/SettingsLib/Spa/gallery/Android.bp
@@ -19,7 +19,7 @@ package {
}
android_app {
- name: "SpaLibCodelab",
+ name: "SpaLibGallery",
srcs: ["src/**/*.kt"],
diff --git a/packages/SettingsLib/Spa/codelab/AndroidManifest.xml b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
index 9a89e5efdddb..b5be7cd45688 100644
--- a/packages/SettingsLib/Spa/codelab/AndroidManifest.xml
+++ b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
@@ -13,16 +13,16 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
- -->
+-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.settingslib.spa.codelab">
+ package="com.android.settingslib.spa.gallery">
<application
- android:label="@string/app_name"
- android:supportsRtl="true"
- android:theme="@style/Theme.SettingsLib.Compose.DayNight">
+ android:icon="@mipmap/ic_launcher"
+ android:label="@string/app_label"
+ android:supportsRtl="true">
<activity
- android:name="com.android.settingslib.spa.codelab.MainActivity"
+ android:name="com.android.settingslib.spa.gallery.MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
diff --git a/packages/SettingsLib/Spa/codelab/build.gradle b/packages/SettingsLib/Spa/gallery/build.gradle
index 5251ddd8c01d..98e67459aa57 100644
--- a/packages/SettingsLib/Spa/codelab/build.gradle
+++ b/packages/SettingsLib/Spa/gallery/build.gradle
@@ -20,12 +20,12 @@ plugins {
}
android {
- namespace 'com.android.settingslib.spa.codelab'
+ namespace 'com.android.settingslib.spa.gallery'
compileSdk 33
defaultConfig {
- applicationId "com.android.settingslib.spa.codelab"
- minSdk minSdk_version
+ applicationId "com.android.settingslib.spa.gallery"
+ minSdk spa_min_sdk
targetSdk 33
versionCode 1
versionName "1.0"
@@ -52,7 +52,7 @@ android {
compose true
}
composeOptions {
- kotlinCompilerExtensionVersion compose_version
+ kotlinCompilerExtensionVersion jetpack_compose_version
}
packagingOptions {
resources {
diff --git a/packages/SettingsLib/Spa/gallery/res/mipmap-anydpi-v26/ic_launcher.xml b/packages/SettingsLib/Spa/gallery/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 000000000000..623ef564a266
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+ -->
+
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <background android:drawable="@mipmap/ic_launcher_background" />
+ <foreground android:drawable="@mipmap/ic_launcher_foreground" />
+</adaptive-icon>
diff --git a/packages/SettingsLib/Spa/gallery/res/mipmap-xxxhdpi/ic_launcher.png b/packages/SettingsLib/Spa/gallery/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 000000000000..c3f1ab9d5f36
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/gallery/res/mipmap-xxxhdpi/ic_launcher_background.png b/packages/SettingsLib/Spa/gallery/res/mipmap-xxxhdpi/ic_launcher_background.png
new file mode 100644
index 000000000000..7da154964846
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/res/mipmap-xxxhdpi/ic_launcher_background.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/gallery/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/packages/SettingsLib/Spa/gallery/res/mipmap-xxxhdpi/ic_launcher_foreground.png
new file mode 100644
index 000000000000..187964fe0fff
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/res/mipmap-xxxhdpi/ic_launcher_foreground.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/gallery/res/values/strings.xml b/packages/SettingsLib/Spa/gallery/res/values/strings.xml
new file mode 100644
index 000000000000..0d08d682b4ac
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/res/values/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+ -->
+<resources>
+ <!-- Gallery App label. [DO NOT TRANSLATE] -->
+ <string name="app_label" translatable="false">Gallery</string>
+ <!-- Gallery App name. [DO NOT TRANSLATE] -->
+ <string name="app_name" translatable="false">SpaLib Gallery</string>
+</resources>
diff --git a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/MainActivity.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/MainActivity.kt
index eef81e0ad351..7db53b4fd2ab 100644
--- a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/MainActivity.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/MainActivity.kt
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.codelab
+package com.android.settingslib.spa.gallery
-import com.android.settingslib.spa.codelab.page.codelabPageRepository
import com.android.settingslib.spa.framework.SpaActivity
+import com.android.settingslib.spa.gallery.page.galleryPageRepository
-class MainActivity : SpaActivity(codelabPageRepository)
+class MainActivity : SpaActivity(galleryPageRepository)
diff --git a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/ArgumentPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt
index 5c6b609b25a4..937e59493632 100644
--- a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/ArgumentPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.codelab.page
+package com.android.settingslib.spa.gallery.page
import android.os.Bundle
import androidx.compose.foundation.layout.Column
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPage.kt
new file mode 100644
index 000000000000..143c365d3a74
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPage.kt
@@ -0,0 +1,71 @@
+/*
+ * 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.settingslib.spa.gallery.page
+
+import android.os.Bundle
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import com.android.settingslib.spa.framework.api.SettingsPageProvider
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.framework.compose.stateOf
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.ui.Footer
+
+object FooterPageProvider : SettingsPageProvider {
+ override val name = "Footer"
+
+ @Composable
+ override fun Page(arguments: Bundle?) {
+ FooterPage()
+ }
+
+ @Composable
+ fun EntryItem() {
+ Preference(object : PreferenceModel {
+ override val title = "Sample Footer"
+ override val onClick = navigator(name)
+ })
+ }
+}
+
+@Composable
+private fun FooterPage() {
+ Column(Modifier.verticalScroll(rememberScrollState())) {
+ Preference(remember {
+ object : PreferenceModel {
+ override val title = "Some Preference"
+ override val summary = stateOf("Some summary")
+ }
+ })
+ Footer(footerText = "Footer text always at the end of page.")
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+private fun FooterPagePreview() {
+ SettingsTheme {
+ FooterPage()
+ }
+}
diff --git a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/HomePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/HomePage.kt
index 171a16118052..ee077f4a25e6 100644
--- a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/HomePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/HomePage.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.codelab.page
+package com.android.settingslib.spa.gallery.page
import android.os.Bundle
import androidx.compose.foundation.layout.Column
@@ -25,10 +25,10 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
-import com.android.settingslib.spa.codelab.R
import com.android.settingslib.spa.framework.api.SettingsPageProvider
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.gallery.R
object HomePageProvider : SettingsPageProvider {
override val name = Destinations.Home
@@ -50,10 +50,12 @@ private fun HomePage() {
)
PreferencePageProvider.EntryItem()
-
+ SwitchPreferencePageProvider.EntryItem()
ArgumentPageProvider.EntryItem(stringParam = "foo", intParam = 0)
SliderPageProvider.EntryItem()
+ SettingsPagerPageProvider.EntryItem()
+ FooterPageProvider.EntryItem()
}
}
diff --git a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/PageRepository.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/PageRepository.kt
index c24541a903da..64652257c20b 100644
--- a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/PageRepository.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/PageRepository.kt
@@ -14,23 +14,27 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.codelab.page
+package com.android.settingslib.spa.gallery.page
import com.android.settingslib.spa.framework.api.SettingsPageRepository
object Destinations {
const val Home = "Home"
const val Preference = "Preference"
+ const val SwitchPreference = "SwitchPreference"
const val Argument = "Argument"
const val Slider = "Slider"
}
-val codelabPageRepository = SettingsPageRepository(
+val galleryPageRepository = SettingsPageRepository(
allPages = listOf(
HomePageProvider,
PreferencePageProvider,
+ SwitchPreferencePageProvider,
ArgumentPageProvider,
SliderPageProvider,
+ SettingsPagerPageProvider,
+ FooterPageProvider,
),
startDestination = Destinations.Home,
)
diff --git a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/PreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/PreferencePage.kt
index d53562d31566..8a29d35ad9d9 100644
--- a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/PreferencePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/PreferencePage.kt
@@ -14,19 +14,22 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.codelab.page
+package com.android.settingslib.spa.gallery.page
import android.os.Bundle
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.DisabledByDefault
+import androidx.compose.material.icons.outlined.TouchApp
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState
-import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
@@ -36,6 +39,7 @@ import com.android.settingslib.spa.framework.compose.toState
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.ui.SettingsIcon
import kotlinx.coroutines.delay
object PreferencePageProvider : SettingsPageProvider {
@@ -75,14 +79,17 @@ private fun PreferencePage() {
}
})
- var count by remember { mutableStateOf(0) }
+ var count by rememberSaveable { mutableStateOf(0) }
Preference(object : PreferenceModel {
override val title = "Click me"
override val summary = derivedStateOf { count.toString() }
override val onClick: (() -> Unit) = { count++ }
+ override val icon = @Composable {
+ SettingsIcon(imageVector = Icons.Outlined.TouchApp)
+ }
})
- var ticks by remember { mutableStateOf(0) }
+ var ticks by rememberSaveable { mutableStateOf(0) }
LaunchedEffect(ticks) {
delay(1000L)
ticks++
@@ -96,6 +103,9 @@ private fun PreferencePage() {
override val title = "Disabled"
override val summary = "Disabled".toState()
override val enabled = false.toState()
+ override val icon = @Composable {
+ SettingsIcon(imageVector = Icons.Outlined.DisabledByDefault)
+ }
})
}
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SettingsPagerPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SettingsPagerPage.kt
new file mode 100644
index 000000000000..5351ea65da47
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SettingsPagerPage.kt
@@ -0,0 +1,66 @@
+/*
+ * 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.settingslib.spa.gallery.page
+
+import android.os.Bundle
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import com.android.settingslib.spa.framework.api.SettingsPageProvider
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.scaffold.SettingsPager
+import com.android.settingslib.spa.widget.ui.SettingsTitle
+
+object SettingsPagerPageProvider : SettingsPageProvider {
+ override val name = "SettingsPager"
+
+ @Composable
+ override fun Page(arguments: Bundle?) {
+ SettingsPagerPage()
+ }
+
+ @Composable
+ fun EntryItem() {
+ Preference(object : PreferenceModel {
+ override val title = "Sample SettingsPager"
+ override val onClick = navigator(name)
+ })
+ }
+}
+
+@Composable
+private fun SettingsPagerPage() {
+ SettingsPager(listOf("Personal", "Work")) {
+ Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
+ SettingsTitle(title = "Page $it")
+ }
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+private fun SettingsPagerPagePreview() {
+ SettingsTheme {
+ SettingsPagerPage()
+ }
+}
diff --git a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/SliderPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.kt
index 6e965813afc4..9bcac1b84a0b 100644
--- a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/SliderPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.codelab.page
+package com.android.settingslib.spa.gallery.page
import android.os.Bundle
import androidx.compose.foundation.layout.Column
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SwitchPreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SwitchPreferencePage.kt
new file mode 100644
index 000000000000..b6f725821728
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SwitchPreferencePage.kt
@@ -0,0 +1,129 @@
+/*
+ * 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.settingslib.spa.gallery.page
+
+import android.os.Bundle
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.produceState
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import com.android.settingslib.spa.framework.api.SettingsPageProvider
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.framework.compose.stateOf
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.preference.SwitchPreference
+import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
+import kotlinx.coroutines.delay
+
+object SwitchPreferencePageProvider : SettingsPageProvider {
+ override val name = Destinations.SwitchPreference
+
+ @Composable
+ override fun Page(arguments: Bundle?) {
+ SwitchPreferencePage()
+ }
+
+ @Composable
+ fun EntryItem() {
+ Preference(object : PreferenceModel {
+ override val title = "Sample SwitchPreference"
+ override val onClick = navigator(Destinations.SwitchPreference)
+ })
+ }
+}
+
+@Composable
+private fun SwitchPreferencePage() {
+ Column(Modifier.verticalScroll(rememberScrollState())) {
+ SampleSwitchPreference()
+ SampleSwitchPreferenceWithSummary()
+ SampleSwitchPreferenceWithAsyncSummary()
+ SampleNotChangeableSwitchPreference()
+ }
+}
+
+@Composable
+private fun SampleSwitchPreference() {
+ val checked = rememberSaveable { mutableStateOf(false) }
+ SwitchPreference(remember {
+ object : SwitchPreferenceModel {
+ override val title = "SwitchPreference"
+ override val checked = checked
+ override val onCheckedChange = { newChecked: Boolean -> checked.value = newChecked }
+ }
+ })
+}
+
+@Composable
+private fun SampleSwitchPreferenceWithSummary() {
+ val checked = rememberSaveable { mutableStateOf(true) }
+ SwitchPreference(remember {
+ object : SwitchPreferenceModel {
+ override val title = "SwitchPreference"
+ override val summary = stateOf("With summary")
+ override val checked = checked
+ override val onCheckedChange = { newChecked: Boolean -> checked.value = newChecked }
+ }
+ })
+}
+
+@Composable
+private fun SampleSwitchPreferenceWithAsyncSummary() {
+ val checked = rememberSaveable { mutableStateOf(true) }
+ val summary = produceState(initialValue = " ") {
+ delay(1000L)
+ value = "Async summary"
+ }
+ SwitchPreference(remember {
+ object : SwitchPreferenceModel {
+ override val title = "SwitchPreference"
+ override val summary = summary
+ override val checked = checked
+ override val onCheckedChange = { newChecked: Boolean -> checked.value = newChecked }
+ }
+ })
+}
+
+@Composable
+private fun SampleNotChangeableSwitchPreference() {
+ val checked = rememberSaveable { mutableStateOf(true) }
+ SwitchPreference(remember {
+ object : SwitchPreferenceModel {
+ override val title = "SwitchPreference"
+ override val summary = stateOf("Not changeable")
+ override val changeable = stateOf(false)
+ override val checked = checked
+ override val onCheckedChange = { newChecked: Boolean -> checked.value = newChecked }
+ }
+ })
+}
+
+@Preview(showBackground = true)
+@Composable
+private fun SwitchPreferencePagePreview() {
+ SettingsTheme {
+ SwitchPreferencePage()
+ }
+}
diff --git a/packages/SettingsLib/Spa/settings.gradle b/packages/SettingsLib/Spa/settings.gradle
index 1b69c1eebe0b..897fa675888d 100644
--- a/packages/SettingsLib/Spa/settings.gradle
+++ b/packages/SettingsLib/Spa/settings.gradle
@@ -30,5 +30,5 @@ dependencyResolutionManagement {
}
rootProject.name = "SpaLib"
include ':spa'
-include ':codelab'
+include ':gallery'
include ':tests'
diff --git a/packages/SettingsLib/Spa/spa/Android.bp b/packages/SettingsLib/Spa/spa/Android.bp
index 463c0765943f..3c8d91edc05b 100644
--- a/packages/SettingsLib/Spa/spa/Android.bp
+++ b/packages/SettingsLib/Spa/spa/Android.bp
@@ -31,6 +31,9 @@ android_library {
"androidx.navigation_navigation-compose",
"com.google.android.material_material",
],
- kotlincflags: ["-Xjvm-default=all"],
+ kotlincflags: [
+ "-Xjvm-default=all",
+ "-Xopt-in=kotlin.RequiresOptIn",
+ ],
min_sdk_version: "31",
}
diff --git a/packages/SettingsLib/Spa/spa/build.gradle b/packages/SettingsLib/Spa/spa/build.gradle
index 60794c88d00d..ad69da314735 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle
+++ b/packages/SettingsLib/Spa/spa/build.gradle
@@ -24,7 +24,7 @@ android {
compileSdk 33
defaultConfig {
- minSdk minSdk_version
+ minSdk spa_min_sdk
targetSdk 33
}
@@ -43,13 +43,13 @@ android {
}
kotlinOptions {
jvmTarget = '1.8'
- freeCompilerArgs = ["-Xjvm-default=all"]
+ freeCompilerArgs = ["-Xjvm-default=all", "-Xopt-in=kotlin.RequiresOptIn"]
}
buildFeatures {
compose true
}
composeOptions {
- kotlinCompilerExtensionVersion compose_version
+ kotlinCompilerExtensionVersion jetpack_compose_version
}
packagingOptions {
resources {
@@ -59,11 +59,11 @@ android {
}
dependencies {
- api "androidx.compose.material3:material3:$compose_material3_version"
- api "androidx.compose.material:material-icons-extended:$compose_version"
- api "androidx.compose.runtime:runtime-livedata:$compose_version"
- api "androidx.compose.ui:ui-tooling-preview:$compose_version"
+ api "androidx.compose.material3:material3:$jetpack_compose_material3_version"
+ api "androidx.compose.material:material-icons-extended:$jetpack_compose_version"
+ api "androidx.compose.runtime:runtime-livedata:$jetpack_compose_version"
+ api "androidx.compose.ui:ui-tooling-preview:$jetpack_compose_version"
api 'androidx.navigation:navigation-compose:2.5.0'
api 'com.google.android.material:material:1.6.1'
- debugApi "androidx.compose.ui:ui-tooling:$compose_version"
+ debugApi "androidx.compose.ui:ui-tooling:$jetpack_compose_version"
}
diff --git a/packages/SettingsLib/Spa/spa/res/values-night/themes.xml b/packages/SettingsLib/Spa/spa/res/values-night/themes.xml
index 8b52b507bdd9..67dd2b0cc5e0 100644
--- a/packages/SettingsLib/Spa/spa/res/values-night/themes.xml
+++ b/packages/SettingsLib/Spa/spa/res/values-night/themes.xml
@@ -13,8 +13,8 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
- -->
+-->
<resources>
- <style name="Theme.SettingsLib.Compose.DayNight" />
+ <style name="Theme.SpaLib.DayNight" />
</resources>
diff --git a/packages/SettingsLib/Spa/spa/res/values/themes.xml b/packages/SettingsLib/Spa/spa/res/values/themes.xml
index 01f9ea592f6d..e0e5fc211ec6 100644
--- a/packages/SettingsLib/Spa/spa/res/values/themes.xml
+++ b/packages/SettingsLib/Spa/spa/res/values/themes.xml
@@ -13,15 +13,15 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
- -->
+-->
<resources>
- <style name="Theme.SettingsLib.Compose" parent="Theme.Material3.DayNight.NoActionBar">
+ <style name="Theme.SpaLib" parent="Theme.Material3.DayNight.NoActionBar">
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
</style>
- <style name="Theme.SettingsLib.Compose.DayNight">
+ <style name="Theme.SpaLib.DayNight">
<item name="android:windowLightStatusBar">true</item>
</style>
</resources>
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaActivity.kt
index 5b39b6e08e6a..51d3714ce5ee 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaActivity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaActivity.kt
@@ -24,6 +24,7 @@ import androidx.compose.runtime.CompositionLocalProvider
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
+import com.android.settingslib.spa.R
import com.android.settingslib.spa.framework.api.SettingsPageProvider
import com.android.settingslib.spa.framework.api.SettingsPageRepository
import com.android.settingslib.spa.framework.compose.localNavController
@@ -33,6 +34,7 @@ open class SpaActivity(
private val settingsPageRepository: SettingsPageRepository,
) : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
+ setTheme(R.style.Theme_SpaLib_DayNight)
super.onCreate(savedInstanceState)
setContent {
MainContent()
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/DrawablePainter.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/DrawablePainter.kt
new file mode 100644
index 000000000000..ae325f8862eb
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/DrawablePainter.kt
@@ -0,0 +1,180 @@
+/*
+ * 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.settingslib.spa.framework.compose
+
+import android.graphics.drawable.Animatable
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.ColorDrawable
+import android.graphics.drawable.Drawable
+import android.os.Handler
+import android.os.Looper
+import android.view.View
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.RememberObserver
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ColorFilter
+import androidx.compose.ui.graphics.asAndroidColorFilter
+import androidx.compose.ui.graphics.asImageBitmap
+import androidx.compose.ui.graphics.drawscope.DrawScope
+import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
+import androidx.compose.ui.graphics.nativeCanvas
+import androidx.compose.ui.graphics.painter.BitmapPainter
+import androidx.compose.ui.graphics.painter.ColorPainter
+import androidx.compose.ui.graphics.painter.Painter
+import androidx.compose.ui.graphics.withSave
+import androidx.compose.ui.unit.LayoutDirection
+import kotlin.math.roundToInt
+
+/**
+ * *************************************************************************************************
+ * This file was forked from
+ * https://github.com/google/accompanist/blob/main/drawablepainter/src/main/java/com/google/accompanist/drawablepainter/DrawablePainter.kt
+ * and will be removed once it lands in AndroidX.
+ */
+
+private val MAIN_HANDLER by lazy(LazyThreadSafetyMode.NONE) {
+ Handler(Looper.getMainLooper())
+}
+
+/**
+ * A [Painter] which draws an Android [Drawable] and supports [Animatable] drawables. Instances
+ * should be remembered to be able to start and stop [Animatable] animations.
+ *
+ * Instances are usually retrieved from [rememberDrawablePainter].
+ */
+class DrawablePainter(
+ val drawable: Drawable
+) : Painter(), RememberObserver {
+ private var drawInvalidateTick by mutableStateOf(0)
+ private var drawableIntrinsicSize by mutableStateOf(drawable.intrinsicSize)
+
+ private val callback: Drawable.Callback by lazy {
+ object : Drawable.Callback {
+ override fun invalidateDrawable(d: Drawable) {
+ // Update the tick so that we get re-drawn
+ drawInvalidateTick++
+ // Update our intrinsic size too
+ drawableIntrinsicSize = drawable.intrinsicSize
+ }
+
+ override fun scheduleDrawable(d: Drawable, what: Runnable, time: Long) {
+ MAIN_HANDLER.postAtTime(what, time)
+ }
+
+ override fun unscheduleDrawable(d: Drawable, what: Runnable) {
+ MAIN_HANDLER.removeCallbacks(what)
+ }
+ }
+ }
+
+ init {
+ if (drawable.intrinsicWidth >= 0 && drawable.intrinsicHeight >= 0) {
+ // Update the drawable's bounds to match the intrinsic size
+ drawable.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight)
+ }
+ }
+
+ override fun onRemembered() {
+ drawable.callback = callback
+ drawable.setVisible(true, true)
+ if (drawable is Animatable) drawable.start()
+ }
+
+ override fun onAbandoned() = onForgotten()
+
+ override fun onForgotten() {
+ if (drawable is Animatable) drawable.stop()
+ drawable.setVisible(false, false)
+ drawable.callback = null
+ }
+
+ override fun applyAlpha(alpha: Float): Boolean {
+ drawable.alpha = (alpha * 255).roundToInt().coerceIn(0, 255)
+ return true
+ }
+
+ override fun applyColorFilter(colorFilter: ColorFilter?): Boolean {
+ drawable.colorFilter = colorFilter?.asAndroidColorFilter()
+ return true
+ }
+
+ override fun applyLayoutDirection(layoutDirection: LayoutDirection): Boolean =
+ drawable.setLayoutDirection(
+ when (layoutDirection) {
+ LayoutDirection.Ltr -> View.LAYOUT_DIRECTION_LTR
+ LayoutDirection.Rtl -> View.LAYOUT_DIRECTION_RTL
+ }
+ )
+
+ override val intrinsicSize: Size get() = drawableIntrinsicSize
+
+ override fun DrawScope.onDraw() {
+ drawIntoCanvas { canvas ->
+ // Reading this ensures that we invalidate when invalidateDrawable() is called
+ drawInvalidateTick
+
+ // Update the Drawable's bounds
+ drawable.setBounds(0, 0, size.width.roundToInt(), size.height.roundToInt())
+
+ canvas.withSave {
+ drawable.draw(canvas.nativeCanvas)
+ }
+ }
+ }
+}
+
+/**
+ * Remembers [Drawable] wrapped up as a [Painter]. This function attempts to un-wrap the
+ * drawable contents and use Compose primitives where possible.
+ *
+ * If the provided [drawable] is `null`, an empty no-op painter is returned.
+ *
+ * This function tries to dispatch lifecycle events to [drawable] as much as possible from
+ * within Compose.
+ *
+ * @sample com.google.accompanist.sample.drawablepainter.BasicSample
+ */
+@Composable
+fun rememberDrawablePainter(drawable: Drawable?): Painter = remember(drawable) {
+ when (drawable) {
+ null -> EmptyPainter
+ is BitmapDrawable -> BitmapPainter(drawable.bitmap.asImageBitmap())
+ is ColorDrawable -> ColorPainter(Color(drawable.color))
+ // Since the DrawablePainter will be remembered and it implements RememberObserver, it
+ // will receive the necessary events
+ else -> DrawablePainter(drawable.mutate())
+ }
+}
+
+private val Drawable.intrinsicSize: Size
+ get() = when {
+ // Only return a finite size if the drawable has an intrinsic size
+ intrinsicWidth >= 0 && intrinsicHeight >= 0 -> {
+ Size(width = intrinsicWidth.toFloat(), height = intrinsicHeight.toFloat())
+ }
+ else -> Size.Unspecified
+ }
+
+internal object EmptyPainter : Painter() {
+ override val intrinsicSize: Size get() = Size.Unspecified
+ override fun DrawScope.onDraw() {}
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/RuntimeUtils.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/RuntimeUtils.kt
index 7c8608da6724..ba8854653b0b 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/RuntimeUtils.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/RuntimeUtils.kt
@@ -16,9 +16,17 @@
package com.android.settingslib.spa.framework.compose
+import android.content.Context
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.remember
+import androidx.compose.ui.platform.LocalContext
+
+@Composable
+fun <T> rememberContext(constructor: (Context) -> T): T {
+ val context = LocalContext.current
+ return remember(context) { constructor(context) }
+}
/**
* Remember the [State] initialized with the [this].
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
index 9a67c12dfd0b..e1ca69bd7940 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
@@ -20,6 +20,7 @@ import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.ui.unit.dp
object SettingsDimension {
+ val itemIconSize = 24.dp
val itemIconContainerSize = 72.dp
val itemPaddingStart = 24.dp
val itemPaddingEnd = 16.dp
diff --git a/lowpan/java/android/net/lowpan/ILowpanNetScanCallback.aidl b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsShape.kt
index 9743fce0b128..20020d0e7275 100644
--- a/lowpan/java/android/net/lowpan/ILowpanNetScanCallback.aidl
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsShape.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 The Android Open Source Project
+ * 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.
@@ -14,12 +14,11 @@
* limitations under the License.
*/
-package android.net.lowpan;
+package com.android.settingslib.spa.framework.theme
-import android.net.lowpan.LowpanBeaconInfo;
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.ui.unit.dp
-/** {@hide} */
-interface ILowpanNetScanCallback {
- oneway void onNetScanBeacon(in LowpanBeaconInfo beacon);
- oneway void onNetScanFinished();
+object SettingsShape {
+ val CornerMedium = RoundedCornerShape(12.dp)
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt
index 6bdc294d888a..9a34dbf36735 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt
@@ -25,8 +25,6 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material3.Divider
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.ui.Alignment
@@ -39,6 +37,7 @@ import com.android.settingslib.spa.framework.compose.toState
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.framework.theme.SettingsOpacity
import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.widget.ui.SettingsTitle
@Composable
internal fun BaseLayout(
@@ -94,11 +93,7 @@ private fun BaseIcon(
@Composable
private fun Titles(title: String, subTitle: @Composable () -> Unit, modifier: Modifier) {
Column(modifier) {
- Text(
- text = title,
- color = MaterialTheme.colorScheme.onSurface,
- style = MaterialTheme.typography.titleMedium,
- )
+ SettingsTitle(title)
subTitle()
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BasePreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BasePreference.kt
index 3b99d36e630b..4b2c8e41a388 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BasePreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BasePreference.kt
@@ -19,8 +19,6 @@ package com.android.settingslib.spa.widget.preference
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.BatteryChargingFull
import androidx.compose.material3.Icon
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.ui.Modifier
@@ -29,6 +27,7 @@ import androidx.compose.ui.unit.Dp
import com.android.settingslib.spa.framework.compose.toState
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.widget.ui.SettingsBody
@Composable
internal fun BasePreference(
@@ -44,15 +43,7 @@ internal fun BasePreference(
) {
BaseLayout(
title = title,
- subTitle = {
- if (summary.value.isNotEmpty()) {
- Text(
- text = summary.value,
- color = MaterialTheme.colorScheme.onSurfaceVariant,
- style = MaterialTheme.typography.bodyMedium,
- )
- }
- },
+ subTitle = { SettingsBody(summary) },
modifier = modifier,
icon = icon,
enabled = enabled,
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt
index 1a80ed20b031..0e6f53abca40 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt
@@ -19,6 +19,7 @@ package com.android.settingslib.spa.widget.preference
import androidx.compose.foundation.clickable
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
+import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import com.android.settingslib.spa.framework.compose.stateOf
@@ -38,6 +39,14 @@ interface PreferenceModel {
get() = stateOf("")
/**
+ * The icon of this [Preference].
+ *
+ * Default is `null` which means no icon.
+ */
+ val icon: (@Composable () -> Unit)?
+ get() = null
+
+ /**
* Indicates whether this [Preference] is enabled.
*
* Disabled [Preference] will be displayed in disabled style.
@@ -61,13 +70,16 @@ interface PreferenceModel {
*/
@Composable
fun Preference(model: PreferenceModel) {
- val modifier = model.onClick?.let { onClick ->
- Modifier.clickable(enabled = model.enabled.value) { onClick() }
- } ?: Modifier
+ val modifier = remember(model.enabled.value, model.onClick) {
+ model.onClick?.let { onClick ->
+ Modifier.clickable(enabled = model.enabled.value, onClick = onClick)
+ } ?: Modifier
+ }
BasePreference(
title = model.title,
summary = model.summary,
modifier = modifier,
+ icon = model.icon,
enabled = model.enabled,
)
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SwitchPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SwitchPreference.kt
new file mode 100644
index 000000000000..b6d6936442e7
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/SwitchPreference.kt
@@ -0,0 +1,146 @@
+/*
+ * 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.settingslib.spa.widget.preference
+
+import androidx.compose.foundation.LocalIndication
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.selection.toggleable
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.Dp
+import com.android.settingslib.spa.framework.compose.stateOf
+import com.android.settingslib.spa.framework.compose.toState
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.widget.ui.SettingsSwitch
+
+/**
+ * The widget model for [SwitchPreference] widget.
+ */
+interface SwitchPreferenceModel {
+ /**
+ * The title of this [SwitchPreference].
+ */
+ val title: String
+
+ /**
+ * The summary of this [SwitchPreference].
+ */
+ val summary: State<String>
+ get() = stateOf("")
+
+ /**
+ * Indicates whether this [SwitchPreference] is checked.
+ *
+ * This can be `null` during the data loading before the data is available.
+ */
+ val checked: State<Boolean?>
+
+ /**
+ * Indicates whether this [SwitchPreference] is changeable.
+ *
+ * Not changeable [SwitchPreference] will be displayed in disabled style.
+ */
+ val changeable: State<Boolean>
+ get() = stateOf(true)
+
+ /**
+ * The switch change handler of this [SwitchPreference].
+ *
+ * If `null`, this [SwitchPreference] is not [toggleable].
+ */
+ val onCheckedChange: ((newChecked: Boolean) -> Unit)?
+}
+
+/**
+ * SwitchPreference widget.
+ *
+ * Data is provided through [SwitchPreferenceModel].
+ */
+@Composable
+fun SwitchPreference(model: SwitchPreferenceModel) {
+ InternalSwitchPreference(
+ title = model.title,
+ summary = model.summary,
+ checked = model.checked,
+ changeable = model.changeable,
+ onCheckedChange = model.onCheckedChange,
+ )
+}
+
+@Composable
+internal fun InternalSwitchPreference(
+ title: String,
+ summary: State<String> = "".toState(),
+ checked: State<Boolean?>,
+ changeable: State<Boolean> = true.toState(),
+ paddingStart: Dp = SettingsDimension.itemPaddingStart,
+ paddingEnd: Dp = SettingsDimension.itemPaddingEnd,
+ paddingVertical: Dp = SettingsDimension.itemPaddingVertical,
+ onCheckedChange: ((newChecked: Boolean) -> Unit)?,
+) {
+ val checkedValue = checked.value
+ val indication = LocalIndication.current
+ val modifier = remember(checkedValue, changeable.value) {
+ if (checkedValue != null && onCheckedChange != null) {
+ Modifier.toggleable(
+ value = checkedValue,
+ interactionSource = MutableInteractionSource(),
+ indication = indication,
+ enabled = changeable.value,
+ role = Role.Switch,
+ onValueChange = onCheckedChange,
+ )
+ } else Modifier
+ }
+ BasePreference(
+ title = title,
+ summary = summary,
+ modifier = modifier,
+ enabled = changeable,
+ paddingStart = paddingStart,
+ paddingEnd = paddingEnd,
+ paddingVertical = paddingVertical,
+ ) {
+ SettingsSwitch(checked = checked, changeable = changeable)
+ }
+}
+
+@Preview
+@Composable
+private fun SwitchPreferencePreview() {
+ SettingsTheme {
+ Column {
+ InternalSwitchPreference(
+ title = "Use Dark theme",
+ checked = true.toState(),
+ onCheckedChange = {},
+ )
+ InternalSwitchPreference(
+ title = "Use Dark theme",
+ summary = "Summary".toState(),
+ checked = false.toState(),
+ onCheckedChange = {},
+ )
+ }
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsPager.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsPager.kt
new file mode 100644
index 000000000000..1ec2390741ed
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsPager.kt
@@ -0,0 +1,60 @@
+/*
+ * 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.settingslib.spa.widget.scaffold
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.TabRow
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+
+@Composable
+fun SettingsPager(titles: List<String>, content: @Composable (page: Int) -> Unit) {
+ check(titles.isNotEmpty())
+ if (titles.size == 1) {
+ content(0)
+ return
+ }
+
+ Column {
+ var currentPage by rememberSaveable { mutableStateOf(0) }
+
+ TabRow(
+ selectedTabIndex = currentPage,
+ modifier = Modifier.padding(horizontal = SettingsDimension.itemPaddingEnd),
+ containerColor = Color.Transparent,
+ indicator = {},
+ divider = {},
+ ) {
+ titles.forEachIndexed { page, title ->
+ SettingsTab(
+ title = title,
+ selected = currentPage == page,
+ onClick = { currentPage = page },
+ )
+ }
+ }
+
+ content(currentPage)
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsTab.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsTab.kt
new file mode 100644
index 000000000000..16d8dbc9bf74
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsTab.kt
@@ -0,0 +1,72 @@
+/*
+ * 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.settingslib.spa.widget.scaffold
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Tab
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.android.settingslib.spa.framework.theme.SettingsShape
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+
+@Composable
+internal fun SettingsTab(
+ title: String,
+ selected: Boolean,
+ onClick: () -> Unit,
+) {
+ Tab(
+ selected = selected,
+ onClick = onClick,
+ modifier = Modifier
+ .height(48.dp)
+ .padding(horizontal = 4.dp, vertical = 6.dp)
+ .clip(SettingsShape.CornerMedium)
+ .background(color = when {
+ selected -> SettingsTheme.colorScheme.primaryContainer
+ else -> SettingsTheme.colorScheme.surface
+ }),
+ ) {
+ Text(
+ text = title,
+ style = MaterialTheme.typography.labelLarge,
+ color = when {
+ selected -> SettingsTheme.colorScheme.onPrimaryContainer
+ else -> SettingsTheme.colorScheme.secondaryText
+ },
+ )
+ }
+}
+
+@Preview
+@Composable
+private fun SettingsTabPreview() {
+ SettingsTheme {
+ Column {
+ SettingsTab(title = "Personal", selected = true) {}
+ SettingsTab(title = "Work", selected = false) {}
+ }
+ }
+}
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
new file mode 100644
index 000000000000..296cf3ba609f
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Footer.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.ui
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.Info
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+
+@Composable
+fun Footer(footerText: String) {
+ if (footerText.isEmpty()) return
+ Column(Modifier.padding(SettingsDimension.itemPadding)) {
+ Icon(
+ imageVector = Icons.Outlined.Info,
+ contentDescription = null,
+ modifier = Modifier.size(SettingsDimension.itemIconSize),
+ tint = MaterialTheme.colorScheme.onSurfaceVariant,
+ )
+ Spacer(modifier = Modifier.height(SettingsDimension.itemPaddingVertical))
+ SettingsBody(footerText)
+ }
+}
+
+@Preview
+@Composable
+private fun FooterPreview() {
+ SettingsTheme {
+ Footer("Footer text always at the end of page.")
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Icon.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Icon.kt
new file mode 100644
index 000000000000..4f28e378e510
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Icon.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.settingslib.spa.widget.ui
+
+import androidx.compose.foundation.layout.size
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.vector.ImageVector
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+
+@Composable
+fun SettingsIcon(imageVector: ImageVector) {
+ Icon(
+ imageVector = imageVector,
+ contentDescription = null,
+ modifier = Modifier.size(SettingsDimension.itemIconSize),
+ tint = MaterialTheme.colorScheme.onSurface,
+ )
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Switch.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Switch.kt
new file mode 100644
index 000000000000..45d5f6baa9cb
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Switch.kt
@@ -0,0 +1,47 @@
+/*
+ * 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.settingslib.spa.widget.ui
+
+import androidx.compose.material3.Checkbox
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun SettingsSwitch(
+ checked: State<Boolean?>,
+ changeable: State<Boolean>,
+ onCheckedChange: ((newChecked: Boolean) -> Unit)? = null,
+) {
+ // TODO: Replace Checkbox with Switch when the androidx.compose.material3_material3 library is
+ // updated to date.
+ val checkedValue = checked.value
+ if (checkedValue != null) {
+ Checkbox(
+ checked = checkedValue,
+ onCheckedChange = onCheckedChange,
+ enabled = changeable.value,
+ )
+ } else {
+ Checkbox(
+ checked = false,
+ onCheckedChange = null,
+ enabled = false,
+ )
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt
new file mode 100644
index 000000000000..a414c89dc24c
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt
@@ -0,0 +1,52 @@
+/*
+ * 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.settingslib.spa.widget.ui
+
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+
+@Composable
+fun SettingsTitle(title: State<String>) {
+ SettingsTitle(title.value)
+}
+
+@Composable
+fun SettingsTitle(title: String) {
+ Text(
+ text = title,
+ color = MaterialTheme.colorScheme.onSurface,
+ style = MaterialTheme.typography.titleMedium,
+ )
+}
+
+@Composable
+fun SettingsBody(body: State<String>) {
+ SettingsBody(body.value)
+}
+
+@Composable
+fun SettingsBody(body: String) {
+ if (body.isNotEmpty()) {
+ Text(
+ text = body,
+ color = MaterialTheme.colorScheme.onSurfaceVariant,
+ style = MaterialTheme.typography.bodyMedium,
+ )
+ }
+}
diff --git a/packages/SettingsLib/Spa/tests/build.gradle b/packages/SettingsLib/Spa/tests/build.gradle
index 707017e7e17f..be5a5ec40c4f 100644
--- a/packages/SettingsLib/Spa/tests/build.gradle
+++ b/packages/SettingsLib/Spa/tests/build.gradle
@@ -24,7 +24,7 @@ android {
compileSdk 33
defaultConfig {
- minSdk minSdk_version
+ minSdk spa_min_sdk
targetSdk 33
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -50,7 +50,7 @@ android {
compose true
}
composeOptions {
- kotlinCompilerExtensionVersion compose_version
+ kotlinCompilerExtensionVersion jetpack_compose_version
}
packagingOptions {
resources {
@@ -62,6 +62,6 @@ android {
dependencies {
androidTestImplementation(project(":spa"))
androidTestImplementation 'androidx.test.ext:junit-ktx:1.1.3'
- androidTestImplementation("androidx.compose.ui:ui-test-junit4:$compose_version")
- androidTestDebugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version"
+ androidTestImplementation("androidx.compose.ui:ui-test-junit4:$jetpack_compose_version")
+ androidTestDebugImplementation "androidx.compose.ui:ui-test-manifest:$jetpack_compose_version"
}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/SwitchPreferenceTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/SwitchPreferenceTest.kt
new file mode 100644
index 000000000000..d6c8fbc9dc9e
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/SwitchPreferenceTest.kt
@@ -0,0 +1,91 @@
+/*
+ * 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.settingslib.spa.widget.preference
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsOff
+import androidx.compose.ui.test.assertIsOn
+import androidx.compose.ui.test.isToggleable
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.framework.compose.stateOf
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SwitchPreferenceTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ @Test
+ fun title_displayed() {
+ composeTestRule.setContent {
+ TestSwitchPreference(changeable = true)
+ }
+
+ composeTestRule.onNodeWithText("SwitchPreference").assertIsDisplayed()
+ }
+
+ @Test
+ fun toggleable_initialStateIsCorrect() {
+ composeTestRule.setContent {
+ TestSwitchPreference(changeable = true)
+ }
+
+ composeTestRule.onNode(isToggleable()).assertIsOff()
+ }
+
+ @Test
+ fun click_changeable_withEffect() {
+ composeTestRule.setContent {
+ TestSwitchPreference(changeable = true)
+ }
+
+ composeTestRule.onNodeWithText("SwitchPreference").performClick()
+ composeTestRule.onNode(isToggleable()).assertIsOn()
+ }
+
+ @Test
+ fun click_notChangeable_noEffect() {
+ composeTestRule.setContent {
+ TestSwitchPreference(changeable = false)
+ }
+
+ composeTestRule.onNodeWithText("SwitchPreference").performClick()
+ composeTestRule.onNode(isToggleable()).assertIsOff()
+ }
+}
+
+@Composable
+private fun TestSwitchPreference(changeable: Boolean) {
+ val checked = rememberSaveable { mutableStateOf(false) }
+ SwitchPreference(remember {
+ object : SwitchPreferenceModel {
+ override val title = "SwitchPreference"
+ override val checked = checked
+ override val changeable = stateOf(changeable)
+ override val onCheckedChange = { newChecked: Boolean -> checked.value = newChecked }
+ }
+ })
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsPagerKtTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsPagerKtTest.kt
new file mode 100644
index 000000000000..f608e1003163
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsPagerKtTest.kt
@@ -0,0 +1,82 @@
+/*
+ * 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.settingslib.spa.widget.scaffold
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsNotSelected
+import androidx.compose.ui.test.assertIsSelected
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.widget.ui.SettingsTitle
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SettingsPagerKtTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ @Test
+ fun twoPage_initialState() {
+ composeTestRule.setContent {
+ TestTwoPage()
+ }
+
+ composeTestRule.onNodeWithText("Personal").assertIsSelected()
+ composeTestRule.onNodeWithText("Page 0").assertIsDisplayed()
+ composeTestRule.onNodeWithText("Work").assertIsNotSelected()
+ composeTestRule.onNodeWithText("Page 1").assertDoesNotExist()
+ }
+
+ @Test
+ fun twoPage_afterSwitch() {
+ composeTestRule.setContent {
+ TestTwoPage()
+ }
+
+ composeTestRule.onNodeWithText("Work").performClick()
+
+ composeTestRule.onNodeWithText("Personal").assertIsNotSelected()
+ composeTestRule.onNodeWithText("Page 0").assertDoesNotExist()
+ composeTestRule.onNodeWithText("Work").assertIsSelected()
+ composeTestRule.onNodeWithText("Page 1").assertIsDisplayed()
+ }
+
+ @Test
+ fun onePage_initialState() {
+ composeTestRule.setContent {
+ SettingsPager(listOf("Personal")) {
+ SettingsTitle(title = "Page $it")
+ }
+ }
+
+ composeTestRule.onNodeWithText("Personal").assertDoesNotExist()
+ composeTestRule.onNodeWithText("Page 0").assertIsDisplayed()
+ composeTestRule.onNodeWithText("Page 1").assertDoesNotExist()
+ }
+}
+
+@Composable
+private fun TestTwoPage() {
+ SettingsPager(listOf("Personal", "Work")) {
+ SettingsTitle(title = "Page $it")
+ }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/Android.bp b/packages/SettingsLib/SpaPrivileged/Android.bp
new file mode 100644
index 000000000000..ecbb219c64b5
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/Android.bp
@@ -0,0 +1,32 @@
+//
+// 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 {
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_library {
+ name: "SpaPrivilegedLib",
+
+ srcs: ["src/**/*.kt"],
+
+ static_libs: [
+ "SpaLib",
+ "SettingsLib",
+ "androidx.compose.runtime_runtime",
+ ],
+ kotlincflags: ["-Xjvm-default=all"],
+}
diff --git a/packages/SettingsLib/Spa/codelab/res/values/strings.xml b/packages/SettingsLib/SpaPrivileged/AndroidManifest.xml
index 6fdbba09050e..2efa10744bb3 100644
--- a/packages/SettingsLib/Spa/codelab/res/values/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/AndroidManifest.xml
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2022 The Android Open Source Project
@@ -12,8 +13,6 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
- -->
-<resources>
- <!-- Codelab App name. [DO NOT TRANSLATE] -->
- <string name="app_name" translatable="false">SpaLib Codelab</string>
-</resources>
+-->
+
+<manifest package="com.android.settingslib.spaprivileged" />
diff --git a/packages/SettingsLib/SpaPrivileged/OWNERS b/packages/SettingsLib/SpaPrivileged/OWNERS
new file mode 100644
index 000000000000..9256ca5cc2b0
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/OWNERS
@@ -0,0 +1 @@
+include platform/frameworks/base:/packages/SettingsLib/Spa/OWNERS
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppOpsController.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppOpsController.kt
new file mode 100644
index 000000000000..3808f64386ef
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppOpsController.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.framework.app
+
+import android.app.AppOpsManager
+import android.app.AppOpsManager.MODE_ALLOWED
+import android.app.AppOpsManager.MODE_ERRORED
+import android.app.AppOpsManager.Mode
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+
+class AppOpsController(
+ context: Context,
+ private val app: ApplicationInfo,
+ private val op: Int,
+) {
+ private val appOpsManager = checkNotNull(context.getSystemService(AppOpsManager::class.java))
+
+ val isAllowed: LiveData<Boolean>
+ get() = _isAllowed
+
+ fun setAllowed(allowed: Boolean) {
+ val mode = if (allowed) MODE_ALLOWED else MODE_ERRORED
+ appOpsManager.setMode(op, app.uid, app.packageName, mode)
+ _isAllowed.postValue(allowed)
+ }
+
+ @Mode
+ private fun getMode(): Int = appOpsManager.checkOpNoThrow(op, app.uid, app.packageName)
+
+ private val _isAllowed = object : MutableLiveData<Boolean>() {
+ override fun onActive() {
+ postValue(getMode() == MODE_ALLOWED)
+ }
+
+ override fun onInactive() {
+ }
+ }
+}
diff --git a/lowpan/java/android/net/lowpan/ILowpanEnergyScanCallback.aidl b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppRecord.kt
index f09dbe3d077e..8dde897cb23d 100644
--- a/lowpan/java/android/net/lowpan/ILowpanEnergyScanCallback.aidl
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppRecord.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 The Android Open Source Project
+ * 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.
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-package android.net.lowpan;
+package com.android.settingslib.spaprivileged.framework.app
-/** {@hide} */
-interface ILowpanEnergyScanCallback {
- oneway void onEnergyScanResult(int channel, int rssi);
- oneway void onEnergyScanFinished();
+import android.content.pm.ApplicationInfo
+
+interface AppRecord {
+ val app: ApplicationInfo
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppRepository.kt
new file mode 100644
index 000000000000..a6378ef53437
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/AppRepository.kt
@@ -0,0 +1,58 @@
+/*
+ * 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.settingslib.spaprivileged.framework.app
+
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.graphics.drawable.Drawable
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+import androidx.compose.runtime.produceState
+import com.android.settingslib.Utils
+import com.android.settingslib.spa.framework.compose.rememberContext
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+
+@Composable
+fun rememberAppRepository(): AppRepository = rememberContext(::AppRepositoryImpl)
+
+interface AppRepository {
+ @Composable
+ fun produceLabel(app: ApplicationInfo): State<String>
+
+ @Composable
+ fun produceIcon(app: ApplicationInfo): State<Drawable?>
+}
+
+private class AppRepositoryImpl(private val context: Context) : AppRepository {
+ private val packageManager = context.packageManager
+
+ @Composable
+ override fun produceLabel(app: ApplicationInfo) = produceState(initialValue = "", app) {
+ withContext(Dispatchers.Default) {
+ value = app.loadLabel(packageManager).toString()
+ }
+ }
+
+ @Composable
+ override fun produceIcon(app: ApplicationInfo) =
+ produceState<Drawable?>(initialValue = null, app) {
+ withContext(Dispatchers.Default) {
+ value = Utils.getBadgedIcon(context, app)
+ }
+ }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/Apps.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/Apps.kt
new file mode 100644
index 000000000000..f6755459c1b7
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/Apps.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.settingslib.spaprivileged.framework.app
+
+import android.content.pm.ApplicationInfo
+import android.os.UserHandle
+
+val ApplicationInfo.userId: Int
+ get() = UserHandle.getUserId(uid)
+
+fun ApplicationInfo.toRoute() = "$packageName/$userId"
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/KeyguardUseCaseModule.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/PackageManagers.kt
index 403d34352c7b..66b05da03b3f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/KeyguardUseCaseModule.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/app/PackageManagers.kt
@@ -14,21 +14,16 @@
* limitations under the License.
*/
-package com.android.systemui.keyguard.domain.usecase
+package com.android.settingslib.spaprivileged.framework.app
-import dagger.Binds
-import dagger.Module
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageInfo
+import android.content.pm.PackageManager
-@Module
-interface KeyguardUseCaseModule {
+object PackageManagers {
+ fun getPackageInfoAsUser(packageName: String, userId: Int): PackageInfo =
+ PackageManager.getPackageInfoAsUserCached(packageName, 0, userId)
- @Binds
- fun launchQuickAffordance(
- impl: LaunchKeyguardQuickAffordanceUseCaseImpl
- ): LaunchKeyguardQuickAffordanceUseCase
-
- @Binds
- fun observeKeyguardQuickAffordance(
- impl: ObserveKeyguardQuickAffordanceUseCaseImpl
- ): ObserveKeyguardQuickAffordanceUseCase
+ fun getApplicationInfoAsUser(packageName: String, userId: Int): ApplicationInfo =
+ PackageManager.getApplicationInfoAsUserCached(packageName, 0, userId)
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/enterprise/EnterpriseRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/enterprise/EnterpriseRepository.kt
new file mode 100644
index 000000000000..ae0cb772533c
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/enterprise/EnterpriseRepository.kt
@@ -0,0 +1,38 @@
+/*
+ * 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.settingslib.spaprivileged.framework.enterprise
+
+import android.app.admin.DevicePolicyManager
+import android.app.admin.DevicePolicyResources.Strings.Settings.PERSONAL_CATEGORY_HEADER
+import android.app.admin.DevicePolicyResources.Strings.Settings.WORK_CATEGORY_HEADER
+import android.content.Context
+import com.android.settingslib.spaprivileged.R
+
+class EnterpriseRepository(private val context: Context) {
+ private val resources by lazy {
+ checkNotNull(context.getSystemService(DevicePolicyManager::class.java)).resources
+ }
+
+ fun getEnterpriseString(updatableStringId: String, resId: Int): String =
+ resources.getString(updatableStringId) { context.getString(resId) }
+
+ fun getProfileTitle(isManagedProfile: Boolean): String = if (isManagedProfile) {
+ getEnterpriseString(WORK_CATEGORY_HEADER, R.string.category_work)
+ } else {
+ getEnterpriseString(PERSONAL_CATEGORY_HEADER, R.string.category_personal)
+ }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt
new file mode 100644
index 000000000000..5ae514cfb524
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt
@@ -0,0 +1,70 @@
+/*
+ * 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.settingslib.spaprivileged.template.app
+
+import android.content.pm.ApplicationInfo
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import com.android.settingslib.spa.framework.compose.rememberDrawablePainter
+import com.android.settingslib.spa.widget.ui.SettingsBody
+import com.android.settingslib.spa.widget.ui.SettingsTitle
+import com.android.settingslib.spaprivileged.framework.app.PackageManagers
+import com.android.settingslib.spaprivileged.framework.app.rememberAppRepository
+
+@Composable
+fun AppInfo(packageName: String, userId: Int) {
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(16.dp),
+ horizontalAlignment = Alignment.CenterHorizontally) {
+ val packageInfo = remember { PackageManagers.getPackageInfoAsUser(packageName, userId) }
+ Box(modifier = Modifier.padding(8.dp)) {
+ AppIcon(app = packageInfo.applicationInfo, size = 48)
+ }
+ AppLabel(packageInfo.applicationInfo)
+ Spacer(modifier = Modifier.height(4.dp))
+ SettingsBody(packageInfo.versionName)
+ }
+}
+
+@Composable
+fun AppIcon(app: ApplicationInfo, size: Int) {
+ val appRepository = rememberAppRepository()
+ Image(
+ painter = rememberDrawablePainter(appRepository.produceIcon(app).value),
+ contentDescription = null,
+ modifier = Modifier.size(size.dp)
+ )
+}
+
+@Composable
+fun AppLabel(app: ApplicationInfo) {
+ val appRepository = rememberAppRepository()
+ SettingsTitle(appRepository.produceLabel(app))
+}
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
new file mode 100644
index 000000000000..06d7547be309
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfoPage.kt
@@ -0,0 +1,53 @@
+/*
+ * 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.settingslib.spaprivileged.template.app
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.widget.ui.Footer
+
+@Composable
+fun AppInfoPage(
+ title: String,
+ packageName: String,
+ userId: Int,
+ footerText: String,
+ content: @Composable () -> Unit,
+) {
+ // TODO: Replace with SettingsScaffold
+ Column(Modifier.verticalScroll(rememberScrollState())) {
+ Text(
+ text = title,
+ modifier = Modifier.padding(SettingsDimension.itemPadding),
+ color = MaterialTheme.colorScheme.onSurface,
+ style = MaterialTheme.typography.headlineMedium,
+ )
+
+ AppInfo(packageName, userId)
+
+ content()
+
+ 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
new file mode 100644
index 000000000000..57e9e9ac1b9b
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
@@ -0,0 +1,121 @@
+/*
+ * 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.settingslib.spaprivileged.template.app
+
+import android.content.Context
+import android.os.Bundle
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.State
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
+import androidx.navigation.NavType
+import androidx.navigation.navArgument
+import com.android.settingslib.spa.framework.api.SettingsPageProvider
+import com.android.settingslib.spa.framework.compose.rememberContext
+import com.android.settingslib.spa.widget.preference.SwitchPreference
+import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
+import com.android.settingslib.spaprivileged.framework.app.AppRecord
+import com.android.settingslib.spaprivileged.framework.app.PackageManagers
+import kotlinx.coroutines.Dispatchers
+
+private const val PERMISSION = "permission"
+private const val PACKAGE_NAME = "packageName"
+private const val USER_ID = "userId"
+
+open class TogglePermissionAppInfoPageProvider(
+ private val factory: TogglePermissionAppListModelFactory,
+) : SettingsPageProvider {
+ override val name = "TogglePermissionAppInfoPage"
+
+ override val arguments = listOf(
+ navArgument(PERMISSION) { type = NavType.StringType },
+ navArgument(PACKAGE_NAME) { type = NavType.StringType },
+ navArgument(USER_ID) { type = NavType.IntType },
+ )
+
+ @Composable
+ override fun Page(arguments: Bundle?) {
+ checkNotNull(arguments)
+ val permission = checkNotNull(arguments.getString(PERMISSION))
+ val packageName = checkNotNull(arguments.getString(PACKAGE_NAME))
+ val userId = arguments.getInt(USER_ID)
+ val listModel = rememberContext { context -> factory.createModel(permission, context) }
+ TogglePermissionAppInfoPage(listModel, packageName, userId)
+ }
+
+ fun getRoute(permissionType: String, packageName: String, userId: Int): String =
+ "$name/$permissionType/$packageName/$userId"
+}
+
+@Composable
+private fun TogglePermissionAppInfoPage(
+ listModel: TogglePermissionAppListModel<out AppRecord>,
+ packageName: String,
+ userId: Int,
+) {
+ AppInfoPage(
+ title = stringResource(listModel.pageTitleResId),
+ packageName = packageName,
+ userId = userId,
+ footerText = stringResource(listModel.footerResId),
+ ) {
+ val model = createSwitchModel(listModel, packageName, userId)
+ LaunchedEffect(model, Dispatchers.Default) {
+ model.initState()
+ }
+ SwitchPreference(model)
+ }
+}
+
+@Composable
+private fun <T : AppRecord> createSwitchModel(
+ listModel: TogglePermissionAppListModel<T>,
+ packageName: String,
+ userId: Int,
+): TogglePermissionSwitchModel<T> {
+ val record = remember {
+ val app = PackageManagers.getApplicationInfoAsUser(packageName, userId)
+ listModel.transformItem(app)
+ }
+ val context = LocalContext.current
+ val isAllowed = listModel.isAllowed(record)
+ return remember {
+ TogglePermissionSwitchModel(context, listModel, record, isAllowed)
+ }
+}
+
+private class TogglePermissionSwitchModel<T : AppRecord>(
+ context: Context,
+ private val listModel: TogglePermissionAppListModel<T>,
+ private val record: T,
+ isAllowed: State<Boolean?>,
+) : SwitchPreferenceModel {
+ override val title: String = context.getString(listModel.switchTitleResId)
+ override val checked = isAllowed
+ override val changeable = mutableStateOf(true)
+
+ fun initState() {
+ changeable.value = listModel.isChangeable(record)
+ }
+
+ override val onCheckedChange: (Boolean) -> Unit = { newChecked ->
+ listModel.setAllowed(record, newChecked)
+ }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListModel.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListModel.kt
new file mode 100644
index 000000000000..88ad9daa85b3
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListModel.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.template.app
+
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+import com.android.settingslib.spaprivileged.framework.app.AppRecord
+
+interface TogglePermissionAppListModel<T : AppRecord> {
+ val pageTitleResId: Int
+ val switchTitleResId: Int
+ val footerResId: Int
+
+ fun transformItem(app: ApplicationInfo): T
+
+ @Composable
+ fun isAllowed(record: T): State<Boolean?>
+
+ fun isChangeable(record: T): Boolean
+ fun setAllowed(record: T, newAllowed: Boolean)
+}
+
+interface TogglePermissionAppListModelFactory {
+ fun createModel(
+ permission: String,
+ context: Context,
+ ): TogglePermissionAppListModel<out AppRecord>
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/WorkProfilePager.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/WorkProfilePager.kt
new file mode 100644
index 000000000000..09864a17adfc
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/WorkProfilePager.kt
@@ -0,0 +1,45 @@
+/*
+ * 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.settingslib.spaprivileged.template.scaffold
+
+import android.content.pm.UserInfo
+import android.os.UserHandle
+import android.os.UserManager
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.platform.LocalContext
+import com.android.settingslib.spa.widget.scaffold.SettingsPager
+import com.android.settingslib.spaprivileged.framework.enterprise.EnterpriseRepository
+
+@Composable
+fun WorkProfilePager(content: @Composable (userInfo: UserInfo) -> Unit) {
+ val context = LocalContext.current
+ val profiles = remember {
+ val userManager = checkNotNull(context.getSystemService(UserManager::class.java))
+ userManager.getProfiles(UserHandle.myUserId())
+ }
+ val titles = remember {
+ val enterpriseRepository = EnterpriseRepository(context)
+ profiles.map {
+ enterpriseRepository.getProfileTitle(isManagedProfile = it.isManagedProfile)
+ }
+ }
+
+ SettingsPager(titles) { page ->
+ content(profiles[page])
+ }
+}
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 11cb9c1b54c9..aab0d3a08154 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -488,15 +488,15 @@
<!-- Default speech rate choices -->
<string-array name="tts_rate_entries">
- <item>Very slow</item>
- <item>Slow</item>
- <item>Normal</item>
- <item>Fast</item>
- <item>Faster</item>
- <item>Very fast</item>
- <item>Rapid</item>
- <item>Very rapid</item>
- <item>Fastest</item>
+ <item>60%</item>
+ <item>80%</item>
+ <item>100%</item>
+ <item>150%</item>
+ <item>200%</item>
+ <item>250%</item>
+ <item>300%</item>
+ <item>350%</item>
+ <item>400%</item>
</string-array>
<!-- Do not translate. -->
<string-array name="tts_rate_values">
@@ -1125,7 +1125,7 @@
<!-- [CHAR_LIMIT=40] Label for battery level chart when charging with duration -->
<string name="power_charging_duration"><xliff:g id="level">%1$s</xliff:g> - <xliff:g id="time">%2$s</xliff:g> left until full</string>
<!-- [CHAR_LIMIT=80] Label for battery level chart when charge been limited -->
- <string name="power_charging_limited"><xliff:g id="level">%1$s</xliff:g> - Charging temporarily limited</string>
+ <string name="power_charging_limited"><xliff:g id="level">%1$s</xliff:g> - Charging is paused</string>
<!-- Battery Info screen. Value for a status item. Used for diagnostic info screens, precise translation isn't needed -->
<string name="battery_info_status_unknown">Unknown</string>
@@ -1138,7 +1138,7 @@
<!-- [CHAR_LIMIT=20] Battery use screen. Battery status shown in chart label when charging wirelessly. -->
<string name="battery_info_status_charging_wireless">Charging wirelessly</string>
<!-- [CHAR_LIMIT=20] Battery use screen. Battery status shown in chart label when the device is dock charging. -->
- <string name="battery_info_status_charging_dock">Charging Dock</string>
+ <string name="battery_info_status_charging_dock">Charging</string>
<!-- Battery Info screen. Value for a status item. Used for diagnostic info screens, precise translation isn't needed -->
<string name="battery_info_status_discharging">Not charging</string>
<!-- Battery Info screen. Value for a status item. A state which device is connected with any charger(e.g. USB, Adapter or Wireless) but not charging yet. Used for diagnostic info screens, precise translation isn't needed -->
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
index 7fbd10025d60..cd3242a9f7c2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
@@ -297,6 +297,9 @@ public class CachedBluetoothDeviceManager {
mCachedDevices.remove(i);
}
}
+
+ // To clear the SetMemberPair flag when the Bluetooth is turning off.
+ mOngoingSetMemberPair = null;
}
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 42b992fb44d3..bc9490fc8f9d 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -212,6 +212,8 @@ public class SecureSettings {
Settings.Secure.STATUS_BAR_SHOW_VIBRATE_ICON,
Settings.Secure.WEAR_TALKBACK_ENABLED,
Settings.Secure.HBM_SETTING_KEY,
- Settings.Secure.ACCESSIBILITY_SOFTWARE_CURSOR_ENABLED
+ Settings.Secure.ACCESSIBILITY_SOFTWARE_CURSOR_ENABLED,
+ Settings.Secure.ACCESSIBILITY_SOFTWARE_CURSOR_TRIGGER_HINTS_ENABLED,
+ Settings.Secure.ACCESSIBILITY_SOFTWARE_CURSOR_KEYBOARD_SHIFT_ENABLED,
};
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 14b58550d162..2c99d713d236 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -346,5 +346,9 @@ public class SecureSettingsValidators {
VALIDATORS.put(Secure.WEAR_TALKBACK_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.HBM_SETTING_KEY, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ACCESSIBILITY_SOFTWARE_CURSOR_ENABLED, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(
+ Secure.ACCESSIBILITY_SOFTWARE_CURSOR_TRIGGER_HINTS_ENABLED, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(
+ Secure.ACCESSIBILITY_SOFTWARE_CURSOR_KEYBOARD_SHIFT_ENABLED, BOOLEAN_VALIDATOR);
}
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index c3b645e7bc0d..a2ffcf385880 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1828,6 +1828,12 @@ class SettingsProtoDumpUtil {
dumpSetting(s, p,
Settings.Secure.ACCESSIBILITY_SOFTWARE_CURSOR_ENABLED,
SecureSettingsProto.Accessibility.ACCESSIBILITY_SOFTWARE_CURSOR_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_SOFTWARE_CURSOR_TRIGGER_HINTS_ENABLED,
+ SecureSettingsProto.Accessibility.SoftwareCursorSettings.TRIGGER_HINTS_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_SOFTWARE_CURSOR_KEYBOARD_SHIFT_ENABLED,
+ SecureSettingsProto.Accessibility.SoftwareCursorSettings.KEYBOARD_SHIFT_ENABLED);
p.end(accessibilityToken);
final long adaptiveSleepToken = p.start(SecureSettingsProto.ADAPTIVE_SLEEP);
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 634df39731eb..a25f56737d3d 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -238,6 +238,9 @@ android_library {
"com.android.systemui",
],
plugins: ["dagger2-compiler"],
+ lint: {
+ test: true,
+ },
}
// Opt-in config for optimizing the SystemUI target using R8.
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
index cc7d23e97d0c..dc2c63561d79 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
@@ -165,6 +165,8 @@ class ViewHierarchyAnimator {
* @param includeFadeIn true if the animator should also fade in the view and child views.
* @param fadeInInterpolator the interpolator to use when fading in the view. Unused if
* [includeFadeIn] is false.
+ * @param onAnimationEnd an optional runnable that will be run once the animation
+ * finishes successfully. Will not be run if the animation is cancelled.
*/
@JvmOverloads
fun animateAddition(
@@ -174,7 +176,8 @@ class ViewHierarchyAnimator {
duration: Long = DEFAULT_DURATION,
includeMargins: Boolean = false,
includeFadeIn: Boolean = false,
- fadeInInterpolator: Interpolator = DEFAULT_FADE_IN_INTERPOLATOR
+ fadeInInterpolator: Interpolator = DEFAULT_FADE_IN_INTERPOLATOR,
+ onAnimationEnd: Runnable? = null,
): Boolean {
if (
occupiesSpace(
@@ -193,7 +196,8 @@ class ViewHierarchyAnimator {
origin,
interpolator,
duration,
- ignorePreviousValues = !includeMargins
+ ignorePreviousValues = !includeMargins,
+ onAnimationEnd,
)
addListener(rootView, listener, recursive = true)
@@ -246,14 +250,16 @@ class ViewHierarchyAnimator {
origin: Hotspot,
interpolator: Interpolator,
duration: Long,
- ignorePreviousValues: Boolean
+ ignorePreviousValues: Boolean,
+ onAnimationEnd: Runnable? = null,
): View.OnLayoutChangeListener {
return createListener(
interpolator,
duration,
ephemeral = true,
origin = origin,
- ignorePreviousValues = ignorePreviousValues
+ ignorePreviousValues = ignorePreviousValues,
+ onAnimationEnd,
)
}
@@ -272,7 +278,8 @@ class ViewHierarchyAnimator {
duration: Long,
ephemeral: Boolean,
origin: Hotspot? = null,
- ignorePreviousValues: Boolean = false
+ ignorePreviousValues: Boolean = false,
+ onAnimationEnd: Runnable? = null,
): View.OnLayoutChangeListener {
return object : View.OnLayoutChangeListener {
override fun onLayoutChange(
@@ -340,7 +347,8 @@ class ViewHierarchyAnimator {
endValues,
interpolator,
duration,
- ephemeral
+ ephemeral,
+ onAnimationEnd,
)
}
}
@@ -903,7 +911,8 @@ class ViewHierarchyAnimator {
endValues: Map<Bound, Int>,
interpolator: Interpolator,
duration: Long,
- ephemeral: Boolean
+ ephemeral: Boolean,
+ onAnimationEnd: Runnable? = null,
) {
val propertyValuesHolders =
buildList {
@@ -941,6 +950,9 @@ class ViewHierarchyAnimator {
// listener.
recursivelyRemoveListener(view)
}
+ if (!cancelled) {
+ onAnimationEnd?.run()
+ }
}
override fun onAnimationCancel(animation: Animator?) {
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/GetMainLooperViaContextDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/GetMainLooperViaContextDetector.kt
new file mode 100644
index 000000000000..a629eeeb0102
--- /dev/null
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/GetMainLooperViaContextDetector.kt
@@ -0,0 +1,66 @@
+/*
+ * 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.internal.systemui.lint
+
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import com.intellij.psi.PsiMethod
+import org.jetbrains.uast.UCallExpression
+
+@Suppress("UnstableApiUsage")
+class GetMainLooperViaContextDetector : Detector(), SourceCodeScanner {
+
+ override fun getApplicableMethodNames(): List<String> {
+ return listOf("getMainThreadHandler", "getMainLooper", "getMainExecutor")
+ }
+
+ override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
+ if (context.evaluator.isMemberInSubClassOf(method, "android.content.Context")) {
+ context.report(
+ ISSUE,
+ method,
+ context.getNameLocation(node),
+ "Please inject a @Main Executor instead."
+ )
+ }
+ }
+
+ companion object {
+ @JvmField
+ val ISSUE: Issue =
+ Issue.create(
+ id = "GetMainLooperViaContextDetector",
+ briefDescription = "Please use idiomatic SystemUI executors, injecting " +
+ "them via Dagger.",
+ explanation = "Injecting the @Main Executor is preferred in order to make" +
+ "dependencies explicit and increase testability. It's much " +
+ "easier to pass a FakeExecutor on your test ctor than to " +
+ "deal with loopers in unit tests.",
+ category = Category.LINT,
+ priority = 8,
+ severity = Severity.WARNING,
+ implementation = Implementation(GetMainLooperViaContextDetector::class.java,
+ Scope.JAVA_FILE_SCOPE)
+ )
+ }
+}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt
new file mode 100644
index 000000000000..b72d03db0617
--- /dev/null
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt
@@ -0,0 +1,66 @@
+/*
+ * 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.internal.systemui.lint
+
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import com.intellij.psi.PsiMethod
+import org.jetbrains.uast.UCallExpression
+
+class RegisterReceiverViaContextDetector : Detector(), SourceCodeScanner {
+
+ override fun getApplicableMethodNames(): List<String> {
+ return listOf("registerReceiver", "registerReceiverAsUser", "registerReceiverForAllUsers")
+ }
+
+ override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
+ if (context.evaluator.isMemberInSubClassOf(method, "android.content.Context")) {
+ context.report(
+ ISSUE,
+ method,
+ context.getNameLocation(node),
+ "BroadcastReceivers should be registered via BroadcastDispatcher."
+ )
+ }
+ }
+
+ companion object {
+ @JvmField
+ val ISSUE: Issue =
+ Issue.create(
+ id = "RegisterReceiverViaContextDetector",
+ briefDescription = "Broadcast registrations via Context are blocking " +
+ "calls. Please use BroadcastDispatcher.",
+ explanation =
+ "Context#registerReceiver is a blocking call to the system server, " +
+ "making it very likely that you'll drop a frame. Please use " +
+ "BroadcastDispatcher instead (or move this call to a " +
+ "@Background Executor.)",
+ category = Category.PERFORMANCE,
+ priority = 8,
+ severity = Severity.WARNING,
+ implementation = Implementation(RegisterReceiverViaContextDetector::class.java,
+ Scope.JAVA_FILE_SCOPE)
+ )
+ }
+}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt
new file mode 100644
index 000000000000..a584894fed71
--- /dev/null
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt
@@ -0,0 +1,72 @@
+/*
+ * 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.internal.systemui.lint
+
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import com.intellij.psi.PsiElement
+import com.intellij.psi.PsiField
+import org.jetbrains.uast.UReferenceExpression
+
+@Suppress("UnstableApiUsage")
+class SoftwareBitmapDetector : Detector(), SourceCodeScanner {
+
+ override fun getApplicableReferenceNames(): List<String> {
+ return mutableListOf("ALPHA_8", "RGB_565", "ARGB_8888", "RGBA_F16", "RGBA_1010102")
+ }
+
+ override fun visitReference(
+ context: JavaContext,
+ reference: UReferenceExpression,
+ referenced: PsiElement
+ ) {
+
+ val evaluator = context.evaluator
+ if (evaluator.isMemberInClass(referenced as? PsiField, "android.graphics.Bitmap.Config")) {
+ context.report(
+ ISSUE,
+ referenced,
+ context.getNameLocation(referenced),
+ "Usage of Config.HARDWARE is highly encouraged."
+ )
+ }
+ }
+
+ companion object {
+ @JvmField
+ val ISSUE: Issue =
+ Issue.create(
+ id = "SoftwareBitmapDetector",
+ briefDescription = "Software bitmap detected. Please use Config.HARDWARE instead.",
+ explanation =
+ "Software bitmaps occupy twice as much memory, when compared to Config.HARDWARE. " +
+ "In case you need to manipulate the pixels, please consider to either use" +
+ "a shader (encouraged), or a short lived software bitmap.",
+ category = Category.PERFORMANCE,
+ priority = 8,
+ severity = Severity.WARNING,
+ implementation = Implementation(SoftwareBitmapDetector::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 226aebbd0464..c7c73d3c86a1 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,8 +27,13 @@ import com.google.auto.service.AutoService
class SystemUIIssueRegistry : IssueRegistry() {
override val issues: List<Issue>
- get() = listOf(BindServiceViaContextDetector.ISSUE,
- BroadcastSentViaContextDetector.ISSUE)
+ get() = listOf(
+ BindServiceViaContextDetector.ISSUE,
+ BroadcastSentViaContextDetector.ISSUE,
+ GetMainLooperViaContextDetector.ISSUE,
+ RegisterReceiverViaContextDetector.ISSUE,
+ SoftwareBitmapDetector.ISSUE,
+ )
override val api: Int
get() = CURRENT_API
diff --git a/packages/SystemUI/checks/tests/com/android/systemui/lint/GetMainLooperViaContextDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/systemui/lint/GetMainLooperViaContextDetectorTest.kt
new file mode 100644
index 000000000000..ec761cd7660d
--- /dev/null
+++ b/packages/SystemUI/checks/tests/com/android/systemui/lint/GetMainLooperViaContextDetectorTest.kt
@@ -0,0 +1,135 @@
+/*
+ * 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.internal.systemui.lint
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.checks.infrastructure.TestFile
+import com.android.tools.lint.checks.infrastructure.TestFiles
+import com.android.tools.lint.checks.infrastructure.TestLintTask
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+import org.junit.Test
+
+class GetMainLooperViaContextDetectorTest : LintDetectorTest() {
+
+ override fun getDetector(): Detector = GetMainLooperViaContextDetector()
+ override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
+
+ override fun getIssues(): List<Issue> = listOf(GetMainLooperViaContextDetector.ISSUE)
+
+ private val explanation = "Please inject a @Main Executor instead."
+
+ @Test
+ fun testGetMainThreadHandler() {
+ lint().files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import android.content.Context;
+ import android.os.Handler;
+
+ public class TestClass1 {
+ public void test(Context context) {
+ Handler mainThreadHandler = context.getMainThreadHandler();
+ }
+ }
+ """
+ ).indented(),
+ *stubs)
+ .issues(GetMainLooperViaContextDetector.ISSUE)
+ .run()
+ .expectWarningCount(1)
+ .expectContains(explanation)
+ }
+
+ @Test
+ fun testGetMainLooper() {
+ lint().files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import android.content.Context;
+ import android.os.Looper;
+
+ public class TestClass1 {
+ public void test(Context context) {
+ Looper mainLooper = context.getMainLooper();
+ }
+ }
+ """
+ ).indented(),
+ *stubs)
+ .issues(GetMainLooperViaContextDetector.ISSUE)
+ .run()
+ .expectWarningCount(1)
+ .expectContains(explanation)
+ }
+
+ @Test
+ fun testGetMainExecutor() {
+ lint().files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import android.content.Context;
+ import java.util.concurrent.Executor;
+
+ public class TestClass1 {
+ public void test(Context context) {
+ Executor mainExecutor = context.getMainExecutor();
+ }
+ }
+ """
+ ).indented(),
+ *stubs)
+ .issues(GetMainLooperViaContextDetector.ISSUE)
+ .run()
+ .expectWarningCount(1)
+ .expectContains(explanation)
+ }
+
+ private val contextStub: TestFile = java(
+ """
+ package android.content;
+ import android.os.Handler;import android.os.Looper;import java.util.concurrent.Executor;
+
+ public class Context {
+ public Looper getMainLooper() { return null; };
+ public Executor getMainExecutor() { return null; };
+ public Handler getMainThreadHandler() { return null; };
+ }
+ """
+ )
+
+ private val looperStub: TestFile = java(
+ """
+ package android.os;
+
+ public class Looper {}
+ """
+ )
+
+ private val handlerStub: TestFile = java(
+ """
+ package android.os;
+
+ public class Handler {}
+ """
+ )
+
+ private val stubs = arrayOf(contextStub, looperStub, handlerStub)
+}
diff --git a/packages/SystemUI/checks/tests/com/android/systemui/lint/RegisterReceiverViaContextDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
new file mode 100644
index 000000000000..76c0519d917d
--- /dev/null
+++ b/packages/SystemUI/checks/tests/com/android/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
@@ -0,0 +1,171 @@
+/*
+ * 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.internal.systemui.lint
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.checks.infrastructure.TestFile
+import com.android.tools.lint.checks.infrastructure.TestFiles
+import com.android.tools.lint.checks.infrastructure.TestLintTask
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+import org.junit.Test
+
+class RegisterReceiverViaContextDetectorTest : LintDetectorTest() {
+
+ override fun getDetector(): Detector = RegisterReceiverViaContextDetector()
+ override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
+
+ override fun getIssues(): List<Issue> = listOf(
+ RegisterReceiverViaContextDetector.ISSUE)
+
+ private val explanation = "BroadcastReceivers should be registered via BroadcastDispatcher."
+
+ @Test
+ fun testRegisterReceiver() {
+ lint().files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import android.content.BroadcastReceiver;
+ import android.content.Context;
+ import android.content.IntentFilter;
+
+ public class TestClass1 {
+ public void bind(Context context, BroadcastReceiver receiver,
+ IntentFilter filter) {
+ context.registerReceiver(receiver, filter, 0);
+ }
+ }
+ """
+ ).indented(),
+ *stubs)
+ .issues(RegisterReceiverViaContextDetector.ISSUE)
+ .run()
+ .expectWarningCount(1)
+ .expectContains(explanation)
+ }
+
+ @Test
+ fun testRegisterReceiverAsUser() {
+ lint().files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import android.content.BroadcastReceiver;
+ import android.content.Context;
+ import android.content.IntentFilter;
+ import android.os.Handler;
+ import android.os.UserHandle;
+
+ public class TestClass1 {
+ public void bind(Context context, BroadcastReceiver receiver,
+ IntentFilter filter, Handler handler) {
+ context.registerReceiverAsUser(receiver, UserHandle.ALL, filter,
+ "permission", handler);
+ }
+ }
+ """
+ ).indented(),
+ *stubs)
+ .issues(RegisterReceiverViaContextDetector.ISSUE)
+ .run()
+ .expectWarningCount(1)
+ .expectContains(explanation)
+ }
+
+ @Test
+ fun testRegisterReceiverForAllUsers() {
+ lint().files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import android.content.BroadcastReceiver;
+ import android.content.Context;
+ import android.content.IntentFilter;
+ import android.os.Handler;
+ import android.os.UserHandle;
+
+ public class TestClass1 {
+ public void bind(Context context, BroadcastReceiver receiver,
+ IntentFilter filter, Handler handler) {
+ context.registerReceiverForAllUsers(receiver, filter, "permission",
+ handler);
+ }
+ }
+ """
+ ).indented(),
+ *stubs)
+ .issues(RegisterReceiverViaContextDetector.ISSUE)
+ .run()
+ .expectWarningCount(1)
+ .expectContains(explanation)
+ }
+
+ private val contextStub: TestFile = java(
+ """
+ package android.content;
+ import android.os.Handler;
+ import android.os.UserHandle;
+
+ public class Context {
+ public void registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
+ int flags) {};
+ public void registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user,
+ IntentFilter filter, String broadcastPermission, Handler scheduler) {};
+ public void registerReceiverForAllUsers(BroadcastReceiver receiver, IntentFilter filter,
+ String broadcastPermission, Handler scheduler) {};
+ }
+ """
+ )
+
+ private val broadcastReceiverStub: TestFile = java(
+ """
+ package android.content;
+
+ public class BroadcastReceiver {}
+ """
+ )
+
+ private val intentFilterStub: TestFile = java(
+ """
+ package android.content;
+
+ public class IntentFilter {}
+ """
+ )
+
+ private val handlerStub: TestFile = java(
+ """
+ package android.os;
+
+ public class Handler {}
+ """
+ )
+
+ private val userHandleStub: TestFile = java(
+ """
+ package android.os;
+
+ public enum UserHandle {
+ ALL
+ }
+ """
+ )
+
+ private val stubs = arrayOf(contextStub, broadcastReceiverStub, intentFilterStub, handlerStub,
+ userHandleStub)
+}
diff --git a/packages/SystemUI/checks/tests/com/android/systemui/lint/SoftwareBitmapDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/systemui/lint/SoftwareBitmapDetectorTest.kt
new file mode 100644
index 000000000000..890f2b8eb924
--- /dev/null
+++ b/packages/SystemUI/checks/tests/com/android/systemui/lint/SoftwareBitmapDetectorTest.kt
@@ -0,0 +1,97 @@
+/*
+ * 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.internal.systemui.lint
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.checks.infrastructure.TestFile
+import com.android.tools.lint.checks.infrastructure.TestFiles
+import com.android.tools.lint.checks.infrastructure.TestLintTask
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+import org.junit.Test
+
+@Suppress("UnstableApiUsage")
+class SoftwareBitmapDetectorTest : LintDetectorTest() {
+
+ override fun getDetector(): Detector = SoftwareBitmapDetector()
+ override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
+
+ override fun getIssues(): List<Issue> = listOf(SoftwareBitmapDetector.ISSUE)
+
+ private val explanation = "Usage of Config.HARDWARE is highly encouraged."
+
+ @Test
+ fun testSoftwareBitmap() {
+ lint().files(
+ TestFiles.java(
+ """
+ import android.graphics.Bitmap;
+
+ public class TestClass1 {
+ public void test() {
+ Bitmap.createBitmap(300, 300, Bitmap.Config.RGB_565);
+ Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888);
+ }
+ }
+ """
+ ).indented(),
+ *stubs)
+ .issues(SoftwareBitmapDetector.ISSUE)
+ .run()
+ .expectWarningCount(2)
+ .expectContains(explanation)
+ }
+
+ @Test
+ fun testHardwareBitmap() {
+ lint().files(
+ TestFiles.java(
+ """
+ import android.graphics.Bitmap;
+
+ public class TestClass1 {
+ public void test() {
+ Bitmap.createBitmap(300, 300, Bitmap.Config.HARDWARE);
+ }
+ }
+ """
+ ).indented(),
+ *stubs)
+ .issues(SoftwareBitmapDetector.ISSUE)
+ .run()
+ .expectWarningCount(0)
+ }
+
+ private val bitmapStub: TestFile = java(
+ """
+ package android.graphics;
+
+ public class Bitmap {
+ public enum Config {
+ ARGB_8888,
+ RGB_565,
+ HARDWARE
+ }
+ public static Bitmap createBitmap(int width, int height, Config config) {
+ return null;
+ }
+ }
+ """
+ )
+
+ private val stubs = arrayOf(bitmapStub)
+}
diff --git a/packages/SystemUI/compose/features/Android.bp b/packages/SystemUI/compose/features/Android.bp
index 40218de94258..325ede613de8 100644
--- a/packages/SystemUI/compose/features/Android.bp
+++ b/packages/SystemUI/compose/features/Android.bp
@@ -30,6 +30,7 @@ android_library {
],
static_libs: [
+ "SystemUI-core",
"SystemUIComposeCore",
"androidx.compose.runtime_runtime",
diff --git a/packages/SystemUI/compose/features/AndroidManifest.xml b/packages/SystemUI/compose/features/AndroidManifest.xml
index 0aea99d4e960..eada40e6a40d 100644
--- a/packages/SystemUI/compose/features/AndroidManifest.xml
+++ b/packages/SystemUI/compose/features/AndroidManifest.xml
@@ -16,7 +16,38 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.systemui.compose.features">
-
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.android.systemui.compose.features">
+ <application
+ android:name="android.app.Application"
+ android:appComponentFactory="androidx.core.app.AppComponentFactory"
+ tools:replace="android:name,android:appComponentFactory">
+ <!-- Disable providers from SystemUI -->
+ <provider android:name="com.android.systemui.keyguard.KeyguardSliceProvider"
+ android:authorities="com.android.systemui.test.keyguard.disabled"
+ android:enabled="false"
+ tools:replace="android:authorities"
+ tools:node="remove" />
+ <provider android:name="com.google.android.systemui.keyguard.KeyguardSliceProviderGoogle"
+ android:authorities="com.android.systemui.test.keyguard.disabled"
+ android:enabled="false"
+ tools:replace="android:authorities"
+ tools:node="remove" />
+ <provider android:name="com.android.keyguard.clock.ClockOptionsProvider"
+ android:authorities="com.android.systemui.test.keyguard.clock.disabled"
+ android:enabled="false"
+ tools:replace="android:authorities"
+ tools:node="remove" />
+ <provider android:name="com.android.systemui.people.PeopleProvider"
+ android:authorities="com.android.systemui.test.people.disabled"
+ android:enabled="false"
+ tools:replace="android:authorities"
+ tools:node="remove" />
+ <provider android:name="androidx.core.content.FileProvider"
+ android:authorities="com.android.systemui.test.fileprovider.disabled"
+ android:enabled="false"
+ tools:replace="android:authorities"
+ tools:node="remove"/>
+ </application>
</manifest>
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt
new file mode 100644
index 000000000000..2bf1937a1c1e
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt
@@ -0,0 +1,233 @@
+/*
+ * 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.people.ui.compose
+
+import android.annotation.StringRes
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.LazyListScope
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Divider
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.asImageBitmap
+import androidx.compose.ui.platform.LocalLifecycleOwner
+import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.R
+import com.android.systemui.compose.theme.LocalAndroidColorScheme
+import com.android.systemui.people.ui.viewmodel.PeopleTileViewModel
+import com.android.systemui.people.ui.viewmodel.PeopleViewModel
+import kotlinx.coroutines.flow.collect
+
+/**
+ * Compose the screen associated to a [PeopleViewModel].
+ *
+ * @param viewModel the [PeopleViewModel] that should be composed.
+ * @param onResult the callback called with the result of this screen. Callers should usually finish
+ * the Activity/Fragment/View hosting this Composable once a result is available.
+ */
+@Composable
+fun PeopleScreen(
+ viewModel: PeopleViewModel,
+ onResult: (PeopleViewModel.Result) -> Unit,
+) {
+ val priorityTiles by viewModel.priorityTiles.collectAsState()
+ val recentTiles by viewModel.recentTiles.collectAsState()
+
+ // Make sure to refresh the tiles/conversations when the lifecycle is resumed, so that it
+ // updates them when going back to the Activity after leaving it.
+ val lifecycleOwner = LocalLifecycleOwner.current
+ LaunchedEffect(lifecycleOwner, viewModel) {
+ lifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) {
+ viewModel.onTileRefreshRequested()
+ }
+ }
+
+ // Call [onResult] this activity when the ViewModel tells us so.
+ LaunchedEffect(viewModel.result) {
+ viewModel.result.collect { result ->
+ if (result != null) {
+ viewModel.clearResult()
+ onResult(result)
+ }
+ }
+ }
+
+ // Make sure to use the Android colors and not the default Material3 colors to have the exact
+ // same colors as the View implementation.
+ val androidColors = LocalAndroidColorScheme.current
+ Surface(
+ color = androidColors.colorBackground,
+ contentColor = androidColors.textColorPrimary,
+ modifier = Modifier.fillMaxSize(),
+ ) {
+ if (priorityTiles.isNotEmpty() || recentTiles.isNotEmpty()) {
+ PeopleScreenWithConversations(priorityTiles, recentTiles, viewModel::onTileClicked)
+ } else {
+ PeopleScreenEmpty(viewModel::onUserJourneyCancelled)
+ }
+ }
+}
+
+@Composable
+private fun PeopleScreenWithConversations(
+ priorityTiles: List<PeopleTileViewModel>,
+ recentTiles: List<PeopleTileViewModel>,
+ onTileClicked: (PeopleTileViewModel) -> Unit,
+) {
+ Column {
+ Column(
+ Modifier.fillMaxWidth().padding(PeopleSpacePadding),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ Text(
+ stringResource(R.string.select_conversation_title),
+ style = MaterialTheme.typography.headlineSmall,
+ textAlign = TextAlign.Center,
+ )
+
+ Spacer(Modifier.height(24.dp))
+
+ Text(
+ stringResource(R.string.select_conversation_text),
+ Modifier.padding(horizontal = 24.dp),
+ style = MaterialTheme.typography.bodyLarge,
+ textAlign = TextAlign.Center,
+ )
+ }
+
+ LazyColumn(
+ Modifier.fillMaxWidth(),
+ contentPadding =
+ PaddingValues(
+ top = 16.dp,
+ bottom = PeopleSpacePadding,
+ start = 8.dp,
+ end = 8.dp,
+ )
+ ) {
+ ConversationList(R.string.priority_conversations, priorityTiles, onTileClicked)
+ item { Spacer(Modifier.height(35.dp)) }
+ ConversationList(R.string.recent_conversations, recentTiles, onTileClicked)
+ }
+ }
+}
+
+private fun LazyListScope.ConversationList(
+ @StringRes headerTextResource: Int,
+ tiles: List<PeopleTileViewModel>,
+ onTileClicked: (PeopleTileViewModel) -> Unit
+) {
+ item {
+ Text(
+ stringResource(headerTextResource),
+ Modifier.padding(start = 16.dp),
+ style = MaterialTheme.typography.labelLarge,
+ color = LocalAndroidColorScheme.current.colorAccentPrimaryVariant,
+ )
+
+ Spacer(Modifier.height(10.dp))
+ }
+
+ tiles.forEachIndexed { index, tile ->
+ if (index > 0) {
+ item {
+ Divider(
+ color = LocalAndroidColorScheme.current.colorBackground,
+ thickness = 2.dp,
+ )
+ }
+ }
+
+ item(tile.key.toString()) {
+ Tile(
+ tile,
+ onTileClicked,
+ withTopCornerRadius = index == 0,
+ withBottomCornerRadius = index == tiles.lastIndex,
+ )
+ }
+ }
+}
+
+@Composable
+private fun Tile(
+ tile: PeopleTileViewModel,
+ onTileClicked: (PeopleTileViewModel) -> Unit,
+ withTopCornerRadius: Boolean,
+ withBottomCornerRadius: Boolean,
+) {
+ val androidColors = LocalAndroidColorScheme.current
+ val cornerRadius = dimensionResource(R.dimen.people_space_widget_radius)
+ val topCornerRadius = if (withTopCornerRadius) cornerRadius else 0.dp
+ val bottomCornerRadius = if (withBottomCornerRadius) cornerRadius else 0.dp
+
+ Surface(
+ color = androidColors.colorSurface,
+ contentColor = androidColors.textColorPrimary,
+ shape =
+ RoundedCornerShape(
+ topStart = topCornerRadius,
+ topEnd = topCornerRadius,
+ bottomStart = bottomCornerRadius,
+ bottomEnd = bottomCornerRadius,
+ ),
+ ) {
+ Row(
+ Modifier.fillMaxWidth().clickable { onTileClicked(tile) }.padding(12.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ Image(
+ tile.icon.asImageBitmap(),
+ // TODO(b/238993727): Add a content description.
+ contentDescription = null,
+ Modifier.size(dimensionResource(R.dimen.avatar_size_for_medium)),
+ )
+
+ Text(
+ tile.username ?: "",
+ Modifier.padding(horizontal = 16.dp),
+ style = MaterialTheme.typography.titleLarge,
+ )
+ }
+ }
+}
+
+/** The padding applied to the PeopleSpace screen. */
+internal val PeopleSpacePadding = 24.dp
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreenEmpty.kt b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreenEmpty.kt
new file mode 100644
index 000000000000..5c9358f99858
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreenEmpty.kt
@@ -0,0 +1,124 @@
+/*
+ * 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.people.ui.compose
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.defaultMinSize
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Button
+import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.dp
+import com.android.systemui.R
+import com.android.systemui.compose.theme.LocalAndroidColorScheme
+
+@Composable
+internal fun PeopleScreenEmpty(
+ onGotItClicked: () -> Unit,
+) {
+ Column(
+ Modifier.fillMaxSize().padding(PeopleSpacePadding),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ Text(
+ stringResource(R.string.select_conversation_title),
+ style = MaterialTheme.typography.headlineSmall,
+ textAlign = TextAlign.Center,
+ )
+
+ Spacer(Modifier.height(50.dp))
+
+ Text(
+ stringResource(R.string.no_conversations_text),
+ style = MaterialTheme.typography.bodyLarge,
+ textAlign = TextAlign.Center,
+ )
+
+ Spacer(Modifier.weight(1f))
+ ExampleTile()
+ Spacer(Modifier.weight(1f))
+
+ val androidColors = LocalAndroidColorScheme.current
+ Button(
+ onGotItClicked,
+ Modifier.fillMaxWidth().defaultMinSize(minHeight = 56.dp),
+ colors =
+ ButtonDefaults.buttonColors(
+ containerColor = androidColors.colorAccentPrimary,
+ contentColor = androidColors.textColorOnAccent,
+ )
+ ) { Text(stringResource(R.string.got_it)) }
+ }
+}
+
+@Composable
+private fun ExampleTile() {
+ val androidColors = LocalAndroidColorScheme.current
+ Surface(
+ shape = RoundedCornerShape(28.dp),
+ color = androidColors.colorSurface,
+ contentColor = androidColors.textColorPrimary,
+ ) {
+ Row(
+ Modifier.padding(vertical = 20.dp, horizontal = 16.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ Column(horizontalAlignment = Alignment.CenterHorizontally) {
+ // TODO(b/238993727): Add a content description.
+ Image(
+ painterResource(R.drawable.ic_avatar_with_badge),
+ contentDescription = null,
+ Modifier.size(40.dp),
+ )
+ Spacer(Modifier.height(2.dp))
+ Text(
+ stringResource(R.string.empty_user_name),
+ style = MaterialTheme.typography.labelMedium,
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis,
+ )
+ }
+
+ Spacer(Modifier.width(24.dp))
+
+ Text(
+ stringResource(R.string.empty_status),
+ style = MaterialTheme.typography.labelMedium,
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis,
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/gallery/Android.bp b/packages/SystemUI/compose/gallery/Android.bp
index 40504dc30c33..b0f5cc112120 100644
--- a/packages/SystemUI/compose/gallery/Android.bp
+++ b/packages/SystemUI/compose/gallery/Android.bp
@@ -27,6 +27,7 @@ android_library {
srcs: [
"src/**/*.kt",
+ ":SystemUI-tests-utils",
],
resource_dirs: [
@@ -45,6 +46,14 @@ android_library {
"androidx.navigation_navigation-compose",
"androidx.appcompat_appcompat",
+
+ // TODO(b/240431193): Remove the dependencies and depend on
+ // SystemUI-test-utils directly.
+ "androidx.test.runner",
+ "mockito-target-extended-minus-junit4",
+ "testables",
+ "truth-prebuilt",
+ "androidx.test.uiautomator",
],
kotlincflags: ["-Xjvm-default=all"],
diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/GalleryApp.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/GalleryApp.kt
index c341867bfb59..bb98fb350a2e 100644
--- a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/GalleryApp.kt
+++ b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/GalleryApp.kt
@@ -13,7 +13,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
@@ -33,6 +33,28 @@ object GalleryAppScreens {
val AndroidColors = ChildScreen("android_colors") { AndroidColorsScreen() }
val ExampleFeature = ChildScreen("example_feature") { ExampleFeatureScreen() }
+ val PeopleEmpty =
+ ChildScreen("people_empty") { navController ->
+ EmptyPeopleScreen(onResult = { navController.popBackStack() })
+ }
+ val PeopleFew =
+ ChildScreen("people_few") { navController ->
+ FewPeopleScreen(onResult = { navController.popBackStack() })
+ }
+ val PeopleFull =
+ ChildScreen("people_full") { navController ->
+ FullPeopleScreen(onResult = { navController.popBackStack() })
+ }
+ val People =
+ ParentScreen(
+ "people",
+ mapOf(
+ "Empty" to PeopleEmpty,
+ "Few" to PeopleFew,
+ "Full" to PeopleFull,
+ )
+ )
+
val Home =
ParentScreen(
"home",
@@ -41,20 +63,21 @@ object GalleryAppScreens {
"Material colors" to MaterialColors,
"Android colors" to AndroidColors,
"Example feature" to ExampleFeature,
+ "People" to People,
)
)
}
/** The main content of the app, that shows [GalleryAppScreens.Home] by default. */
@Composable
-private fun MainContent() {
+private fun MainContent(onControlToggleRequested: () -> Unit) {
Box(Modifier.fillMaxSize()) {
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = GalleryAppScreens.Home.identifier,
) {
- screen(GalleryAppScreens.Home, navController)
+ screen(GalleryAppScreens.Home, navController, onControlToggleRequested)
}
}
}
@@ -69,7 +92,7 @@ fun GalleryApp(
onChangeTheme: () -> Unit,
) {
val systemFontScale = LocalDensity.current.fontScale
- var fontScale: FontScale by remember {
+ var fontScale: FontScale by rememberSaveable {
mutableStateOf(
FontScale.values().firstOrNull { it.scale == systemFontScale } ?: FontScale.Normal
)
@@ -87,7 +110,7 @@ fun GalleryApp(
}
val systemLayoutDirection = LocalLayoutDirection.current
- var layoutDirection by remember { mutableStateOf(systemLayoutDirection) }
+ var layoutDirection by rememberSaveable { mutableStateOf(systemLayoutDirection) }
val onChangeLayoutDirection = {
layoutDirection =
when (layoutDirection) {
@@ -105,19 +128,24 @@ fun GalleryApp(
Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background,
) {
- Column(Modifier.fillMaxSize().systemBarsPadding().padding(16.dp)) {
- ConfigurationControls(
- theme,
- fontScale,
- layoutDirection,
- onChangeTheme,
- onChangeLayoutDirection,
- onChangeFontScale,
- )
+ Column(Modifier.fillMaxSize().systemBarsPadding()) {
+ var showControls by rememberSaveable { mutableStateOf(true) }
+
+ if (showControls) {
+ ConfigurationControls(
+ theme,
+ fontScale,
+ layoutDirection,
+ onChangeTheme,
+ onChangeLayoutDirection,
+ onChangeFontScale,
+ Modifier.padding(horizontal = 16.dp),
+ )
- Spacer(Modifier.height(4.dp))
+ Spacer(Modifier.height(4.dp))
+ }
- MainContent()
+ MainContent(onControlToggleRequested = { showControls = !showControls })
}
}
}
diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/PeopleScreen.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/PeopleScreen.kt
new file mode 100644
index 000000000000..2f0df7790ffd
--- /dev/null
+++ b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/PeopleScreen.kt
@@ -0,0 +1,46 @@
+/*
+ * 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.compose.gallery
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.platform.LocalContext
+import com.android.systemui.people.emptyPeopleSpaceViewModel
+import com.android.systemui.people.fewPeopleSpaceViewModel
+import com.android.systemui.people.fullPeopleSpaceViewModel
+import com.android.systemui.people.ui.compose.PeopleScreen
+import com.android.systemui.people.ui.viewmodel.PeopleViewModel
+
+@Composable
+fun EmptyPeopleScreen(onResult: (PeopleViewModel.Result) -> Unit) {
+ val context = LocalContext.current.applicationContext
+ val viewModel = emptyPeopleSpaceViewModel(context)
+ PeopleScreen(viewModel, onResult)
+}
+
+@Composable
+fun FewPeopleScreen(onResult: (PeopleViewModel.Result) -> Unit) {
+ val context = LocalContext.current.applicationContext
+ val viewModel = fewPeopleSpaceViewModel(context)
+ PeopleScreen(viewModel, onResult)
+}
+
+@Composable
+fun FullPeopleScreen(onResult: (PeopleViewModel.Result) -> Unit) {
+ val context = LocalContext.current.applicationContext
+ val viewModel = fullPeopleSpaceViewModel(context)
+ PeopleScreen(viewModel, onResult)
+}
diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/Screen.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/Screen.kt
index 467dac044b79..d7d0d721b01c 100644
--- a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/Screen.kt
+++ b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/Screen.kt
@@ -52,17 +52,29 @@ class ChildScreen(
) : Screen(identifier)
/** Create the navigation graph for [screen]. */
-fun NavGraphBuilder.screen(screen: Screen, navController: NavController) {
+fun NavGraphBuilder.screen(
+ screen: Screen,
+ navController: NavController,
+ onControlToggleRequested: () -> Unit,
+) {
when (screen) {
is ChildScreen -> composable(screen.identifier) { screen.content(navController) }
is ParentScreen -> {
val menuRoute = "${screen.identifier}_menu"
navigation(startDestination = menuRoute, route = screen.identifier) {
// The menu to navigate to one of the children screens.
- composable(menuRoute) { ScreenMenu(screen, navController) }
+ composable(menuRoute) {
+ ScreenMenu(screen, navController, onControlToggleRequested)
+ }
// The content of the child screens.
- screen.children.forEach { (_, child) -> screen(child, navController) }
+ screen.children.forEach { (_, child) ->
+ screen(
+ child,
+ navController,
+ onControlToggleRequested,
+ )
+ }
}
}
}
@@ -72,8 +84,27 @@ fun NavGraphBuilder.screen(screen: Screen, navController: NavController) {
private fun ScreenMenu(
screen: ParentScreen,
navController: NavController,
+ onControlToggleRequested: () -> Unit,
) {
- LazyColumn(verticalArrangement = Arrangement.spacedBy(8.dp)) {
+ LazyColumn(
+ Modifier.padding(horizontal = 16.dp),
+ verticalArrangement = Arrangement.spacedBy(8.dp),
+ ) {
+ item {
+ Surface(
+ Modifier.fillMaxWidth(),
+ color = MaterialTheme.colorScheme.tertiaryContainer,
+ shape = CircleShape,
+ ) {
+ Column(
+ Modifier.clickable(onClick = onControlToggleRequested).padding(16.dp),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ Text("Toggle controls")
+ }
+ }
+ }
+
screen.children.forEach { (name, child) ->
item {
Surface(
diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/people/Fakes.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/people/Fakes.kt
new file mode 100644
index 000000000000..0966c3233ad5
--- /dev/null
+++ b/packages/SystemUI/compose/gallery/src/com/android/systemui/people/Fakes.kt
@@ -0,0 +1,156 @@
+/*
+ * 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.people
+
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.Paint
+import android.graphics.drawable.Icon
+import androidx.core.graphics.drawable.toIcon
+import com.android.systemui.R
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.people.data.model.PeopleTileModel
+import com.android.systemui.people.ui.viewmodel.PeopleViewModel
+import com.android.systemui.people.widget.PeopleTileKey
+
+/** A [PeopleViewModel] that does not have any conversations. */
+fun emptyPeopleSpaceViewModel(@Application context: Context): PeopleViewModel {
+ return fakePeopleSpaceViewModel(context, emptyList(), emptyList())
+}
+
+/** A [PeopleViewModel] that has a few conversations. */
+fun fewPeopleSpaceViewModel(@Application context: Context): PeopleViewModel {
+ return fakePeopleSpaceViewModel(
+ context,
+ priorityTiles =
+ listOf(
+ fakeTile(context, id = "0", Color.RED, "Priority"),
+ fakeTile(context, id = "1", Color.BLUE, "Priority NewStory", hasNewStory = true),
+ ),
+ recentTiles =
+ listOf(
+ fakeTile(context, id = "2", Color.GREEN, "Recent Important", isImportant = true),
+ fakeTile(context, id = "3", Color.CYAN, "Recent DndBlocking", isDndBlocking = true),
+ ),
+ )
+}
+
+/** A [PeopleViewModel] that has a lot of conversations. */
+fun fullPeopleSpaceViewModel(@Application context: Context): PeopleViewModel {
+ return fakePeopleSpaceViewModel(
+ context,
+ priorityTiles =
+ listOf(
+ fakeTile(context, id = "0", Color.RED, "Priority"),
+ fakeTile(context, id = "1", Color.BLUE, "Priority NewStory", hasNewStory = true),
+ fakeTile(context, id = "2", Color.GREEN, "Priority Important", isImportant = true),
+ fakeTile(
+ context,
+ id = "3",
+ Color.CYAN,
+ "Priority DndBlocking",
+ isDndBlocking = true,
+ ),
+ fakeTile(
+ context,
+ id = "4",
+ Color.MAGENTA,
+ "Priority NewStory Important",
+ hasNewStory = true,
+ isImportant = true,
+ ),
+ ),
+ recentTiles =
+ listOf(
+ fakeTile(
+ context,
+ id = "5",
+ Color.RED,
+ "Recent NewStory DndBlocking",
+ hasNewStory = true,
+ isDndBlocking = true,
+ ),
+ fakeTile(
+ context,
+ id = "6",
+ Color.BLUE,
+ "Recent Important DndBlocking",
+ isImportant = true,
+ isDndBlocking = true,
+ ),
+ fakeTile(
+ context,
+ id = "7",
+ Color.GREEN,
+ "Recent NewStory Important DndBlocking",
+ hasNewStory = true,
+ isImportant = true,
+ isDndBlocking = true,
+ ),
+ fakeTile(context, id = "8", Color.CYAN, "Recent"),
+ fakeTile(context, id = "9", Color.MAGENTA, "Recent"),
+ ),
+ )
+}
+
+private fun fakePeopleSpaceViewModel(
+ @Application context: Context,
+ priorityTiles: List<PeopleTileModel>,
+ recentTiles: List<PeopleTileModel>,
+): PeopleViewModel {
+ return PeopleViewModel(
+ context,
+ FakePeopleTileRepository(priorityTiles, recentTiles),
+ FakePeopleWidgetRepository(),
+ )
+}
+
+private fun fakeTile(
+ @Application context: Context,
+ id: String,
+ iconColor: Int,
+ username: String,
+ hasNewStory: Boolean = false,
+ isImportant: Boolean = false,
+ isDndBlocking: Boolean = false
+): PeopleTileModel {
+ return PeopleTileModel(
+ PeopleTileKey(id, /* userId= */ 0, /* packageName */ ""),
+ username,
+ fakeUserIcon(context, iconColor),
+ hasNewStory,
+ isImportant,
+ isDndBlocking,
+ )
+}
+
+private fun fakeUserIcon(@Application context: Context, color: Int): Icon {
+ val size = context.resources.getDimensionPixelSize(R.dimen.avatar_size_for_medium)
+ val bitmap =
+ Bitmap.createBitmap(
+ size,
+ size,
+ Bitmap.Config.ARGB_8888,
+ )
+ val canvas = Canvas(bitmap)
+ val paint = Paint().apply { this.color = color }
+ val radius = size / 2f
+ canvas.drawCircle(/* cx= */ radius, /* cy= */ radius, /* radius= */ radius, paint)
+ return bitmap.toIcon()
+}
diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml
index babe924c4615..8fa22048cd97 100644
--- a/packages/SystemUI/res-keyguard/values/strings.xml
+++ b/packages/SystemUI/res-keyguard/values/strings.xml
@@ -37,8 +37,8 @@
<!-- When the lock screen is showing and the phone plugged in, and the battery is not fully charged, say that it's wirelessly charging. [CHAR LIMIT=50] -->
<string name="keyguard_plugged_in_wireless"><xliff:g id="percentage" example="20%">%s</xliff:g> • Charging wirelessly</string>
- <!-- When the lock screen is showing and the phone plugged in, and the battery is not fully charged, say that it's dock charging. [CHAR LIMIT=50] -->
- <string name="keyguard_plugged_in_dock"><xliff:g id="percentage" example="20%">%s</xliff:g> • Charging Dock</string>
+ <!-- When the lock screen is showing and the phone plugged in, and the battery is not fully charged, say that it's charging. [CHAR LIMIT=50] -->
+ <string name="keyguard_plugged_in_dock"><xliff:g id="percentage" example="20%">%s</xliff:g> • Charging</string>
<!-- When the lock screen is showing and the phone plugged in, and the battery
is not fully charged, say that it's charging. -->
@@ -53,7 +53,7 @@
<string name="keyguard_plugged_in_charging_slowly"><xliff:g id="percentage">%s</xliff:g> • Charging slowly</string>
<!-- When the lock screen is showing and the phone plugged in, and the defend mode is triggered, say that charging is temporarily limited. -->
- <string name="keyguard_plugged_in_charging_limited"><xliff:g id="percentage">%s</xliff:g> • Charging temporarily limited</string>
+ <string name="keyguard_plugged_in_charging_limited"><xliff:g id="percentage">%s</xliff:g> • Charging is paused to protect battery</string>
<!-- On the keyguard screen, when pattern lock is disabled, only tell them to press menu to unlock. This is shown in small font at the bottom. -->
<string name="keyguard_instructions_when_pattern_disabled">Press Menu to unlock.</string>
diff --git a/packages/SystemUI/res/drawable/accessibility_magnification_setting_view_bg.xml b/packages/SystemUI/res/drawable/accessibility_magnification_setting_view_bg.xml
new file mode 100644
index 000000000000..4da47af665f2
--- /dev/null
+++ b/packages/SystemUI/res/drawable/accessibility_magnification_setting_view_bg.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+-->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item>
+ <shape android:shape="rectangle">
+ <solid android:color="@color/accessibility_magnifier_bg" />
+ <corners android:radius="24dp" />
+ <stroke
+ android:color="@color/accessibility_magnifier_bg_stroke"
+ android:width="1dp" />
+ </shape>
+ </item>
+</layer-list> \ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/accessibility_magnification_setting_view_btn_bg.xml b/packages/SystemUI/res/drawable/accessibility_magnification_setting_view_btn_bg.xml
new file mode 100644
index 000000000000..5c9dd569b9d7
--- /dev/null
+++ b/packages/SystemUI/res/drawable/accessibility_magnification_setting_view_btn_bg.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+android:color="?android:attr/colorControlHighlight">
+<item android:id="@android:id/mask">
+ <shape android:shape="oval">
+ <solid android:color="@color/accessibility_magnifier_bg" />
+ <size
+ android:width="56dp"
+ android:height="56dp"/>
+ <corners android:radius="2dp"/>
+ </shape>
+</item>
+</ripple> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/tv_pip_button_bg.xml b/packages/SystemUI/res/drawable/accessibility_magnifier_btn_bg.xml
index 1938f4562e97..f633b3e3702d 100644
--- a/libs/WindowManager/Shell/res/drawable/tv_pip_button_bg.xml
+++ b/packages/SystemUI/res/drawable/accessibility_magnifier_btn_bg.xml
@@ -1,21 +1,27 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2020 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle">
- <corners android:radius="@dimen/pip_menu_button_radius" />
- <solid android:color="@color/tv_pip_menu_icon_bg" />
-</shape> \ No newline at end of file
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="oval">
+ <solid android:color="@color/accessibility_magnifier_bg" />
+ <size
+ android:width="56dp"
+ android:height="56dp"/>
+ <corners android:radius="2dp"/>
+ <stroke
+ android:color="@color/accessibility_magnifier_bg_stroke"
+ android:width="1dp" />
+ </shape> \ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_fp.xml b/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_fp.xml
deleted file mode 100644
index 5084ca48e608..000000000000
--- a/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_fp.xml
+++ /dev/null
@@ -1 +0,0 @@
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="80dp" android:width="80dp" android:viewportHeight="80" android:viewportWidth="80"><group android:name="_R_G"><group android:name="_R_G_L_3_G" android:translateX="-0.25" android:translateY="-0.25"><path android:name="_R_G_L_3_G_D_0_P_0" android:fillColor="#474747" android:fillAlpha="0" android:fillType="nonZero" android:pathData=" M40.21 0.25 C18.13,0.25 0.25,18.17 0.25,40.25 C0.25,62.33 18.13,80.25 40.21,80.25 C62.33,80.25 80.25,62.33 80.25,40.25 C80.25,18.17 62.33,0.25 40.21,0.25c "/></group><group android:name="_R_G_L_2_G" android:translateX="-0.25" android:translateY="-0.25" android:pivotX="40.25" android:pivotY="40.25" android:scaleX="0.975" android:scaleY="0.975"><path android:name="_R_G_L_2_G_D_0_P_0" android:strokeColor="#f2b8b5" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2.5" android:strokeAlpha="1" android:pathData=" M40.21 0.25 C18.13,0.25 0.25,18.17 0.25,40.25 C0.25,62.33 18.13,80.25 40.21,80.25 C62.33,80.25 80.25,62.33 80.25,40.25 C80.25,18.17 62.33,0.25 40.21,0.25c "/></group><group android:name="_R_G_L_1_G" android:translateX="9.950000000000003" android:translateY="10" android:pivotX="30" android:pivotY="30" android:scaleX="1.2" android:scaleY="1.2"><group android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0" android:translateX="30" android:translateY="38.75" android:scaleX="1" android:scaleY="1"><path android:name="_R_G_L_1_G_D_0_P_0" android:fillColor="#f2b8b5" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-1.2 -1.25 C-1.2,-1.25 1.2,-1.25 1.2,-1.25 C1.2,-1.25 1.2,1.25 1.2,1.25 C1.2,1.25 -1.2,1.25 -1.2,1.25 C-1.2,1.25 -1.2,-1.25 -1.2,-1.25c "/></group><group android:name="_R_G_L_1_G_D_1_P_0_G_0_T_0" android:translateX="30" android:translateY="25" android:pivotX="0.002" android:pivotY="7.488" android:scaleX="1" android:scaleY="1"><path android:name="_R_G_L_1_G_D_1_P_0" android:fillColor="#f2b8b5" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-1.2 -7.5 C-1.2,-7.5 1.2,-7.5 1.2,-7.5 C1.2,-7.5 1.2,7.5 1.2,7.5 C1.2,7.5 -1.2,7.5 -1.2,7.5 C-1.2,7.5 -1.2,-7.5 -1.2,-7.5c "/></group></group><group android:name="_R_G_L_0_G" android:translateX="20.659" android:translateY="15.75"><path android:name="_R_G_L_0_G_D_0_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" android:trimPathEnd="0" android:trimPathOffset="0" android:pathData=" M27.52 38.98 C26.49,39.95 25.29,40.73 23.98,41.29 C23.17,41.65 22.31,41.91 21.41,42.07 C20.74,42.19 20.05,42.25 19.34,42.25 C18.44,42.25 17.56,42.15 16.72,41.96 C15.93,41.77 15.16,41.51 14.43,41.18 C13.23,40.63 12.13,39.88 11.16,38.98 "/><path android:name="_R_G_L_0_G_D_1_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" android:trimPathEnd="0" android:trimPathOffset="0" android:pathData=" M8.64 34.07 C7.89,31.97 7.89,29.85 7.89,29.85 C7.89,24.05 12.81,19.34 19.34,19.34 C25.87,19.34 30.8,24.05 30.8,29.85 C30.8,29.85 30.8,30.16 30.8,30.16 C30.8,32.32 29.04,34.07 26.89,34.07 C25.28,34.07 23.86,33.1 23.27,31.61 C23.27,31.61 21.96,28.34 21.96,28.34 C21.37,26.85 19.93,25.89 18.34,25.89 C16.18,25.89 14.43,27.64 14.43,29.8 C14.43,31.42 14.87,32.99 15.68,34.36 C16.22,35.26 16.93,36.08 17.77,36.75 C17.77,36.75 18.52,37.34 18.52,37.34 "/><path android:name="_R_G_L_0_G_D_2_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" android:trimPathEnd="0" android:trimPathOffset="0" android:pathData=" M6.25 19.34 C7.48,17.3 9.46,15.58 11.9,14.42 C12.93,13.94 14.03,13.55 15.2,13.27 C16.51,12.96 17.9,12.8 19.34,12.8 C20.77,12.8 22.14,12.96 23.45,13.26 C24.9,13.6 26.26,14.12 27.48,14.78 C29.6,15.92 31.32,17.5 32.43,19.34 "/><path android:name="_R_G_L_0_G_D_3_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" android:trimPathEnd="0" android:trimPathOffset="0" android:pathData=" M9.52 8.7 C10.98,7.91 12.58,7.28 14.28,6.86 C15.89,6.46 17.58,6.25 19.34,6.25 C21.06,6.25 22.72,6.45 24.3,6.83 C26.04,7.25 27.67,7.89 29.16,8.7 "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_3_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="fillAlpha" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="fillAlpha" android:duration="167" android:startOffset="67" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="strokeWidth" android:duration="167" android:startOffset="0" android:valueFrom="2.5" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="strokeAlpha" android:duration="83" android:startOffset="0" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="strokeAlpha" android:duration="83" android:startOffset="83" android:valueFrom="1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="0" android:valueFrom="0" android:valueTo="0.975" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_2_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="167" android:valueFrom="0.975" android:valueTo="0" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="0" android:valueFrom="1" android:valueTo="1.1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.853,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="0" android:valueFrom="1" android:valueTo="1.1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.853,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="100" android:startOffset="67" android:valueFrom="1.1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.06 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="100" android:startOffset="67" android:valueFrom="1.1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.06 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_1_P_0_G_0_T_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="0" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.659,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="0" android:valueFrom="1" android:valueTo="1.1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="100" android:startOffset="67" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="100" android:startOffset="67" android:valueFrom="1.1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.096 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="trimPathEnd" android:duration="83" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.543,0 0.299,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="trimPathEnd" android:duration="250" android:startOffset="83" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.543,0 0.299,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_1_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="trimPathEnd" android:duration="83" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.543,0 0.299,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="trimPathEnd" android:duration="250" android:startOffset="83" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.543,0 0.299,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_2_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="trimPathEnd" android:duration="83" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.543,0 0.299,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="trimPathEnd" android:duration="250" android:startOffset="83" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.543,0 0.299,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_3_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="trimPathEnd" android:duration="83" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.543,0 0.299,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="trimPathEnd" android:duration="250" android:startOffset="83" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.543,0 0.299,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="417" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector> \ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_unlock.xml b/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_unlock.xml
deleted file mode 100644
index c4f818146011..000000000000
--- a/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_unlock.xml
+++ /dev/null
@@ -1 +0,0 @@
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="80dp" android:width="80dp" android:viewportHeight="80" android:viewportWidth="80"><group android:name="_R_G"><group android:name="_R_G_L_3_G" android:translateX="-0.25" android:translateY="-0.25"><path android:name="_R_G_L_3_G_D_0_P_0" android:fillColor="#474747" android:fillAlpha="0" android:fillType="nonZero" android:pathData=" M40.21 0.25 C18.13,0.25 0.25,18.17 0.25,40.25 C0.25,62.33 18.13,80.25 40.21,80.25 C62.33,80.25 80.25,62.33 80.25,40.25 C80.25,18.17 62.33,0.25 40.21,0.25c "/></group><group android:name="_R_G_L_2_G" android:translateX="-0.25" android:translateY="-0.25" android:pivotX="40.25" android:pivotY="40.25" android:scaleX="0.975" android:scaleY="0.975"><path android:name="_R_G_L_2_G_D_0_P_0" android:strokeColor="#f2b8b5" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2.5" android:strokeAlpha="1" android:pathData=" M40.21 0.25 C18.13,0.25 0.25,18.17 0.25,40.25 C0.25,62.33 18.13,80.25 40.21,80.25 C62.33,80.25 80.25,62.33 80.25,40.25 C80.25,18.17 62.33,0.25 40.21,0.25c "/></group><group android:name="_R_G_L_1_G" android:translateX="9.950000000000003" android:translateY="10" android:pivotX="30" android:pivotY="30" android:scaleX="1.2" android:scaleY="1.2"><group android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0" android:translateX="30" android:translateY="38.75" android:scaleX="1" android:scaleY="1"><path android:name="_R_G_L_1_G_D_0_P_0" android:fillColor="#f2b8b5" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-1.2 -1.25 C-1.2,-1.25 1.2,-1.25 1.2,-1.25 C1.2,-1.25 1.2,1.25 1.2,1.25 C1.2,1.25 -1.2,1.25 -1.2,1.25 C-1.2,1.25 -1.2,-1.25 -1.2,-1.25c "/></group><group android:name="_R_G_L_1_G_D_1_P_0_G_0_T_0" android:translateX="30" android:translateY="25" android:pivotX="0.002" android:pivotY="7.488" android:scaleX="1" android:scaleY="1"><path android:name="_R_G_L_1_G_D_1_P_0" android:fillColor="#f2b8b5" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-1.2 -7.5 C-1.2,-7.5 1.2,-7.5 1.2,-7.5 C1.2,-7.5 1.2,7.5 1.2,7.5 C1.2,7.5 -1.2,7.5 -1.2,7.5 C-1.2,7.5 -1.2,-7.5 -1.2,-7.5c "/></group></group><group android:name="_R_G_L_0_G" android:translateX="20.75" android:translateY="15.75" android:pivotX="19.341" android:pivotY="24.25" android:scaleX="0.5" android:scaleY="0"><path android:name="_R_G_L_0_G_D_0_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M30.64 30.14 C30.64,30.14 30.64,38.14 30.64,38.14 C30.64,38.77 30.36,39.32 29.91,39.69 C29.57,39.97 29.12,40.14 28.64,40.14 C28.64,40.14 10.14,40.14 10.14,40.14 C9.04,40.14 8.14,39.25 8.14,38.14 C8.14,38.14 8.14,30.14 8.14,30.14 "/><path android:name="_R_G_L_0_G_D_1_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M19.42 31.53 C18.15,31.52 18.11,30.33 18.11,30.33 C18.11,29.59 18.66,28.98 19.4,28.98 C20.13,28.98 20.69,29.59 20.69,30.33 C20.69,30.33 20.69,30.37 20.69,30.37 C20.69,30.64 20.49,30.87 20.25,30.87 C20.07,30.87 19.91,30.74 19.84,30.55 C19.84,30.55 19.69,30.14 19.69,30.14 C19.63,29.94 19.46,29.82 19.28,29.82 C19.04,29.82 18.61,30.02 18.61,30.29 C18.61,30.43 18.6,30.75 18.76,31.03 C18.87,31.21 19.21,31.77 19.96,31.41 C20.69,31.01 20.69,30.34 20.69,30.34 "/><path android:name="_R_G_L_0_G_D_2_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M8.14 30.22 C8.14,30.22 8.14,22.22 8.14,22.22 C8.14,21.71 8.33,21.25 8.64,20.9 C9,20.48 9.54,20.22 10.14,20.22 C10.14,20.22 28.64,20.22 28.64,20.22 C29.75,20.22 30.64,21.11 30.64,22.22 C30.64,22.22 30.64,30.14 30.64,30.14 "/><path android:name="_R_G_L_0_G_D_3_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M13.12 20.04 C13.12,20.04 13.11,14.15 13.11,14.15 C13.11,10.77 15.91,8.04 19.36,8.04 C22.81,8.04 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_3_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="fillAlpha" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="fillAlpha" android:duration="167" android:startOffset="67" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="strokeWidth" android:duration="167" android:startOffset="0" android:valueFrom="2.5" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="strokeAlpha" android:duration="83" android:startOffset="0" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="strokeAlpha" android:duration="83" android:startOffset="83" android:valueFrom="1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="0" android:valueFrom="0" android:valueTo="0.975" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_2_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="167" android:valueFrom="0.975" android:valueTo="0" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="0" android:valueFrom="1" android:valueTo="1.1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.853,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="0" android:valueFrom="1" android:valueTo="1.1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.853,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="100" android:startOffset="67" android:valueFrom="1.1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.06 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="100" android:startOffset="67" android:valueFrom="1.1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.06 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_1_P_0_G_0_T_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="0" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.659,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="0" android:valueFrom="1" android:valueTo="1.1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="100" android:startOffset="67" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.6,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="100" android:startOffset="67" android:valueFrom="1.1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.096 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_3_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="233" android:startOffset="0" android:valueFrom="M13.12 20.04 C13.12,20.04 13.11,14.15 13.11,14.15 C13.11,10.77 15.91,8.04 19.36,8.04 C22.81,8.04 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " android:valueTo="M13.12 20.04 C13.12,20.04 13.11,14.15 13.11,14.15 C13.11,10.77 15.91,8.04 19.36,8.04 C22.81,8.04 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="267" android:startOffset="233" android:valueFrom="M13.12 20.04 C13.12,20.04 13.11,14.15 13.11,14.15 C13.11,10.77 15.91,8.04 19.36,8.04 C22.81,8.04 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " android:valueTo="M37.91 20.05 C37.91,20.05 37.89,14.16 37.89,14.16 C37.89,10.79 35.15,8.05 31.86,8.03 C28.46,8.01 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="167" android:startOffset="0" android:valueFrom="0.5" android:valueTo="0.5" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.05,1.4 0.1,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="167" android:startOffset="0" android:valueFrom="0.5" android:valueTo="0.5" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.05,1.4 0.1,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="333" android:startOffset="167" android:valueFrom="0.5" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.05,1.4 0.1,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="333" android:startOffset="167" android:valueFrom="0.5" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.05,1.4 0.1,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="167" android:valueFrom="0" android:valueTo="0.5" android:valueType="floatType"/></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="683" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector> \ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_error.xml b/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_error.xml
deleted file mode 100644
index c05a8d55c16c..000000000000
--- a/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_error.xml
+++ /dev/null
@@ -1 +0,0 @@
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="80dp" android:width="80dp" android:viewportHeight="80" android:viewportWidth="80"><group android:name="_R_G"><group android:name="_R_G_L_3_G" android:translateX="-0.25" android:translateY="-0.25"><path android:name="_R_G_L_3_G_D_0_P_0" android:fillColor="#474747" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M40.21 0.25 C18.13,0.25 0.25,18.17 0.25,40.25 C0.25,62.33 18.13,80.25 40.21,80.25 C62.33,80.25 80.25,62.33 80.25,40.25 C80.25,18.17 62.33,0.25 40.21,0.25c "/></group><group android:name="_R_G_L_2_G" android:translateX="-0.25" android:translateY="-0.25" android:pivotX="40.25" android:pivotY="40.25" android:scaleX="0.975" android:scaleY="0"><path android:name="_R_G_L_2_G_D_0_P_0" android:strokeColor="#f2b8b5" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="0" android:strokeAlpha="0" android:pathData=" M40.21 0.25 C18.13,0.25 0.25,18.17 0.25,40.25 C0.25,62.33 18.13,80.25 40.21,80.25 C62.33,80.25 80.25,62.33 80.25,40.25 C80.25,18.17 62.33,0.25 40.21,0.25c "/></group><group android:name="_R_G_L_1_G" android:translateX="9.950000000000003" android:translateY="10" android:pivotX="30" android:pivotY="30" android:scaleX="1.2" android:scaleY="1.2"><group android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0" android:translateX="30" android:translateY="38.75" android:scaleX="0" android:scaleY="0"><path android:name="_R_G_L_1_G_D_0_P_0" android:fillColor="#f2b8b5" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-1.2 -1.25 C-1.2,-1.25 1.2,-1.25 1.2,-1.25 C1.2,-1.25 1.2,1.25 1.2,1.25 C1.2,1.25 -1.2,1.25 -1.2,1.25 C-1.2,1.25 -1.2,-1.25 -1.2,-1.25c "/></group><group android:name="_R_G_L_1_G_D_1_P_0_G_0_T_0" android:translateX="30" android:translateY="25" android:pivotX="0.002" android:pivotY="7.488" android:scaleX="1" android:scaleY="0"><path android:name="_R_G_L_1_G_D_1_P_0" android:fillColor="#f2b8b5" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-1.2 -7.5 C-1.2,-7.5 1.2,-7.5 1.2,-7.5 C1.2,-7.5 1.2,7.5 1.2,7.5 C1.2,7.5 -1.2,7.5 -1.2,7.5 C-1.2,7.5 -1.2,-7.5 -1.2,-7.5c "/></group></group><group android:name="_R_G_L_0_G" android:translateX="20.659" android:translateY="15.75"><path android:name="_R_G_L_0_G_D_0_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" android:trimPathEnd="1" android:trimPathOffset="0" android:pathData=" M27.52 38.98 C26.49,39.95 25.29,40.73 23.98,41.29 C23.17,41.65 22.31,41.91 21.41,42.07 C20.74,42.19 20.05,42.25 19.34,42.25 C18.44,42.25 17.56,42.15 16.72,41.96 C15.93,41.77 15.16,41.51 14.43,41.18 C13.23,40.63 12.13,39.88 11.16,38.98 "/><path android:name="_R_G_L_0_G_D_1_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" android:trimPathEnd="1" android:trimPathOffset="0" android:pathData=" M8.64 34.07 C7.89,31.97 7.89,29.85 7.89,29.85 C7.89,24.05 12.81,19.34 19.34,19.34 C25.87,19.34 30.8,24.05 30.8,29.85 C30.8,29.85 30.8,30.16 30.8,30.16 C30.8,32.32 29.04,34.07 26.89,34.07 C25.28,34.07 23.86,33.1 23.27,31.61 C23.27,31.61 21.96,28.34 21.96,28.34 C21.37,26.85 19.93,25.89 18.34,25.89 C16.18,25.89 14.43,27.64 14.43,29.8 C14.43,31.42 14.87,32.99 15.68,34.36 C16.22,35.26 16.93,36.08 17.77,36.75 C17.77,36.75 18.52,37.34 18.52,37.34 "/><path android:name="_R_G_L_0_G_D_2_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" android:trimPathEnd="1" android:trimPathOffset="0" android:pathData=" M6.25 19.34 C7.48,17.3 9.46,15.58 11.9,14.42 C12.93,13.94 14.03,13.55 15.2,13.27 C16.51,12.96 17.9,12.8 19.34,12.8 C20.77,12.8 22.14,12.96 23.45,13.26 C24.9,13.6 26.26,14.12 27.48,14.78 C29.6,15.92 31.32,17.5 32.43,19.34 "/><path android:name="_R_G_L_0_G_D_3_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0" android:trimPathEnd="1" android:trimPathOffset="0" android:pathData=" M9.52 8.7 C10.98,7.91 12.58,7.28 14.28,6.86 C15.89,6.46 17.58,6.25 19.34,6.25 C21.06,6.25 22.72,6.45 24.3,6.83 C26.04,7.25 27.67,7.89 29.16,8.7 "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_3_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="fillAlpha" android:duration="83" android:startOffset="0" android:valueFrom="1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="strokeWidth" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="strokeWidth" android:duration="233" android:startOffset="67" android:valueFrom="0" android:valueTo="2.5" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="strokeAlpha" android:duration="67" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="strokeAlpha" android:duration="83" android:startOffset="67" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_2_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleY" android:duration="0" android:startOffset="67" android:valueFrom="0" android:valueTo="0.975" android:valueType="floatType"/></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="167" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.06 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="167" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.06 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="100" android:startOffset="167" android:valueFrom="0" android:valueTo="1.1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.06 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="100" android:startOffset="167" android:valueFrom="0" android:valueTo="1.1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.06 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="267" android:valueFrom="1.1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.147,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="267" android:valueFrom="1.1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.147,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_1_P_0_G_0_T_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="167" android:startOffset="0" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="167" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.096 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="100" android:startOffset="167" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="100" android:startOffset="167" android:valueFrom="0" android:valueTo="1.1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.096 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="67" android:startOffset="267" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.341,0 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="67" android:startOffset="267" android:valueFrom="1.1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="trimPathStart" android:duration="167" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_1_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="trimPathStart" android:duration="167" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_2_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="trimPathStart" android:duration="167" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_3_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="trimPathStart" android:duration="167" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="350" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector> \ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_unlock.xml b/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_unlock.xml
deleted file mode 100644
index 16944294a94e..000000000000
--- a/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_unlock.xml
+++ /dev/null
@@ -1 +0,0 @@
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="80dp" android:width="80dp" android:viewportHeight="80" android:viewportWidth="80"><group android:name="_R_G"><group android:name="_R_G_L_2_G" android:translateX="-0.25" android:translateY="-0.25"><path android:name="_R_G_L_2_G_D_0_P_0" android:fillColor="#474747" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M40.21 0.25 C18.13,0.25 0.25,18.17 0.25,40.25 C0.25,62.33 18.13,80.25 40.21,80.25 C62.33,80.25 80.25,62.33 80.25,40.25 C80.25,18.17 62.33,0.25 40.21,0.25c "/></group><group android:name="_R_G_L_1_G" android:translateX="20.75" android:translateY="15.75"><path android:name="_R_G_L_1_G_D_0_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M27.52 38.98 C26.49,39.95 25.29,40.73 23.98,41.29 C23.17,41.65 22.31,41.91 21.41,42.07 C20.74,42.19 20.05,42.25 19.34,42.25 C18.44,42.25 17.56,42.15 16.72,41.96 C15.93,41.77 15.16,41.51 14.43,41.18 C13.23,40.63 12.13,39.88 11.16,38.98 "/><path android:name="_R_G_L_1_G_D_1_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M8.64 34.07 C7.89,31.97 7.89,29.85 7.89,29.85 C7.89,24.05 12.81,19.34 19.34,19.34 C25.87,19.34 30.8,24.05 30.8,29.85 C30.8,29.85 30.8,30.16 30.8,30.16 C30.8,32.32 29.04,34.07 26.89,34.07 C25.28,34.07 23.86,33.1 23.27,31.61 C23.27,31.61 21.96,28.34 21.96,28.34 C21.37,26.85 19.93,25.89 18.34,25.89 C16.18,25.89 14.43,27.64 14.43,29.8 C14.43,31.42 14.87,32.99 15.68,34.36 C16.22,35.26 16.93,36.08 17.77,36.75 C17.77,36.75 18.52,37.34 18.52,37.34 "/><path android:name="_R_G_L_1_G_D_2_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M6.25 19.34 C7.48,17.3 9.46,15.58 11.9,14.42 C12.93,13.94 14.03,13.55 15.2,13.27 C16.51,12.96 17.9,12.8 19.34,12.8 C20.77,12.8 22.14,12.96 23.45,13.26 C24.9,13.6 26.26,14.12 27.48,14.78 C29.6,15.92 31.32,17.5 32.43,19.34 "/><path android:name="_R_G_L_1_G_D_3_P_0" android:strokeColor="#d3e3fd" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M9.52 8.7 C10.98,7.91 12.58,7.28 14.28,6.86 C15.89,6.46 17.58,6.25 19.34,6.25 C21.06,6.25 22.72,6.45 24.3,6.83 C26.04,7.25 27.67,7.89 29.16,8.7 "/></group><group android:name="_R_G_L_0_G" android:translateX="37.357" android:translateY="43.25" android:pivotX="2.75" android:pivotY="2.75" android:scaleX="1.41866" android:scaleY="1.41866"><path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#d3e3fd" android:fillAlpha="0" android:fillType="nonZero" android:pathData=" M2.75 5.25 C4.13,5.25 5.25,4.13 5.25,2.75 C5.25,1.37 4.13,0.25 2.75,0.25 C1.37,0.25 0.25,1.37 0.25,2.75 C0.25,4.13 1.37,5.25 2.75,5.25c "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="_R_G_L_1_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="107" android:startOffset="0" android:valueFrom="M27.52 38.98 C26.49,39.95 25.29,40.73 23.98,41.29 C23.17,41.65 22.31,41.91 21.41,42.07 C20.74,42.19 20.05,42.25 19.34,42.25 C18.44,42.25 17.56,42.15 16.72,41.96 C15.93,41.77 15.16,41.51 14.43,41.18 C13.23,40.63 12.13,39.88 11.16,38.98 " android:valueTo="M30.81 32.26 C30.56,32.49 30.27,38.76 29.96,38.9 C29.77,39.46 29.13,39.94 28.57,40.26 C28.15,40.51 26.93,40.65 26.4,40.65 C26.18,40.65 11.91,40.62 11.71,40.58 C10.68,40.53 9.06,39.79 8.89,38.88 C8.6,38.74 8.34,32.48 8.1,32.27 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.833,0.767 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="143" android:startOffset="107" android:valueFrom="M30.81 32.26 C30.56,32.49 30.27,38.76 29.96,38.9 C29.77,39.46 29.13,39.94 28.57,40.26 C28.15,40.51 26.93,40.65 26.4,40.65 C26.18,40.65 11.91,40.62 11.71,40.58 C10.68,40.53 9.06,39.79 8.89,38.88 C8.6,38.74 8.34,32.48 8.1,32.27 " android:valueTo="M30.64 30.14 C30.64,30.14 30.64,38.14 30.64,38.14 C30.64,38.77 30.36,39.32 29.91,39.69 C29.57,39.97 29.12,40.14 28.64,40.14 C28.64,40.14 10.14,40.14 10.14,40.14 C9.04,40.14 8.14,39.25 8.14,38.14 C8.14,38.14 8.14,30.14 8.14,30.14 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0.331,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_1_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="strokeAlpha" android:duration="140" android:startOffset="0" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="strokeAlpha" android:duration="50" android:startOffset="140" android:valueFrom="1" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_1_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="107" android:startOffset="0" android:valueFrom="M8.64 34.07 C7.89,31.97 7.89,29.85 7.89,29.85 C7.89,24.05 12.81,19.34 19.34,19.34 C25.87,19.34 30.8,24.05 30.8,29.85 C30.8,29.85 30.8,30.16 30.8,30.16 C30.8,32.32 29.04,34.07 26.89,34.07 C25.28,34.07 23.86,33.1 23.27,31.61 C23.27,31.61 21.96,28.34 21.96,28.34 C21.37,26.85 19.93,25.89 18.34,25.89 C16.18,25.89 14.43,27.64 14.43,29.8 C14.43,31.42 14.87,32.99 15.68,34.36 C16.22,35.26 16.93,36.08 17.77,36.75 C17.77,36.75 18.52,37.34 18.52,37.34 " android:valueTo="M18.93 32.18 C17.11,32.68 16.62,30.26 16.62,30.26 C16.62,28.78 17.81,27.59 19.39,27.59 C20.96,27.59 22.15,28.78 22.15,30.26 C22.15,30.26 22.15,30.34 22.15,30.34 C22.15,30.89 21.11,32.54 19.57,32.19 C19.19,32.1 20.48,31.09 20.34,30.71 C20.34,30.71 20.02,29.88 20.02,29.88 C19.88,29.5 19.53,29.25 19.15,29.25 C18.63,29.25 18,29.67 18,30.22 C18,30.57 18.06,31.08 18.32,31.51 C18.49,31.8 19.02,32.25 19.79,32.04 C20.41,31.7 20.38,31.36 20.38,31.36 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.833,0.767 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="107" android:startOffset="107" android:valueFrom="M18.93 32.18 C17.11,32.68 16.62,30.26 16.62,30.26 C16.62,28.78 17.81,27.59 19.39,27.59 C20.96,27.59 22.15,28.78 22.15,30.26 C22.15,30.26 22.15,30.34 22.15,30.34 C22.15,30.89 21.11,32.54 19.57,32.19 C19.19,32.1 20.48,31.09 20.34,30.71 C20.34,30.71 20.02,29.88 20.02,29.88 C19.88,29.5 19.53,29.25 19.15,29.25 C18.63,29.25 18,29.67 18,30.22 C18,30.57 18.06,31.08 18.32,31.51 C18.49,31.8 19.02,32.25 19.79,32.04 C20.41,31.7 20.38,31.36 20.38,31.36 " android:valueTo="M19.42 31.53 C18.15,31.52 18.11,30.33 18.11,30.33 C18.11,29.59 18.66,28.98 19.4,28.98 C20.13,28.98 20.69,29.59 20.69,30.33 C20.69,30.33 20.69,30.37 20.69,30.37 C20.69,30.64 20.49,30.87 20.25,30.87 C20.07,30.87 19.91,30.74 19.84,30.55 C19.84,30.55 19.69,30.14 19.69,30.14 C19.63,29.94 19.46,29.82 19.28,29.82 C19.04,29.82 18.61,30.02 18.61,30.29 C18.61,30.43 18.6,30.75 18.76,31.03 C18.87,31.21 19.21,31.77 19.96,31.41 C20.69,31.01 20.69,30.34 20.69,30.34 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_2_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="250" android:startOffset="0" android:valueFrom="M6.25 19.34 C7.48,17.3 9.46,15.58 11.9,14.42 C12.93,13.94 14.03,13.55 15.2,13.27 C16.51,12.96 17.9,12.8 19.34,12.8 C20.77,12.8 22.14,12.96 23.45,13.26 C24.9,13.6 26.26,14.12 27.48,14.78 C29.6,15.92 31.32,17.5 32.43,19.34 " android:valueTo="M8.14 30.22 C8.14,30.22 8.14,22.22 8.14,22.22 C8.14,21.71 8.33,21.25 8.64,20.9 C9,20.48 9.54,20.22 10.14,20.22 C10.14,20.22 28.64,20.22 28.64,20.22 C29.75,20.22 30.64,21.11 30.64,22.22 C30.64,22.22 30.64,30.14 30.64,30.14 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.189,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_1_G_D_3_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="pathData" android:duration="95" android:startOffset="0" android:valueFrom="M9.52 8.7 C10.98,7.91 12.58,7.28 14.28,6.86 C15.89,6.46 17.58,6.25 19.34,6.25 C21.06,6.25 22.72,6.45 24.3,6.83 C26.04,7.25 27.67,7.89 29.16,8.7 " android:valueTo="M11.47 14.84 C11.47,14.84 12.21,11.43 13.54,9.84 C14.84,8.28 16.68,7.22 19.35,7.22 C22.01,7.22 23.98,8.4 25.19,10.18 C26.39,11.96 27.25,14.84 27.25,14.84 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.833,0.767 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="24" android:startOffset="95" android:valueFrom="M11.47 14.84 C11.47,14.84 12.21,11.43 13.54,9.84 C14.84,8.28 16.68,7.22 19.35,7.22 C22.01,7.22 23.98,8.4 25.19,10.18 C26.39,11.96 27.25,14.84 27.25,14.84 " android:valueTo="M12.11 16.85 C12.11,16.85 12.82,12.71 13.37,11.5 C14.17,9.24 16.38,7.53 19.35,7.53 C22.32,7.53 24.61,9.32 25.35,11.72 C25.61,12.64 26.62,16.85 26.62,16.85 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0.833,0.767 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="81" android:startOffset="119" android:valueFrom="M12.11 16.85 C12.11,16.85 12.82,12.71 13.37,11.5 C14.17,9.24 16.38,7.53 19.35,7.53 C22.32,7.53 24.61,9.32 25.35,11.72 C25.61,12.64 26.62,16.85 26.62,16.85 " android:valueTo="M13.12 20.04 C13.12,20.04 13.11,14.15 13.11,14.15 C13.11,10.77 15.91,8.04 19.36,8.04 C22.81,8.04 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0.261,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="pathData" android:duration="233" android:startOffset="200" android:valueFrom="M13.12 20.04 C13.12,20.04 13.11,14.15 13.11,14.15 C13.11,10.77 15.91,8.04 19.36,8.04 C22.81,8.04 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " android:valueTo="M37.91 20.05 C37.91,20.05 37.89,14.16 37.89,14.16 C37.89,10.79 35.15,8.05 31.86,8.03 C28.46,8.01 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " android:valueType="pathType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.123,0 0.23,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G_D_0_P_0"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="fillAlpha" android:duration="120" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="fillAlpha" android:duration="20" android:startOffset="120" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="_R_G_L_0_G"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="scaleX" android:duration="120" android:startOffset="0" android:valueFrom="1.4186600000000003" android:valueTo="1.4186600000000003" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="120" android:startOffset="0" android:valueFrom="1.4186600000000003" android:valueTo="1.4186600000000003" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleX" android:duration="130" android:startOffset="120" android:valueFrom="1.4186600000000003" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/></aapt:attr></objectAnimator><objectAnimator android:propertyName="scaleY" android:duration="130" android:startOffset="120" android:valueFrom="1.4186600000000003" android:valueTo="1" android:valueType="floatType"><aapt:attr name="android:interpolator"><pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/></aapt:attr></objectAnimator></set></aapt:attr></target><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="517" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector> \ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_magnification_menu_close.xml b/packages/SystemUI/res/drawable/ic_magnification_menu_close.xml
new file mode 100644
index 000000000000..a44a484d6e39
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_magnification_menu_close.xml
@@ -0,0 +1,29 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="32dp"
+ android:height="32dp"
+ android:viewportWidth="32"
+ android:viewportHeight="32">
+ <path
+ android:pathData="M7.3334,24.6667L24.6674,7.3334M7.3334,7.3334L24.6674,24.6667"
+ android:strokeLineJoin="round"
+ android:strokeWidth="1.5"
+ android:fillColor="#00000000"
+ android:strokeColor="#000000"
+ android:fillType="evenOdd"
+ android:strokeLineCap="round"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_magnification_menu_large.xml b/packages/SystemUI/res/drawable/ic_magnification_menu_large.xml
new file mode 100644
index 000000000000..1ab0d4d3bf12
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_magnification_menu_large.xml
@@ -0,0 +1,25 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:width="24dp"
+ android:height="24dp">
+ <path
+ android:pathData="M3 21L21 21C22.1 21 23 20.1 23 19L23 5C23 3.9 22.1 3 21 3L3 3C1.9 3 1 3.9 1 5L1 19C1 20.1 1.9 21 3 21ZM3 5L21 5L21 19L3 19L3 5ZM4 15L16.75 15L16.75 6L4 6L4 15Z"
+ android:fillType="evenOdd"
+ android:fillColor="#000000" />
+</vector> \ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_magnification_menu_medium.xml b/packages/SystemUI/res/drawable/ic_magnification_menu_medium.xml
new file mode 100644
index 000000000000..6fc89a69f36e
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_magnification_menu_medium.xml
@@ -0,0 +1,25 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:width="24dp"
+ android:height="24dp">
+ <path
+ android:pathData="M3 21L21 21C22.1 21 23 20.1 23 19L23 5C23 3.9 22.1 3 21 3L3 3C1.9 3 1 3.9 1 5L1 19C1 20.1 1.9 21 3 21ZM3 5L21 5L21 19L3 19L3 5ZM4 12.75L13.75 12.75L13.75 6L4 6L4 12.75Z"
+ android:fillType="evenOdd"
+ android:fillColor="#000000" />
+</vector> \ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_magnification_menu_small.xml b/packages/SystemUI/res/drawable/ic_magnification_menu_small.xml
new file mode 100644
index 000000000000..fd7357424f32
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_magnification_menu_small.xml
@@ -0,0 +1,25 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:width="24dp"
+ android:height="24dp">
+ <path
+ android:pathData="M3 21L21 21C22.1 21 23 20.1 23 19L23 5C23 3.9 22.1 3 21 3L3 3C1.9 3 1 3.9 1 5L1 19C1 20.1 1.9 21 3 21ZM3 5L21 5L21 19L3 19L3 5ZM4 10.5L8.5 10.5L8.5 6L4 6L4 10.5Z"
+ android:fillType="evenOdd"
+ android:fillColor="#000000" />
+</vector> \ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_move_magnification.xml b/packages/SystemUI/res/drawable/ic_move_magnification.xml
index 96db3651ac25..1bff559ff3df 100644
--- a/packages/SystemUI/res/drawable/ic_move_magnification.xml
+++ b/packages/SystemUI/res/drawable/ic_move_magnification.xml
@@ -23,18 +23,21 @@
<size
android:height="@dimen/magnification_drag_view_size"
android:width="@dimen/magnification_drag_view_size"/>
+ <corners android:topLeftRadius="12dp"/>
+
</shape>
</item>
<item
android:gravity="center">
+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="30dp"
- android:height="30dp"
android:viewportWidth="24"
- android:viewportHeight="24">
+ android:viewportHeight="24"
+ android:width="24dp"
+ android:height="24dp">
<path
- android:pathData="M18.19,12.44l-3.24,-1.62c1.29,-1 2.12,-2.56 2.12,-4.32c0,-3.03 -2.47,-5.5 -5.5,-5.5s-5.5,2.47 -5.5,5.5c0,2.13 1.22,3.98 3,4.89v3.26c-2.11,-0.45 -2.01,-0.44 -2.26,-0.44c-0.53,0 -1.03,0.21 -1.41,0.59L4,16.22l5.09,5.09C9.52,21.75 10.12,22 10.74,22h6.3c0.98,0 1.81,-0.7 1.97,-1.67l0.8,-4.71C20.03,14.32 19.38,13.04 18.19,12.44zM17.84,15.29L17.04,20h-6.3c-0.09,0 -0.17,-0.04 -0.24,-0.1l-3.68,-3.68l4.25,0.89V6.5c0,-0.28 0.22,-0.5 0.5,-0.5c0.28,0 0.5,0.22 0.5,0.5v6h1.76l3.46,1.73C17.69,14.43 17.91,14.86 17.84,15.29zM8.07,6.5c0,-1.93 1.57,-3.5 3.5,-3.5s3.5,1.57 3.5,3.5c0,0.95 -0.38,1.81 -1,2.44V6.5c0,-1.38 -1.12,-2.5 -2.5,-2.5c-1.38,0 -2.5,1.12 -2.5,2.5v2.44C8.45,8.31 8.07,7.45 8.07,6.5z"
- android:fillColor="#FFFFFF"/>
+ android:pathData="M13.2217 21.7734C12.8857 22.1094 12.288 22.712 12 23C12 23 11.1143 22.1094 10.7783 21.7734L8.33494 19.3301L9.55662 18.1084L12 20.5518L14.4434 18.1084L15.665 19.3301L13.2217 21.7734ZM19.3301 15.665L18.1084 14.4433L20.5518 12L18.1084 9.5566L19.3301 8.33492L21.7735 10.7783C22.1094 11.1142 22.4241 11.4241 23 12C22.4241 12.5759 22.3963 12.5988 21.7735 13.2217L19.3301 15.665ZM14.4434 14.4433C13.7714 15.1153 12.957 15.4512 12 15.4512C11.043 15.4512 10.2285 15.1153 9.55662 14.4433C8.88469 13.7714 8.54873 12.957 8.54873 12C8.54873 11.043 8.88469 10.2285 9.55662 9.5566C10.2285 8.88468 11.043 8.54871 12 8.54871C12.957 8.54871 13.7714 8.88467 14.4434 9.5566C15.1153 10.2285 15.4512 11.043 15.4512 12C15.4512 12.957 15.1153 13.7714 14.4434 14.4433ZM4.66988 15.665L2.22651 13.2217C1.89055 12.8857 1.28791 12.288 1 12C1.28791 11.712 1.89055 11.1143 2.22651 10.7783L4.66988 8.33492L5.89157 9.5566L3.4482 12L5.89157 14.4433L4.66988 15.665ZM14.4434 5.89155L12 3.44818L9.55662 5.89155L8.33494 4.66987L10.7783 2.2265C11.1389 1.86592 11.2963 1.70369 12 1C12.5758 1.57585 12.8857 1.89053 13.2217 2.2265L15.665 4.66986L14.4434 5.89155Z"
+ android:fillColor="#ffffff" />
</vector>
</item>
</layer-list>
diff --git a/packages/SystemUI/res/drawable/ic_open_in_new_fullscreen.xml b/packages/SystemUI/res/drawable/ic_open_in_new_fullscreen.xml
index 36a1224a78c9..c7434f5e46fd 100644
--- a/packages/SystemUI/res/drawable/ic_open_in_new_fullscreen.xml
+++ b/packages/SystemUI/res/drawable/ic_open_in_new_fullscreen.xml
@@ -13,35 +13,19 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
-
- <item>
- <shape android:shape="rectangle">
- <solid android:color="@color/magnification_switch_button_color" />
- <size
- android:width="48dp"
- android:height="48dp" />
- </shape>
- </item>
-
- <item
- android:gravity="center">
- <vector
- android:width="36dp"
- android:height="36dp"
- android:viewportWidth="24"
- android:viewportHeight="24">
- <group>
- <clip-path
- android:pathData="M0,0h24v24h-24z"/>
- <path
- android:pathData="M11,6.05V8.05H14.59L8,14.64V11.05H6V18.05H13V16.05H9.41L16,9.46V13.05H18V6.05H11Z"
- android:fillColor="#ffffff"/>
- <path
- android:pathData="M20,4.05V20.05H4V4.05H20ZM22,2.05H2V22.05H22V2.05Z"
- android:fillColor="#ffffff"/>
- </group>
- </vector>
- </item>
-
-</layer-list>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="36dp"
+ android:height="36dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <group>
+ <clip-path
+ android:pathData="M0,0h24v24h-24z"/>
+ <path
+ android:pathData="M11,6.05V8.05H14.59L8,14.64V11.05H6V18.05H13V16.05H9.41L16,9.46V13.05H18V6.05H11Z"
+ android:fillColor="#000000"/>
+ <path
+ android:pathData="M20,4.05V20.05H4V4.05H20ZM22,2.05H2V22.05H22V2.05Z"
+ android:fillColor="#000000"/>
+ </group>
+</vector>
diff --git a/packages/SystemUI/res/layout/auth_biometric_contents.xml b/packages/SystemUI/res/layout/auth_biometric_contents.xml
index e1b294f2d757..d633803c920b 100644
--- a/packages/SystemUI/res/layout/auth_biometric_contents.xml
+++ b/packages/SystemUI/res/layout/auth_biometric_contents.xml
@@ -49,11 +49,11 @@
<FrameLayout
android:id="@+id/biometric_icon_frame"
- android:layout_width="match_parent"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal">
- <ImageView
+ <com.airbnb.lottie.LottieAnimationView
android:id="@+id/biometric_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
diff --git a/packages/SystemUI/res/layout/auth_biometric_fingerprint_view.xml b/packages/SystemUI/res/layout/auth_biometric_fingerprint_view.xml
index ce53e278302a..01ea31f8bdd2 100644
--- a/packages/SystemUI/res/layout/auth_biometric_fingerprint_view.xml
+++ b/packages/SystemUI/res/layout/auth_biometric_fingerprint_view.xml
@@ -17,7 +17,7 @@
<com.android.systemui.biometrics.AuthBiometricFingerprintView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/contents"
- android:layout_width="match_parent"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
diff --git a/packages/SystemUI/res/layout/window_magnification_settings_view.xml b/packages/SystemUI/res/layout/window_magnification_settings_view.xml
new file mode 100644
index 000000000000..6d8847cce644
--- /dev/null
+++ b/packages/SystemUI/res/layout/window_magnification_settings_view.xml
@@ -0,0 +1,152 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/magnifier_panel_view"
+ android:layout_width="@dimen/magnification_max_size"
+ android:layout_height="match_parent"
+ android:background="@drawable/accessibility_magnification_setting_view_bg"
+ android:orientation="vertical">
+ <LinearLayout
+ android:layout_width="@dimen/magnification_max_size"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+ <TextView
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="@string/accessibility_magnifier_size"
+ android:textAppearance="?android:attr/textAppearanceListItem"
+ android:textColor="?android:attr/textColorAlertDialogListItem"
+ android:focusable="true"
+ android:layout_gravity="center_vertical|left"
+ android:layout_marginStart="20dp"/>
+
+ <Button
+ android:id="@+id/magnifier_edit_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/accessibility_magnifier_edit"
+ android:textAppearance="?android:attr/textAppearanceListItem"
+ android:textColor="?android:attr/textColorAlertDialogListItem"
+ android:focusable="true"
+ android:layout_gravity="right"
+ android:layout_marginEnd="20dp"/>
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="@dimen/magnification_max_size"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+ <ImageButton
+ android:id="@+id/magnifier_small_button"
+ android:layout_width="0dp"
+ android:layout_height="56dp"
+ android:scaleType="center"
+ android:layout_weight="1"
+ android:layout_marginStart="12dp"/>
+
+ <ImageButton
+ android:id="@+id/magnifier_medium_button"
+ android:layout_width="0dp"
+ android:layout_height="56dp"
+ android:scaleType="center"
+ android:layout_weight="1"/>
+
+ <ImageButton
+ android:id="@+id/magnifier_large_button"
+ android:layout_width="0dp"
+ android:layout_height="56dp"
+ android:scaleType="center"
+ android:layout_weight="1"/>
+
+ <ImageButton
+ android:id="@+id/magnifier_full_button"
+ android:layout_width="0dp"
+ android:layout_height="56dp"
+ android:scaleType="center"
+ android:layout_weight="1"
+ android:layout_marginEnd="12dp"/>
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:paddingTop="8dp"
+ android:paddingEnd="20dp"
+ android:paddingStart="20dp"
+ android:focusable="true">
+
+ <LinearLayout
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:background="?android:attr/selectableItemBackground"
+ android:ellipsize="marquee"
+ android:gravity="center_vertical"
+ android:minHeight="?android:attr/listPreferredItemHeightSmall"
+ android:orientation="vertical">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:text="@string/accessibility_allow_diagonal_scrolling"
+ android:textAppearance="?android:attr/textAppearanceListItem"
+ android:textColor="?android:attr/textColorAlertDialogListItem" />
+ </LinearLayout>
+
+ <Switch
+ android:id="@+id/magnifier_horizontal_lock_switch"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="right|center"
+ android:theme="@android:style/Theme.DeviceDefault.DayNight"/>
+ </LinearLayout>
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/accessibility_magnification_zoom"
+ android:textAppearance="?android:attr/textAppearanceListItem"
+ android:textColor="?android:attr/textColorAlertDialogListItem"
+ android:focusable="true"
+ android:layout_marginStart="20dp"
+ android:paddingTop="2dp"
+ android:paddingBottom="10dp"/>
+
+ <SeekBar
+ android:id="@+id/magnifier_zoom_seekbar"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:progress="0"
+ android:max="6"
+ android:layout_marginEnd="20dp"
+ android:theme="@android:style/Theme.DeviceDefault.DayNight"/>
+
+
+ <Button
+ android:id="@+id/magnifier_close_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/accessibility_magnification_close"
+ android:textAppearance="?android:attr/textAppearanceListItem"
+ android:textColor="?android:attr/textColorAlertDialogListItem"
+ android:focusable="true"
+ android:layout_gravity="center_horizontal"
+ android:paddingBottom="24dp"/>
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/window_magnifier_view.xml b/packages/SystemUI/res/layout/window_magnifier_view.xml
index 7c755e546589..0bff47ccf722 100644
--- a/packages/SystemUI/res/layout/window_magnifier_view.xml
+++ b/packages/SystemUI/res/layout/window_magnifier_view.xml
@@ -16,33 +16,25 @@
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:screenReaderFocusable="true">
- <View
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_margin="@dimen/magnification_outer_border_margin"
- android:importantForAccessibility="no"
- android:background="@android:color/black"/>
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
<View
+ android:id="@+id/magnification_inner_border"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:layout_margin="@dimen/magnification_inner_border_margin"
- android:importantForAccessibility="no"
+ android:layout_margin="@dimen/magnification_outer_border_margin"
android:background="@color/magnification_border_color"/>
<RelativeLayout
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:importantForAccessibility="noHideDescendants">
+ android:layout_height="match_parent">
<View
android:id="@+id/left_handle"
android:layout_width="@dimen/magnification_border_drag_size"
android:layout_height="match_parent"
- android:layout_above="@+id/bottom_handle"/>
+ android:layout_alignParentStart="true"/>
<View
android:id="@+id/top_handle"
@@ -54,7 +46,6 @@
android:id="@+id/right_handle"
android:layout_width="@dimen/magnification_border_drag_size"
android:layout_height="match_parent"
- android:layout_above="@+id/bottom_handle"
android:layout_alignParentEnd="true"/>
<View
@@ -68,7 +59,6 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="@dimen/magnification_mirror_surface_margin"/>
-
</RelativeLayout>
<ImageView
@@ -79,7 +69,17 @@
android:layout_gravity="right|bottom"
android:padding="@dimen/magnifier_drag_handle_padding"
android:scaleType="center"
- android:importantForAccessibility="no"
android:src="@drawable/ic_move_magnification"/>
-</FrameLayout> \ No newline at end of file
+ <ImageView
+ android:id="@+id/close_button"
+ android:layout_width="40dp"
+ android:layout_height="40dp"
+ android:layout_margin="30dp"
+ android:padding="@dimen/magnification_switch_button_padding"
+ android:layout_gravity="right|bottom"
+ android:scaleType="center"
+ android:visibility="gone"
+ android:background="@drawable/accessibility_magnifier_btn_bg"
+ android:src="@drawable/ic_magnification_menu_close" />
+</FrameLayout>
diff --git a/packages/SystemUI/res/raw/fingerprint_dialogue_error_to_fingerprint_lottie.json b/packages/SystemUI/res/raw/fingerprint_dialogue_error_to_fingerprint_lottie.json
new file mode 100644
index 000000000000..cc68a83c0653
--- /dev/null
+++ b/packages/SystemUI/res/raw/fingerprint_dialogue_error_to_fingerprint_lottie.json
@@ -0,0 +1 @@
+{"v":"5.9.0","fr":60,"ip":0,"op":25,"w":80,"h":80,"nm":"error_to_fingerprint","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".colorAccentPrimary","cl":"colorAccentPrimary","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[19.341,24.25,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-1.701,0.42],[-1.757,0],[-1.577,-0.381],[-1.485,-0.816]],"o":[[1.455,-0.799],[1.608,-0.397],[1.719,0],[1.739,0.42],[0,0]],"v":[[-9.818,1.227],[-5.064,-0.618],[0,-1.227],[4.96,-0.643],[9.818,1.227]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450990677,0.890196084976,0.992156863213,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,7.477],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Top","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-2.446,1.161],[-1.168,0.275],[-1.439,0],[-1.301,-0.304],[-1.225,-0.66],[-1.11,-1.844]],"o":[[1.23,-2.044],[1.024,-0.486],[1.312,-0.31],[1.425,0],[1.454,0.34],[2.122,1.143],[0,0]],"v":[[-13.091,3.273],[-7.438,-1.646],[-4.14,-2.797],[0,-3.273],[4.104,-2.805],[8.141,-1.29],[13.091,3.273]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,16.069],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Mid Top","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[-6.53,0],[0,-5.793],[0,0],[2.159,0],[0.59,1.489],[0,0],[1.587,0],[0,-2.16],[-0.81,-1.363],[-0.844,-0.674],[0,0]],"o":[[-0.753,-2.095],[0,-5.793],[6.529,0],[0,0],[0,2.16],[-1.604,0],[0,0],[-0.589,-1.489],[-2.161,0],[0,1.62],[0.54,0.909],[0,0],[0,0]],"v":[[-10.702,5.728],[-11.454,1.506],[0.001,-9],[11.454,1.506],[11.454,1.817],[7.544,5.728],[3.926,3.273],[2.618,0],[-0.997,-2.454],[-4.91,1.457],[-3.657,6.014],[-1.57,8.412],[-0.818,9]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,28.341],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Inside to dot ","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[1.307,-0.561],[0.894,-0.16],[0.706,0],[0.844,0.193],[0.728,0.334],[0.967,0.901]],"o":[[-1.038,0.967],[-0.817,0.351],[-0.673,0.12],[-0.9,0],[-0.794,-0.182],[-1.203,-0.551],[0,0]],"v":[[8.182,-1.636],[4.642,0.681],[2.07,1.453],[-0.001,1.636],[-2.621,1.341],[-4.909,0.563],[-8.182,-1.636]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,40.614],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Bottom","np":2,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.299],"y":[1]},"o":{"x":[0.543],"y":[0]},"t":5,"s":[0]},{"t":20,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":5,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".red200","cl":"red200","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[39.95,40,0],"ix":2,"l":2},"a":{"a":0,"k":[30,30,0],"ix":1,"l":2},"s":{"a":0,"k":[120,120,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-1.2,-7.5],[1.2,-7.5],[1.2,7.5],[-1.2,7.5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.949019607843,0.721568627451,0.709803921569,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[30.002,32.488],"ix":2},"a":{"a":0,"k":[0.002,7.488],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.659,0.6],"y":[1,1]},"o":{"x":[0.8,0.8],"y":[0,0]},"t":0,"s":[100,100]},{"i":{"x":[0.6,0.92],"y":[1,1.096]},"o":{"x":[0.8,0.8],"y":[0,0]},"t":4,"s":[100,110]},{"t":10,"s":[100,0]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Top!","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-1.2,-1.25],[1.2,-1.25],[1.2,1.25],[-1.2,1.25]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.949019607843,0.721568627451,0.709803921569,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[30,38.75],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.6,0.6],"y":[1,1]},"o":{"x":[0.853,0.853],"y":[0,0]},"t":0,"s":[100,100]},{"i":{"x":[0.92,0.92],"y":[1.06,1.06]},"o":{"x":[0.8,0.8],"y":[0,0]},"t":4,"s":[110,110]},{"t":10,"s":[0,0]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Bottom!","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":25,"st":-30,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".red200","cl":"red200","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":5,"s":[100]},{"t":10,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[40.25,40.25,0],"ix":1,"l":2},"s":{"a":0,"k":[97.5,97.5,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[22.12,0],[0,-22.08],[-22.08,0],[0,22.08]],"o":[[-22.08,0],[0,22.08],[22.12,0],[0,-22.08]],"v":[[-0.04,-40],[-40,0],[-0.04,40],[40,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[40.25,40.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":5,"s":[100]},{"t":10,"s":[0]}],"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.949019607843,0.721568627451,0.709803921569,1],"ix":3},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":5,"s":[100]},{"t":10,"s":[0]}],"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[2.5]},{"t":10,"s":[0]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":0,"op":10,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".grey700","cl":"grey700","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[40.25,40.25,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[22.12,0],[0,-22.08],[-22.08,0],[0,22.08]],"o":[[-22.08,0],[0,22.08],[22.12,0],[0,-22.08]],"v":[[-0.04,-40],[-40,0],[-0.04,40],[40,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.278431385756,0.278431385756,0.278431385756,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":4,"s":[0]},{"t":14,"s":[100]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[40.25,40.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0}],"markers":[]} \ No newline at end of file
diff --git a/packages/SystemUI/res/raw/fingerprint_dialogue_error_to_unlock_lottie.json b/packages/SystemUI/res/raw/fingerprint_dialogue_error_to_unlock_lottie.json
new file mode 100644
index 000000000000..aaf7e587eb6b
--- /dev/null
+++ b/packages/SystemUI/res/raw/fingerprint_dialogue_error_to_unlock_lottie.json
@@ -0,0 +1 @@
+{"v":"5.9.0","fr":60,"ip":0,"op":41,"w":80,"h":80,"nm":"error_to_unlock","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".colorAccentPrimary","cl":"colorAccentPrimary","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40.091,40,0],"ix":2,"l":2},"a":{"a":0,"k":[19.341,24.25,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.1,0.1,0.1],"y":[1,1,1]},"o":{"x":[0.05,0.05,0.05],"y":[1.4,1.4,0]},"t":10,"s":[50,50,100]},{"t":30,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.2,"y":0},"t":14,"s":[{"i":[[0,0],[0,0],[-3.452,0],[0,-3.375],[0,0]],"o":[[0,0],[0,-3.375],[3.452,0],[0,0],[0,0]],"v":[[-6.217,12.558],[-6.234,6.669],[0.016,0.558],[6.266,6.669],[6.283,12.558]],"c":false}]},{"t":30,"s":[{"i":[[0,0],[0,0],[3.292,0.021],[0,-3.375],[0,0]],"o":[[0,0],[0,-3.375],[-3.393,-0.022],[0,0],[0,0]],"v":[[18.568,12.573],[18.552,6.684],[12.516,0.553],[6.266,6.669],[6.283,12.558]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450990677,0.890196084976,0.992156863213,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,7.477],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Top","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[-0.307,0.352],[-0.601,0],[0,0],[0,-1.104],[0,0]],"o":[[0,0],[0,-0.503],[0.367,-0.42],[0,0],[1.104,0],[0,0],[0,0]],"v":[[-11.2,14.15],[-11.198,6.146],[-10.705,4.831],[-9.198,4.146],[9.302,4.146],[11.302,6.146],[11.3,14.07]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,16.069],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Mid Top","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[-0.736,0],[0,-0.741],[0,0],[0.243,0],[0.066,0.191],[0,0],[0.179,0],[0,-0.276],[-0.162,-0.273],[-0.755,0.357],[0,0]],"o":[[-1.273,-0.008],[0,-0.741],[0.736,0],[0,0],[0,0.276],[-0.181,0],[0,0],[-0.066,-0.191],[-0.243,0],[-0.002,0.139],[0.109,0.182],[0.727,-0.402],[0,0]],"v":[[0.082,3.187],[-1.235,1.986],[0.055,0.642],[1.346,1.986],[1.346,2.026],[0.905,2.527],[0.498,2.212],[0.35,1.794],[-0.057,1.479],[-0.733,1.951],[-0.58,2.686],[0.619,3.071],[1.351,2]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,28.341],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Inside to dot ","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0.446,-0.367],[0.481,0],[0,0],[0,1.104],[0,0]],"o":[[0,0],[0,0.623],[-0.345,0.284],[0,0],[-1.104,0],[0,0],[0,0]],"v":[[11.302,-10.469],[11.302,-2.469],[10.57,-0.923],[9.302,-0.469],[-9.198,-0.469],[-11.198,-2.469],[-11.198,-10.469]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,40.614],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Bottom","np":2,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false}],"ip":10,"op":600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".red200","cl":"red200","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[39.95,40,0],"ix":2,"l":2},"a":{"a":0,"k":[30,30,0],"ix":1,"l":2},"s":{"a":0,"k":[120,120,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-1.2,-7.5],[1.2,-7.5],[1.2,7.5],[-1.2,7.5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.949019607843,0.721568627451,0.709803921569,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[30.002,32.488],"ix":2},"a":{"a":0,"k":[0.002,7.488],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.659,0.6],"y":[1,1]},"o":{"x":[0.8,0.8],"y":[0,0]},"t":0,"s":[100,100]},{"i":{"x":[0.6,0.92],"y":[1,1.096]},"o":{"x":[0.8,0.8],"y":[0,0]},"t":4,"s":[100,110]},{"t":10,"s":[100,0]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Top!","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-1.2,-1.25],[1.2,-1.25],[1.2,1.25],[-1.2,1.25]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.949019607843,0.721568627451,0.709803921569,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[30,38.75],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.6,0.6],"y":[1,1]},"o":{"x":[0.853,0.853],"y":[0,0]},"t":0,"s":[100,100]},{"i":{"x":[0.92,0.92],"y":[1.06,1.06]},"o":{"x":[0.8,0.8],"y":[0,0]},"t":4,"s":[110,110]},{"t":10,"s":[0,0]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Bottom!","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":86,"st":-30,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".red200","cl":"red200","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":5,"s":[100]},{"t":10,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[40.25,40.25,0],"ix":1,"l":2},"s":{"a":0,"k":[97.5,97.5,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[22.12,0],[0,-22.08],[-22.08,0],[0,22.08]],"o":[[-22.08,0],[0,22.08],[22.12,0],[0,-22.08]],"v":[[-0.04,-40],[-40,0],[-0.04,40],[40,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[40.25,40.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":5,"s":[100]},{"t":10,"s":[0]}],"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.949019607843,0.721568627451,0.709803921569,1],"ix":3},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":5,"s":[100]},{"t":10,"s":[0]}],"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[2.5]},{"t":10,"s":[0]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":0,"op":10,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".grey700","cl":"grey700","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[40.25,40.25,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[22.12,0],[0,-22.08],[-22.08,0],[0,22.08]],"o":[[-22.08,0],[0,22.08],[22.12,0],[0,-22.08]],"v":[[-0.04,-40],[-40,0],[-0.04,40],[40,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.278431385756,0.278431385756,0.278431385756,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":4,"s":[0]},{"t":14,"s":[100]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[40.25,40.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0}],"markers":[]} \ No newline at end of file
diff --git a/packages/SystemUI/res/raw/fingerprint_dialogue_fingerprint_to_error_lottie.json b/packages/SystemUI/res/raw/fingerprint_dialogue_fingerprint_to_error_lottie.json
new file mode 100644
index 000000000000..78bccba83ecb
--- /dev/null
+++ b/packages/SystemUI/res/raw/fingerprint_dialogue_fingerprint_to_error_lottie.json
@@ -0,0 +1 @@
+{"v":"5.9.0","fr":60,"ip":0,"op":21,"w":80,"h":80,"nm":"fingerprint_to_error","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".colorAccentPrimary","cl":"colorAccentPrimary","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[19.341,24.25,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-1.701,0.42],[-1.757,0],[-1.577,-0.381],[-1.485,-0.816]],"o":[[1.455,-0.799],[1.608,-0.397],[1.719,0],[1.739,0.42],[0,0]],"v":[[-9.818,1.227],[-5.064,-0.618],[0,-1.227],[4.96,-0.643],[9.818,1.227]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,7.477],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Top","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-2.446,1.161],[-1.168,0.275],[-1.439,0],[-1.301,-0.304],[-1.225,-0.66],[-1.11,-1.844]],"o":[[1.23,-2.044],[1.024,-0.486],[1.312,-0.31],[1.425,0],[1.454,0.34],[2.122,1.143],[0,0]],"v":[[-13.091,3.273],[-7.438,-1.646],[-4.14,-2.797],[0,-3.273],[4.104,-2.805],[8.141,-1.29],[13.091,3.273]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,16.069],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Mid Top","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[-6.53,0],[0,-5.793],[0,0],[2.159,0],[0.59,1.489],[0,0],[1.587,0],[0,-2.16],[-0.81,-1.363],[-0.844,-0.674],[0,0]],"o":[[-0.753,-2.095],[0,-5.793],[6.529,0],[0,0],[0,2.16],[-1.604,0],[0,0],[-0.589,-1.489],[-2.161,0],[0,1.62],[0.54,0.909],[0,0],[0,0]],"v":[[-10.702,5.728],[-11.454,1.506],[0.001,-9],[11.454,1.506],[11.454,1.817],[7.544,5.728],[3.926,3.273],[2.618,0],[-0.997,-2.454],[-4.91,1.457],[-3.657,6.014],[-1.57,8.412],[-0.818,9]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,28.341],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Inside to dot ","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[1.307,-0.561],[0.894,-0.16],[0.706,0],[0.844,0.193],[0.728,0.334],[0.967,0.901]],"o":[[-1.038,0.967],[-0.817,0.351],[-0.673,0.12],[-0.9,0],[-0.794,-0.182],[-1.203,-0.551],[0,0]],"v":[[8.182,-1.636],[4.642,0.681],[2.07,1.453],[-0.001,1.636],[-2.621,1.341],[-4.909,0.563],[-8.182,-1.636]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450990677,0.890196084976,0.992156863213,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,40.614],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Bottom","np":2,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.2],"y":[0]},"t":0,"s":[0]},{"t":10,"s":[100]}],"ix":1},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":5,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".red200","cl":"red200","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[39.95,40,0],"ix":2,"l":2},"a":{"a":0,"k":[30,30,0],"ix":1,"l":2},"s":{"a":0,"k":[120,120,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-1.2,-7.5],[1.2,-7.5],[1.2,7.5],[-1.2,7.5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.949019610882,0.721568644047,0.709803938866,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[30.002,32.488],"ix":2},"a":{"a":0,"k":[0.002,7.488],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.4,0.08],"y":[0,0.096]},"t":10,"s":[100,0]},{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.341,0.4],"y":[0,0]},"t":16,"s":[100,110]},{"t":20,"s":[100,100]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Top!","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-1.2,-1.25],[1.2,-1.25],[1.2,1.25],[-1.2,1.25]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.949019610882,0.721568644047,0.709803938866,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[30,38.75],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2],"y":[1,1]},"o":{"x":[0.08,0.08],"y":[0.06,0.06]},"t":10,"s":[0,0]},{"i":{"x":[0.147,0.147],"y":[1,1]},"o":{"x":[0.4,0.4],"y":[0,0]},"t":16,"s":[110,110]},{"t":20,"s":[100,100]}],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Bottom!","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":21,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".red200","cl":"red200","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":4,"s":[0]},{"t":9,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[40.25,40.25,0],"ix":1,"l":2},"s":{"a":0,"k":[97.5,97.5,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[22.12,0],[0,-22.08],[-22.08,0],[0,22.08]],"o":[[-22.08,0],[0,22.08],[22.12,0],[0,-22.08]],"v":[[-0.04,-40],[-40,0],[-0.04,40],[40,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[40.25,40.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":4,"s":[0]},{"t":9,"s":[100]}],"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.949019607843,0.721568627451,0.709803921569,1],"ix":3},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":4,"s":[0]},{"t":9,"s":[100]}],"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":4,"s":[0]},{"t":18,"s":[2.5]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":4,"op":600,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".grey700","cl":"grey700","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[40.25,40.25,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[22.12,0],[0,-22.08],[-22.08,0],[0,22.08]],"o":[[-22.08,0],[0,22.08],[22.12,0],[0,-22.08]],"v":[[-0.04,-40],[-40,0],[-0.04,40],[40,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.278431385756,0.278431385756,0.278431385756,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"t":5,"s":[0]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[40.25,40.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0}],"markers":[]} \ No newline at end of file
diff --git a/packages/SystemUI/res/raw/fingerprint_dialogue_fingerprint_to_unlock_lottie.json b/packages/SystemUI/res/raw/fingerprint_dialogue_fingerprint_to_unlock_lottie.json
new file mode 100644
index 000000000000..313c6c59539c
--- /dev/null
+++ b/packages/SystemUI/res/raw/fingerprint_dialogue_fingerprint_to_unlock_lottie.json
@@ -0,0 +1 @@
+{"v":"5.9.0","fr":60,"ip":0,"op":31,"w":80,"h":80,"nm":"fingerprint_to_unlock","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":2,"ty":4,"nm":".colorAccentPrimary","cl":"colorAccentPrimary","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40.107,46,0],"ix":2,"l":2},"a":{"a":0,"k":[2.75,2.75,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.43,0.43,0.2],"y":[1,1,1]},"o":{"x":[0.001,0.001,0.001],"y":[0,0,0]},"t":7.199,"s":[141.866,141.866,100]},{"t":15,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.381,0],[0,1.381],[1.381,0],[0,-1.381]],"o":[[1.381,0],[0,-1.381],[-1.381,0],[0,1.381]],"v":[[0,2.5],[2.5,0],[0,-2.5],[-2.5,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":7.199,"s":[0]},{"t":8.400390625,"s":[100]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[2.75,2.75],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".colorAccentPrimary","cl":"colorAccentPrimary","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40.091,40,0],"ix":2,"l":2},"a":{"a":0,"k":[19.341,24.25,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.767},"o":{"x":0.541,"y":0},"t":0,"s":[{"i":[[0,0],[-1.701,0.42],[-1.757,0],[-1.577,-0.381],[-1.485,-0.816]],"o":[[1.455,-0.799],[1.608,-0.397],[1.719,0],[1.739,0.42],[0,0]],"v":[[-9.818,1.227],[-5.064,-0.618],[0,-1.227],[4.96,-0.643],[9.818,1.227]],"c":false}]},{"i":{"x":0.833,"y":0.767},"o":{"x":0.167,"y":0.233},"t":5.715,"s":[{"i":[[0,0],[-1.323,1.591],[-2.674,0],[-1.207,-1.781],[0,0]],"o":[[0,0],[1.298,-1.562],[2.657,0],[1.206,1.781],[0,0]],"v":[[-7.87,7.358],[-5.804,2.36],[0.009,-0.261],[5.845,2.706],[7.905,7.358]],"c":false}]},{"i":{"x":0.261,"y":1},"o":{"x":0.167,"y":0.233},"t":7.143,"s":[{"i":[[0,0],[-0.549,1.21],[-2.975,0],[-0.74,-2.398],[0,0]],"o":[[0,0],[0.796,-2.263],[2.964,0],[0.258,0.927],[0,0]],"v":[[-7.231,9.37],[-5.97,4.027],[0.012,0.056],[6.008,4.239],[7.277,9.37]],"c":false}]},{"i":{"x":0.23,"y":1},"o":{"x":0.123,"y":0},"t":12,"s":[{"i":[[0,0],[0,0],[-3.452,0],[0,-3.375],[0,0]],"o":[[0,0],[0,-3.375],[3.452,0],[0,0],[0,0]],"v":[[-6.217,12.558],[-6.234,6.669],[0.016,0.558],[6.266,6.669],[6.283,12.558]],"c":false}]},{"t":26,"s":[{"i":[[0,0],[0,0],[3.292,0.021],[0,-3.375],[0,0]],"o":[[0,0],[0,-3.375],[-3.393,-0.022],[0,0],[0,0]],"v":[[18.568,12.573],[18.552,6.684],[12.516,0.553],[6.266,6.669],[6.283,12.558]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,7.477],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Top","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.189,"y":1},"o":{"x":0.541,"y":0},"t":0,"s":[{"i":[[0,0],[-2.446,1.161],[-1.168,0.275],[-1.439,0],[-1.301,-0.304],[-1.225,-0.66],[-1.11,-1.844]],"o":[[1.23,-2.044],[1.024,-0.486],[1.312,-0.31],[1.425,0],[1.454,0.34],[2.122,1.143],[0,0]],"v":[[-13.091,3.273],[-7.438,-1.646],[-4.14,-2.797],[0,-3.273],[4.104,-2.805],[8.141,-1.29],[13.091,3.273]],"c":false}]},{"t":15,"s":[{"i":[[0,0],[0,0],[-0.307,0.352],[-0.601,0],[0,0],[0,-1.104],[0,0]],"o":[[0,0],[0,-0.503],[0.367,-0.42],[0,0],[1.104,0],[0,0],[0,0]],"v":[[-11.2,14.15],[-11.198,6.146],[-10.705,4.831],[-9.198,4.146],[9.302,4.146],[11.302,6.146],[11.3,14.07]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,16.069],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Mid Top","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.767},"o":{"x":0.541,"y":0},"t":0,"s":[{"i":[[0,0],[0,0],[-6.53,0],[0,-5.793],[0,0],[2.159,0],[0.59,1.489],[0,0],[1.587,0],[0,-2.16],[-0.81,-1.363],[-0.844,-0.674],[0,0]],"o":[[-0.753,-2.095],[0,-5.793],[6.529,0],[0,0],[0,2.16],[-1.604,0],[0,0],[-0.589,-1.489],[-2.161,0],[0,1.62],[0.54,0.909],[0,0],[0,0]],"v":[[-10.702,5.728],[-11.454,1.506],[0.001,-9],[11.454,1.506],[11.454,1.817],[7.544,5.728],[3.926,3.273],[2.618,0],[-0.997,-2.454],[-4.91,1.457],[-3.657,6.014],[-1.57,8.412],[-0.818,9]],"c":false}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.233},"t":6.428,"s":[{"i":[[0,0],[0,0],[-1.576,0],[0,-1.474],[0,0],[1.541,0.347],[0.142,0.379],[0,0],[0.383,0],[0,-0.549],[-0.256,-0.431],[-0.768,0.207],[0,0]],"o":[[-1.823,0.497],[0,-1.474],[1.576,0],[0,0],[0,0.549],[-0.378,-0.085],[0,0],[-0.142,-0.379],[-0.521,0],[-0.002,0.353],[0.171,0.288],[0.622,-0.344],[0,0]],"v":[[-0.41,3.841],[-2.717,1.917],[0.047,-0.756],[2.811,1.917],[2.811,1.996],[0.225,3.848],[0.995,2.366],[0.679,1.534],[-0.193,0.909],[-1.338,1.879],[-1.026,3.169],[0.445,3.702],[1.036,3.015]],"c":false}]},{"t":12.857421875,"s":[{"i":[[0,0],[0,0],[-0.736,0],[0,-0.741],[0,0],[0.243,0],[0.066,0.191],[0,0],[0.179,0],[0,-0.276],[-0.162,-0.273],[-0.755,0.357],[0,0]],"o":[[-1.273,-0.008],[0,-0.741],[0.736,0],[0,0],[0,0.276],[-0.181,0],[0,0],[-0.066,-0.191],[-0.243,0],[-0.002,0.139],[0.109,0.182],[0.727,-0.402],[0,0]],"v":[[0.082,3.187],[-1.235,1.986],[0.055,0.642],[1.346,1.986],[1.346,2.026],[0.905,2.527],[0.498,2.212],[0.35,1.794],[-0.057,1.479],[-0.733,1.951],[-0.58,2.686],[0.619,3.071],[1.351,2]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":8.4,"s":[100]},{"t":11.3984375,"s":[0]}],"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,28.341],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Inside to dot ","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.767},"o":{"x":0.541,"y":0},"t":0,"s":[{"i":[[0,0],[1.307,-0.561],[0.894,-0.16],[0.706,0],[0.844,0.193],[0.728,0.334],[0.967,0.901]],"o":[[-1.038,0.967],[-0.817,0.351],[-0.673,0.12],[-0.9,0],[-0.794,-0.182],[-1.203,-0.551],[0,0]],"v":[[8.182,-1.636],[4.642,0.681],[2.07,1.453],[-0.001,1.636],[-2.621,1.341],[-4.909,0.563],[-8.182,-1.636]],"c":false}]},{"i":{"x":0.331,"y":1},"o":{"x":0.167,"y":0.233},"t":6.428,"s":[{"i":[[0,0],[0.313,-0.134],[0.554,-0.317],[0.535,0],[0.203,0.046],[0.175,0.919],[0.232,0.216]],"o":[[-0.249,0.232],[-0.196,0.557],[-0.424,0.245],[-0.216,0],[-1.03,-0.044],[-0.288,-0.132],[0,0]],"v":[[11.468,-8.353],[10.62,-1.716],[9.232,-0.353],[7.057,0.034],[-7.634,-0.037],[-10.453,-1.739],[-11.238,-8.347]],"c":false}]},{"t":15,"s":[{"i":[[0,0],[0,0],[0.446,-0.367],[0.481,0],[0,0],[0,1.104],[0,0]],"o":[[0,0],[0,0.623],[-0.345,0.284],[0,0],[-1.104,0],[0,0],[0,0]],"v":[[11.302,-10.469],[11.302,-2.469],[10.57,-0.923],[9.302,-0.469],[-9.198,-0.469],[-11.198,-2.469],[-11.198,-10.469]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450980392,0.890196078431,0.992156862745,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[19.341,40.614],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Bottom","np":2,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".grey700","cl":"grey700","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[40.25,40.25,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[22.12,0],[0,-22.08],[-22.08,0],[0,22.08]],"o":[[-22.08,0],[0,22.08],[22.12,0],[0,-22.08]],"v":[[-0.04,-40],[-40,0],[-0.04,40],[40,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.278431385756,0.278431385756,0.278431385756,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[40.25,40.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0}],"markers":[{"tm":30,"cm":"1","dr":0},{"tm":51,"cm":"350ms\r","dr":0},{"tm":69,"cm":"650ms\r","dr":0}]} \ No newline at end of file
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 1eece4cee179..26bf1034df11 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -166,8 +166,12 @@
<!-- Window magnification colors -->
<color name="magnification_border_color">#FF9900</color>
+ <color name="magnification_border_color_change">#0000FF</color>
<color name="magnification_switch_button_color">#7F000000</color>
<color name="magnification_drag_handle_color">#B3000000</color>
+ <color name="accessibility_magnifier_bg">#FCFCFC</color>
+ <color name="accessibility_magnifier_bg_stroke">#E0E0E0</color>
+ <color name="accessibility_magnifier_icon_color">#252525</color>
<!-- Volume dialog colors -->
<color name="volume_dialog_background_color">@android:color/transparent</color>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 786b6b884605..1168378fb14f 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -926,7 +926,8 @@
<!-- Biometric Dialog values -->
<dimen name="biometric_dialog_face_icon_size">64dp</dimen>
- <dimen name="biometric_dialog_fingerprint_icon_size">80dp</dimen>
+ <dimen name="biometric_dialog_fingerprint_icon_width">80dp</dimen>
+ <dimen name="biometric_dialog_fingerprint_icon_height">80dp</dimen>
<dimen name="biometric_dialog_button_negative_max_width">160dp</dimen>
<dimen name="biometric_dialog_button_positive_max_width">136dp</dimen>
<dimen name="biometric_dialog_corner_size">4dp</dimen>
@@ -1065,8 +1066,10 @@
<dimen name="magnification_frame_move_long">25dp</dimen>
<dimen name="magnification_drag_view_size">36dp</dimen>
<dimen name="magnification_controls_size">90dp</dimen>
- <dimen name="magnification_switch_button_size">48dp</dimen>
+ <dimen name="magnification_switch_button_size">56dp</dimen>
+ <dimen name="magnification_switch_button_padding">6dp</dimen>
<dimen name="magnification_switch_button_margin">16dp</dimen>
+ <dimen name="magnification_close_button_padding">15dp</dimen>
<dimen name="magnifier_left_right_controls_width">35dp</dimen>
<dimen name="magnifier_left_right_controls_height">45dp</dimen>
<dimen name="magnifier_up_down_controls_width">45dp</dimen>
@@ -1074,10 +1077,15 @@
<!-- The extra padding to show the whole outer border -->
<dimen name="magnifier_drag_handle_padding">3dp</dimen>
<dimen name="magnification_max_frame_size">300dp</dimen>
+
<!-- How far from the right edge of the screen you need to drag the window before the button
repositions to the other side. -->
<dimen name="magnification_button_reposition_threshold_from_edge">32dp</dimen>
+ <dimen name="magnification_drag_size">15dp</dimen>
+ <dimen name="magnification_max_size">360dp</dimen>
+ <dimen name="magnifier_panel_size">265dp</dimen>
+
<!-- Home Controls -->
<dimen name="controls_header_menu_size">48dp</dimen>
<dimen name="controls_header_bottom_margin">24dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 7f3caeca5a62..bfdb17041ad7 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -884,7 +884,7 @@
<string name="keyguard_indication_charging_time_slowly"><xliff:g id="percentage">%2$s</xliff:g> • Charging slowly • Full in <xliff:g id="charging_time_left" example="4 hr, 2 min">%1$s</xliff:g></string>
<!-- Indication on the keyguard that is shown when the device is dock charging. [CHAR LIMIT=80]-->
- <string name="keyguard_indication_charging_time_dock"><xliff:g id="percentage" example="20%">%2$s</xliff:g> • Charging Dock • Full in <xliff:g id="charging_time_left" example="4 hr, 2 min">%1$s</xliff:g></string>
+ <string name="keyguard_indication_charging_time_dock"><xliff:g id="percentage" example="20%">%2$s</xliff:g> • Charging • Full in <xliff:g id="charging_time_left" example="4 hr, 2 min">%1$s</xliff:g></string>
<!-- Related to user switcher --><skip/>
@@ -2109,6 +2109,41 @@
<!-- Click action label for magnification switch. [CHAR LIMIT=NONE] -->
<string name="magnification_mode_switch_click_label">Switch</string>
+ <!-- Title of the magnification option button allow diagonal scrolling [CHAR LIMIT=NONE]-->
+ <string name="accessibility_allow_diagonal_scrolling">Allow diagonal scrolling</string>
+ <!-- Title of the magnification option button Resize [CHAR LIMIT=NONE]-->
+ <string name="accessibility_resize">Resize</string>
+ <!-- Title of the magnification option button Change type [CHAR LIMIT=NONE]-->
+ <string name="accessibility_change_magnification_type">Change magnification type</string>
+ <!-- Title of the magnification option button End resizing [CHAR LIMIT=NONE]-->
+ <string name="accessibility_magnification_end_resizing">End resizing</string>
+
+ <!-- Description of the window magnification Top handle [CHAR LIMIT=NONE]-->
+ <string name="accessibility_magnification_top_handle">Top handle</string>
+ <!-- Description of the window magnification Left handle [CHAR LIMIT=NONE]-->
+ <string name="accessibility_magnification_left_handle">Left handle</string>
+ <!-- Description of the window magnification Right handle [CHAR LIMIT=NONE]-->
+ <string name="accessibility_magnification_right_handle">Right handle</string>
+ <!-- Description of the window magnification Bottom handle [CHAR LIMIT=NONE]-->
+ <string name="accessibility_magnification_bottom_handle">Bottom handle</string>
+
+ <!-- Title of the window magnification panel option Magnifier size [CHAR LIMIT=NONE]-->
+ <string name="accessibility_magnifier_size">Magnifier size</string>
+ <!-- Title of the window magnification panel option Zoom [CHAR LIMIT=NONE]-->
+ <string name="accessibility_magnification_zoom">Zoom</string>
+ <!-- Click action label for magnification panel medium size [CHAR LIMIT=NONE]-->
+ <string name="accessibility_magnification_medium">Medium</string>
+ <!-- Click action label for magnification panel small size [CHAR LIMIT=NONE]-->
+ <string name="accessibility_magnification_small">Small</string>
+ <!-- Click action label for magnification panel large size [CHAR LIMIT=NONE]-->
+ <string name="accessibility_magnification_large">Large</string>
+ <!-- Click action label for magnification panel Close [CHAR LIMIT=NONE]-->
+ <string name="accessibility_magnification_close">Close</string>
+ <!-- Click action label for edit magnification size [CHAR LIMIT=NONE]-->
+ <string name="accessibility_magnifier_edit">Edit</string>
+ <!-- Click action label for magnification panel settings [CHAR LIMIT=NONE]-->
+ <string name="accessibility_magnification_magnifier_window_settings">Magnifier window settings</string>
+
<!-- Accessibility floating menu strings -->
<!-- Message for the accessibility floating button migration tooltip. It shows when the user use gestural navigation then upgrade their system. It will tell the user they could customize or replace the floating button in Settings. [CHAR LIMIT=100] -->
<string name="accessibility_floating_button_migration_tooltip">Tap to open accessibility features. Customize or replace this button in Settings.\n\n<annotation id="link">View settings</annotation></string>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 8b5e3c11b2f6..2f9bc1c59624 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -86,7 +86,6 @@ import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
import android.telephony.TelephonyCallback;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
-import android.util.Log;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
@@ -714,7 +713,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private void handleFingerprintAuthFailed() {
Assert.isMainThread();
if (mHandler.hasCallbacks(mFpCancelNotReceived)) {
- Log.d(TAG, "handleFingerprintAuthFailed()"
+ mLogger.d("handleFingerprintAuthFailed()"
+ " triggered while waiting for cancellation, removing watchdog");
mHandler.removeCallbacks(mFpCancelNotReceived);
}
@@ -749,7 +748,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private void handleFingerprintAuthenticated(int authUserId, boolean isStrongBiometric) {
Trace.beginSection("KeyGuardUpdateMonitor#handlerFingerPrintAuthenticated");
if (mHandler.hasCallbacks(mFpCancelNotReceived)) {
- Log.d(TAG, "handleFingerprintAuthenticated()"
+ mLogger.d("handleFingerprintAuthenticated()"
+ " triggered while waiting for cancellation, removing watchdog");
mHandler.removeCallbacks(mFpCancelNotReceived);
}
@@ -824,7 +823,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
if (msgId == FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE
|| msgId == FingerprintManager.BIOMETRIC_ERROR_POWER_PRESSED) {
- Log.d(TAG, "Fingerprint retrying auth due to(" + msgId + ") -> " + errString);
+ mLogger.logRetryAfterFpError(msgId, errString);
mHandler.postDelayed(mRetryFingerprintAuthentication, HAL_ERROR_RETRY_TIMEOUT);
}
@@ -2565,8 +2564,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
private boolean isOnlyFaceEnrolled() {
- return isFaceAuthEnabledForUser(getCurrentUser())
- && !isUnlockWithFingerprintPossible(getCurrentUser());
+ return isFaceEnrolled()
+ && !getCachedIsUnlockWithFingerprintPossible(sCurrentUser);
}
private void maybeLogListenerModelData(KeyguardListenModel model) {
@@ -2681,7 +2680,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
return isUnlockWithFacePossible(userId) || isUnlockWithFingerprintPossible(userId);
}
- private boolean isUnlockWithFingerprintPossible(int userId) {
+ @VisibleForTesting
+ boolean isUnlockWithFingerprintPossible(int userId) {
+ // TODO (b/242022358), make this rely on onEnrollmentChanged event and update it only once.
mIsUnlockWithFingerprintPossible.put(userId, mFpm != null && mFpm.isHardwareDetected()
&& !isFingerprintDisabled(userId) && mFpm.hasEnrolledTemplates(userId));
return mIsUnlockWithFingerprintPossible.get(userId);
@@ -2703,6 +2704,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
* If face hardware is available, user has enrolled and enabled auth via setting.
*/
public boolean isFaceAuthEnabledForUser(int userId) {
+ // TODO (b/242022358), make this rely on onEnrollmentChanged event and update it only once.
updateFaceEnrolled(userId);
return mIsFaceEnrolled;
}
@@ -3402,7 +3404,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
mHandler.sendMessage(mHandler.obtainMessage(MSG_ASSISTANT_STACK_CHANGED,
info.visible));
} catch (RemoteException e) {
- Log.e(TAG, "unable to check task stack", e);
+ mLogger.logException(e, "unable to check task stack ");
}
}
};
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
index 035b7f07a5f4..d718a240bbfd 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -198,6 +198,15 @@ class KeyguardUpdateMonitorLogger @Inject constructor(
{ "Retrying face after HW unavailable, attempt $int1" })
}
+ fun logRetryAfterFpError(msgId: Int, errString: String?) {
+ logBuffer.log(TAG, DEBUG, {
+ int1 = msgId
+ str1 = "$errString"
+ }, {
+ "Fingerprint retrying auth due to($int1) -> $str1"
+ })
+ }
+
fun logRetryAfterFpHwUnavailable(retryCount: Int) {
logBuffer.log(TAG, WARNING,
{ int1 = retryCount },
@@ -270,12 +279,12 @@ class KeyguardUpdateMonitorLogger @Inject constructor(
{ str1 = newTimeFormat },
{ "handleTimeFormatUpdate timeFormat=$str1" })
}
-
fun logUdfpsPointerDown(sensorId: Int) {
logBuffer.log(TAG, DEBUG,
{ int1 = sensorId },
{ "onUdfpsPointerDown, sensorId: $int1" })
}
+
fun logUdfpsPointerUp(sensorId: Int) {
logBuffer.log(TAG, DEBUG,
{ int1 = sensorId },
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationGestureDetector.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationGestureDetector.java
index 4b30ec3e6f6f..c91082c618c5 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationGestureDetector.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationGestureDetector.java
@@ -23,6 +23,7 @@ import android.graphics.PointF;
import android.os.Handler;
import android.view.Display;
import android.view.MotionEvent;
+import android.view.View;
import android.view.ViewConfiguration;
/**
@@ -41,7 +42,7 @@ class MagnificationGestureDetector {
*
* @return {@code true} if this gesture is handled.
*/
- boolean onSingleTap();
+ boolean onSingleTap(View view);
/**
* Called when the user is performing dragging gesture. It is started after the offset
@@ -52,7 +53,7 @@ class MagnificationGestureDetector {
* @param offsetY The Y offset in screen coordinate.
* @return {@code true} if this gesture is handled.
*/
- boolean onDrag(float offsetX, float offsetY);
+ boolean onDrag(View view, float offsetX, float offsetY);
/**
* Notified when a tap occurs with the down {@link MotionEvent} that triggered it. This will
@@ -109,7 +110,7 @@ class MagnificationGestureDetector {
* @param event The current motion event.
* @return {@code True} if the {@link OnGestureListener} consumes the event, else false.
*/
- boolean onTouch(MotionEvent event) {
+ boolean onTouch(View view, MotionEvent event) {
final float rawX = event.getRawX();
final float rawY = event.getRawY();
boolean handled = false;
@@ -125,12 +126,12 @@ class MagnificationGestureDetector {
break;
case MotionEvent.ACTION_MOVE:
stopSingleTapDetectionIfNeeded(rawX, rawY);
- handled |= notifyDraggingGestureIfNeeded(rawX, rawY);
+ handled |= notifyDraggingGestureIfNeeded(view, rawX, rawY);
break;
case MotionEvent.ACTION_UP:
stopSingleTapDetectionIfNeeded(rawX, rawY);
if (mDetectSingleTap) {
- handled |= mOnGestureListener.onSingleTap();
+ handled |= mOnGestureListener.onSingleTap(view);
}
// Fall through
case MotionEvent.ACTION_CANCEL:
@@ -163,7 +164,7 @@ class MagnificationGestureDetector {
mDetectSingleTap = false;
}
- private boolean notifyDraggingGestureIfNeeded(float x, float y) {
+ private boolean notifyDraggingGestureIfNeeded(View view, float x, float y) {
if (!mDraggingDetected) {
return false;
}
@@ -173,7 +174,7 @@ class MagnificationGestureDetector {
final float offsetX = x - mPointerLocation.x;
final float offsetY = y - mPointerLocation.y;
mPointerLocation.set(x, y);
- return mOnGestureListener.onDrag(offsetX, offsetY);
+ return mOnGestureListener.onDrag(view, offsetX, offsetY);
}
private void reset() {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java
index dbd215d9c713..59a5b1534990 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java
@@ -213,18 +213,18 @@ class MagnificationModeSwitch implements MagnificationGestureDetector.OnGestureL
if (!mIsVisible) {
return false;
}
- return mGestureDetector.onTouch(event);
+ return mGestureDetector.onTouch(v, event);
}
@Override
- public boolean onSingleTap() {
+ public boolean onSingleTap(View v) {
mSingleTapDetected = true;
handleSingleTap();
return true;
}
@Override
- public boolean onDrag(float offsetX, float offsetY) {
+ public boolean onDrag(View v, float offsetX, float offsetY) {
moveButton(offsetX, offsetY);
return true;
}
@@ -292,9 +292,12 @@ class MagnificationModeSwitch implements MagnificationGestureDetector.OnGestureL
* @param resetPosition if the button position needs be reset
*/
private void showButton(int mode, boolean resetPosition) {
+ if (mode != Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN) {
+ return;
+ }
if (mMagnificationMode != mode) {
mMagnificationMode = mode;
- mImageView.setImageResource(getIconResId(mode));
+ mImageView.setImageResource(getIconResId(mMagnificationMode));
}
if (!mIsVisible) {
onConfigurationChanged(mContext.getResources().getConfiguration());
@@ -408,6 +411,7 @@ class MagnificationModeSwitch implements MagnificationGestureDetector.OnGestureL
private static ImageView createView(Context context) {
ImageView imageView = new ImageView(context);
+ imageView.setScaleType(ImageView.ScaleType.CENTER);
imageView.setClickable(true);
imageView.setFocusable(true);
imageView.setAlpha(0f);
@@ -415,10 +419,8 @@ class MagnificationModeSwitch implements MagnificationGestureDetector.OnGestureL
}
@VisibleForTesting
- static int getIconResId(int mode) {
- return (mode == Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN)
- ? R.drawable.ic_open_in_new_window
- : R.drawable.ic_open_in_new_fullscreen;
+ static int getIconResId(int mode) { // TODO(b/242233514): delete non used param
+ return R.drawable.ic_open_in_new_window;
}
private static LayoutParams createLayoutParams(Context context) {
@@ -461,4 +463,4 @@ class MagnificationModeSwitch implements MagnificationGestureDetector.OnGestureL
new Rect(0, 0, mImageView.getWidth(), mImageView.getHeight())));
});
}
-}
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
index d7fead16e269..f4701ed50ccc 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
@@ -233,6 +233,13 @@ public class WindowMagnification extends CoreStartable implements WindowMagnifie
}
@Override
+ public void onModeSwitch(int displayId, int newMode) {
+ if (mWindowMagnificationConnectionImpl != null) {
+ mWindowMagnificationConnectionImpl.onChangeMagnificationMode(displayId, newMode);
+ }
+ }
+
+ @Override
public void requestWindowMagnificationConnection(boolean connect) {
if (connect) {
setWindowMagnificationConnection();
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index 1eedae6f01b0..813f4ddbab0b 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -19,8 +19,11 @@ package com.android.systemui.accessibility;
import static android.view.WindowInsets.Type.systemGestures;
import static android.view.WindowManager.LayoutParams;
+import static com.android.systemui.accessibility.WindowMagnificationSettings.MagnificationSize;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_MAGNIFICATION_OVERLAP;
+import static java.lang.Math.abs;
+
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.annotation.NonNull;
@@ -41,6 +44,8 @@ import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;
+import android.os.UserHandle;
+import android.provider.Settings;
import android.util.Log;
import android.util.Range;
import android.util.Size;
@@ -62,6 +67,7 @@ import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import android.view.accessibility.IRemoteMagnificationAnimationCallback;
import android.widget.FrameLayout;
+import android.widget.ImageView;
import androidx.core.math.MathUtils;
@@ -93,6 +99,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
private static final Range<Float> A11Y_ACTION_SCALE_RANGE = new Range<>(2.0f, 8.0f);
private static final float A11Y_CHANGE_SCALE_DIFFERENCE = 1.0f;
private static final float ANIMATION_BOUNCE_EFFECT_SCALE = 1.05f;
+ private static final float[] MAGNIFICATION_SCALE_OPTIONS = {1.0f, 1.4f, 1.8f, 2.5f};
private final Context mContext;
private final Resources mResources;
@@ -145,7 +152,8 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
// The root of the mirrored content
private SurfaceControl mMirrorSurface;
- private View mDragView;
+ private ImageView mDragView;
+ private ImageView mCloseView;
private View mLeftDrag;
private View mTopDrag;
private View mRightDrag;
@@ -162,6 +170,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
private final Runnable mWindowInsetChangeRunnable;
// MirrorView is the mirror window which displays the magnified content.
private View mMirrorView;
+ private View mMirrorBorderView;
private SurfaceView mMirrorSurfaceView;
private int mMirrorSurfaceMargin;
private int mBorderDragSize;
@@ -172,6 +181,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
* repositions to the other side.
*/
private int mButtonRepositionThresholdFromEdge;
+
// The boundary of magnification frame.
private final Rect mMagnificationFrameBoundary = new Rect();
// The top Y of the system gesture rect at the bottom. Set to -1 if it is invalid.
@@ -192,6 +202,18 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
private boolean mOverlapWithGestureInsets;
private boolean mIsDragging;
+ // Window Magnification Setting view
+ private WindowMagnificationSettings mWindowMagnificationSettings;
+
+ private static final int MAX_HORIZONTAL_MOVE_ANGLE = 50;
+ private static final int HORIZONTAL = 1;
+ private static final int VERTICAL = 0;
+ private static final double HORIZONTAL_LOCK_BASE =
+ Math.tan(Math.toRadians(MAX_HORIZONTAL_MOVE_ANGLE));
+
+ private boolean mAllowDiagonalScrolling = false;
+ private boolean mEditSizeEnable = false;
+
@Nullable
private MirrorWindowControl mMirrorWindowControl;
@@ -223,7 +245,12 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
mWindowBounds = new Rect(mWm.getCurrentWindowMetrics().getBounds());
mResources = mContext.getResources();
- mScale = mResources.getInteger(R.integer.magnification_default_scale);
+ mScale = Settings.Secure.getFloatForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
+ mResources.getInteger(R.integer.magnification_default_scale),
+ UserHandle.USER_CURRENT);
+
+
mBounceEffectDuration = mResources.getInteger(
com.android.internal.R.integer.config_shortAnimTime);
updateDimensions();
@@ -241,6 +268,10 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
mGestureDetector =
new MagnificationGestureDetector(mContext, handler, this);
+ mWindowMagnificationSettings =
+ new WindowMagnificationSettings(mContext, mWindowMagnificationSettingsCallback,
+ mSfVsyncFrameProvider);
+
// Initialize listeners.
mMirrorViewRunnable = () -> {
if (mMirrorView != null) {
@@ -326,6 +357,26 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
return false;
}
+ private void changeMagnificationSize(@MagnificationSize int index) {
+ final int initSize = Math.min(mWindowBounds.width(), mWindowBounds.height()) / 3;
+ int size = (int) (initSize * MAGNIFICATION_SCALE_OPTIONS[index]);
+ setWindowSize(size, size);
+ }
+
+ private void setEditMagnifierSizeMode(boolean enable) {
+ mEditSizeEnable = enable;
+ applyResourcesValues();
+
+ if (isWindowVisible()) {
+ updateDimensions();
+ applyTapExcludeRegion();
+ }
+ }
+
+ private void setDiagonalScrolling(boolean enable) {
+ mAllowDiagonalScrolling = enable;
+ }
+
/**
* Wraps {@link WindowMagnificationController#deleteWindowMagnification()}} with transition
* animation. If the window magnification is enabling, it runs the animation in reverse.
@@ -346,6 +397,9 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
if (!isWindowVisible()) {
return;
}
+
+ closeMagnificationSettings();
+
if (mMirrorSurface != null) {
mTransaction.remove(mMirrorSurface).apply();
mMirrorSurface = null;
@@ -368,6 +422,8 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
mMirrorViewBounds.setEmpty();
mSourceBounds.setEmpty();
updateSystemUIStateIfNeeded();
+ setEditMagnifierSizeMode(false);
+
mContext.unregisterComponentCallbacks(this);
// Notify source bounds empty when magnification is deleted.
mWindowMagnifierCallback.onSourceBoundsChanged(mDisplayId, new Rect());
@@ -505,7 +561,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
/** Returns the rotation degree change of two {@link Surface.Rotation} */
private int getDegreeFromRotation(@Surface.Rotation int newRotation,
- @Surface.Rotation int oldRotation) {
+ @Surface.Rotation int oldRotation) {
final int rotationDiff = oldRotation - newRotation;
final int degree = (rotationDiff + 4) % 4 * 90;
return degree;
@@ -534,6 +590,8 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
mMirrorView = LayoutInflater.from(mContext).inflate(R.layout.window_magnifier_view, null);
mMirrorSurfaceView = mMirrorView.findViewById(R.id.surface_view);
+ mMirrorBorderView = mMirrorView.findViewById(R.id.magnification_inner_border);
+
// Allow taps to go through to the mirror SurfaceView below.
mMirrorSurfaceView.addOnLayoutChangeListener(mMirrorSurfaceViewLayoutChangeListener);
@@ -600,6 +658,18 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
}
}
+ private void showMagnificationSettings() {
+ if (mWindowMagnificationSettings != null) {
+ mWindowMagnificationSettings.showSettingPanel();
+ }
+ }
+
+ private void closeMagnificationSettings() {
+ if (mWindowMagnificationSettings != null) {
+ mWindowMagnificationSettings.hideSettingPanel();
+ }
+ }
+
/**
* Sets the window size with given width and height in pixels without changing the
* window center. The width or the height will be clamped in the range
@@ -668,12 +738,14 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
mTopDrag = mMirrorView.findViewById(R.id.top_handle);
mRightDrag = mMirrorView.findViewById(R.id.right_handle);
mBottomDrag = mMirrorView.findViewById(R.id.bottom_handle);
+ mCloseView = mMirrorView.findViewById(R.id.close_button);
mDragView.setOnTouchListener(this);
mLeftDrag.setOnTouchListener(this);
mTopDrag.setOnTouchListener(this);
mRightDrag.setOnTouchListener(this);
mBottomDrag.setOnTouchListener(this);
+ mCloseView.setOnTouchListener(this);
}
/**
@@ -743,8 +815,8 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
@Override
public boolean onTouch(View v, MotionEvent event) {
if (v == mDragView || v == mLeftDrag || v == mTopDrag || v == mRightDrag
- || v == mBottomDrag) {
- return mGestureDetector.onTouch(event);
+ || v == mBottomDrag || v == mCloseView) {
+ return mGestureDetector.onTouch(v, event);
}
return false;
}
@@ -768,6 +840,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
int right = displayFrame.right - (halfWidth - (int) (halfWidth / scale));
int top = displayFrame.top + (halfHeight - (int) (halfHeight / scale));
int bottom = displayFrame.bottom - (halfHeight - (int) (halfHeight / scale));
+
mSourceBounds.set(left, top, right, bottom);
// SourceBound's center is equal to center[X,Y] but calculated from MagnificationFrame's
@@ -950,7 +1023,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
* or {@link Float#NaN} to leave unchanged.
*/
void enableWindowMagnificationInternal(float scale, float centerX, float centerY,
- float magnificationFrameOffsetRatioX, float magnificationFrameOffsetRatioY) {
+ float magnificationFrameOffsetRatioX, float magnificationFrameOffsetRatioY) {
if (Float.compare(scale, 1.0f) <= 0) {
deleteWindowMagnification();
return;
@@ -983,6 +1056,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
if (!isWindowVisible()) {
createMirrorWindow();
showControls();
+ applyResourcesValues();
} else {
modifyWindowMagnification(false);
}
@@ -997,6 +1071,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
if (mAnimationController.isAnimating() || !isWindowVisible() || mScale == scale) {
return;
}
+
enableWindowMagnificationInternal(scale, Float.NaN, Float.NaN);
mHandler.removeCallbacks(mUpdateStateDescriptionRunnable);
mHandler.postDelayed(mUpdateStateDescriptionRunnable, UPDATE_STATE_DESCRIPTION_DELAY_MS);
@@ -1014,19 +1089,42 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
if (mAnimationController.isAnimating() || mMirrorSurfaceView == null) {
return;
}
+
+ if (!mAllowDiagonalScrolling) {
+ int direction = selectDirectionForMove(abs(offsetX), abs(offsetY));
+
+ if (direction == HORIZONTAL) {
+ offsetY = 0;
+ } else {
+ offsetX = 0;
+ }
+ }
+
if (updateMagnificationFramePosition((int) offsetX, (int) offsetY)) {
modifyWindowMagnification(false);
}
}
void moveWindowMagnifierToPosition(float positionX, float positionY,
- IRemoteMagnificationAnimationCallback callback) {
+ IRemoteMagnificationAnimationCallback callback) {
if (mMirrorSurfaceView == null) {
return;
}
mAnimationController.moveWindowMagnifierToPosition(positionX, positionY, callback);
}
+ private int selectDirectionForMove(float diffX, float diffY) {
+ int direction = 0;
+ float result = diffY / diffX;
+
+ if (result <= HORIZONTAL_LOCK_BASE) {
+ direction = HORIZONTAL; // horizontal move
+ } else {
+ direction = VERTICAL; // vertical move
+ }
+ return direction;
+ }
+
/**
* Gets the scale.
*
@@ -1072,17 +1170,143 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
}
@Override
- public boolean onSingleTap() {
- animateBounceEffect();
+ public boolean onSingleTap(View view) {
+ handleSingleTap(view);
return true;
}
@Override
- public boolean onDrag(float offsetX, float offsetY) {
- move((int) offsetX, (int) offsetY);
+ public boolean onDrag(View view, float offsetX, float offsetY) {
+ if (mEditSizeEnable) {
+ changeWindowSize(view, offsetX, offsetY);
+ } else {
+ move((int) offsetX, (int) offsetY);
+ }
+ return true;
+ }
+
+ private void handleSingleTap(View view) {
+ int id = view.getId();
+ if (id == R.id.drag_handle) {
+ showMagnificationSettings();
+ } else if (id == R.id.close_button) {
+ setEditMagnifierSizeMode(false);
+ } else {
+ animateBounceEffect();
+ }
+ }
+
+ private void applyResourcesValues() {
+ mMirrorBorderView.setBackgroundColor(mResources.getColor(mEditSizeEnable
+ ? R.color.magnification_border_color_change : R.color.magnification_border_color));
+
+ if (mEditSizeEnable) {
+ mDragView.setVisibility(View.GONE);
+ mCloseView.setVisibility(View.VISIBLE);
+ } else {
+ mDragView.setVisibility(View.VISIBLE);
+ mCloseView.setVisibility(View.GONE);
+ }
+ }
+
+ public boolean changeWindowSize(View view, float offsetX, float offsetY) {
+ boolean bRTL = isRTL(mContext);
+ final int initSize = Math.min(mWindowBounds.width(), mWindowBounds.height()) / 3;
+
+ final int maxHeightSize = mWindowBounds.height() - 2 * mMirrorSurfaceMargin;
+ final int maxWidthSize = mWindowBounds.width() - 2 * mMirrorSurfaceMargin;
+
+ Rect tempRect = new Rect();
+ tempRect.set(mMagnificationFrame);
+
+ if (view == mLeftDrag) {
+ if (bRTL) {
+ tempRect.right += offsetX;
+ if (tempRect.right > mWindowBounds.width()) {
+ return false;
+ }
+ } else {
+ tempRect.left += offsetX;
+ if (tempRect.left < 0) {
+ return false;
+ }
+ }
+ } else if (view == mRightDrag) {
+ if (bRTL) {
+ tempRect.left += offsetX;
+ if (tempRect.left < 0) {
+ return false;
+ }
+ } else {
+ tempRect.right += offsetX;
+ if (tempRect.right > mWindowBounds.width()) {
+ return false;
+ }
+ }
+ } else if (view == mTopDrag) {
+ tempRect.top += offsetY;
+ if (tempRect.top < 0) {
+ return false;
+ }
+ } else if (view == mBottomDrag) {
+ tempRect.bottom += offsetY;
+ if (tempRect.bottom > mWindowBounds.height()) {
+ return false;
+ }
+ }
+
+ if (tempRect.width() < initSize || tempRect.height() < initSize
+ || tempRect.width() > maxWidthSize || tempRect.height() > maxHeightSize) {
+ return false;
+ }
+
+ mMagnificationFrame.set(tempRect);
+
+ computeBounceAnimationScale();
+ calculateMagnificationFrameBoundary();
+
+ modifyWindowMagnification(true);
return true;
}
+ private static boolean isRTL(Context context) {
+ Configuration config = context.getResources().getConfiguration();
+ if (config == null) {
+ return false;
+ }
+ return (config.screenLayout & Configuration.SCREENLAYOUT_LAYOUTDIR_MASK)
+ == Configuration.SCREENLAYOUT_LAYOUTDIR_RTL;
+ }
+
+ private WindowMagnificationSettingsCallback mWindowMagnificationSettingsCallback =
+ new WindowMagnificationSettingsCallback() {
+ @Override
+ public void onSetDiagonalScrolling(boolean enable) {
+ setDiagonalScrolling(enable);
+ }
+
+ @Override
+ public void onModeSwitch(int newMode) {
+ mWindowMagnifierCallback.onModeSwitch(mDisplayId, newMode);
+ }
+
+ @Override
+ public void onSetMagnifierSize(@MagnificationSize int index) {
+ changeMagnificationSize(index);
+ }
+
+ @Override
+ public void onEditMagnifierSizeMode(boolean enable) {
+ setEditMagnifierSizeMode(enable);
+ }
+
+ @Override
+ public void onMagnifierScale(float scale) {
+ mWindowMagnifierCallback.onPerformScaleAction(mDisplayId,
+ A11Y_ACTION_SCALE_RANGE.clamp(scale));
+ }
+ };
+
@Override
public boolean onStart(float x, float y) {
mIsDragging = true;
@@ -1138,7 +1362,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
pw.println(" mMagnificationFrame:"
+ (isWindowVisible() ? mMagnificationFrame : "empty"));
pw.println(" mSourceBounds:"
- + (mSourceBounds.isEmpty() ? "empty" : mSourceBounds));
+ + (mSourceBounds.isEmpty() ? "empty" : mSourceBounds));
pw.println(" mSystemGestureTop:" + mSystemGestureTop);
pw.println(" mMagnificationFrameOffsetX:" + mMagnificationFrameOffsetX);
pw.println(" mMagnificationFrameOffsetY:" + mMagnificationFrameOffsetY);
@@ -1149,6 +1373,11 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
@Override
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(host, info);
+ final AccessibilityAction clickAction = new AccessibilityAction(
+ AccessibilityAction.ACTION_CLICK.getId(), mContext.getResources().getString(
+ R.string.magnification_mode_switch_click_label));
+ info.addAction(clickAction);
+ info.setClickable(true);
info.addAction(
new AccessibilityAction(R.id.accessibility_action_zoom_in,
mContext.getString(R.string.accessibility_control_zoom_in)));
@@ -1176,7 +1405,10 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
}
private boolean performA11yAction(int action) {
- if (action == R.id.accessibility_action_zoom_in) {
+ if (action == AccessibilityAction.ACTION_CLICK.getId()) {
+ // Simulate tapping the drag view so it opens the Settings.
+ handleSingleTap(mDragView);
+ } else if (action == R.id.accessibility_action_zoom_in) {
final float scale = mScale + A11Y_CHANGE_SCALE_DIFFERENCE;
mWindowMagnifierCallback.onPerformScaleAction(mDisplayId,
A11Y_ACTION_SCALE_RANGE.clamp(scale));
@@ -1199,4 +1431,4 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
return true;
}
}
-}
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
new file mode 100644
index 000000000000..9cffd5d609a7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
@@ -0,0 +1,592 @@
+/*
+ * 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.accessibility;
+
+import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+
+import android.annotation.IntDef;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
+import android.graphics.Insets;
+import android.graphics.PixelFormat;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.MathUtils;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.AccessibilityDelegate;
+import android.view.ViewGroup;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
+import android.view.WindowMetrics;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
+import android.widget.Button;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+import android.widget.SeekBar;
+import android.widget.Switch;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
+import com.android.systemui.R;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Collections;
+
+/**
+ * Class to set value about WindowManificationSettings.
+ */
+class WindowMagnificationSettings implements MagnificationGestureDetector.OnGestureListener {
+ private static final String TAG = "WindowMagnificationSettings";
+ private final Context mContext;
+ private final AccessibilityManager mAccessibilityManager;
+ private final WindowManager mWindowManager;
+
+ private final Runnable mWindowInsetChangeRunnable;
+ private final SfVsyncFrameCallbackProvider mSfVsyncFrameProvider;
+
+ private final LayoutParams mParams;
+ @VisibleForTesting
+ final Rect mDraggableWindowBounds = new Rect();
+ private boolean mIsVisible = false;
+ private final MagnificationGestureDetector mGestureDetector;
+ private boolean mSingleTapDetected = false;
+
+ private SeekBar mZoomSeekbar;
+ private Switch mAllowDiagonalScrollingSwitch;
+ private LinearLayout mPanelView;
+ private LinearLayout mSettingView;
+ private LinearLayout mButtonView;
+ private ImageButton mSmallButton;
+ private ImageButton mMediumButton;
+ private ImageButton mLargeButton;
+ private Button mCloseButton;
+ private Button mEditButton;
+ private ImageButton mChangeModeButton;
+ private boolean mAllowDiagonalScrolling = false;
+ private static final float A11Y_CHANGE_SCALE_DIFFERENCE = 1.0f;
+ private static final float A11Y_SCALE_MIN_VALUE = 2.0f;
+ private WindowMagnificationSettingsCallback mCallback;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ MagnificationSize.NONE,
+ MagnificationSize.SMALL,
+ MagnificationSize.MEDIUM,
+ MagnificationSize.LARGE,
+ })
+ /** Denotes the Magnification size type. */
+ @interface MagnificationSize {
+ int NONE = 0;
+ int SMALL = 1;
+ int MEDIUM = 2;
+ int LARGE = 3;
+ }
+
+ @VisibleForTesting
+ WindowMagnificationSettings(Context context, WindowMagnificationSettingsCallback callback,
+ SfVsyncFrameCallbackProvider sfVsyncFrameProvider) {
+ mContext = context;
+ mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
+ mWindowManager = mContext.getSystemService(WindowManager.class);
+ mSfVsyncFrameProvider = sfVsyncFrameProvider;
+ mCallback = callback;
+
+ mAllowDiagonalScrolling = Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_ALLOW_DIAGONAL_SCROLLING, 0,
+ UserHandle.USER_CURRENT) == 1;
+
+ float scale = Settings.Secure.getFloatForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, 0,
+ UserHandle.USER_CURRENT);
+
+ mSettingView = (LinearLayout) View.inflate(context,
+ R.layout.window_magnification_settings_view, null);
+
+ mSettingView.setClickable(true);
+ mSettingView.setFocusable(true);
+ mSettingView.setOnTouchListener(this::onTouch);
+
+ mPanelView = mSettingView.findViewById(R.id.magnifier_panel_view);
+ mSmallButton = mSettingView.findViewById(R.id.magnifier_small_button);
+ mMediumButton = mSettingView.findViewById(R.id.magnifier_medium_button);
+ mLargeButton = mSettingView.findViewById(R.id.magnifier_large_button);
+ mCloseButton = mSettingView.findViewById(R.id.magnifier_close_button);
+ mEditButton = mSettingView.findViewById(R.id.magnifier_edit_button);
+ mChangeModeButton = mSettingView.findViewById(R.id.magnifier_full_button);
+
+ mZoomSeekbar = mSettingView.findViewById(R.id.magnifier_zoom_seekbar);
+ mZoomSeekbar.setOnSeekBarChangeListener(new ZoomSeekbarChangeListener());
+ setSeekbarProgress(scale);
+ mAllowDiagonalScrollingSwitch =
+ (Switch) mSettingView.findViewById(R.id.magnifier_horizontal_lock_switch);
+ mAllowDiagonalScrollingSwitch.setChecked(mAllowDiagonalScrolling);
+ mAllowDiagonalScrollingSwitch.setOnCheckedChangeListener((view, checked) -> {
+ toggleDiagonalScrolling();
+ });
+
+ mParams = createLayoutParams(context);
+ applyResourcesValues();
+
+ mSmallButton.setAccessibilityDelegate(mButtonDelegate);
+ mSmallButton.setOnClickListener(mButtonClickListener);
+
+ mMediumButton.setAccessibilityDelegate(mButtonDelegate);
+ mMediumButton.setOnClickListener(mButtonClickListener);
+
+ mLargeButton.setAccessibilityDelegate(mButtonDelegate);
+ mLargeButton.setOnClickListener(mButtonClickListener);
+
+ mCloseButton.setAccessibilityDelegate(mButtonDelegate);
+ mCloseButton.setOnClickListener(mButtonClickListener);
+
+ mChangeModeButton.setAccessibilityDelegate(mButtonDelegate);
+ mChangeModeButton.setOnClickListener(mButtonClickListener);
+
+ mEditButton.setAccessibilityDelegate(mButtonDelegate);
+ mEditButton.setOnClickListener(mButtonClickListener);
+
+ mWindowInsetChangeRunnable = this::onWindowInsetChanged;
+ mSettingView.setOnApplyWindowInsetsListener((v, insets) -> {
+ // Adds a pending post check to avoiding redundant calculation because this callback
+ // is sent frequently when the switch icon window dragged by the users.
+ if (!mSettingView.getHandler().hasCallbacks(mWindowInsetChangeRunnable)) {
+ mSettingView.getHandler().post(mWindowInsetChangeRunnable);
+ }
+ return v.onApplyWindowInsets(insets);
+ });
+
+ mGestureDetector = new MagnificationGestureDetector(context,
+ context.getMainThreadHandler(), this);
+ }
+
+ private class ZoomSeekbarChangeListener implements SeekBar.OnSeekBarChangeListener {
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ float scale = progress * A11Y_CHANGE_SCALE_DIFFERENCE + A11Y_SCALE_MIN_VALUE;
+ Settings.Secure.putFloatForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, scale,
+ UserHandle.USER_CURRENT);
+ mCallback.onMagnifierScale(scale);
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ // Do nothing
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ // Do nothing
+ }
+ }
+
+ private CharSequence formatContentDescription(int viewId) {
+ if (viewId == R.id.magnifier_small_button) {
+ return mContext.getResources().getString(
+ R.string.accessibility_magnification_small);
+ } else if (viewId == R.id.magnifier_medium_button) {
+ return mContext.getResources().getString(
+ R.string.accessibility_magnification_medium);
+ } else if (viewId == R.id.magnifier_large_button) {
+ return mContext.getResources().getString(
+ R.string.accessibility_magnification_large);
+ } else if (viewId == R.id.magnifier_close_button) {
+ return mContext.getResources().getString(
+ R.string.accessibility_magnification_close);
+ } else if (viewId == R.id.magnifier_edit_button) {
+ return mContext.getResources().getString(
+ R.string.accessibility_resize);
+ } else {
+ return mContext.getResources().getString(
+ R.string.magnification_mode_switch_description);
+ }
+ }
+
+ private void applyResourcesValues() {
+ final int padding = mContext.getResources().getDimensionPixelSize(
+ R.dimen.magnification_switch_button_padding);
+
+ PorterDuffColorFilter filter = new PorterDuffColorFilter(mContext.getColor(
+ R.color.accessibility_magnifier_icon_color), PorterDuff.Mode.SRC_ATOP);
+
+ mSmallButton.setImageResource(R.drawable.ic_magnification_menu_small);
+ mSmallButton.setColorFilter(filter);
+ mSmallButton.setBackgroundResource(
+ R.drawable.accessibility_magnification_setting_view_btn_bg);
+ mSmallButton.setPadding(padding, padding, padding, padding);
+
+ mMediumButton.setImageResource(R.drawable.ic_magnification_menu_medium);
+ mMediumButton.setColorFilter(filter);
+ mMediumButton.setBackgroundResource(
+ R.drawable.accessibility_magnification_setting_view_btn_bg);
+ mMediumButton.setPadding(padding, padding, padding, padding);
+
+ mLargeButton.setImageResource(R.drawable.ic_magnification_menu_large);
+ mLargeButton.setColorFilter(filter);
+ mLargeButton.setBackgroundResource(
+ R.drawable.accessibility_magnification_setting_view_btn_bg);
+ mLargeButton.setPadding(padding, padding, padding, padding);
+
+ mChangeModeButton.setImageResource(R.drawable.ic_open_in_new_fullscreen);
+ mChangeModeButton.setColorFilter(filter);
+ mChangeModeButton.setBackgroundResource(
+ R.drawable.accessibility_magnification_setting_view_btn_bg);
+ mChangeModeButton.setPadding(padding, padding, padding, padding);
+
+ mCloseButton.setBackgroundResource(
+ R.drawable.accessibility_magnification_setting_view_btn_bg);
+ mEditButton.setBackgroundResource(
+ R.drawable.accessibility_magnification_setting_view_btn_bg);
+ }
+
+ private final AccessibilityDelegate mButtonDelegate = new AccessibilityDelegate() {
+ @Override
+ public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(host, info);
+
+ info.setContentDescription(formatContentDescription(host.getId()));
+ final AccessibilityAction clickAction = new AccessibilityAction(
+ AccessibilityAction.ACTION_CLICK.getId(), mContext.getResources().getString(
+ R.string.magnification_mode_switch_click_label));
+ info.addAction(clickAction);
+ info.setClickable(true);
+ info.addAction(new AccessibilityAction(R.id.accessibility_action_move_up,
+ mContext.getString(R.string.accessibility_control_move_up)));
+ info.addAction(new AccessibilityAction(R.id.accessibility_action_move_down,
+ mContext.getString(R.string.accessibility_control_move_down)));
+ info.addAction(new AccessibilityAction(R.id.accessibility_action_move_left,
+ mContext.getString(R.string.accessibility_control_move_left)));
+ info.addAction(new AccessibilityAction(R.id.accessibility_action_move_right,
+ mContext.getString(R.string.accessibility_control_move_right)));
+ }
+
+ @Override
+ public boolean performAccessibilityAction(View host, int action, Bundle args) {
+ if (performA11yAction(host, action)) {
+ return true;
+ }
+ return super.performAccessibilityAction(host, action, args);
+ }
+
+ private boolean performA11yAction(View view, int action) {
+ final Rect windowBounds = mWindowManager.getCurrentWindowMetrics().getBounds();
+ if (action == AccessibilityAction.ACTION_CLICK.getId()) {
+ handleSingleTap(view);
+ } else if (action == R.id.accessibility_action_move_up) {
+ moveButton(0, -windowBounds.height());
+ } else if (action == R.id.accessibility_action_move_down) {
+ moveButton(0, windowBounds.height());
+ } else if (action == R.id.accessibility_action_move_left) {
+ moveButton(-windowBounds.width(), 0);
+ } else if (action == R.id.accessibility_action_move_right) {
+ moveButton(windowBounds.width(), 0);
+ } else {
+ return false;
+ }
+ return true;
+ }
+ };
+
+ private void applyResourcesValuesWithDensityChanged() {
+ if (mIsVisible) {
+ // Reset button to make its window layer always above the mirror window.
+ hideSettingPanel();
+ showSettingPanel(false);
+ }
+ }
+
+ private boolean onTouch(View v, MotionEvent event) {
+ if (!mIsVisible) {
+ return false;
+ }
+ return mGestureDetector.onTouch(v, event);
+ }
+
+ private View.OnClickListener mButtonClickListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ int id = view.getId();
+ if (id == R.id.magnifier_small_button) {
+ setMagnifierSize(MagnificationSize.SMALL);
+ } else if (id == R.id.magnifier_medium_button) {
+ setMagnifierSize(MagnificationSize.MEDIUM);
+ } else if (id == R.id.magnifier_large_button) {
+ setMagnifierSize(MagnificationSize.LARGE);
+ } else if (id == R.id.magnifier_edit_button) {
+ editMagnifierSizeMode(true);
+ } else if (id == R.id.magnifier_close_button) {
+ hideSettingPanel();
+ } else if (id == R.id.magnifier_full_button) {
+ hideSettingPanel();
+ toggleMagnificationMode();
+ } else {
+ hideSettingPanel();
+ }
+ }
+ };
+
+ @Override
+ public boolean onSingleTap(View view) {
+ mSingleTapDetected = true;
+ handleSingleTap(view);
+ return true;
+ }
+
+ @Override
+ public boolean onDrag(View v, float offsetX, float offsetY) {
+ moveButton(offsetX, offsetY);
+ return true;
+ }
+
+ @Override
+ public boolean onStart(float x, float y) {
+ return true;
+ }
+
+ @Override
+ public boolean onFinish(float xOffset, float yOffset) {
+ if (!mSingleTapDetected) {
+ showSettingPanel();
+ }
+ mSingleTapDetected = false;
+ return true;
+ }
+
+ @VisibleForTesting
+ public ViewGroup getSettingView() {
+ return mSettingView;
+ }
+
+ private void moveButton(float offsetX, float offsetY) {
+ mSfVsyncFrameProvider.postFrameCallback(l -> {
+ mParams.x += offsetX;
+ mParams.y += offsetY;
+ updateButtonViewLayoutIfNeeded();
+ });
+ }
+
+ public void hideSettingPanel() {
+ if (!mIsVisible) {
+ return;
+ }
+
+ // Reset button status.
+ mWindowManager.removeView(mSettingView);
+ mIsVisible = false;
+ mParams.x = 0;
+ mParams.y = 0;
+
+ mContext.unregisterReceiver(mScreenOffReceiver);
+ }
+
+ public void showSettingPanel() {
+ showSettingPanel(true);
+ }
+
+ public void setScaleSeekbar(float scale) {
+ setSeekbarProgress(scale);
+ }
+
+ private void toggleMagnificationMode() {
+ mCallback.onModeSwitch(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
+ }
+
+ /**
+ * Shows magnification panel for set window magnification.
+ * When the panel is going to be visible by calling this method, the layout position can be
+ * reset depending on the flag.
+ *
+ * @param resetPosition if the button position needs be reset
+ */
+ private void showSettingPanel(boolean resetPosition) {
+ if (!mIsVisible) {
+ if (resetPosition) {
+ mDraggableWindowBounds.set(getDraggableWindowBounds());
+ mParams.x = mDraggableWindowBounds.right;
+ mParams.y = mDraggableWindowBounds.bottom;
+ }
+
+ mWindowManager.addView(mSettingView, mParams);
+
+ // Exclude magnification switch button from system gesture area.
+ setSystemGestureExclusion();
+ mIsVisible = true;
+ }
+ mContext.registerReceiver(mScreenOffReceiver, new IntentFilter(Intent.ACTION_SCREEN_OFF));
+ }
+
+ private final BroadcastReceiver mScreenOffReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ hideSettingPanel();
+ }
+ };
+
+ private void setSeekbarProgress(float scale) {
+ int index = (int) ((scale - A11Y_SCALE_MIN_VALUE) / A11Y_CHANGE_SCALE_DIFFERENCE);
+ if (index < 0) {
+ index = 0;
+ }
+ mZoomSeekbar.setProgress(index);
+ }
+
+ void onConfigurationChanged(int configDiff) {
+ if ((configDiff & ActivityInfo.CONFIG_UI_MODE) != 0) {
+ applyResourcesValues();
+ return;
+ }
+ if ((configDiff & ActivityInfo.CONFIG_ORIENTATION) != 0) {
+ final Rect previousDraggableBounds = new Rect(mDraggableWindowBounds);
+ mDraggableWindowBounds.set(getDraggableWindowBounds());
+ // Keep the Y position with the same height ratio before the window bounds and
+ // draggable bounds are changed.
+ final float windowHeightFraction = (float) (mParams.y - previousDraggableBounds.top)
+ / previousDraggableBounds.height();
+ mParams.y = (int) (windowHeightFraction * mDraggableWindowBounds.height())
+ + mDraggableWindowBounds.top;
+ return;
+ }
+ if ((configDiff & ActivityInfo.CONFIG_DENSITY) != 0) {
+ applyResourcesValuesWithDensityChanged();
+ return;
+ }
+ if ((configDiff & ActivityInfo.CONFIG_LOCALE) != 0) {
+ updateAccessibilityWindowTitle();
+ return;
+ }
+ }
+
+ private void onWindowInsetChanged() {
+ final Rect newBounds = getDraggableWindowBounds();
+ if (mDraggableWindowBounds.equals(newBounds)) {
+ return;
+ }
+ mDraggableWindowBounds.set(newBounds);
+ }
+
+ private void updateButtonViewLayoutIfNeeded() {
+ if (mIsVisible) {
+ mParams.x = MathUtils.constrain(mParams.x, mDraggableWindowBounds.left,
+ mDraggableWindowBounds.right);
+ mParams.y = MathUtils.constrain(mParams.y, mDraggableWindowBounds.top,
+ mDraggableWindowBounds.bottom);
+ mWindowManager.updateViewLayout(mSettingView, mParams);
+ }
+ }
+
+ private void updateAccessibilityWindowTitle() {
+ mParams.accessibilityTitle = getAccessibilityWindowTitle(mContext);
+ if (mIsVisible) {
+ mWindowManager.updateViewLayout(mSettingView, mParams);
+ }
+ }
+
+ private void handleSingleTap(View view) {
+ int id = view.getId();
+ if (id == R.id.magnifier_small_button) {
+ setMagnifierSize(MagnificationSize.SMALL);
+ } else if (id == R.id.magnifier_medium_button) {
+ setMagnifierSize(MagnificationSize.MEDIUM);
+ } else if (id == R.id.magnifier_large_button) {
+ setMagnifierSize(MagnificationSize.LARGE);
+ } else if (id == R.id.magnifier_full_button) {
+ hideSettingPanel();
+ toggleMagnificationMode();
+ } else {
+ hideSettingPanel();
+ }
+ }
+
+ public void editMagnifierSizeMode(boolean enable) {
+ setEditMagnifierSizeMode(enable);
+ hideSettingPanel();
+ }
+
+ private void setMagnifierSize(@MagnificationSize int index) {
+ mCallback.onSetMagnifierSize(index);
+ }
+
+ private void toggleDiagonalScrolling() {
+ boolean enabled = Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_ALLOW_DIAGONAL_SCROLLING, 0,
+ UserHandle.USER_CURRENT) == 1;
+
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_ALLOW_DIAGONAL_SCROLLING, enabled ? 0 : 1,
+ UserHandle.USER_CURRENT);
+
+ mCallback.onSetDiagonalScrolling(!enabled);
+ }
+
+ private void setEditMagnifierSizeMode(boolean enable) {
+ mCallback.onEditMagnifierSizeMode(enable);
+ }
+
+ private static LayoutParams createLayoutParams(Context context) {
+ final LayoutParams params = new LayoutParams(
+ LayoutParams.WRAP_CONTENT,
+ LayoutParams.WRAP_CONTENT,
+ LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY,
+ LayoutParams.FLAG_NOT_FOCUSABLE,
+ PixelFormat.TRANSPARENT);
+ params.gravity = Gravity.TOP | Gravity.START;
+ params.accessibilityTitle = getAccessibilityWindowTitle(context);
+ params.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+ return params;
+ }
+
+ private Rect getDraggableWindowBounds() {
+ final int layoutMargin = mContext.getResources().getDimensionPixelSize(
+ R.dimen.magnification_switch_button_margin);
+ final WindowMetrics windowMetrics = mWindowManager.getCurrentWindowMetrics();
+ final Insets windowInsets = windowMetrics.getWindowInsets().getInsetsIgnoringVisibility(
+ WindowInsets.Type.systemBars() | WindowInsets.Type.displayCutout());
+ final Rect boundRect = new Rect(windowMetrics.getBounds());
+ boundRect.offsetTo(0, 0);
+ boundRect.inset(0, 0, mParams.width, mParams.height);
+ boundRect.inset(windowInsets);
+ boundRect.inset(layoutMargin, layoutMargin);
+
+ return boundRect;
+ }
+
+ private static String getAccessibilityWindowTitle(Context context) {
+ return context.getString(com.android.internal.R.string.android_system_label);
+ }
+
+ private void setSystemGestureExclusion() {
+ mSettingView.post(() -> {
+ mSettingView.setSystemGestureExclusionRects(
+ Collections.singletonList(
+ new Rect(0, 0, mSettingView.getWidth(), mSettingView.getHeight())));
+ });
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettingsCallback.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettingsCallback.java
new file mode 100644
index 000000000000..22ec65001101
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettingsCallback.java
@@ -0,0 +1,64 @@
+/*
+ * 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.accessibility;
+
+import static com.android.systemui.accessibility.WindowMagnificationSettings.MagnificationSize;
+
+/**
+ * A callback to inform WindowMagnificationController about
+ * the setting value change or the user interaction.
+ */
+public interface WindowMagnificationSettingsCallback {
+
+ /**
+ * Called when change magnification size.
+ *
+ * @param index Magnification size index.
+ * 0 : MagnificationSize.NONE, 1 : MagnificationSize.SMALL,
+ * 2 : MagnificationSize.MEDIUM, 3: MagnificationSize.LARGE
+ */
+ void onSetMagnifierSize(@MagnificationSize int index);
+
+ /**
+ * Called when set allow diagonal scrolling.
+ *
+ * @param enable Allow diagonal scrolling enable value.
+ */
+ void onSetDiagonalScrolling(boolean enable);
+
+ /**
+ * Called when change magnification size on free mode.
+ *
+ * @param enable Free mode enable value.
+ */
+ void onEditMagnifierSizeMode(boolean enable);
+
+ /**
+ * Called when set magnification scale.
+ *
+ * @param scale Magnification scale value.
+ */
+ void onMagnifierScale(float scale);
+
+ /**
+ * Called when magnification mode changed.
+ *
+ * @param newMode Magnification mode
+ * 1 : ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN, 2 : ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW
+ */
+ void onModeSwitch(int newMode);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnifierCallback.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnifierCallback.java
index c334ca664c46..19caaf431e0a 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnifierCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnifierCallback.java
@@ -60,4 +60,12 @@ interface WindowMagnifierCallback {
* @param displayId The logical display id.
*/
void onMove(int displayId);
+
+ /**
+ * Called when magnification mode changed.
+ *
+ * @param displayId The logical display id.
+ * @param newMode Magnification mode.
+ */
+ void onModeSwitch(int displayId, int newMode);
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt
index 55611f7d7ada..e60d4e10f957 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt
@@ -18,7 +18,7 @@ package com.android.systemui.biometrics
import android.content.Context
import android.graphics.drawable.Drawable
import android.util.Log
-import android.widget.ImageView
+import com.airbnb.lottie.LottieAnimationView
import com.android.systemui.R
import com.android.systemui.biometrics.AuthBiometricView.BiometricState
import com.android.systemui.biometrics.AuthBiometricView.STATE_AUTHENTICATED
@@ -33,8 +33,8 @@ private const val TAG = "AuthBiometricFaceIconController"
/** Face only icon animator for BiometricPrompt. */
class AuthBiometricFaceIconController(
- context: Context,
- iconView: ImageView
+ context: Context,
+ iconView: LottieAnimationView
) : AuthIconController(context, iconView) {
// false = dark to light, true = light to dark
@@ -76,44 +76,44 @@ class AuthBiometricFaceIconController(
if (newState == STATE_AUTHENTICATING_ANIMATING_IN) {
showStaticDrawable(R.drawable.face_dialog_pulse_dark_to_light)
iconView.contentDescription = context.getString(
- R.string.biometric_dialog_face_icon_description_authenticating
+ R.string.biometric_dialog_face_icon_description_authenticating
)
} else if (newState == STATE_AUTHENTICATING) {
startPulsing()
iconView.contentDescription = context.getString(
- R.string.biometric_dialog_face_icon_description_authenticating
+ R.string.biometric_dialog_face_icon_description_authenticating
)
} else if (oldState == STATE_PENDING_CONFIRMATION && newState == STATE_AUTHENTICATED) {
animateIconOnce(R.drawable.face_dialog_dark_to_checkmark)
iconView.contentDescription = context.getString(
- R.string.biometric_dialog_face_icon_description_confirmed
+ R.string.biometric_dialog_face_icon_description_confirmed
)
} else if (lastStateIsErrorIcon && newState == STATE_IDLE) {
animateIconOnce(R.drawable.face_dialog_error_to_idle)
iconView.contentDescription = context.getString(
- R.string.biometric_dialog_face_icon_description_idle
+ R.string.biometric_dialog_face_icon_description_idle
)
} else if (lastStateIsErrorIcon && newState == STATE_AUTHENTICATED) {
animateIconOnce(R.drawable.face_dialog_dark_to_checkmark)
iconView.contentDescription = context.getString(
- R.string.biometric_dialog_face_icon_description_authenticated
+ R.string.biometric_dialog_face_icon_description_authenticated
)
} else if (newState == STATE_ERROR && oldState != STATE_ERROR) {
animateIconOnce(R.drawable.face_dialog_dark_to_error)
} else if (oldState == STATE_AUTHENTICATING && newState == STATE_AUTHENTICATED) {
animateIconOnce(R.drawable.face_dialog_dark_to_checkmark)
iconView.contentDescription = context.getString(
- R.string.biometric_dialog_face_icon_description_authenticated
+ R.string.biometric_dialog_face_icon_description_authenticated
)
} else if (newState == STATE_PENDING_CONFIRMATION) {
animateIconOnce(R.drawable.face_dialog_wink_from_dark)
iconView.contentDescription = context.getString(
- R.string.biometric_dialog_face_icon_description_authenticated
+ R.string.biometric_dialog_face_icon_description_authenticated
)
} else if (newState == STATE_IDLE) {
showStaticDrawable(R.drawable.face_dialog_idle_static)
iconView.contentDescription = context.getString(
- R.string.biometric_dialog_face_icon_description_idle
+ R.string.biometric_dialog_face_icon_description_idle
)
} else {
Log.w(TAG, "Unhandled state: $newState")
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt
index 3e4e573c9531..40d1eff2a887 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt
@@ -16,42 +16,43 @@
package com.android.systemui.biometrics
+import android.annotation.RawRes
import android.content.Context
-import android.graphics.drawable.Drawable
-import android.widget.ImageView
+import com.airbnb.lottie.LottieAnimationView
import com.android.systemui.R
import com.android.systemui.biometrics.AuthBiometricView.BiometricState
-import com.android.systemui.biometrics.AuthBiometricView.STATE_PENDING_CONFIRMATION
import com.android.systemui.biometrics.AuthBiometricView.STATE_AUTHENTICATED
import com.android.systemui.biometrics.AuthBiometricView.STATE_ERROR
import com.android.systemui.biometrics.AuthBiometricView.STATE_HELP
+import com.android.systemui.biometrics.AuthBiometricView.STATE_PENDING_CONFIRMATION
/** Face/Fingerprint combined icon animator for BiometricPrompt. */
class AuthBiometricFingerprintAndFaceIconController(
- context: Context,
- iconView: ImageView
+ context: Context,
+ iconView: LottieAnimationView
) : AuthBiometricFingerprintIconController(context, iconView) {
override val actsAsConfirmButton: Boolean = true
override fun shouldAnimateForTransition(
- @BiometricState oldState: Int,
- @BiometricState newState: Int
+ @BiometricState oldState: Int,
+ @BiometricState newState: Int
): Boolean = when (newState) {
STATE_PENDING_CONFIRMATION -> true
STATE_AUTHENTICATED -> false
else -> super.shouldAnimateForTransition(oldState, newState)
}
+ @RawRes
override fun getAnimationForTransition(
- @BiometricState oldState: Int,
- @BiometricState newState: Int
- ): Drawable? = when (newState) {
+ @BiometricState oldState: Int,
+ @BiometricState newState: Int
+ ): Int? = when (newState) {
STATE_PENDING_CONFIRMATION -> {
if (oldState == STATE_ERROR || oldState == STATE_HELP) {
- context.getDrawable(R.drawable.fingerprint_dialog_error_to_unlock)
+ R.raw.fingerprint_dialogue_error_to_unlock_lottie
} else {
- context.getDrawable(R.drawable.fingerprint_dialog_fp_to_unlock)
+ R.raw.fingerprint_dialogue_fingerprint_to_unlock_lottie
}
}
STATE_AUTHENTICATED -> null
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
index 606a73a6ac38..589ec0e72b3b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
@@ -16,10 +16,9 @@
package com.android.systemui.biometrics
+import android.annotation.RawRes
import android.content.Context
-import android.graphics.drawable.AnimatedVectorDrawable
-import android.graphics.drawable.Drawable
-import android.widget.ImageView
+import com.airbnb.lottie.LottieAnimationView
import com.android.systemui.R
import com.android.systemui.biometrics.AuthBiometricView.BiometricState
import com.android.systemui.biometrics.AuthBiometricView.STATE_AUTHENTICATED
@@ -32,42 +31,42 @@ import com.android.systemui.biometrics.AuthBiometricView.STATE_PENDING_CONFIRMAT
/** Fingerprint only icon animator for BiometricPrompt. */
open class AuthBiometricFingerprintIconController(
- context: Context,
- iconView: ImageView
+ context: Context,
+ iconView: LottieAnimationView
) : AuthIconController(context, iconView) {
- var iconLayoutParamsSize = 0
+ var iconLayoutParamSize: Pair<Int, Int> = Pair(1, 1)
set(value) {
if (field == value) {
return
}
- iconView.layoutParams.width = value
- iconView.layoutParams.height = value
+ iconView.layoutParams.width = value.first
+ iconView.layoutParams.height = value.second
field = value
}
init {
- iconLayoutParamsSize = context.resources.getDimensionPixelSize(
- R.dimen.biometric_dialog_fingerprint_icon_size
- )
+ iconLayoutParamSize = Pair(context.resources.getDimensionPixelSize(
+ R.dimen.biometric_dialog_fingerprint_icon_width),
+ context.resources.getDimensionPixelSize(
+ R.dimen.biometric_dialog_fingerprint_icon_height))
}
override fun updateIcon(@BiometricState lastState: Int, @BiometricState newState: Int) {
val icon = getAnimationForTransition(lastState, newState) ?: return
- iconView.setImageDrawable(icon)
+ if (!(lastState == STATE_AUTHENTICATING_ANIMATING_IN && newState == STATE_AUTHENTICATING)) {
+ iconView.setAnimation(icon)
+ }
val iconContentDescription = getIconContentDescription(newState)
if (iconContentDescription != null) {
iconView.contentDescription = iconContentDescription
}
- (icon as? AnimatedVectorDrawable)?.apply {
- reset()
- if (shouldAnimateForTransition(lastState, newState)) {
- forceAnimationOnUI()
- start()
- }
+ iconView.frame = 0
+ if (shouldAnimateForTransition(lastState, newState)) {
+ iconView.playAnimation()
}
}
@@ -86,8 +85,8 @@ open class AuthBiometricFingerprintIconController(
}
protected open fun shouldAnimateForTransition(
- @BiometricState oldState: Int,
- @BiometricState newState: Int
+ @BiometricState oldState: Int,
+ @BiometricState newState: Int
) = when (newState) {
STATE_HELP,
STATE_ERROR -> true
@@ -97,24 +96,27 @@ open class AuthBiometricFingerprintIconController(
else -> false
}
+ @RawRes
protected open fun getAnimationForTransition(
- @BiometricState oldState: Int,
- @BiometricState newState: Int
- ): Drawable? {
+ @BiometricState oldState: Int,
+ @BiometricState newState: Int
+ ): Int? {
val id = when (newState) {
STATE_HELP,
- STATE_ERROR -> R.drawable.fingerprint_dialog_fp_to_error
+ STATE_ERROR -> {
+ R.raw.fingerprint_dialogue_fingerprint_to_error_lottie
+ }
STATE_AUTHENTICATING_ANIMATING_IN,
STATE_AUTHENTICATING -> {
if (oldState == STATE_ERROR || oldState == STATE_HELP) {
- R.drawable.fingerprint_dialog_error_to_fp
+ R.raw.fingerprint_dialogue_error_to_fingerprint_lottie
} else {
- R.drawable.fingerprint_dialog_fp_to_error
+ R.raw.fingerprint_dialogue_fingerprint_to_error_lottie
}
}
- STATE_AUTHENTICATED -> R.drawable.fingerprint_dialog_fp_to_error
+ STATE_AUTHENTICATED -> R.raw.fingerprint_dialogue_fingerprint_to_error_lottie
else -> return null
}
- return if (id != null) context.getDrawable(id) else null
+ return if (id != null) return id else null
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.kt
index 24046f08e489..31baa0ff1154 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.kt
@@ -90,8 +90,9 @@ open class AuthBiometricFingerprintView(
fun updateOverrideIconLayoutParamsSize() {
udfpsAdapter?.let {
- (mIconController as? AuthBiometricFingerprintIconController)?.iconLayoutParamsSize =
- it.getSensorDiameter(scaleFactorProvider?.provide() ?: 1.0f)
+ val sensorDiameter = it.getSensorDiameter(scaleFactorProvider?.provide() ?: 1.0f)
+ (mIconController as? AuthBiometricFingerprintIconController)?.iconLayoutParamSize =
+ Pair(sensorDiameter, sensorDiameter)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricIconController.kt
index ce5e600e6a77..15f487b05630 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricIconController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricIconController.kt
@@ -22,15 +22,15 @@ import android.graphics.drawable.Animatable2
import android.graphics.drawable.AnimatedVectorDrawable
import android.graphics.drawable.Drawable
import android.util.Log
-import android.widget.ImageView
+import com.airbnb.lottie.LottieAnimationView
import com.android.systemui.biometrics.AuthBiometricView.BiometricState
private const val TAG = "AuthIconController"
/** Controller for animating the BiometricPrompt icon/affordance. */
abstract class AuthIconController(
- protected val context: Context,
- protected val iconView: ImageView
+ protected val context: Context,
+ protected val iconView: LottieAnimationView
) : Animatable2.AnimationCallback() {
/** If this controller should ignore events and pause. */
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
index d7ae9ef841bf..e866b9c0bb25 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
@@ -41,14 +41,14 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityManager;
import android.widget.Button;
-import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.R;
-import com.android.systemui.util.LargeScreenUtils;
+
+import com.airbnb.lottie.LottieAnimationView;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -133,7 +133,7 @@ public abstract class AuthBiometricView extends LinearLayout {
private TextView mSubtitleView;
private TextView mDescriptionView;
private View mIconHolderView;
- protected ImageView mIconView;
+ protected LottieAnimationView mIconView;
protected TextView mIndicatorView;
@VisibleForTesting @NonNull AuthIconController mIconController;
@@ -824,25 +824,12 @@ public abstract class AuthBiometricView extends LinearLayout {
return new AuthDialog.LayoutParams(width, totalHeight);
}
- private boolean isLargeDisplay() {
- return LargeScreenUtils.shouldUseSplitNotificationShade(getResources());
- }
-
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int width = MeasureSpec.getSize(widthMeasureSpec);
final int height = MeasureSpec.getSize(heightMeasureSpec);
- final boolean isLargeDisplay = isLargeDisplay();
-
- final int newWidth;
- if (isLargeDisplay) {
- // TODO(b/201811580): Unless we can come up with a one-size-fits-all equation, we may
- // want to consider moving this to an overlay.
- newWidth = 2 * Math.min(width, height) / 3;
- } else {
- newWidth = Math.min(width, height);
- }
+ final int newWidth = Math.min(width, height);
// Use "newWidth" instead, so the landscape dialog width is the same as the portrait
// width.
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index cf50f7f8524b..d1589b23454d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -517,8 +517,6 @@ public class UdfpsController implements DozeReceiver {
scaledMajor);
Log.v(TAG, "onTouch | finger down: " + touchInfo);
mTouchLogTime = mSystemClock.elapsedRealtime();
- mPowerManager.userActivity(mSystemClock.uptimeMillis(),
- PowerManager.USER_ACTIVITY_EVENT_TOUCH, 0);
handled = true;
} else if (sinceLastLog >= MIN_TOUCH_LOG_INTERVAL) {
Log.v(TAG, "onTouch | finger move: " + touchInfo);
@@ -846,6 +844,9 @@ public class UdfpsController implements DozeReceiver {
return;
}
mLatencyTracker.onActionStart(LatencyTracker.ACTION_UDFPS_ILLUMINATE);
+ // Refresh screen timeout and boost process priority if possible.
+ mPowerManager.userActivity(mSystemClock.uptimeMillis(),
+ PowerManager.USER_ACTIVITY_EVENT_TOUCH, 0);
if (!mOnFingerDown) {
playStartHaptic();
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
index 47ea27ff8ccb..08393386c27f 100644
--- a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
@@ -30,6 +30,8 @@ import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
+import androidx.core.graphics.ColorUtils;
+
import com.android.settingslib.Utils;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
@@ -42,7 +44,8 @@ import java.text.NumberFormat;
* @hide
*/
final class WirelessChargingLayout extends FrameLayout {
- private static final long RIPPLE_ANIMATION_DURATION = 1500;
+ private static final long CIRCLE_RIPPLE_ANIMATION_DURATION = 1500;
+ private static final long ROUNDED_BOX_RIPPLE_ANIMATION_DURATION = 1750;
private static final int SCRIM_COLOR = 0x4C000000;
private static final int SCRIM_FADE_DURATION = 300;
private RippleView mRippleView;
@@ -131,17 +134,30 @@ final class WirelessChargingLayout extends FrameLayout {
"backgroundColor", SCRIM_COLOR, Color.TRANSPARENT);
scrimFadeOutAnimator.setDuration(SCRIM_FADE_DURATION);
scrimFadeOutAnimator.setInterpolator(Interpolators.LINEAR);
- scrimFadeOutAnimator.setStartDelay(RIPPLE_ANIMATION_DURATION - SCRIM_FADE_DURATION);
+ scrimFadeOutAnimator.setStartDelay((rippleShape == RippleShape.CIRCLE
+ ? CIRCLE_RIPPLE_ANIMATION_DURATION : ROUNDED_BOX_RIPPLE_ANIMATION_DURATION)
+ - SCRIM_FADE_DURATION);
AnimatorSet animatorSetScrim = new AnimatorSet();
animatorSetScrim.playTogether(scrimFadeInAnimator, scrimFadeOutAnimator);
animatorSetScrim.start();
mRippleView = findViewById(R.id.wireless_charging_ripple);
mRippleView.setupShader(rippleShape);
+ if (mRippleView.getRippleShape() == RippleShape.ROUNDED_BOX) {
+ mRippleView.setDuration(ROUNDED_BOX_RIPPLE_ANIMATION_DURATION);
+ mRippleView.setSparkleStrength(0.22f);
+ int color = Utils.getColorAttr(mRippleView.getContext(),
+ android.R.attr.colorAccent).getDefaultColor();
+ mRippleView.setColor(ColorUtils.setAlphaComponent(color, 28));
+ } else {
+ mRippleView.setDuration(CIRCLE_RIPPLE_ANIMATION_DURATION);
+ mRippleView.setColor(Utils.getColorAttr(mRippleView.getContext(),
+ android.R.attr.colorAccent).getDefaultColor());
+ }
+
OnAttachStateChangeListener listener = new OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View view) {
- mRippleView.setDuration(RIPPLE_ANIMATION_DURATION);
mRippleView.startRipple();
mRippleView.removeOnAttachStateChangeListener(this);
}
@@ -232,13 +248,13 @@ final class WirelessChargingLayout extends FrameLayout {
int height = getMeasuredHeight();
mRippleView.setCenter(width * 0.5f, height * 0.5f);
if (mRippleView.getRippleShape() == RippleShape.ROUNDED_BOX) {
- mRippleView.setMaxSize(width * 1.5f, height * 1.5f);
+ // Those magic numbers are introduced for visual polish. This aspect ratio maps with
+ // the tablet's docking station.
+ mRippleView.setMaxSize(width * 1.36f, height * 1.46f);
} else {
float maxSize = Math.max(width, height);
mRippleView.setMaxSize(maxSize, maxSize);
}
- mRippleView.setColor(Utils.getColorAttr(mRippleView.getContext(),
- android.R.attr.colorAccent).getDefaultColor());
}
super.onLayout(changed, left, top, right, bottom);
diff --git a/packages/SystemUI/src/com/android/systemui/common/coroutine/ChannelExt.kt b/packages/SystemUI/src/com/android/systemui/common/coroutine/ChannelExt.kt
index 6f3beac2ac85..a0b19dc5c96e 100644
--- a/packages/SystemUI/src/com/android/systemui/common/coroutine/ChannelExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/coroutine/ChannelExt.kt
@@ -35,7 +35,7 @@ object ChannelExt {
* " - downstream canceled or failed.",
* it,
* )
- *}
+ * }
* ```
*/
fun <T> SendChannel<T>.trySendWithFailureLogging(
diff --git a/packages/SystemUI/src/com/android/systemui/common/data/model/Position.kt b/packages/SystemUI/src/com/android/systemui/common/shared/model/Position.kt
index 7c9df102ef1d..52f6167ba7ae 100644
--- a/packages/SystemUI/src/com/android/systemui/common/data/model/Position.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/shared/model/Position.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.common.data.model
+package com.android.systemui.common.shared.model
/** Models a two-dimensional position */
data class Position(
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index 6eb77bd32a71..42e6b02ea432 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -92,7 +92,7 @@ public class Flags {
* Whether the KeyguardBottomArea(View|Controller) should use the modern architecture or the old
* one.
*/
- public static final UnreleasedFlag MODERN_BOTTOM_AREA = new UnreleasedFlag(206, true);
+ public static final ReleasedFlag MODERN_BOTTOM_AREA = new ReleasedFlag(206, true);
public static final UnreleasedFlag LOCKSCREEN_CUSTOM_CLOCKS = new UnreleasedFlag(207);
@@ -151,8 +151,8 @@ public class Flags {
public static final ResourceBooleanFlag STATUS_BAR_USER_SWITCHER =
new ResourceBooleanFlag(602, R.bool.flag_user_switcher_chip);
- public static final UnreleasedFlag STATUS_BAR_LETTERBOX_APPEARANCE =
- new UnreleasedFlag(603, false);
+ public static final ReleasedFlag STATUS_BAR_LETTERBOX_APPEARANCE =
+ new ReleasedFlag(603, false);
public static final UnreleasedFlag NEW_STATUS_BAR_PIPELINE = new UnreleasedFlag(604, true);
@@ -237,6 +237,9 @@ public class Flags {
public static final UnreleasedFlag SCREENSHOT_REQUEST_PROCESSOR = new UnreleasedFlag(1300);
+ // 1400 - columbus, b/242800729
+ public static final UnreleasedFlag QUICK_TAP_IN_PCC = new UnreleasedFlag(1400);
+
// Pay no attention to the reflection behind the curtain.
// ========================== Curtain ==========================
// | |
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 430b59cd4027..56f1ac46a875 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -44,7 +44,6 @@ import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.data.repository.KeyguardRepositoryModule;
import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceModule;
-import com.android.systemui.keyguard.domain.usecase.KeyguardUseCaseModule;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -73,7 +72,6 @@ import dagger.Provides;
FalsingModule.class,
KeyguardQuickAffordanceModule.class,
KeyguardRepositoryModule.class,
- KeyguardUseCaseModule.class,
})
public class KeyguardModule {
/**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index 62cf1a624d9b..e52d9ee7b9d4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -18,7 +18,7 @@ package com.android.systemui.keyguard.data.repository
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
-import com.android.systemui.common.data.model.Position
+import com.android.systemui.common.shared.model.Position
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.policy.KeyguardStateController
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.kt
new file mode 100644
index 000000000000..ede50b068de3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.kt
@@ -0,0 +1,51 @@
+/*
+ * 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.keyguard.domain.interactor
+
+import com.android.systemui.common.shared.model.Position
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+/** Encapsulates business-logic specifically related to the keyguard bottom area. */
+@SysUISingleton
+class KeyguardBottomAreaInteractor
+@Inject
+constructor(
+ private val repository: KeyguardRepository,
+) {
+ /** Whether to animate the next doze mode transition. */
+ val animateDozingTransitions: Flow<Boolean> = repository.animateBottomAreaDozingTransitions
+ /** The amount of alpha for the UI components of the bottom area. */
+ val alpha: Flow<Float> = repository.bottomAreaAlpha
+ /** The position of the keyguard clock. */
+ val clockPosition: Flow<Position> = repository.clockPosition
+
+ fun setClockPosition(x: Int, y: Int) {
+ repository.setClockPosition(x, y)
+ }
+
+ fun setAlpha(alpha: Float) {
+ repository.setBottomAreaAlpha(alpha)
+ }
+
+ fun setAnimateDozingTransitions(animate: Boolean) {
+ repository.setAnimateDozingTransitions(animate)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveIsKeyguardShowingUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 11af123c1650..dccc94178ed5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveIsKeyguardShowingUseCase.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -15,25 +15,29 @@
*
*/
-package com.android.systemui.keyguard.domain.usecase
+package com.android.systemui.keyguard.domain.interactor
+import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
/**
- * Use-case for observing whether the keyguard is currently being shown.
- *
- * Note: this is also `true` when the lock-screen is occluded with an `Activity` "above" it in the
- * z-order (which is not really above the system UI window, but rather - the lock-screen becomes
- * invisible to reveal the "occluding activity").
+ * Encapsulates business-logic related to the keyguard but not to a more specific part within it.
*/
-class ObserveIsKeyguardShowingUseCase
+@SysUISingleton
+class KeyguardInteractor
@Inject
constructor(
- private val repository: KeyguardRepository,
+ repository: KeyguardRepository,
) {
- operator fun invoke(): Flow<Boolean> {
- return repository.isKeyguardShowing
- }
+ /**
+ * The amount of doze the system is in, where `1.0` is fully dozing and `0.0` is not dozing at
+ * all.
+ */
+ val dozeAmount: Flow<Float> = repository.dozeAmount
+ /** Whether the system is in doze mode. */
+ val isDozing: Flow<Boolean> = repository.isDozing
+ /** Whether the keyguard is showing ot not. */
+ val isKeyguardShowing: Flow<Boolean> = repository.isKeyguardShowing
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
new file mode 100644
index 000000000000..9a69e26488d9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -0,0 +1,136 @@
+/*
+ * 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.keyguard.domain.interactor
+
+import android.content.Intent
+import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
+import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
+import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceRegistry
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import javax.inject.Inject
+import kotlin.reflect.KClass
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+
+@SysUISingleton
+class KeyguardQuickAffordanceInteractor
+@Inject
+constructor(
+ private val keyguardInteractor: KeyguardInteractor,
+ private val registry: KeyguardQuickAffordanceRegistry<out KeyguardQuickAffordanceConfig>,
+ private val lockPatternUtils: LockPatternUtils,
+ private val keyguardStateController: KeyguardStateController,
+ private val userTracker: UserTracker,
+ private val activityStarter: ActivityStarter,
+) {
+ /** Returns an observable for the quick affordance at the given position. */
+ fun quickAffordance(
+ position: KeyguardQuickAffordancePosition
+ ): Flow<KeyguardQuickAffordanceModel> {
+ return combine(
+ quickAffordanceInternal(position),
+ keyguardInteractor.isDozing,
+ keyguardInteractor.isKeyguardShowing,
+ ) { affordance, isDozing, isKeyguardShowing ->
+ if (!isDozing && isKeyguardShowing) {
+ affordance
+ } else {
+ KeyguardQuickAffordanceModel.Hidden
+ }
+ }
+ }
+
+ /**
+ * Notifies that a quick affordance has been clicked by the user.
+ *
+ * @param configKey The configuration key corresponding to the [KeyguardQuickAffordanceModel] of
+ * the affordance that was clicked
+ * @param animationController An optional controller for the activity-launch animation
+ */
+ fun onQuickAffordanceClicked(
+ configKey: KClass<out KeyguardQuickAffordanceConfig>,
+ animationController: ActivityLaunchAnimator.Controller?,
+ ) {
+ @Suppress("UNCHECKED_CAST") val config = registry.get(configKey as KClass<Nothing>)
+ when (val result = config.onQuickAffordanceClicked(animationController)) {
+ is KeyguardQuickAffordanceConfig.OnClickedResult.StartActivity ->
+ launchQuickAffordance(
+ intent = result.intent,
+ canShowWhileLocked = result.canShowWhileLocked,
+ animationController = animationController
+ )
+ is KeyguardQuickAffordanceConfig.OnClickedResult.Handled -> Unit
+ }
+ }
+
+ private fun quickAffordanceInternal(
+ position: KeyguardQuickAffordancePosition
+ ): Flow<KeyguardQuickAffordanceModel> {
+ val configs = registry.getAll(position)
+ return combine(configs.map { config -> config.state }) { states ->
+ val index = states.indexOfFirst { it is KeyguardQuickAffordanceConfig.State.Visible }
+ if (index != -1) {
+ val visibleState = states[index] as KeyguardQuickAffordanceConfig.State.Visible
+ KeyguardQuickAffordanceModel.Visible(
+ configKey = configs[index]::class,
+ icon = visibleState.icon,
+ contentDescriptionResourceId = visibleState.contentDescriptionResourceId,
+ )
+ } else {
+ KeyguardQuickAffordanceModel.Hidden
+ }
+ }
+ }
+
+ private fun launchQuickAffordance(
+ intent: Intent,
+ canShowWhileLocked: Boolean,
+ animationController: ActivityLaunchAnimator.Controller?,
+ ) {
+ @LockPatternUtils.StrongAuthTracker.StrongAuthFlags
+ val strongAuthFlags =
+ lockPatternUtils.getStrongAuthForUser(userTracker.userHandle.identifier)
+ val needsToUnlockFirst =
+ when {
+ strongAuthFlags ==
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT -> true
+ !canShowWhileLocked && !keyguardStateController.isUnlocked -> true
+ else -> false
+ }
+ if (needsToUnlockFirst) {
+ activityStarter.postStartActivityDismissingKeyguard(
+ intent,
+ 0 /* delay */,
+ animationController
+ )
+ } else {
+ activityStarter.startActivity(
+ intent,
+ true /* dismissShade */,
+ animationController,
+ true /* showOverLockscreenWhenLocked */,
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt
index 411a2ca5ffe2..eff146984176 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt
@@ -42,21 +42,4 @@ sealed class KeyguardQuickAffordanceModel {
*/
@StringRes val contentDescriptionResourceId: Int,
) : KeyguardQuickAffordanceModel()
-
- companion object {
- fun from(
- state: KeyguardQuickAffordanceConfig.State?,
- configKey: KClass<out KeyguardQuickAffordanceConfig>,
- ): KeyguardQuickAffordanceModel {
- return when (state) {
- is KeyguardQuickAffordanceConfig.State.Visible ->
- Visible(
- configKey = configKey,
- icon = state.icon,
- contentDescriptionResourceId = state.contentDescriptionResourceId,
- )
- else -> Hidden
- }
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceModule.kt
index a7b38282d0aa..94024d4a0ace 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceModule.kt
@@ -25,5 +25,5 @@ interface KeyguardQuickAffordanceModule {
@Binds
fun keyguardQuickAffordanceRegistry(
impl: KeyguardQuickAffordanceRegistryImpl
- ): KeyguardQuickAffordanceRegistry
+ ): KeyguardQuickAffordanceRegistry<out KeyguardQuickAffordanceConfig>
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceRegistry.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceRegistry.kt
index 2c37f93de435..ad40ee7a0183 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceRegistry.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceRegistry.kt
@@ -22,9 +22,9 @@ import javax.inject.Inject
import kotlin.reflect.KClass
/** Central registry of all known quick affordance configs. */
-interface KeyguardQuickAffordanceRegistry {
- fun getAll(position: KeyguardQuickAffordancePosition): List<KeyguardQuickAffordanceConfig>
- fun get(configClass: KClass<out KeyguardQuickAffordanceConfig>): KeyguardQuickAffordanceConfig
+interface KeyguardQuickAffordanceRegistry<T : KeyguardQuickAffordanceConfig> {
+ fun getAll(position: KeyguardQuickAffordancePosition): List<T>
+ fun get(configClass: KClass<out T>): T
}
class KeyguardQuickAffordanceRegistryImpl
@@ -33,7 +33,7 @@ constructor(
homeControls: HomeControlsKeyguardQuickAffordanceConfig,
quickAccessWallet: QuickAccessWalletKeyguardQuickAffordanceConfig,
qrCodeScanner: QrCodeScannerKeyguardQuickAffordanceConfig,
-) : KeyguardQuickAffordanceRegistry {
+) : KeyguardQuickAffordanceRegistry<KeyguardQuickAffordanceConfig> {
private val configsByPosition =
mapOf(
KeyguardQuickAffordancePosition.BOTTOM_START to
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/LaunchKeyguardQuickAffordanceUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/LaunchKeyguardQuickAffordanceUseCase.kt
deleted file mode 100644
index 3d60399cf522..000000000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/LaunchKeyguardQuickAffordanceUseCase.kt
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.domain.usecase
-
-import android.content.Intent
-import com.android.internal.widget.LockPatternUtils
-import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.StrongAuthFlags
-import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.settings.UserTracker
-import com.android.systemui.statusbar.policy.KeyguardStateController
-import javax.inject.Inject
-
-/** Defines interface for classes that can launch a quick affordance. */
-interface LaunchKeyguardQuickAffordanceUseCase {
- operator fun invoke(
- intent: Intent,
- canShowWhileLocked: Boolean,
- animationController: ActivityLaunchAnimator.Controller?,
- )
-}
-
-/** Real implementation of [LaunchKeyguardQuickAffordanceUseCase] */
-class LaunchKeyguardQuickAffordanceUseCaseImpl
-@Inject
-constructor(
- private val lockPatternUtils: LockPatternUtils,
- private val keyguardStateController: KeyguardStateController,
- private val userTracker: UserTracker,
- private val activityStarter: ActivityStarter,
-) : LaunchKeyguardQuickAffordanceUseCase {
- override operator fun invoke(
- intent: Intent,
- canShowWhileLocked: Boolean,
- animationController: ActivityLaunchAnimator.Controller?,
- ) {
- @StrongAuthFlags
- val strongAuthFlags =
- lockPatternUtils.getStrongAuthForUser(userTracker.userHandle.identifier)
- val needsToUnlockFirst =
- when {
- strongAuthFlags ==
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT -> true
- !canShowWhileLocked && !keyguardStateController.isUnlocked -> true
- else -> false
- }
- if (needsToUnlockFirst) {
- activityStarter.postStartActivityDismissingKeyguard(
- intent,
- 0 /* delay */,
- animationController
- )
- } else {
- activityStarter.startActivity(
- intent,
- true /* dismissShade */,
- animationController,
- true /* showOverLockscreenWhenLocked */,
- )
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveBottomAreaAlphaUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveBottomAreaAlphaUseCase.kt
deleted file mode 100644
index 151b704a017b..000000000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveBottomAreaAlphaUseCase.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.domain.usecase
-
-import com.android.systemui.keyguard.data.repository.KeyguardRepository
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-
-/** Use-case for observing the alpha of the bottom area */
-class ObserveBottomAreaAlphaUseCase
-@Inject
-constructor(
- private val repository: KeyguardRepository,
-) {
- operator fun invoke(): Flow<Float> {
- return repository.bottomAreaAlpha
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveClockPositionUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveClockPositionUseCase.kt
deleted file mode 100644
index 02c573726db0..000000000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveClockPositionUseCase.kt
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.domain.usecase
-
-import com.android.systemui.common.domain.model.Position
-import com.android.systemui.common.domain.model.Position.Companion.toDomainLayer
-import com.android.systemui.keyguard.data.repository.KeyguardRepository
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.map
-
-/** Use-case for observing the position of the clock. */
-class ObserveClockPositionUseCase
-@Inject
-constructor(
- private val repository: KeyguardRepository,
-) {
- operator fun invoke(): Flow<Position> {
- return repository.clockPosition.map { it.toDomainLayer() }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveDozeAmountUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveDozeAmountUseCase.kt
deleted file mode 100644
index 56d61822a121..000000000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveDozeAmountUseCase.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.domain.usecase
-
-import com.android.systemui.keyguard.data.repository.KeyguardRepository
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-
-/** Use-case for observing the amount of doze the system is in. */
-class ObserveDozeAmountUseCase
-@Inject
-constructor(
- private val repository: KeyguardRepository,
-) {
- operator fun invoke(): Flow<Float> {
- return repository.dozeAmount
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveIsDozingUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveIsDozingUseCase.kt
deleted file mode 100644
index 1d241d90ba5f..000000000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveIsDozingUseCase.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.domain.usecase
-
-import com.android.systemui.keyguard.data.repository.KeyguardRepository
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-
-/** Use-case for observing whether we are dozing. */
-class ObserveIsDozingUseCase
-@Inject
-constructor(
- private val repository: KeyguardRepository,
-) {
- operator fun invoke(): Flow<Boolean> {
- return repository.isDozing
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCase.kt
deleted file mode 100644
index 8dee8b38bdb8..000000000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCase.kt
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.domain.usecase
-
-import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
-import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
-import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceRegistry
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.combine
-
-/** Defines interface for use-case for observing the model of a quick affordance in the keyguard. */
-interface ObserveKeyguardQuickAffordanceUseCase {
- operator fun invoke(
- position: KeyguardQuickAffordancePosition
- ): Flow<KeyguardQuickAffordanceModel>
-}
-
-class ObserveKeyguardQuickAffordanceUseCaseImpl
-@Inject
-constructor(
- private val registry: KeyguardQuickAffordanceRegistry,
- private val isDozingUseCase: ObserveIsDozingUseCase,
- private val isKeyguardShowingUseCase: ObserveIsKeyguardShowingUseCase,
-) : ObserveKeyguardQuickAffordanceUseCase {
- override fun invoke(
- position: KeyguardQuickAffordancePosition
- ): Flow<KeyguardQuickAffordanceModel> {
- return combine(
- affordance(position),
- isDozingUseCase(),
- isKeyguardShowingUseCase(),
- ) { affordance, isDozing, isKeyguardShowing ->
- if (!isDozing && isKeyguardShowing) {
- affordance
- } else {
- KeyguardQuickAffordanceModel.Hidden
- }
- }
- }
-
- private fun affordance(
- position: KeyguardQuickAffordancePosition
- ): Flow<KeyguardQuickAffordanceModel> {
- val configs = registry.getAll(position)
- return combine(configs.map { config -> config.state }) { states ->
- val index =
- states.indexOfFirst { state ->
- state is KeyguardQuickAffordanceConfig.State.Visible
- }
- val visibleState =
- if (index != -1) {
- states[index] as KeyguardQuickAffordanceConfig.State.Visible
- } else {
- null
- }
- KeyguardQuickAffordanceModel.from(visibleState, configs[index]::class)
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/OnKeyguardQuickAffordanceClickedUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/OnKeyguardQuickAffordanceClickedUseCase.kt
deleted file mode 100644
index 93153391ca41..000000000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/OnKeyguardQuickAffordanceClickedUseCase.kt
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.domain.usecase
-
-import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult
-import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceRegistry
-import javax.inject.Inject
-import kotlin.reflect.KClass
-
-/** Use-case for handling a click on a keyguard quick affordance (e.g. bottom button). */
-class OnKeyguardQuickAffordanceClickedUseCase
-@Inject
-constructor(
- private val registry: KeyguardQuickAffordanceRegistry,
- private val launchAffordanceUseCase: LaunchKeyguardQuickAffordanceUseCase,
-) {
- operator fun invoke(
- configKey: KClass<*>,
- animationController: ActivityLaunchAnimator.Controller?,
- ) {
- @Suppress("UNCHECKED_CAST")
- val config = registry.get(configKey as KClass<out KeyguardQuickAffordanceConfig>)
- when (val result = config.onQuickAffordanceClicked(animationController)) {
- is OnClickedResult.StartActivity ->
- launchAffordanceUseCase(
- intent = result.intent,
- canShowWhileLocked = result.canShowWhileLocked,
- animationController = animationController
- )
- is OnClickedResult.Handled -> Unit
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetKeyguardBottomAreaAlphaUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetKeyguardBottomAreaAlphaUseCase.kt
deleted file mode 100644
index 90be1ecded50..000000000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetKeyguardBottomAreaAlphaUseCase.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.domain.usecase
-
-import com.android.systemui.keyguard.data.repository.KeyguardRepository
-import javax.inject.Inject
-
-/** Use-case for setting the alpha that the keyguard bottom area should use */
-class SetKeyguardBottomAreaAlphaUseCase
-@Inject
-constructor(
- private val repository: KeyguardRepository,
-) {
- operator fun invoke(alpha: Float) {
- repository.setBottomAreaAlpha(alpha)
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetKeyguardBottomAreaAnimateDozingTransitionsUseCase.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetKeyguardBottomAreaAnimateDozingTransitionsUseCase.kt
deleted file mode 100644
index 007780a6860a..000000000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetKeyguardBottomAreaAnimateDozingTransitionsUseCase.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.domain.usecase
-
-import com.android.systemui.keyguard.data.repository.KeyguardRepository
-import javax.inject.Inject
-
-/**
- * Use-case for setting whether the keyguard bottom area should animate the next doze transitions
- */
-class SetKeyguardBottomAreaAnimateDozingTransitionsUseCase
-@Inject
-constructor(
- private val repository: KeyguardRepository,
-) {
- operator fun invoke(animate: Boolean) {
- repository.setAnimateDozingTransitions(animate)
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
index 04d30bfb00f7..19c6249a12c0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
@@ -95,35 +95,23 @@ object KeyguardBottomAreaViewBinder {
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
launch {
- combine(viewModel.startButton, viewModel.animateButtonReveal) {
- buttonModel,
- animateReveal ->
- Pair(buttonModel, animateReveal)
- }
- .collect { (buttonModel, animateReveal) ->
- updateButton(
- view = startButton,
- viewModel = buttonModel,
- animateReveal = animateReveal,
- falsingManager = falsingManager,
- )
- }
+ viewModel.startButton.collect { buttonModel ->
+ updateButton(
+ view = startButton,
+ viewModel = buttonModel,
+ falsingManager = falsingManager,
+ )
+ }
}
launch {
- combine(viewModel.endButton, viewModel.animateButtonReveal) {
- buttonModel,
- animateReveal ->
- Pair(buttonModel, animateReveal)
- }
- .collect { (buttonModel, animateReveal) ->
- updateButton(
- view = endButton,
- viewModel = buttonModel,
- animateReveal = animateReveal,
- falsingManager = falsingManager,
- )
- }
+ viewModel.endButton.collect { buttonModel ->
+ updateButton(
+ view = endButton,
+ viewModel = buttonModel,
+ falsingManager = falsingManager,
+ )
+ }
}
launch {
@@ -226,7 +214,6 @@ object KeyguardBottomAreaViewBinder {
private fun updateButton(
view: ImageView,
viewModel: KeyguardQuickAffordanceViewModel,
- animateReveal: Boolean,
falsingManager: FalsingManager,
) {
if (!viewModel.isVisible) {
@@ -236,7 +223,7 @@ object KeyguardBottomAreaViewBinder {
if (!view.isVisible) {
view.isVisible = true
- if (animateReveal) {
+ if (viewModel.animateReveal) {
view.alpha = 0f
view.translationY = view.height / 2f
view
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
index d296e76482ad..01d5e5c493ce 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
@@ -17,15 +17,11 @@
package com.android.systemui.keyguard.ui.viewmodel
import com.android.systemui.doze.util.BurnInHelperWrapper
+import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
-import com.android.systemui.keyguard.domain.usecase.ObserveAnimateBottomAreaTransitionsUseCase
-import com.android.systemui.keyguard.domain.usecase.ObserveBottomAreaAlphaUseCase
-import com.android.systemui.keyguard.domain.usecase.ObserveClockPositionUseCase
-import com.android.systemui.keyguard.domain.usecase.ObserveDozeAmountUseCase
-import com.android.systemui.keyguard.domain.usecase.ObserveIsDozingUseCase
-import com.android.systemui.keyguard.domain.usecase.ObserveKeyguardQuickAffordanceUseCase
-import com.android.systemui.keyguard.domain.usecase.OnKeyguardQuickAffordanceClickedUseCase
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
@@ -36,13 +32,9 @@ import kotlinx.coroutines.flow.map
class KeyguardBottomAreaViewModel
@Inject
constructor(
- private val observeQuickAffordanceUseCase: ObserveKeyguardQuickAffordanceUseCase,
- private val onQuickAffordanceClickedUseCase: OnKeyguardQuickAffordanceClickedUseCase,
- observeBottomAreaAlphaUseCase: ObserveBottomAreaAlphaUseCase,
- observeIsDozingUseCase: ObserveIsDozingUseCase,
- observeAnimateBottomAreaTransitionsUseCase: ObserveAnimateBottomAreaTransitionsUseCase,
- private val observeDozeAmountUseCase: ObserveDozeAmountUseCase,
- observeClockPositionUseCase: ObserveClockPositionUseCase,
+ private val keyguardInteractor: KeyguardInteractor,
+ private val quickAffordanceInteractor: KeyguardQuickAffordanceInteractor,
+ private val bottomAreaInteractor: KeyguardBottomAreaInteractor,
private val burnInHelperWrapper: BurnInHelperWrapper,
) {
/** An observable for the view-model of the "start button" quick affordance. */
@@ -51,17 +43,11 @@ constructor(
/** An observable for the view-model of the "end button" quick affordance. */
val endButton: Flow<KeyguardQuickAffordanceViewModel> =
button(KeyguardQuickAffordancePosition.BOTTOM_END)
- /**
- * An observable for whether the next time a quick action button becomes visible, it should
- * animate.
- */
- val animateButtonReveal: Flow<Boolean> =
- observeAnimateBottomAreaTransitionsUseCase().distinctUntilChanged()
/** An observable for whether the overlay container should be visible. */
val isOverlayContainerVisible: Flow<Boolean> =
- observeIsDozingUseCase().map { !it }.distinctUntilChanged()
+ keyguardInteractor.isDozing.map { !it }.distinctUntilChanged()
/** An observable for the alpha level for the entire bottom area. */
- val alpha: Flow<Float> = observeBottomAreaAlphaUseCase().distinctUntilChanged()
+ val alpha: Flow<Float> = bottomAreaInteractor.alpha.distinctUntilChanged()
/** An observable for whether the indication area should be padded. */
val isIndicationAreaPadded: Flow<Boolean> =
combine(startButton, endButton) { startButtonModel, endButtonModel ->
@@ -70,11 +56,11 @@ constructor(
.distinctUntilChanged()
/** An observable for the x-offset by which the indication area should be translated. */
val indicationAreaTranslationX: Flow<Float> =
- observeClockPositionUseCase().map { it.x.toFloat() }.distinctUntilChanged()
+ bottomAreaInteractor.clockPosition.map { it.x.toFloat() }.distinctUntilChanged()
/** Returns an observable for the y-offset by which the indication area should be translated. */
fun indicationAreaTranslationY(defaultBurnInOffset: Int): Flow<Float> {
- return observeDozeAmountUseCase()
+ return keyguardInteractor.dozeAmount
.map { dozeAmount ->
dozeAmount *
(burnInHelperWrapper.burnInOffset(
@@ -88,21 +74,28 @@ constructor(
private fun button(
position: KeyguardQuickAffordancePosition
): Flow<KeyguardQuickAffordanceViewModel> {
- return observeQuickAffordanceUseCase(position)
- .map { model -> model.toViewModel() }
+ return combine(
+ quickAffordanceInteractor.quickAffordance(position),
+ bottomAreaInteractor.animateDozingTransitions.distinctUntilChanged(),
+ ) { model, animateReveal ->
+ model.toViewModel(animateReveal)
+ }
.distinctUntilChanged()
}
- private fun KeyguardQuickAffordanceModel.toViewModel(): KeyguardQuickAffordanceViewModel {
+ private fun KeyguardQuickAffordanceModel.toViewModel(
+ animateReveal: Boolean,
+ ): KeyguardQuickAffordanceViewModel {
return when (this) {
is KeyguardQuickAffordanceModel.Visible ->
KeyguardQuickAffordanceViewModel(
configKey = configKey,
isVisible = true,
+ animateReveal = animateReveal,
icon = icon,
contentDescriptionResourceId = contentDescriptionResourceId,
onClicked = { parameters ->
- onQuickAffordanceClickedUseCase(
+ quickAffordanceInteractor.onQuickAffordanceClicked(
configKey = parameters.configKey,
animationController = parameters.animationController,
)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt
index 2417998784e4..985ab623764a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt
@@ -19,18 +19,21 @@ package com.android.systemui.keyguard.ui.viewmodel
import androidx.annotation.StringRes
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
import kotlin.reflect.KClass
/** Models the UI state of a keyguard quick affordance button. */
data class KeyguardQuickAffordanceViewModel(
- val configKey: KClass<*>? = null,
+ val configKey: KClass<out KeyguardQuickAffordanceConfig>? = null,
val isVisible: Boolean = false,
+ /** Whether to animate the transition of the quick affordance from invisible to visible. */
+ val animateReveal: Boolean = false,
val icon: ContainedDrawable = ContainedDrawable.WithResource(0),
@StringRes val contentDescriptionResourceId: Int = 0,
val onClicked: (OnClickedParameters) -> Unit = {},
) {
data class OnClickedParameters(
- val configKey: KClass<*>,
+ val configKey: KClass<out KeyguardQuickAffordanceConfig>,
val animationController: ActivityLaunchAnimator.Controller?,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index c858bc3e2c81..c2a87649adef 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -84,6 +84,14 @@ public class LogModule {
return factory.create("LSShadeTransitionLog", 50);
}
+ /** Provides a logging buffer for Shade messages. */
+ @Provides
+ @SysUISingleton
+ @ShadeLog
+ public static LogBuffer provideShadeLogBuffer(LogBufferFactory factory) {
+ return factory.create("ShadeLog", 500, false);
+ }
+
/** Provides a logging buffer for all logs related to managing notification sections. */
@Provides
@SysUISingleton
@@ -262,7 +270,7 @@ public class LogModule {
@SysUISingleton
@StatusBarConnectivityLog
public static LogBuffer provideStatusBarConnectivityBuffer(LogBufferFactory factory) {
- return factory.create("StatusBarConnectivityLog", 64);
+ return factory.create("SbConnectivity", 64);
}
/** Allows logging buffers to be tweaked via adb on debug builds but not on prod builds. */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetClockPositionUseCase.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeLog.java
index 8f746e5765af..bd0d298ebdee 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/SetClockPositionUseCase.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeLog.java
@@ -14,18 +14,20 @@
* limitations under the License.
*/
-package com.android.systemui.keyguard.domain.usecase
+package com.android.systemui.log.dagger;
-import com.android.systemui.keyguard.data.repository.KeyguardRepository
-import javax.inject.Inject
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
-/** Use-case for setting the updated clock position. */
-class SetClockPositionUseCase
-@Inject
-constructor(
- private val keyguardRepository: KeyguardRepository,
-) {
- operator fun invoke(x: Int, y: Int) {
- keyguardRepository.setClockPosition(x, y)
- }
+import com.android.systemui.log.LogBuffer;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+/** A {@link LogBuffer} for Shade touch handling messages. */
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface ShadeLog {
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
index 458ed4059b3b..926002060da7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
@@ -36,6 +36,7 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dreams.DreamOverlayStateController
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.NotifPanelEvents
import com.android.systemui.statusbar.CrossFadeHelper
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.StatusBarState
@@ -85,13 +86,22 @@ class MediaHierarchyManager @Inject constructor(
private val bypassController: KeyguardBypassController,
private val mediaCarouselController: MediaCarouselController,
private val notifLockscreenUserManager: NotificationLockscreenUserManager,
+ private val keyguardViewController: KeyguardViewController,
+ private val dreamOverlayStateController: DreamOverlayStateController,
configurationController: ConfigurationController,
wakefulnessLifecycle: WakefulnessLifecycle,
- private val keyguardViewController: KeyguardViewController,
- private val dreamOverlayStateController: DreamOverlayStateController
+ panelEventsEvents: NotifPanelEvents,
) {
/**
+ * Whether we "skip" QQS during panel expansion.
+ *
+ * This means that when expanding the panel we go directly to QS. Also when we are on QS and
+ * start closing the panel, it fully collapses instead of going to QQS.
+ */
+ private var skipQqsOnExpansion: Boolean = false
+
+ /**
* The root overlay of the hierarchy. This is where the media notification is attached to
* whenever the view is transitioning from one host to another. It also make sure that the
* view is always in its final state when it is attached to a view host.
@@ -504,6 +514,13 @@ class MediaHierarchyManager @Inject constructor(
mediaCarouselController.updateUserVisibility = {
mediaCarouselController.mediaCarouselScrollHandler.visibleToUser = isVisibleToUser()
}
+
+ panelEventsEvents.registerListener(object : NotifPanelEvents.Listener {
+ override fun onExpandImmediateChanged(isExpandImmediateEnabled: Boolean) {
+ skipQqsOnExpansion = isExpandImmediateEnabled
+ updateDesiredLocation()
+ }
+ })
}
private fun updateConfiguration() {
@@ -701,6 +718,9 @@ class MediaHierarchyManager @Inject constructor(
if (isCurrentlyInGuidedTransformation()) {
return false
}
+ if (skipQqsOnExpansion) {
+ return false
+ }
// This is an invalid transition, and can happen when using the camera gesture from the
// lock screen. Disallow.
if (previousLocation == LOCATION_LOCKSCREEN &&
@@ -852,6 +872,9 @@ class MediaHierarchyManager @Inject constructor(
* otherwise
*/
private fun getTransformationProgress(): Float {
+ if (skipQqsOnExpansion) {
+ return -1.0f
+ }
val progress = getQSTransformationProgress()
if (statusbarState != StatusBarState.KEYGUARD && progress >= 0) {
return progress
@@ -1042,6 +1065,10 @@ class MediaHierarchyManager @Inject constructor(
// reattach it without an animation
return LOCATION_LOCKSCREEN
}
+ if (skipQqsOnExpansion) {
+ // When doing an immediate expand or collapse, we want to keep it in QS.
+ return LOCATION_QS
+ }
return location
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
index 7e263d8d5b73..08cf57c7594e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
@@ -296,27 +296,17 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements
mMediaOutputController.setRefreshing(true);
// Update header icon
final int iconRes = getHeaderIconRes();
- final IconCompat iconCompat = getHeaderIcon();
- final Drawable appSourceDrawable = getAppSourceIcon();
+ final IconCompat headerIcon = getHeaderIcon();
+ final IconCompat appSourceIcon = getAppSourceIcon();
boolean colorSetUpdated = false;
mCastAppLayout.setVisibility(
mMediaOutputController.shouldShowLaunchSection()
? View.VISIBLE : View.GONE);
- if (appSourceDrawable != null) {
- mAppResourceIcon.setImageDrawable(appSourceDrawable);
- mAppButton.setCompoundDrawablesWithIntrinsicBounds(resizeDrawable(appSourceDrawable,
- mContext.getResources().getDimensionPixelSize(
- R.dimen.media_output_dialog_app_tier_icon_size
- )),
- null, null, null);
- } else {
- mAppResourceIcon.setVisibility(View.GONE);
- }
if (iconRes != 0) {
mHeaderIcon.setVisibility(View.VISIBLE);
mHeaderIcon.setImageResource(iconRes);
- } else if (iconCompat != null) {
- Icon icon = iconCompat.toIcon(mContext);
+ } else if (headerIcon != null) {
+ Icon icon = headerIcon.toIcon(mContext);
if (icon.getType() != Icon.TYPE_BITMAP && icon.getType() != Icon.TYPE_ADAPTIVE_BITMAP) {
// icon doesn't support getBitmap, use default value for color scheme
updateButtonBackgroundColorFilter();
@@ -336,6 +326,18 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements
} else {
mHeaderIcon.setVisibility(View.GONE);
}
+ if (appSourceIcon != null) {
+ Icon appIcon = appSourceIcon.toIcon(mContext);
+ mAppResourceIcon.setColorFilter(mMediaOutputController.getColorItemContent());
+ mAppResourceIcon.setImageIcon(appIcon);
+ } else {
+ Drawable appIconDrawable = mMediaOutputController.getAppSourceIconFromPackage();
+ if (appIconDrawable != null) {
+ mAppResourceIcon.setImageDrawable(appIconDrawable);
+ } else {
+ mAppResourceIcon.setVisibility(View.GONE);
+ }
+ }
if (mHeaderIcon.getVisibility() == View.VISIBLE) {
final int size = getHeaderIconSize();
final int padding = mContext.getResources().getDimensionPixelSize(
@@ -480,7 +482,7 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements
}
}
- abstract Drawable getAppSourceIcon();
+ abstract IconCompat getAppSourceIcon();
abstract int getHeaderIconRes();
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
index 310469dd5415..35baf0131b9d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
@@ -19,7 +19,6 @@ package com.android.systemui.media.dialog;
import android.app.AlertDialog;
import android.content.Context;
import android.graphics.Bitmap;
-import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.text.method.HideReturnsTransformationMethod;
import android.text.method.PasswordTransformationMethod;
@@ -116,8 +115,8 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog {
}
@Override
- Drawable getAppSourceIcon() {
- return mMediaOutputController.getAppSourceIcon();
+ IconCompat getAppSourceIcon() {
+ return mMediaOutputController.getNotificationSmallIcon();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt
index 0fa326573c9c..2b5d6fd63995 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt
@@ -16,6 +16,7 @@
package com.android.systemui.media.dialog
+import android.app.KeyguardManager
import android.content.Context
import android.media.AudioManager
import android.media.session.MediaSessionManager
@@ -45,7 +46,8 @@ class MediaOutputBroadcastDialogFactory @Inject constructor(
private val dialogLaunchAnimator: DialogLaunchAnimator,
private val nearbyMediaDevicesManagerOptional: Optional<NearbyMediaDevicesManager>,
private val audioManager: AudioManager,
- private val powerExemptionManager: PowerExemptionManager
+ private val powerExemptionManager: PowerExemptionManager,
+ private val keyGuardManager: KeyguardManager
) {
var mediaOutputBroadcastDialog: MediaOutputBroadcastDialog? = null
@@ -57,7 +59,7 @@ class MediaOutputBroadcastDialogFactory @Inject constructor(
val controller = MediaOutputController(context, packageName,
mediaSessionManager, lbm, starter, notifCollection,
dialogLaunchAnimator, nearbyMediaDevicesManagerOptional, audioManager,
- powerExemptionManager)
+ powerExemptionManager, keyGuardManager)
val dialog =
MediaOutputBroadcastDialog(context, aboveStatusBar, broadcastSender, controller)
mediaOutputBroadcastDialog = dialog
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 27095b3882a4..f7d80e0b1dbd 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -20,6 +20,7 @@ import static android.provider.Settings.ACTION_BLUETOOTH_SETTINGS;
import android.annotation.CallbackExecutor;
import android.app.AlertDialog;
+import android.app.KeyguardManager;
import android.app.Notification;
import android.app.WallpaperColors;
import android.bluetooth.BluetoothLeBroadcast;
@@ -120,6 +121,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
final List<MediaDevice> mCachedMediaDevices = new CopyOnWriteArrayList<>();
private final AudioManager mAudioManager;
private final PowerExemptionManager mPowerExemptionManager;
+ private final KeyguardManager mKeyGuardManager;
private final NearbyMediaDevicesManager mNearbyMediaDevicesManager;
private final Map<String, Integer> mNearbyDeviceInfoMap = new ConcurrentHashMap<>();
@@ -154,7 +156,8 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
DialogLaunchAnimator dialogLaunchAnimator,
Optional<NearbyMediaDevicesManager> nearbyMediaDevicesManagerOptional,
AudioManager audioManager,
- PowerExemptionManager powerExemptionManager) {
+ PowerExemptionManager powerExemptionManager,
+ KeyguardManager keyGuardManager) {
mContext = context;
mPackageName = packageName;
mMediaSessionManager = mediaSessionManager;
@@ -163,6 +166,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
mNotifCollection = notifCollection;
mAudioManager = audioManager;
mPowerExemptionManager = powerExemptionManager;
+ mKeyGuardManager = keyGuardManager;
InfoMediaManager imm = new InfoMediaManager(mContext, packageName, null, lbm);
mLocalMediaManager = new LocalMediaManager(mContext, lbm, imm, packageName);
mMetricLogger = new MediaOutputMetricLogger(mContext, mPackageName);
@@ -300,7 +304,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
}
}
- Drawable getAppSourceIcon() {
+ Drawable getAppSourceIconFromPackage() {
if (mPackageName.isEmpty()) {
return null;
}
@@ -411,6 +415,24 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
|| isSelectedDeviceInGroup;
}
+ IconCompat getNotificationSmallIcon() {
+ if (TextUtils.isEmpty(mPackageName)) {
+ return null;
+ }
+ for (NotificationEntry entry : mNotifCollection.getAllNotifs()) {
+ final Notification notification = entry.getSbn().getNotification();
+ if (notification.isMediaNotification()
+ && TextUtils.equals(entry.getSbn().getPackageName(), mPackageName)) {
+ final Icon icon = notification.getSmallIcon();
+ if (icon == null) {
+ break;
+ }
+ return IconCompat.createFromIcon(icon);
+ }
+ }
+ return null;
+ }
+
IconCompat getNotificationIcon() {
if (TextUtils.isEmpty(mPackageName)) {
return null;
@@ -701,7 +723,8 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
ActivityLaunchAnimator.Controller controller =
mDialogLaunchAnimator.createActivityLaunchController(view);
- if (controller == null) {
+ if (controller == null || (mKeyGuardManager != null
+ && mKeyGuardManager.isKeyguardLocked())) {
mCallback.dismissDialog();
}
@@ -753,7 +776,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
MediaOutputController controller = new MediaOutputController(mContext, mPackageName,
mMediaSessionManager, mLocalBluetoothManager, mActivityStarter,
mNotifCollection, mDialogLaunchAnimator, Optional.of(mNearbyMediaDevicesManager),
- mAudioManager, mPowerExemptionManager);
+ mAudioManager, mPowerExemptionManager, mKeyGuardManager);
MediaOutputBroadcastDialog dialog = new MediaOutputBroadcastDialog(mContext, true,
broadcastSender, controller);
mDialogLaunchAnimator.showFromView(dialog, mediaOutputDialog);
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
index 9fb96b56c32c..cb6f5a78ec30 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
@@ -17,7 +17,6 @@
package com.android.systemui.media.dialog;
import android.content.Context;
-import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.view.View;
import android.view.WindowManager;
@@ -81,8 +80,8 @@ public class MediaOutputDialog extends MediaOutputBaseDialog {
}
@Override
- Drawable getAppSourceIcon() {
- return mMediaOutputController.getAppSourceIcon();
+ IconCompat getAppSourceIcon() {
+ return mMediaOutputController.getNotificationSmallIcon();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
index 8249a7c0779c..543efed8378e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
@@ -16,6 +16,7 @@
package com.android.systemui.media.dialog
+import android.app.KeyguardManager
import android.content.Context
import android.media.AudioManager
import android.media.session.MediaSessionManager
@@ -47,7 +48,8 @@ class MediaOutputDialogFactory @Inject constructor(
private val dialogLaunchAnimator: DialogLaunchAnimator,
private val nearbyMediaDevicesManagerOptional: Optional<NearbyMediaDevicesManager>,
private val audioManager: AudioManager,
- private val powerExemptionManager: PowerExemptionManager
+ private val powerExemptionManager: PowerExemptionManager,
+ private val keyGuardManager: KeyguardManager
) {
companion object {
private const val INTERACTION_JANK_TAG = "media_output"
@@ -63,7 +65,7 @@ class MediaOutputDialogFactory @Inject constructor(
context, packageName,
mediaSessionManager, lbm, starter, notifCollection,
dialogLaunchAnimator, nearbyMediaDevicesManagerOptional, audioManager,
- powerExemptionManager)
+ powerExemptionManager, keyGuardManager)
val dialog =
MediaOutputDialog(context, aboveStatusBar, broadcastSender, controller, uiEventLogger)
mediaOutputDialog = dialog
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
index 87579040372f..00b0ff9b128d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
@@ -68,7 +68,7 @@ class MediaTttCommandLineHelper @Inject constructor(
.addFeature("feature")
val useAppIcon = !(args.size >= 3 && args[2] == "useAppIcon=false")
if (useAppIcon) {
- routeInfo.setPackageName(TEST_PACKAGE_NAME)
+ routeInfo.setClientPackageName(TEST_PACKAGE_NAME)
}
statusBarManager.updateMediaTapToTransferSenderDisplay(
@@ -134,7 +134,7 @@ class MediaTttCommandLineHelper @Inject constructor(
.addFeature("feature")
val useAppIcon = !(args.size >= 2 && args[1] == "useAppIcon=false")
if (useAppIcon) {
- routeInfo.setPackageName(TEST_PACKAGE_NAME)
+ routeInfo.setClientPackageName(TEST_PACKAGE_NAME)
}
statusBarManager.updateMediaTapToTransferReceiverDisplay(
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
index 9ab83b84277e..2278938c398e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
@@ -56,7 +56,7 @@ abstract class MediaTttChipControllerCommon<T : ChipInfoCommon>(
internal val logger: MediaTttLogger,
internal val windowManager: WindowManager,
private val viewUtil: ViewUtil,
- @Main internal val mainExecutor: DelayableExecutor,
+ @Main private val mainExecutor: DelayableExecutor,
private val accessibilityManager: AccessibilityManager,
private val configurationController: ConfigurationController,
private val powerManager: PowerManager,
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt
index d3b5bc62b0e1..aa10f7e2738f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt
@@ -18,7 +18,6 @@ package com.android.systemui.media.taptotransfer.common
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogLevel
-import com.android.systemui.log.dagger.MediaTttSenderLogBuffer
/**
* A logger for media tap-to-transfer events.
@@ -27,7 +26,7 @@ import com.android.systemui.log.dagger.MediaTttSenderLogBuffer
*/
class MediaTttLogger(
private val deviceTypeTag: String,
- @MediaTttSenderLogBuffer private val buffer: LogBuffer
+ private val buffer: LogBuffer
){
/** Logs a change in the chip state for the given [mediaRouteId]. */
fun logStateChange(stateName: String, mediaRouteId: String) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
index 0f1ae00ae8fc..196ea222e50d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
@@ -143,7 +143,7 @@ class MediaTttChipControllerReceiver @Inject constructor(
super.updateChipView(newChipInfo, currentChipView)
setIcon(
currentChipView,
- newChipInfo.routeInfo.packageName,
+ newChipInfo.routeInfo.clientPackageName,
newChipInfo.appIconDrawableOverride,
newChipInfo.appNameOverride
)
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
index b94b8bfabfc1..933548963390 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
@@ -122,7 +122,7 @@ class MediaTttChipControllerSender @Inject constructor(
val chipState = newChipInfo.state
// App icon
- val iconName = setIcon(currentChipView, newChipInfo.routeInfo.packageName)
+ val iconName = setIcon(currentChipView, newChipInfo.routeInfo.clientPackageName)
// Text
val otherDeviceName = newChipInfo.routeInfo.name.toString()
@@ -160,12 +160,8 @@ class MediaTttChipControllerSender @Inject constructor(
duration = ANIMATION_DURATION,
includeMargins = true,
includeFadeIn = true,
- )
-
- // We can only request focus once the animation finishes.
- mainExecutor.executeDelayed(
- { chipInnerView.requestAccessibilityFocus() },
- ANIMATION_DURATION
+ // We can only request focus once the animation finishes.
+ onAnimationEnd = { chipInnerView.requestAccessibilityFocus() },
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/people/ui/viewmodel/PeopleViewModel.kt b/packages/SystemUI/src/com/android/systemui/people/ui/viewmodel/PeopleViewModel.kt
index 0834a5ae75f6..e27bfb34ee3e 100644
--- a/packages/SystemUI/src/com/android/systemui/people/ui/viewmodel/PeopleViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/people/ui/viewmodel/PeopleViewModel.kt
@@ -31,7 +31,6 @@ import com.android.systemui.people.data.model.PeopleTileModel
import com.android.systemui.people.data.repository.PeopleTileRepository
import com.android.systemui.people.data.repository.PeopleWidgetRepository
import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
@@ -52,7 +51,7 @@ class PeopleViewModel(
* reactive and you have to manually call [onTileRefreshRequested] to refresh the tiles.
*/
private val _priorityTiles = MutableStateFlow(priorityTiles())
- val priorityTiles: Flow<List<PeopleTileViewModel>> = _priorityTiles.asStateFlow()
+ val priorityTiles: StateFlow<List<PeopleTileViewModel>> = _priorityTiles.asStateFlow()
/**
* The list of the priority tiles/conversations.
@@ -61,7 +60,7 @@ class PeopleViewModel(
* reactive and you have to manually call [onTileRefreshRequested] to refresh the tiles.
*/
private val _recentTiles = MutableStateFlow(recentTiles())
- val recentTiles: Flow<List<PeopleTileViewModel>> = _recentTiles.asStateFlow()
+ val recentTiles: StateFlow<List<PeopleTileViewModel>> = _recentTiles.asStateFlow()
/** The ID of the widget currently being edited/added. */
private val _appWidgetId = MutableStateFlow(INVALID_APPWIDGET_ID)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
index 833573dd3873..be4420234c29 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
@@ -39,6 +39,7 @@ import java.util.List;
import javax.inject.Inject;
import javax.inject.Named;
+import javax.inject.Provider;
/** Controller for {@link QuickQSPanel}. */
@QSScope
@@ -52,20 +53,21 @@ public class QuickQSPanelController extends QSPanelControllerBase<QuickQSPanel>
}
};
- private final boolean mUsingCollapsedLandscapeMedia;
+ private final Provider<Boolean> mUsingCollapsedLandscapeMediaProvider;
@Inject
QuickQSPanelController(QuickQSPanel view, QSTileHost qsTileHost,
QSCustomizerController qsCustomizerController,
@Named(QS_USING_MEDIA_PLAYER) boolean usingMediaPlayer,
@Named(QUICK_QS_PANEL) MediaHost mediaHost,
- @Named(QS_USING_COLLAPSED_LANDSCAPE_MEDIA) boolean usingCollapsedLandscapeMedia,
+ @Named(QS_USING_COLLAPSED_LANDSCAPE_MEDIA)
+ Provider<Boolean> usingCollapsedLandscapeMediaProvider,
MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger,
DumpManager dumpManager
) {
super(view, qsTileHost, qsCustomizerController, usingMediaPlayer, mediaHost, metricsLogger,
uiEventLogger, qsLogger, dumpManager);
- mUsingCollapsedLandscapeMedia = usingCollapsedLandscapeMedia;
+ mUsingCollapsedLandscapeMediaProvider = usingCollapsedLandscapeMediaProvider;
}
@Override
@@ -80,7 +82,8 @@ public class QuickQSPanelController extends QSPanelControllerBase<QuickQSPanel>
int rotation = getRotation();
boolean isLandscape = rotation == RotationUtils.ROTATION_LANDSCAPE
|| rotation == RotationUtils.ROTATION_SEASCAPE;
- if (!mUsingCollapsedLandscapeMedia || !isLandscape) {
+ boolean usingCollapsedLandscapeMedia = mUsingCollapsedLandscapeMediaProvider.get();
+ if (!usingCollapsedLandscapeMedia || !isLandscape) {
mMediaHost.setExpansion(MediaHost.EXPANDED);
} else {
mMediaHost.setExpansion(MediaHost.COLLAPSED);
@@ -126,7 +129,6 @@ public class QuickQSPanelController extends QSPanelControllerBase<QuickQSPanel>
super.setTiles(tiles, /* collapsedView */ true);
}
- /** */
public void setContentMargins(int marginStart, int marginEnd) {
mView.setContentMargins(marginStart, marginEnd, mMediaHost.getHostView());
}
diff --git a/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt b/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt
index 56a187429af6..db7c1fd86be0 100644
--- a/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt
+++ b/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt
@@ -67,7 +67,7 @@ class RippleShader internal constructor(rippleShape: RippleShape = RippleShape.C
float rippleInsideAlpha = (1.-inside) * in_fadeFill;
float rippleRingAlpha = (1.-sparkleRing) * in_fadeRing;
- float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * 0.45;
+ float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * in_color.a;
vec4 ripple = in_color * rippleAlpha;
return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength);
}
@@ -83,7 +83,7 @@ class RippleShader internal constructor(rippleShape: RippleShape = RippleShape.C
float rippleInsideAlpha = (1.-inside) * in_fadeFill;
float rippleRingAlpha = (1.-sparkleRing) * in_fadeRing;
- float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * 0.45;
+ float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * in_color.a;
vec4 ripple = in_color * rippleAlpha;
return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength);
}
@@ -99,7 +99,7 @@ class RippleShader internal constructor(rippleShape: RippleShape = RippleShape.C
float rippleInsideAlpha = (1.-inside) * in_fadeFill;
float rippleRingAlpha = (1.-sparkleRing) * in_fadeRing;
- float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * 0.45;
+ float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * in_color.a;
vec4 ripple = in_color * rippleAlpha;
return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength);
}
diff --git a/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt b/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt
index 8b0120177268..60c8f3719a2e 100644
--- a/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt
@@ -81,6 +81,7 @@ open class RippleView(context: Context?, attrs: AttributeSet?) : View(context, a
rippleShader.color = RIPPLE_DEFAULT_COLOR
rippleShader.progress = 0f
rippleShader.sparkleStrength = RIPPLE_SPARKLE_STRENGTH
+ rippleShader.pixelDensity = resources.displayMetrics.density
ripplePaint.shader = rippleShader
}
@@ -124,6 +125,13 @@ open class RippleView(context: Context?, attrs: AttributeSet?) : View(context, a
rippleShader.rippleFill = rippleFill
}
+ /**
+ * Set the intensity of the sparkles.
+ */
+ fun setSparkleStrength(strength: Float) {
+ rippleShader.sparkleStrength = strength
+ }
+
override fun onDraw(canvas: Canvas?) {
if (canvas == null || !canvas.isHardwareAccelerated) {
// Drawing with the ripple shader requires hardware acceleration, so skip
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotifPanelEvents.kt b/packages/SystemUI/src/com/android/systemui/shade/NotifPanelEvents.kt
index ce9d89f89ae1..4558061de1a2 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotifPanelEvents.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotifPanelEvents.kt
@@ -29,11 +29,25 @@ interface NotifPanelEvents {
interface Listener {
/** Invoked when the notification panel starts or stops collapsing. */
- fun onPanelCollapsingChanged(isCollapsing: Boolean)
+ @JvmDefault
+ fun onPanelCollapsingChanged(isCollapsing: Boolean) {}
/**
* Invoked when the notification panel starts or stops launching an [android.app.Activity].
*/
- fun onLaunchingActivityChanged(isLaunchingActivity: Boolean)
+ @JvmDefault
+ fun onLaunchingActivityChanged(isLaunchingActivity: Boolean) {}
+
+ /**
+ * Invoked when the "expand immediate" attribute changes.
+ *
+ * An example of expanding immediately is when swiping down from the top with two fingers.
+ * Instead of going to QQS, we immediately expand to full QS.
+ *
+ * Another example is when full QS is showing, and we swipe up from the bottom. Instead of
+ * going to QQS, the panel fully collapses.
+ */
+ @JvmDefault
+ fun onExpandImmediateChanged(isExpandImmediateEnabled: Boolean) {}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 7f0e76b9d336..7a4c87766114 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -127,9 +127,7 @@ import com.android.systemui.flags.Flags;
import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
import com.android.systemui.fragments.FragmentService;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
-import com.android.systemui.keyguard.domain.usecase.SetClockPositionUseCase;
-import com.android.systemui.keyguard.domain.usecase.SetKeyguardBottomAreaAlphaUseCase;
-import com.android.systemui.keyguard.domain.usecase.SetKeyguardBottomAreaAnimateDozingTransitionsUseCase;
+import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor;
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel;
import com.android.systemui.media.KeyguardMediaController;
import com.android.systemui.media.MediaDataManager;
@@ -444,7 +442,6 @@ public final class NotificationPanelViewController extends PanelViewController {
*/
private boolean mQsAnimatorExpand;
private boolean mIsLaunchTransitionFinished;
- private boolean mOnlyAffordanceInThisMotion;
private ValueAnimator mQsSizeChangeAnimator;
private boolean mQsScrimEnabled = true;
@@ -700,11 +697,7 @@ public final class NotificationPanelViewController extends PanelViewController {
private final CameraGestureHelper mCameraGestureHelper;
private final Provider<KeyguardBottomAreaViewModel> mKeyguardBottomAreaViewModelProvider;
- private final Provider<SetClockPositionUseCase> mSetClockPositionUseCaseProvider;
- private final Provider<SetKeyguardBottomAreaAlphaUseCase>
- mSetKeyguardBottomAreaAlphaUseCaseProvider;
- private final Provider<SetKeyguardBottomAreaAnimateDozingTransitionsUseCase>
- mSetKeyguardBottomAreaAnimateDozingTransitionsUseCaseProvider;
+ private final Provider<KeyguardBottomAreaInteractor> mKeyguardBottomAreaInteractorProvider;
@Inject
public NotificationPanelViewController(NotificationPanelView view,
@@ -726,6 +719,7 @@ public final class NotificationPanelViewController extends PanelViewController {
AccessibilityManager accessibilityManager, @DisplayId int displayId,
KeyguardUpdateMonitor keyguardUpdateMonitor,
MetricsLogger metricsLogger,
+ ShadeLogger shadeLogger,
ConfigurationController configurationController,
Provider<FlingAnimationUtils.Builder> flingAnimationUtilsBuilder,
StatusBarTouchableRegionManager statusBarTouchableRegionManager,
@@ -776,10 +770,7 @@ public final class NotificationPanelViewController extends PanelViewController {
SystemClock systemClock,
CameraGestureHelper cameraGestureHelper,
Provider<KeyguardBottomAreaViewModel> keyguardBottomAreaViewModelProvider,
- Provider<SetClockPositionUseCase> setClockPositionUseCaseProvider,
- Provider<SetKeyguardBottomAreaAlphaUseCase> setKeyguardBottomAreaAlphaUseCaseProvider,
- Provider<SetKeyguardBottomAreaAnimateDozingTransitionsUseCase>
- setKeyguardBottomAreaAnimateDozingTransitionsUseCaseProvider) {
+ Provider<KeyguardBottomAreaInteractor> keyguardBottomAreaInteractorProvider) {
super(view,
falsingManager,
dozeLog,
@@ -795,6 +786,7 @@ public final class NotificationPanelViewController extends PanelViewController {
panelExpansionStateManager,
ambientState,
interactionJankMonitor,
+ shadeLogger,
systemClock);
mView = view;
mVibratorHelper = vibratorHelper;
@@ -961,10 +953,7 @@ public final class NotificationPanelViewController extends PanelViewController {
}
});
mCameraGestureHelper = cameraGestureHelper;
- mSetClockPositionUseCaseProvider = setClockPositionUseCaseProvider;
- mSetKeyguardBottomAreaAlphaUseCaseProvider = setKeyguardBottomAreaAlphaUseCaseProvider;
- mSetKeyguardBottomAreaAnimateDozingTransitionsUseCaseProvider =
- setKeyguardBottomAreaAnimateDozingTransitionsUseCaseProvider;
+ mKeyguardBottomAreaInteractorProvider = keyguardBottomAreaInteractorProvider;
}
@VisibleForTesting
@@ -1475,7 +1464,7 @@ public final class NotificationPanelViewController extends PanelViewController {
mKeyguardStatusViewController.getClockBottom(mStatusBarHeaderHeightKeyguard),
mKeyguardStatusViewController.isClockTopAligned());
mClockPositionAlgorithm.run(mClockPositionResult);
- mSetClockPositionUseCaseProvider.get().invoke(
+ mKeyguardBottomAreaInteractorProvider.get().setClockPosition(
mClockPositionResult.clockX, mClockPositionResult.clockY);
boolean animate = mNotificationStackScrollLayoutController.isAddOrRemoveAnimationPending();
boolean animateClock = (animate || mAnimateNextPositionUpdate) && shouldAnimateClockChange;
@@ -1702,12 +1691,17 @@ public final class NotificationPanelViewController extends PanelViewController {
}
if (mQsExpanded) {
- mQsExpandImmediate = true;
+ setQsExpandImmediate(true);
setShowShelfOnly(true);
}
super.collapse(delayed, speedUpFactor);
}
+ private void setQsExpandImmediate(boolean expandImmediate) {
+ mQsExpandImmediate = expandImmediate;
+ mPanelEventsEmitter.notifyExpandImmediateChange(expandImmediate);
+ }
+
private void setShowShelfOnly(boolean shelfOnly) {
mNotificationStackScrollLayoutController.setShouldShowShelfOnly(
shelfOnly && !mSplitShadeEnabled);
@@ -1760,7 +1754,7 @@ public final class NotificationPanelViewController extends PanelViewController {
public void expandWithQs() {
if (isQsExpansionEnabled()) {
- mQsExpandImmediate = true;
+ setQsExpandImmediate(true);
setShowShelfOnly(true);
}
if (mSplitShadeEnabled && isOnKeyguard()) {
@@ -1839,6 +1833,8 @@ public final class NotificationPanelViewController extends PanelViewController {
}
if (mQsExpansionAnimator != null) {
mInitialHeightOnTouch = mQsExpansionHeight;
+ mShadeLog.logMotionEvent(event,
+ "onQsIntercept: down action, QS tracking enabled");
mQsTracking = true;
traceQsJank(true /* startTracing */, false /* wasCancelled */);
mNotificationStackScrollLayoutController.cancelLongPress();
@@ -1866,12 +1862,16 @@ public final class NotificationPanelViewController extends PanelViewController {
setQsExpansion(h + mInitialHeightOnTouch);
trackMovement(event);
return true;
+ } else {
+ mShadeLog.logMotionEvent(event,
+ "onQsIntercept: move ignored because qs tracking disabled");
}
if ((h > getTouchSlop(event) || (h < -getTouchSlop(event) && mQsExpanded))
&& Math.abs(h) > Math.abs(x - mInitialTouchX)
&& shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, h)) {
if (DEBUG_LOGCAT) Log.d(TAG, "onQsIntercept - start tracking expansion");
mView.getParent().requestDisallowInterceptTouchEvent(true);
+ mShadeLog.onQsInterceptMoveQsTrackingEnabled(h);
mQsTracking = true;
traceQsJank(true /* startTracing */, false /* wasCancelled */);
onQsExpansionStarted();
@@ -1887,6 +1887,7 @@ public final class NotificationPanelViewController extends PanelViewController {
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
trackMovement(event);
+ mShadeLog.logMotionEvent(event, "onQsIntercept: up action, QS tracking disabled");
mQsTracking = false;
break;
}
@@ -1924,7 +1925,6 @@ public final class NotificationPanelViewController extends PanelViewController {
private void initDownStates(MotionEvent event) {
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
- mOnlyAffordanceInThisMotion = false;
mQsTouchAboveFalsingThreshold = mQsFullyExpanded;
mDozingOnDown = isDozing();
mDownX = event.getX();
@@ -2063,6 +2063,7 @@ public final class NotificationPanelViewController extends PanelViewController {
&& collapsedQs && isQsExpansionEnabled();
if (action == MotionEvent.ACTION_DOWN && expandedShadeCollapsedQs) {
// Down in the empty area while fully expanded - go to QS.
+ mShadeLog.logMotionEvent(event, "handleQsTouch: down action, QS tracking enabled");
mQsTracking = true;
traceQsJank(true /* startTracing */, false /* wasCancelled */);
mConflictingQsExpansionGesture = true;
@@ -2077,6 +2078,8 @@ public final class NotificationPanelViewController extends PanelViewController {
if (!mQsExpandImmediate && mQsTracking) {
onQsTouch(event);
if (!mConflictingQsExpansionGesture && !mSplitShadeEnabled) {
+ mShadeLog.logMotionEvent(event,
+ "handleQsTouch: not immediate expand or conflicting gesture");
return true;
}
}
@@ -2089,7 +2092,7 @@ public final class NotificationPanelViewController extends PanelViewController {
if (mTwoFingerQsExpandPossible && isOpenQsEvent(event) && event.getY(event.getActionIndex())
< mStatusBarMinHeight) {
mMetricsLogger.count(COUNTER_PANEL_OPEN_QS, 1);
- mQsExpandImmediate = true;
+ setQsExpandImmediate(true);
setShowShelfOnly(true);
requestPanelHeightUpdate();
@@ -2144,6 +2147,7 @@ public final class NotificationPanelViewController extends PanelViewController {
event.getX(), event.getY(), -1)) {
if (DEBUG_LOGCAT) Log.d(TAG, "handleQsDown");
mFalsingCollector.onQsDown();
+ mShadeLog.logMotionEvent(event, "handleQsDown: down action, QS tracking enabled");
mQsTracking = true;
onQsExpansionStarted();
mInitialHeightOnTouch = mQsExpansionHeight;
@@ -2226,6 +2230,7 @@ public final class NotificationPanelViewController extends PanelViewController {
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
+ mShadeLog.logMotionEvent(event, "onQsTouch: down action, QS tracking enabled");
mQsTracking = true;
traceQsJank(true /* startTracing */, false /* wasCancelled */);
mInitialTouchY = y;
@@ -2252,6 +2257,7 @@ public final class NotificationPanelViewController extends PanelViewController {
case MotionEvent.ACTION_MOVE:
if (DEBUG_LOGCAT) Log.d(TAG, "onQSTouch move");
+ mShadeLog.logMotionEvent(event, "onQsTouch: move action, setting QS expansion");
setQsExpansion(h + mInitialHeightOnTouch);
if (h >= getFalsingThreshold()) {
mQsTouchAboveFalsingThreshold = true;
@@ -2261,6 +2267,8 @@ public final class NotificationPanelViewController extends PanelViewController {
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
+ mShadeLog.logMotionEvent(event,
+ "onQsTouch: up/cancel action, QS tracking disabled");
mQsTracking = false;
mTrackingPointer = -1;
trackMovement(event);
@@ -3083,8 +3091,8 @@ public final class NotificationPanelViewController extends PanelViewController {
positionClockAndNotifications();
}
}
- if (mQsExpandImmediate || mQsExpanded && !mQsTracking && mQsExpansionAnimator == null
- && !mQsExpansionFromOverscroll) {
+ if (mQsExpandImmediate || (mQsExpanded && !mQsTracking && mQsExpansionAnimator == null
+ && !mQsExpansionFromOverscroll)) {
float t;
if (mKeyguardShowing) {
@@ -3249,7 +3257,7 @@ public final class NotificationPanelViewController extends PanelViewController {
float alpha = Math.min(expansionAlpha, 1 - computeQsExpansionFraction());
alpha *= mBottomAreaShadeAlpha;
mKeyguardBottomArea.setComponentAlphas(alpha);
- mSetKeyguardBottomAreaAlphaUseCaseProvider.get().invoke(alpha);
+ mKeyguardBottomAreaInteractorProvider.get().setAlpha(alpha);
mLockIconViewController.setAlpha(alpha);
}
@@ -3291,7 +3299,7 @@ public final class NotificationPanelViewController extends PanelViewController {
} else {
setListening(true);
}
- mQsExpandImmediate = false;
+ setQsExpandImmediate(false);
setShowShelfOnly(false);
mTwoFingerQsExpandPossible = false;
updateTrackingHeadsUp(null);
@@ -3349,7 +3357,7 @@ public final class NotificationPanelViewController extends PanelViewController {
super.onTrackingStarted();
mScrimController.onTrackingStarted();
if (mQsFullyExpanded) {
- mQsExpandImmediate = true;
+ setQsExpandImmediate(true);
setShowShelfOnly(true);
}
mNotificationStackScrollLayoutController.onPanelTrackingStarted();
@@ -3449,7 +3457,7 @@ public final class NotificationPanelViewController extends PanelViewController {
private void updateDozingVisibilities(boolean animate) {
mKeyguardBottomArea.setDozing(mDozing, animate);
- mSetKeyguardBottomAreaAnimateDozingTransitionsUseCaseProvider.get().invoke(animate);
+ mKeyguardBottomAreaInteractorProvider.get().setAnimateDozingTransitions(animate);
if (!mDozing && animate) {
mKeyguardStatusBarViewController.animateKeyguardStatusBarIn();
}
@@ -3752,7 +3760,7 @@ public final class NotificationPanelViewController extends PanelViewController {
mDozing = dozing;
mNotificationStackScrollLayoutController.setDozing(mDozing, animate, wakeUpTouchLocation);
mKeyguardBottomArea.setDozing(mDozing, animate);
- mSetKeyguardBottomAreaAnimateDozingTransitionsUseCaseProvider.get().invoke(animate);
+ mKeyguardBottomAreaInteractorProvider.get().setAnimateDozingTransitions(animate);
mKeyguardStatusBarViewController.setDozing(mDozing);
if (dozing) {
@@ -4174,6 +4182,7 @@ public final class NotificationPanelViewController extends PanelViewController {
|| mPulseExpansionHandler.isExpanding();
if (pulseShouldGetTouch && mPulseExpansionHandler.onTouchEvent(event)) {
// We're expanding all the other ones shouldn't get this anymore
+ mShadeLog.logMotionEvent(event, "onTouch: PulseExpansionHandler handled event");
return true;
}
if (mListenForHeadsUp && !mHeadsUpTouchHelper.isTrackingHeadsUp()
@@ -4181,14 +4190,10 @@ public final class NotificationPanelViewController extends PanelViewController {
&& mHeadsUpTouchHelper.onInterceptTouchEvent(event)) {
mMetricsLogger.count(COUNTER_PANEL_OPEN_PEEK, 1);
}
- boolean handled = false;
- if (mOnlyAffordanceInThisMotion) {
- return true;
- }
- handled |= mHeadsUpTouchHelper.onTouchEvent(event);
+ boolean handled = mHeadsUpTouchHelper.onTouchEvent(event);
if (!mHeadsUpTouchHelper.isTrackingHeadsUp() && handleQsTouch(event)) {
- if (DEBUG_LOGCAT) Log.d(TAG, "handleQsTouch true");
+ mShadeLog.logMotionEvent(event, "onTouch: handleQsTouch handled event");
return true;
}
if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isFullyCollapsed()) {
@@ -4756,6 +4761,8 @@ public final class NotificationPanelViewController extends PanelViewController {
}
} else if (!mQsExpanded && mQsExpansionAnimator == null) {
setQsExpansion(mQsMinExpansionHeight + mLastOverscroll);
+ } else {
+ mShadeLog.v("onLayoutChange: qs expansion not set");
}
updateExpandedHeight(getExpandedHeight());
updateHeader();
@@ -4910,7 +4917,7 @@ public final class NotificationPanelViewController extends PanelViewController {
// to locked will trigger this event and we're not actually in the process of opening
// the shade, lockscreen is just always expanded
if (mSplitShadeEnabled && !isOnKeyguard()) {
- mQsExpandImmediate = true;
+ setQsExpandImmediate(true);
}
mCentralSurfaces.makeExpandedVisible(false);
}
@@ -4977,5 +4984,11 @@ public final class NotificationPanelViewController extends PanelViewController {
cb.onPanelCollapsingChanged(isCollapsing);
}
}
+
+ private void notifyExpandImmediateChange(boolean expandImmediateEnabled) {
+ for (NotifPanelEvents.Listener cb : mListeners) {
+ cb.onExpandImmediateChanged(expandImmediateEnabled);
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java
index 4aad245f96fd..73eaa852e345 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java
@@ -202,6 +202,8 @@ public abstract class PanelViewController {
private final InteractionJankMonitor mInteractionJankMonitor;
protected final SystemClock mSystemClock;
+ protected final ShadeLogger mShadeLog;
+
protected abstract void onExpandingFinished();
protected void onExpandingStarted() {
@@ -242,6 +244,7 @@ public abstract class PanelViewController {
PanelExpansionStateManager panelExpansionStateManager,
AmbientState ambientState,
InteractionJankMonitor interactionJankMonitor,
+ ShadeLogger shadeLogger,
SystemClock systemClock) {
keyguardStateController.addCallback(new KeyguardStateController.Callback() {
@Override
@@ -254,6 +257,7 @@ public abstract class PanelViewController {
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
mLockscreenGestureLogger = lockscreenGestureLogger;
mPanelExpansionStateManager = panelExpansionStateManager;
+ mShadeLog = shadeLogger;
TouchHandler touchHandler = createTouchHandler();
mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
@Override
@@ -1275,9 +1279,16 @@ public abstract class PanelViewController {
@Override
public boolean onTouch(View v, MotionEvent event) {
- if (mInstantExpanding || (mTouchDisabled
- && event.getActionMasked() != MotionEvent.ACTION_CANCEL) || (mMotionAborted
- && event.getActionMasked() != MotionEvent.ACTION_DOWN)) {
+ if (mInstantExpanding) {
+ mShadeLog.logMotionEvent(event, "onTouch: touch ignored due to instant expanding");
+ return false;
+ }
+ if (mTouchDisabled && event.getActionMasked() != MotionEvent.ACTION_CANCEL) {
+ mShadeLog.logMotionEvent(event, "onTouch: non-cancel action, touch disabled");
+ return false;
+ }
+ if (mMotionAborted && event.getActionMasked() != MotionEvent.ACTION_DOWN) {
+ mShadeLog.logMotionEvent(event, "onTouch: non-down action, motion was aborted");
return false;
}
@@ -1287,6 +1298,7 @@ public abstract class PanelViewController {
// Turn off tracking if it's on or the shade can get stuck in the down position.
onTrackingStopped(true /* expand */);
}
+ mShadeLog.logMotionEvent(event, "onTouch: drag not enabled");
return false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
new file mode 100644
index 000000000000..f1e44ce5736e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
@@ -0,0 +1,48 @@
+package com.android.systemui.shade
+
+import android.view.MotionEvent
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.LogMessage
+import com.android.systemui.log.dagger.ShadeLog
+import com.google.errorprone.annotations.CompileTimeConstant
+import javax.inject.Inject
+
+private const val TAG = "systemui.shade"
+
+/** Lightweight logging utility for the Shade. */
+class ShadeLogger @Inject constructor(
+ @ShadeLog
+ private val buffer: LogBuffer
+) {
+ fun v(@CompileTimeConstant msg: String) {
+ buffer.log(TAG, LogLevel.VERBOSE, msg)
+ }
+
+ private inline fun log(
+ logLevel: LogLevel,
+ initializer: LogMessage.() -> Unit,
+ noinline printer: LogMessage.() -> String
+ ) {
+ buffer.log(TAG, logLevel, initializer, printer)
+ }
+
+ fun onQsInterceptMoveQsTrackingEnabled(h: Float) {
+ log(LogLevel.VERBOSE,
+ { double1 = h.toDouble() },
+ { "onQsIn[tercept: move action, QS tracking enabled. h = $double1" })
+ }
+
+ fun logMotionEvent(event: MotionEvent, message: String) {
+ log(LogLevel.VERBOSE, {
+ str1 = message
+ long1 = event.eventTime
+ long2 = event.downTime
+ int1 = event.action
+ int2 = event.classification
+ double1 = event.y.toDouble()
+ }, {
+ "$str1\neventTime=$long1,downTime=$long2,y=$double1,action=$int1,classification=$int2"
+ })
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolver.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolver.kt
index c71eade79cdf..0c49713131e8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolver.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolver.kt
@@ -16,7 +16,9 @@
package com.android.systemui.statusbar.notification.collection
+import android.app.Notification
import android.content.Context
+import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.service.notification.StatusBarNotification
import android.util.Log
@@ -39,17 +41,28 @@ class TargetSdkResolver @Inject constructor(
}
private fun resolveNotificationSdk(sbn: StatusBarNotification): Int {
+ val applicationInfo = getApplicationInfoFromExtras(sbn.notification)
+ ?: getApplicationInfoFromPackageManager(sbn)
+
+ return applicationInfo?.targetSdkVersion ?: 0
+ }
+
+ private fun getApplicationInfoFromExtras(notification: Notification): ApplicationInfo? =
+ notification.extras.getParcelable(
+ Notification.EXTRA_BUILDER_APPLICATION_INFO,
+ ApplicationInfo::class.java
+ )
+
+ private fun getApplicationInfoFromPackageManager(sbn: StatusBarNotification): ApplicationInfo? {
val pmUser = CentralSurfaces.getPackageManagerForUser(context, sbn.user.identifier)
- var targetSdk = 0
- // Extract target SDK version.
- try {
- val info = pmUser.getApplicationInfo(sbn.packageName, 0)
- targetSdk = info.targetSdkVersion
+
+ return try {
+ pmUser.getApplicationInfo(sbn.packageName, 0)
} catch (ex: PackageManager.NameNotFoundException) {
Log.e(TAG, "Failed looking up ApplicationInfo for " + sbn.packageName, ex)
+ null
}
- return targetSdk
}
private val TAG = "TargetSdkResolver"
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
index 0b6b929cd93c..c956a2ea1836 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
@@ -1,6 +1,7 @@
package com.android.systemui.statusbar.notification.interruption
import android.app.Notification
+import android.app.Notification.VISIBILITY_SECRET
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
@@ -172,6 +173,8 @@ private class KeyguardNotificationVisibilityProviderImpl @Inject constructor(
!lockscreenUserManager.shouldShowLockscreenNotifications() -> true
// User settings do not allow this notification on the lockscreen, so hide it.
userSettingsDisallowNotification(entry) -> true
+ // Entry is explicitly marked SECRET, so hide it.
+ entry.sbn.notification.visibility == VISIBILITY_SECRET -> true
// if entry is silent, apply custom logic to see if should hide
shouldHideIfEntrySilent(entry) -> true
else -> false
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FeedbackInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FeedbackInfo.java
index 2b782b6e3917..3f4fd5006408 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FeedbackInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FeedbackInfo.java
@@ -165,7 +165,7 @@ public class FeedbackInfo extends LinearLayout implements NotificationGuts.GutsC
}
private void positiveFeedback(View v) {
- mGutsContainer.closeControls(v, false);
+ mGutsContainer.closeControls(v, /* save= */ false);
handleFeedback(true);
}
@@ -176,7 +176,7 @@ public class FeedbackInfo extends LinearLayout implements NotificationGuts.GutsC
menuItem = mMenuRowPlugin.getLongpressMenuItem(mContext);
}
- mGutsContainer.closeControls(v, false);
+ mGutsContainer.closeControls(v, /* save= */ false);
mNotificationGutsManager.openGuts(mExpandableNotificationRow, 0, 0, menuItem);
handleFeedback(false);
}
@@ -203,7 +203,7 @@ public class FeedbackInfo extends LinearLayout implements NotificationGuts.GutsC
}
private void closeControls(View v) {
- mGutsContainer.closeControls(v, false);
+ mGutsContainer.closeControls(v, /* save= */ false);
}
@Override
@@ -232,7 +232,7 @@ public class FeedbackInfo extends LinearLayout implements NotificationGuts.GutsC
}
@Override
- public boolean shouldBeSaved() {
+ public boolean shouldBeSavedOnClose() {
return false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
index 7120fe50adb4..0ce9656a21b5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
@@ -157,7 +157,7 @@ public class NotificationConversationInfo extends LinearLayout implements
mShadeController.animateCollapsePanels();
mPeopleSpaceWidgetManager.requestPinAppWidget(mShortcutInfo, new Bundle());
}
- mGutsContainer.closeControls(v, true);
+ mGutsContainer.closeControls(v, /* save= */ true);
};
public NotificationConversationInfo(Context context, AttributeSet attrs) {
@@ -186,7 +186,6 @@ public class NotificationConversationInfo extends LinearLayout implements
}
public void bindNotification(
- @Action int selectedAction,
ShortcutManager shortcutManager,
PackageManager pm,
PeopleSpaceWidgetManager peopleSpaceWidgetManager,
@@ -205,8 +204,6 @@ public class NotificationConversationInfo extends LinearLayout implements
OnConversationSettingsClickListener onConversationSettingsClickListener,
Optional<BubblesManager> bubblesManagerOptional,
ShadeController shadeController) {
- mPressedApply = false;
- mSelectedAction = selectedAction;
mINotificationManager = iNotificationManager;
mPeopleSpaceWidgetManager = peopleSpaceWidgetManager;
mOnUserInteractionCallback = onUserInteractionCallback;
@@ -417,9 +414,7 @@ public class NotificationConversationInfo extends LinearLayout implements
}
@Override
- public void onFinishedClosing() {
- mSelectedAction = -1;
- }
+ public void onFinishedClosing() { }
@Override
public boolean needsFalsingProtection() {
@@ -564,7 +559,7 @@ public class NotificationConversationInfo extends LinearLayout implements
}
@Override
- public boolean shouldBeSaved() {
+ public boolean shouldBeSavedOnClose() {
return mPressedApply;
}
@@ -578,6 +573,12 @@ public class NotificationConversationInfo extends LinearLayout implements
if (save && mSelectedAction > -1) {
updateChannel();
}
+
+ // Clear the selected importance when closing, so when when we open again,
+ // we starts from a clean state.
+ mSelectedAction = -1;
+ mPressedApply = false;
+
return false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java
index fc296e125794..93f08123ab5a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java
@@ -76,7 +76,7 @@ public class NotificationGuts extends FrameLayout {
switch (action) {
case AccessibilityNodeInfo.ACTION_LONG_CLICK:
- closeControls(host, false);
+ closeControls(host, /* save= */ false);
return true;
}
@@ -123,7 +123,7 @@ public class NotificationGuts extends FrameLayout {
/**
* Return whether something changed and needs to be saved, possibly requiring a bouncer.
*/
- boolean shouldBeSaved();
+ boolean shouldBeSavedOnClose();
/**
* Called when the guts view has finished its close animation.
@@ -259,7 +259,7 @@ public class NotificationGuts extends FrameLayout {
if (mGutsContent != null) {
if ((mGutsContent.isLeavebehind() && leavebehinds)
|| (!mGutsContent.isLeavebehind() && controls)) {
- closeControls(x, y, mGutsContent.shouldBeSaved(), force);
+ closeControls(x, y, mGutsContent.shouldBeSavedOnClose(), force);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index 7b0b0ce3a691..ea12b8263fed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -463,7 +463,6 @@ public class NotificationGutsManager implements NotifGutsViewManager {
R.dimen.notification_guts_conversation_icon_size));
notificationInfoView.bindNotification(
- notificationInfoView.getSelectedAction(),
mShortcutManager,
pmUser,
mPeopleSpaceWidgetManager,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
index 8b01a4790f3c..ea0060a693b2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
@@ -158,7 +158,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
// used by standard ui
private OnClickListener mOnDismissSettings = v -> {
mPressedApply = true;
- mGutsContainer.closeControls(v, true);
+ mGutsContainer.closeControls(v, /* save= */ true);
};
public NotificationInfo(Context context, AttributeSet attrs) {
@@ -541,10 +541,6 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
@Override
public void onFinishedClosing() {
- if (mChosenImportance != null) {
- mStartingChannelImportance = mChosenImportance;
- }
-
bindInlineControls();
logUiEvent(NotificationControlsEvent.NOTIFICATION_CONTROLS_CLOSE);
@@ -604,7 +600,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
}
@Override
- public boolean shouldBeSaved() {
+ public boolean shouldBeSavedOnClose() {
return mPressedApply;
}
@@ -627,6 +623,12 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
if (save) {
saveImportance();
}
+
+ // Clear the selected importance when closing, so when when we open again,
+ // we starts from a clean state.
+ mChosenImportance = null;
+ mPressedApply = false;
+
return false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java
index 512b04968166..adbfa755b63c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java
@@ -384,7 +384,7 @@ public class NotificationSnooze extends LinearLayout
private void undoSnooze(View v) {
mSelectedOption = null;
showSnoozeOptions(false);
- mGutsContainer.closeControls(v, false);
+ mGutsContainer.closeControls(v, /* save= */ false);
}
@Override
@@ -433,7 +433,7 @@ public class NotificationSnooze extends LinearLayout
}
@Override
- public boolean shouldBeSaved() {
+ public boolean shouldBeSavedOnClose() {
return true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PartialConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PartialConversationInfo.java
index 186ffa67f046..ac97e77f84a2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PartialConversationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PartialConversationInfo.java
@@ -16,22 +16,13 @@
package com.android.systemui.statusbar.notification.row;
-import static android.app.Notification.EXTRA_IS_GROUP_CONVERSATION;
-
-import static java.lang.annotation.RetentionPolicy.SOURCE;
-
-import android.annotation.IntDef;
import android.app.INotificationManager;
-import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Icon;
-import android.os.Bundle;
-import android.os.Parcelable;
import android.os.RemoteException;
import android.service.notification.StatusBarNotification;
import android.text.TextUtils;
@@ -46,8 +37,6 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import java.lang.annotation.Retention;
-import java.util.List;
import java.util.Set;
/**
@@ -71,8 +60,6 @@ public class PartialConversationInfo extends LinearLayout implements
private Set<NotificationChannel> mUniqueChannelsInRow;
private Drawable mPkgIcon;
- private @Action int mSelectedAction = -1;
- private boolean mPressedApply;
private boolean mPresentingChannelEditorDialog = false;
private NotificationInfo.OnSettingsClickListener mOnSettingsClickListener;
@@ -82,14 +69,8 @@ public class PartialConversationInfo extends LinearLayout implements
@VisibleForTesting
boolean mSkipPost = false;
- @Retention(SOURCE)
- @IntDef({ACTION_SETTINGS})
- private @interface Action {}
- static final int ACTION_SETTINGS = 5;
-
private OnClickListener mOnDone = v -> {
- mPressedApply = true;
- mGutsContainer.closeControls(v, true);
+ mGutsContainer.closeControls(v, /* save= */ false);
};
public PartialConversationInfo(Context context, AttributeSet attrs) {
@@ -107,7 +88,6 @@ public class PartialConversationInfo extends LinearLayout implements
NotificationInfo.OnSettingsClickListener onSettingsClick,
boolean isDeviceProvisioned,
boolean isNonBlockable) {
- mSelectedAction = -1;
mINotificationManager = iNotificationManager;
mPackageName = pkg;
mSbn = entry.getSbn();
@@ -286,8 +266,8 @@ public class PartialConversationInfo extends LinearLayout implements
}
@Override
- public boolean shouldBeSaved() {
- return mPressedApply;
+ public boolean shouldBeSavedOnClose() {
+ return false;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index d058b75fd1b1..53e08ea8e10d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -10,6 +10,7 @@ import android.os.PowerManager
import android.provider.Settings
import android.view.Surface
import android.view.View
+import android.view.WindowManager.fixScale
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF
import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF_SHOW_AOD
@@ -138,8 +139,8 @@ class UnlockedScreenOffAnimationController @Inject constructor(
}
fun updateAnimatorDurationScale() {
- animatorDurationScale =
- globalSettings.getFloat(Settings.Global.ANIMATOR_DURATION_SCALE, 1f)
+ animatorDurationScale = fixScale(
+ globalSettings.getFloat(Settings.Global.ANIMATOR_DURATION_SCALE, 1f))
}
override fun shouldDelayKeyguardShow(): Boolean =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoCollector.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoCollector.kt
index 6c02b0d44db3..780a02da3410 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoCollector.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoCollector.kt
@@ -16,7 +16,7 @@
package com.android.systemui.statusbar.pipeline
-import com.android.systemui.statusbar.pipeline.repository.NetworkCapabilityInfo
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.NetworkCapabilityInfo
import kotlinx.coroutines.flow.StateFlow
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoCollectorImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoCollectorImpl.kt
index 8d69422c7427..99798f9b38d3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoCollectorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoCollectorImpl.kt
@@ -18,9 +18,9 @@ package com.android.systemui.statusbar.pipeline
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.statusbar.pipeline.repository.NetworkCapabilitiesRepo
-import kotlinx.coroutines.CoroutineScope
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.NetworkCapabilitiesRepo
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted.Companion.Lazily
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt
index 1aae25058ba8..64c47f679142 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt
@@ -20,14 +20,18 @@ import android.content.Context
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.statusbar.pipeline.repository.NetworkCapabilityInfo
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.NetworkCapabilityInfo
+import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel
import javax.inject.Inject
+import javax.inject.Provider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted.Companion.Lazily
+import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
/**
* A processor that transforms raw connectivity information that we get from callbacks and turns it
@@ -42,12 +46,16 @@ import kotlinx.coroutines.flow.stateIn
class ConnectivityInfoProcessor @Inject constructor(
connectivityInfoCollector: ConnectivityInfoCollector,
context: Context,
+ // TODO(b/238425913): Don't use the application scope; instead, use the status bar view's
+ // scope so we only do work when there's UI that cares about it.
@Application private val scope: CoroutineScope,
- statusBarPipelineFlags: StatusBarPipelineFlags,
+ private val statusBarPipelineFlags: StatusBarPipelineFlags,
+ private val wifiViewModelProvider: Provider<WifiViewModel>,
) : CoreStartable(context) {
// Note: This flow will not start running until a client calls `collect` on it, which means that
// [connectivityInfoCollector]'s flow will also not start anything until that `collect` call
// happens.
+ // TODO(b/238425913): Delete this.
val processedInfoFlow: Flow<ProcessedConnectivityInfo> =
if (!statusBarPipelineFlags.isNewPipelineEnabled())
emptyFlow()
@@ -60,6 +68,14 @@ class ConnectivityInfoProcessor @Inject constructor(
)
override fun start() {
+ if (!statusBarPipelineFlags.isNewPipelineEnabled()) {
+ return
+ }
+ // TODO(b/238425913): The view binder should do this instead. For now, do it here so we can
+ // see the logs.
+ scope.launch {
+ wifiViewModelProvider.get().isActivityInVisible.collect { }
+ }
}
private fun RawConnectivityInfo.process(): ProcessedConnectivityInfo {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityPipelineLogger.kt
deleted file mode 100644
index f88e9d67d25d..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityPipelineLogger.kt
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.pipeline
-
-import android.net.Network
-import android.net.NetworkCapabilities
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
-import com.android.systemui.log.dagger.StatusBarConnectivityLog
-import javax.inject.Inject
-
-@SysUISingleton
-class ConnectivityPipelineLogger @Inject constructor(
- @StatusBarConnectivityLog private val buffer: LogBuffer,
-) {
- fun logOnCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
- buffer.log(
- TAG,
- LogLevel.INFO,
- {
- int1 = network.getNetId()
- str1 = networkCapabilities.toString()
- },
- {
- "onCapabilitiesChanged: net=$int1 capabilities=$str1"
- }
- )
- }
-
- fun logOnLost(network: Network) {
- buffer.log(
- TAG,
- LogLevel.INFO,
- {
- int1 = network.getNetId()
- },
- {
- "onLost: net=$int1"
- }
- )
- }
-}
-
-private const val TAG = "SbConnectivityPipeline"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
index c4e2b732f388..7abe19e7bbe0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
@@ -20,6 +20,8 @@ import com.android.systemui.CoreStartable
import com.android.systemui.statusbar.pipeline.ConnectivityInfoCollector
import com.android.systemui.statusbar.pipeline.ConnectivityInfoCollectorImpl
import com.android.systemui.statusbar.pipeline.ConnectivityInfoProcessor
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl
import dagger.Binds
import dagger.Module
import dagger.multibindings.ClassKey
@@ -37,4 +39,7 @@ abstract class StatusBarPipelineModule {
abstract fun provideConnectivityInfoCollector(
impl: ConnectivityInfoCollectorImpl
): ConnectivityInfoCollector
+
+ @Binds
+ abstract fun wifiRepository(impl: WifiRepositoryImpl): WifiRepository
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt
new file mode 100644
index 000000000000..a5fff5e65ebc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt
@@ -0,0 +1,106 @@
+/*
+ * 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.pipeline.shared
+
+import android.net.Network
+import android.net.NetworkCapabilities
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.dagger.StatusBarConnectivityLog
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.toString
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.onEach
+
+@SysUISingleton
+class ConnectivityPipelineLogger @Inject constructor(
+ @StatusBarConnectivityLog private val buffer: LogBuffer,
+) {
+ fun logInputChange(callbackName: String, changeInfo: String) {
+ buffer.log(
+ SB_LOGGING_TAG,
+ LogLevel.INFO,
+ {
+ str1 = callbackName
+ str2 = changeInfo
+ },
+ {
+ "Input: $str1: $str2"
+ }
+ )
+ }
+
+ fun logOutputChange(outputParamName: String, changeInfo: String) {
+ buffer.log(
+ SB_LOGGING_TAG,
+ LogLevel.INFO,
+ {
+ str1 = outputParamName
+ str2 = changeInfo
+ },
+ {
+ "Output: $str1: $str2"
+ }
+ )
+ }
+
+ fun logOnCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
+ buffer.log(
+ SB_LOGGING_TAG,
+ LogLevel.INFO,
+ {
+ int1 = network.getNetId()
+ str1 = networkCapabilities.toString()
+ },
+ {
+ "onCapabilitiesChanged: net=$int1 capabilities=$str1"
+ }
+ )
+ }
+
+ fun logOnLost(network: Network) {
+ buffer.log(
+ SB_LOGGING_TAG,
+ LogLevel.INFO,
+ {
+ int1 = network.getNetId()
+ },
+ {
+ "onLost: net=$int1"
+ }
+ )
+ }
+
+ companion object {
+ const val SB_LOGGING_TAG = "SbConnectivity"
+
+ /**
+ * Log a change in one of the **outputs** to the connectivity pipeline.
+ *
+ * @param prettyPrint an optional function to transform the value into a readable string.
+ * [toString] is used if no custom function is provided.
+ */
+ fun <T : Any> Flow<T>.logOutputChange(
+ logger: ConnectivityPipelineLogger,
+ outputParamName: String,
+ prettyPrint: (T) -> String = { it.toString() }
+ ): Flow<T> {
+ return this.onEach { logger.logOutputChange(outputParamName, prettyPrint(it)) }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiActivityModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiActivityModel.kt
new file mode 100644
index 000000000000..44c04968041e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiActivityModel.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.pipeline.wifi.data.model
+
+/**
+ * Provides information on the current wifi activity.
+ */
+data class WifiActivityModel(
+ /** True if the wifi has activity in (download). */
+ val hasActivityIn: Boolean,
+ /** True if the wifi has activity out (upload). */
+ val hasActivityOut: Boolean,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveAnimateBottomAreaTransitionsUseCase.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiModel.kt
index ca37727072f0..1b7332265b7a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/usecase/ObserveAnimateBottomAreaTransitionsUseCase.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiModel.kt
@@ -14,19 +14,16 @@
* limitations under the License.
*/
-package com.android.systemui.keyguard.domain.usecase
+package com.android.systemui.statusbar.pipeline.wifi.data.model
-import com.android.systemui.keyguard.data.repository.KeyguardRepository
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-
-/** Use-case for observing whether doze state transitions should animate the bottom area */
-class ObserveAnimateBottomAreaTransitionsUseCase
-@Inject
-constructor(
- private val repository: KeyguardRepository,
-) {
- operator fun invoke(): Flow<Boolean> {
- return repository.animateBottomAreaDozingTransitions
- }
-}
+/** Provides information about the current wifi state. */
+data class WifiModel(
+ /** See [android.net.wifi.WifiInfo.ssid]. */
+ val ssid: String? = null,
+ /** See [android.net.wifi.WifiInfo.isPasspointAp]. */
+ val isPasspointAccessPoint: Boolean = false,
+ /** See [android.net.wifi.WifiInfo.isOsuAp]. */
+ val isOnlineSignUpForPasspointAccessPoint: Boolean = false,
+ /** See [android.net.wifi.WifiInfo.passpointProviderFriendlyName]. */
+ val passpointProviderFriendlyName: String? = null,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/repository/NetworkCapabilitiesRepo.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/NetworkCapabilitiesRepo.kt
index e5980c3693ee..6c0a44524e3a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/repository/NetworkCapabilitiesRepo.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/NetworkCapabilitiesRepo.kt
@@ -16,7 +16,7 @@
@file:OptIn(ExperimentalCoroutinesApi::class)
-package com.android.systemui.statusbar.pipeline.repository
+package com.android.systemui.statusbar.pipeline.wifi.data.repository
import android.annotation.SuppressLint
import android.net.ConnectivityManager
@@ -25,7 +25,7 @@ import android.net.NetworkCapabilities
import android.net.NetworkRequest
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.statusbar.pipeline.ConnectivityPipelineLogger
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -35,7 +35,11 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.stateIn
-/** Repository that contains all relevant [NetworkCapabilites] for the current networks */
+/**
+ * Repository that contains all relevant [NetworkCapabilities] for the current networks.
+ *
+ * TODO(b/238425913): Figure out how to merge this with [WifiRepository].
+ */
@SysUISingleton
class NetworkCapabilitiesRepo @Inject constructor(
connectivityManager: ConnectivityManager,
@@ -88,5 +92,3 @@ data class NetworkCapabilityInfo(
val network: Network,
val capabilities: NetworkCapabilities,
)
-
-private const val TAG = "ConnectivityRepository"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
new file mode 100644
index 000000000000..012dde5fb8f2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.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.pipeline.wifi.data.repository
+
+import android.net.wifi.WifiManager
+import android.net.wifi.WifiManager.TrafficStateCallback
+import android.util.Log
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.SB_LOGGING_TAG
+import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiActivityModel
+import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiModel
+import java.util.concurrent.Executor
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+
+/**
+ * Provides data related to the wifi state.
+ */
+interface WifiRepository {
+ /**
+ * Observable for the current state of wifi; `null` when there is no active wifi.
+ */
+ val wifiModel: Flow<WifiModel?>
+
+ /**
+ * Observable for the current wifi network activity.
+ */
+ val wifiActivity: Flow<WifiActivityModel>
+}
+
+/** Real implementation of [WifiRepository]. */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class WifiRepositoryImpl @Inject constructor(
+ wifiManager: WifiManager?,
+ @Main mainExecutor: Executor,
+ logger: ConnectivityPipelineLogger,
+) : WifiRepository {
+
+ // TODO(b/238425913): Actually implement the wifiModel flow.
+ override val wifiModel: Flow<WifiModel?> = flowOf(WifiModel(ssid = "AB"))
+
+ override val wifiActivity: Flow<WifiActivityModel> =
+ if (wifiManager == null) {
+ Log.w(SB_LOGGING_TAG, "Null WifiManager; skipping activity callback")
+ flowOf(ACTIVITY_DEFAULT)
+ } else {
+ conflatedCallbackFlow {
+ val callback = TrafficStateCallback { state ->
+ logger.logInputChange("onTrafficStateChange", prettyPrintActivity(state))
+ trySend(trafficStateToWifiActivityModel(state))
+ }
+
+ wifiManager.registerTrafficStateCallback(mainExecutor, callback)
+ trySend(ACTIVITY_DEFAULT)
+
+ awaitClose { wifiManager.unregisterTrafficStateCallback(callback) }
+ }
+ }
+
+ companion object {
+ val ACTIVITY_DEFAULT = WifiActivityModel(hasActivityIn = false, hasActivityOut = false)
+
+ private fun trafficStateToWifiActivityModel(state: Int): WifiActivityModel {
+ return WifiActivityModel(
+ hasActivityIn = state == TrafficStateCallback.DATA_ACTIVITY_IN ||
+ state == TrafficStateCallback.DATA_ACTIVITY_INOUT,
+ hasActivityOut = state == TrafficStateCallback.DATA_ACTIVITY_OUT ||
+ state == TrafficStateCallback.DATA_ACTIVITY_INOUT,
+ )
+ }
+
+ private fun prettyPrintActivity(activity: Int): String {
+ return when (activity) {
+ TrafficStateCallback.DATA_ACTIVITY_NONE -> "NONE"
+ TrafficStateCallback.DATA_ACTIVITY_IN -> "IN"
+ TrafficStateCallback.DATA_ACTIVITY_OUT -> "OUT"
+ TrafficStateCallback.DATA_ACTIVITY_INOUT -> "INOUT"
+ else -> "INVALID"
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
new file mode 100644
index 000000000000..f705399af85d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.pipeline.wifi.domain.interactor
+
+import android.net.wifi.WifiManager
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.map
+
+/**
+ * The business logic layer for the wifi icon.
+ *
+ * This interactor processes information from our data layer into information that the UI layer can
+ * use.
+ */
+@SysUISingleton
+class WifiInteractor @Inject constructor(
+ repository: WifiRepository,
+) {
+ private val ssid: Flow<String?> = repository.wifiModel.map { info ->
+ when {
+ info == null -> null
+ info.isPasspointAccessPoint || info.isOnlineSignUpForPasspointAccessPoint ->
+ info.passpointProviderFriendlyName
+ info.ssid != WifiManager.UNKNOWN_SSID -> info.ssid
+ else -> null
+ }
+ }
+
+ val hasActivityIn: Flow<Boolean> = combine(repository.wifiActivity, ssid) { activity, ssid ->
+ activity.hasActivityIn && ssid != null
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiConstants.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiConstants.kt
new file mode 100644
index 000000000000..a19d1bdd8e62
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiConstants.kt
@@ -0,0 +1,49 @@
+/*
+ * 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.pipeline.wifi.shared
+
+import android.content.Context
+import com.android.systemui.Dumpable
+import com.android.systemui.R
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.SB_LOGGING_TAG
+import java.io.PrintWriter
+import javax.inject.Inject
+
+/**
+ * An object storing constants that we use for calculating the wifi icon. Stored in a class for
+ * logging purposes.
+ */
+@SysUISingleton
+class WifiConstants @Inject constructor(
+ context: Context,
+ dumpManager: DumpManager,
+) : Dumpable {
+ init {
+ dumpManager.registerDumpable("$SB_LOGGING_TAG:WifiConstants", this)
+ }
+
+ /** True if we should show the activityIn/activityOut icons and false otherwise. */
+ val shouldShowActivityConfig = context.resources.getBoolean(R.bool.config_showActivity)
+
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ pw.apply {
+ println("shouldShowActivityConfig=$shouldShowActivityConfig")
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
new file mode 100644
index 000000000000..b990eb7d9173
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
@@ -0,0 +1,45 @@
+/*
+ * 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.pipeline.wifi.ui.viewmodel
+
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
+import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
+import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+
+/**
+ * Models the UI state for the status bar wifi icon.
+ *
+ * TODO(b/238425913): Hook this up to the real status bar wifi view using a view binder.
+ */
+class WifiViewModel @Inject constructor(
+ private val constants: WifiConstants,
+ private val logger: ConnectivityPipelineLogger,
+ private val interactor: WifiInteractor,
+) {
+ val isActivityInVisible: Flow<Boolean>
+ get() =
+ if (!constants.shouldShowActivityConfig) {
+ flowOf(false)
+ } else {
+ interactor.hasActivityIn
+ }
+ .logOutputChange(logger, "activityInVisible")
+}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
index 8f2a432d0ba1..fc20ac241e38 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
@@ -138,7 +138,7 @@ constructor(
ensureOverlayRemoved()
- val newRoot = SurfaceControlViewHost(context, context.display!!, wwm, false)
+ val newRoot = SurfaceControlViewHost(context, context.display!!, wwm)
val newView =
LightRevealScrim(context, null).apply {
revealEffect = createLightRevealEffect()
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt
index 05d087e232ba..0a44bda70238 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt
@@ -37,5 +37,5 @@ object CoroutinesModule {
@Provides
@SysUISingleton
@Background
- fun bgDispatcher(): CoroutineDispatcher = Dispatchers.Default
+ fun bgDispatcher(): CoroutineDispatcher = Dispatchers.IO
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index c67737136b3b..c281965550e4 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -581,6 +581,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
@Test
public void testTriesToAuthenticate_whenBouncer() {
fingerprintIsNotEnrolled();
+ faceAuthEnabled();
setKeyguardBouncerVisibility(true);
verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
@@ -1218,6 +1219,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
public void testShouldListenForFace_whenFaceIsAlreadyAuthenticated_returnsFalse()
throws RemoteException {
// Face auth should run when the following is true.
+ faceAuthEnabled();
bouncerFullyVisibleAndNotGoingToSleep();
fingerprintIsNotEnrolled();
keyguardNotGoingAway();
@@ -1284,6 +1286,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
public void testShouldListenForFace_whenBiometricsDisabledForUser_returnsFalse()
throws RemoteException {
// Preconditions for face auth to run
+ faceAuthEnabled();
keyguardNotGoingAway();
bouncerFullyVisibleAndNotGoingToSleep();
fingerprintIsNotEnrolled();
@@ -1307,6 +1310,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
public void testShouldListenForFace_whenUserCurrentlySwitching_returnsFalse()
throws RemoteException {
// Preconditions for face auth to run
+ faceAuthEnabled();
keyguardNotGoingAway();
bouncerFullyVisibleAndNotGoingToSleep();
fingerprintIsNotEnrolled();
@@ -1329,6 +1333,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
public void testShouldListenForFace_whenSecureCameraLaunched_returnsFalse()
throws RemoteException {
// Preconditions for face auth to run
+ faceAuthEnabled();
keyguardNotGoingAway();
bouncerFullyVisibleAndNotGoingToSleep();
fingerprintIsNotEnrolled();
@@ -1374,6 +1379,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
public void testShouldListenForFace_whenBouncerShowingAndDeviceIsAwake_returnsTrue()
throws RemoteException {
// Preconditions for face auth to run
+ faceAuthEnabled();
keyguardNotGoingAway();
currentUserIsPrimary();
currentUserDoesNotHaveTrust();
@@ -1539,8 +1545,19 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(anyBoolean())).isEqualTo(true);
}
+ private void faceAuthEnabled() {
+ // this ensures KeyguardUpdateMonitor updates the cached mIsFaceEnrolled flag using the
+ // face manager mock wire-up in setup()
+ mKeyguardUpdateMonitor.isFaceAuthEnabledForUser(mCurrentUserId);
+ }
+
private void fingerprintIsNotEnrolled() {
when(mFingerprintManager.hasEnrolledTemplates(mCurrentUserId)).thenReturn(false);
+ // This updates the cached fingerprint state.
+ // There is no straightforward API to update the fingerprint state.
+ // It currently works updates after enrollment changes because something else invokes
+ // startListeningForFingerprint(), which internally calls this method.
+ mKeyguardUpdateMonitor.isUnlockWithFingerprintPossible(mCurrentUserId);
}
private void statusBarShadeIsNotLocked() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationGestureDetectorTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationGestureDetectorTest.java
index 6f4846a601d9..36ae3c0595ec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationGestureDetectorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationGestureDetectorTest.java
@@ -19,6 +19,7 @@ package com.android.systemui.accessibility;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -27,6 +28,7 @@ import android.os.Handler;
import android.os.SystemClock;
import android.testing.AndroidTestingRunner;
import android.view.MotionEvent;
+import android.view.View;
import android.view.ViewConfiguration;
import androidx.test.filters.SmallTest;
@@ -52,6 +54,7 @@ public class MagnificationGestureDetectorTest extends SysuiTestCase {
private int mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
private MagnificationGestureDetector mGestureDetector;
private MotionEventHelper mMotionEventHelper = new MotionEventHelper();
+ private View mSpyView;
@Mock
private MagnificationGestureDetector.OnGestureListener mListener;
@Mock
@@ -66,6 +69,7 @@ public class MagnificationGestureDetectorTest extends SysuiTestCase {
return null;
}).when(mHandler).postAtTime(any(Runnable.class), anyLong());
mGestureDetector = new MagnificationGestureDetector(mContext, mHandler, mListener);
+ mSpyView = Mockito.spy(new View(mContext));
}
@After
@@ -79,7 +83,7 @@ public class MagnificationGestureDetectorTest extends SysuiTestCase {
final MotionEvent downEvent = mMotionEventHelper.obtainMotionEvent(downTime, downTime,
MotionEvent.ACTION_DOWN, ACTION_DOWN_X, ACTION_DOWN_Y);
- mGestureDetector.onTouch(downEvent);
+ mGestureDetector.onTouch(mSpyView, downEvent);
mListener.onStart(ACTION_DOWN_X, ACTION_DOWN_Y);
}
@@ -92,14 +96,14 @@ public class MagnificationGestureDetectorTest extends SysuiTestCase {
final MotionEvent upEvent = mMotionEventHelper.obtainMotionEvent(downTime, downTime,
MotionEvent.ACTION_UP, ACTION_DOWN_X, ACTION_DOWN_Y);
- mGestureDetector.onTouch(downEvent);
- mGestureDetector.onTouch(upEvent);
+ mGestureDetector.onTouch(mSpyView, downEvent);
+ mGestureDetector.onTouch(mSpyView, upEvent);
InOrder inOrder = Mockito.inOrder(mListener);
inOrder.verify(mListener).onStart(ACTION_DOWN_X, ACTION_DOWN_Y);
- inOrder.verify(mListener).onSingleTap();
+ inOrder.verify(mListener).onSingleTap(mSpyView);
inOrder.verify(mListener).onFinish(ACTION_DOWN_X, ACTION_DOWN_Y);
- verify(mListener, never()).onDrag(anyFloat(), anyFloat());
+ verify(mListener, never()).onDrag(eq(mSpyView), anyFloat(), anyFloat());
}
@Test
@@ -110,10 +114,10 @@ public class MagnificationGestureDetectorTest extends SysuiTestCase {
final MotionEvent cancelEvent = mMotionEventHelper.obtainMotionEvent(downTime, downTime,
MotionEvent.ACTION_CANCEL, ACTION_DOWN_X, ACTION_DOWN_Y);
- mGestureDetector.onTouch(downEvent);
- mGestureDetector.onTouch(cancelEvent);
+ mGestureDetector.onTouch(mSpyView, downEvent);
+ mGestureDetector.onTouch(mSpyView, cancelEvent);
- verify(mListener, never()).onSingleTap();
+ verify(mListener, never()).onSingleTap(mSpyView);
}
@Test
@@ -124,10 +128,10 @@ public class MagnificationGestureDetectorTest extends SysuiTestCase {
final MotionEvent upEvent = mMotionEventHelper.obtainMotionEvent(downTime, downTime,
MotionEvent.ACTION_POINTER_DOWN, ACTION_DOWN_X, ACTION_DOWN_Y);
- mGestureDetector.onTouch(downEvent);
- mGestureDetector.onTouch(upEvent);
+ mGestureDetector.onTouch(mSpyView, downEvent);
+ mGestureDetector.onTouch(mSpyView, upEvent);
- verify(mListener, never()).onSingleTap();
+ verify(mListener, never()).onSingleTap(mSpyView);
}
@Test
@@ -138,15 +142,15 @@ public class MagnificationGestureDetectorTest extends SysuiTestCase {
final MotionEvent upEvent = mMotionEventHelper.obtainMotionEvent(downTime, downTime,
MotionEvent.ACTION_UP, ACTION_DOWN_X, ACTION_DOWN_Y);
- mGestureDetector.onTouch(downEvent);
+ mGestureDetector.onTouch(mSpyView, downEvent);
// Execute the pending message for stopping single-tap detection.
mCancelSingleTapRunnable.run();
- mGestureDetector.onTouch(upEvent);
+ mGestureDetector.onTouch(mSpyView, upEvent);
InOrder inOrder = Mockito.inOrder(mListener);
inOrder.verify(mListener).onStart(ACTION_DOWN_X, ACTION_DOWN_Y);
inOrder.verify(mListener).onFinish(ACTION_DOWN_X, ACTION_DOWN_Y);
- verify(mListener, never()).onSingleTap();
+ verify(mListener, never()).onSingleTap(mSpyView);
}
@Test
@@ -160,14 +164,14 @@ public class MagnificationGestureDetectorTest extends SysuiTestCase {
final MotionEvent upEvent = mMotionEventHelper.obtainMotionEvent(downTime, downTime,
MotionEvent.ACTION_UP, ACTION_DOWN_X, ACTION_DOWN_Y);
- mGestureDetector.onTouch(downEvent);
- mGestureDetector.onTouch(moveEvent);
- mGestureDetector.onTouch(upEvent);
+ mGestureDetector.onTouch(mSpyView, downEvent);
+ mGestureDetector.onTouch(mSpyView, moveEvent);
+ mGestureDetector.onTouch(mSpyView, upEvent);
InOrder inOrder = Mockito.inOrder(mListener);
inOrder.verify(mListener).onStart(ACTION_DOWN_X, ACTION_DOWN_Y);
- inOrder.verify(mListener).onDrag(dragOffset, 0);
+ inOrder.verify(mListener).onDrag(mSpyView, dragOffset, 0);
inOrder.verify(mListener).onFinish(ACTION_DOWN_X, ACTION_DOWN_Y);
- verify(mListener, never()).onSingleTap();
+ verify(mListener, never()).onSingleTap(mSpyView);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java
index bc89da7d504c..00cb49169048 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java
@@ -145,7 +145,7 @@ public class MagnificationModeSwitchTest extends SysuiTestCase {
@Test
public void removeButton_buttonIsShowing_removeViewAndUnregisterComponentCallbacks() {
- mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+ mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
mMagnificationModeSwitch.removeButton();
@@ -167,7 +167,7 @@ public class MagnificationModeSwitchTest extends SysuiTestCase {
@Test
public void showButton_excludeSystemGestureArea() {
- mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+ mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
verify(mSpyImageView).setSystemGestureExclusionRects(any(List.class));
}
@@ -178,7 +178,7 @@ public class MagnificationModeSwitchTest extends SysuiTestCase {
when(mAccessibilityManager.getRecommendedTimeoutMillis(anyInt(), anyInt())).thenReturn(
a11yTimeout);
- mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+ mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
verify(mAccessibilityManager).getRecommendedTimeoutMillis(
DEFAULT_FADE_OUT_ANIMATION_DELAY_MS, AccessibilityManager.FLAG_CONTENT_ICONS
@@ -188,7 +188,7 @@ public class MagnificationModeSwitchTest extends SysuiTestCase {
@Test
public void showMagnificationButton_windowModeAndFadingOut_verifyAnimationEndAction() {
- mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+ mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
executeFadeOutAnimation();
// Verify the end action after fade-out.
@@ -389,15 +389,15 @@ public class MagnificationModeSwitchTest extends SysuiTestCase {
@Test
public void initializeA11yNode_showWindowModeButton_expectedValues() {
- mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+ mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
final AccessibilityNodeInfo nodeInfo = new AccessibilityNodeInfo();
mSpyImageView.onInitializeAccessibilityNodeInfo(nodeInfo);
assertEquals(mContext.getString(R.string.magnification_mode_switch_description),
nodeInfo.getContentDescription());
- assertEquals(mContext.getString(R.string.magnification_mode_switch_state_window),
- nodeInfo.getStateDescription());
+ assertEquals(mContext.getString(R.string.magnification_mode_switch_state_full_screen),
+ nodeInfo.getStateDescription().toString());
assertThat(nodeInfo.getActionList(),
hasItems(new AccessibilityNodeInfo.AccessibilityAction(
ACTION_CLICK.getId(), mContext.getResources().getString(
@@ -422,18 +422,18 @@ public class MagnificationModeSwitchTest extends SysuiTestCase {
@Test
public void performClickA11yActions_showWindowModeButton_verifyTapAction() {
- mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+ mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
resetAndStubMockImageViewAndAnimator();
mSpyImageView.performAccessibilityAction(
ACTION_CLICK.getId(), null);
- verifyTapAction(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
+ verifyTapAction(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
}
@Test
public void performMoveLeftA11yAction_showButtonAtRightEdge_moveToLeftEdge() {
- mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+ mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
mSpyImageView.performAccessibilityAction(
R.id.accessibility_action_move_left, null);
@@ -456,7 +456,7 @@ public class MagnificationModeSwitchTest extends SysuiTestCase {
@Test
public void showButton_hasAccessibilityWindowTitle() {
- mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+ mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
final WindowManager.LayoutParams layoutPrams =
mWindowManager.getLayoutParamsFromAttachedView();
@@ -468,7 +468,7 @@ public class MagnificationModeSwitchTest extends SysuiTestCase {
@Test
public void showButton_registerComponentCallbacks() {
- mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+ mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
verify(mContext).registerComponentCallbacks(mMagnificationModeSwitch);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java
index a56218b08224..82ae6ff1e945 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java
@@ -24,6 +24,7 @@ import android.provider.Settings;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.Display;
+import android.view.View;
import androidx.test.filters.SmallTest;
@@ -46,6 +47,7 @@ public class ModeSwitchesControllerTest extends SysuiTestCase {
private FakeSwitchSupplier mSupplier;
private MagnificationModeSwitch mModeSwitch;
private ModeSwitchesController mModeSwitchesController;
+ private View mSpyView;
@Mock
private MagnificationModeSwitch.SwitchListener mListener;
@@ -57,6 +59,7 @@ public class ModeSwitchesControllerTest extends SysuiTestCase {
mModeSwitchesController = new ModeSwitchesController(mSupplier);
mModeSwitchesController.setSwitchListenerDelegate(mListener);
mModeSwitch = Mockito.spy(new MagnificationModeSwitch(mContext, mModeSwitchesController));
+ mSpyView = Mockito.spy(new View(mContext));
}
@After
@@ -94,12 +97,12 @@ public class ModeSwitchesControllerTest extends SysuiTestCase {
@Test
public void testOnSwitchClick_showWindowModeButton_invokeListener() {
mModeSwitchesController.showButton(Display.DEFAULT_DISPLAY,
- Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
- mModeSwitch.onSingleTap();
+ mModeSwitch.onSingleTap(mSpyView);
verify(mListener).onSwitch(mContext.getDisplayId(),
- Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
+ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
}
private class FakeSwitchSupplier extends DisplayIdIndexSupplier<MagnificationModeSwitch> {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
index 3d77d64a7988..69ccc8b88bd8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
@@ -19,6 +19,8 @@ package com.android.systemui.accessibility;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.view.Choreographer.FrameCallback;
+import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_UP;
import static android.view.WindowInsets.Type.systemGestures;
import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
@@ -46,6 +48,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.animation.ValueAnimator;
+import android.annotation.IdRes;
import android.app.Instrumentation;
import android.content.Context;
import android.content.pm.ActivityInfo;
@@ -65,6 +68,7 @@ import android.testing.TestableResources;
import android.text.TextUtils;
import android.view.Display;
import android.view.IWindowSession;
+import android.view.MotionEvent;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.View;
@@ -126,8 +130,13 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
private WindowMagnificationController mWindowMagnificationController;
private Instrumentation mInstrumentation;
private final ValueAnimator mValueAnimator = ValueAnimator.ofFloat(0, 1.0f).setDuration(0);
+
private IWindowSession mWindowSessionSpy;
+ private View mSpyView;
+ private View.OnTouchListener mTouchListener;
+ private MotionEventHelper mMotionEventHelper = new MotionEventHelper();
+
@Before
public void setUp() throws RemoteException {
MockitoAnnotations.initMocks(this);
@@ -165,6 +174,12 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
verify(mMirrorWindowControl).setWindowDelegate(
any(MirrorWindowControl.MirrorWindowDelegate.class));
+ mSpyView = Mockito.spy(new View(mContext));
+ doAnswer((invocation) -> {
+ mTouchListener = invocation.getArgument(0);
+ return null;
+ }).when(mSpyView).setOnTouchListener(
+ any(View.OnTouchListener.class));
}
@After
@@ -702,7 +717,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
});
mInstrumentation.runOnMainSync(() -> {
- mWindowMagnificationController.onSingleTap();
+ mWindowMagnificationController.onSingleTap(mSpyView);
});
final View mirrorView = mWindowManager.getAttachedView();
@@ -910,6 +925,38 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
assertTrue(magnificationCenterY.get() < bounds.bottom);
}
+ @Test
+ public void performSingleTap_DragHandle() {
+ final Rect bounds = mWindowManager.getCurrentWindowMetrics().getBounds();
+ mInstrumentation.runOnMainSync(
+ () -> {
+ mWindowMagnificationController.enableWindowMagnificationInternal(
+ 1.5f, bounds.centerX(), bounds.centerY());
+ });
+ View dragButton = getInternalView(R.id.drag_handle);
+
+ // Perform a single-tap
+ final long downTime = SystemClock.uptimeMillis();
+ dragButton.dispatchTouchEvent(
+ obtainMotionEvent(downTime, 0, ACTION_DOWN, 100, 100));
+ dragButton.dispatchTouchEvent(
+ obtainMotionEvent(downTime, downTime, ACTION_UP, 100, 100));
+
+ verify(mWindowManager).addView(any(View.class), any());
+ }
+
+ private <T extends View> T getInternalView(@IdRes int idRes) {
+ View mirrorView = mWindowManager.getAttachedView();
+ T view = mirrorView.findViewById(idRes);
+ assertNotNull(view);
+ return view;
+ }
+
+ private MotionEvent obtainMotionEvent(long downTime, long eventTime, int action, float x,
+ float y) {
+ return mMotionEventHelper.obtainMotionEvent(downTime, eventTime, action, x, y);
+ }
+
private CharSequence getAccessibilityWindowTitle() {
final View mirrorView = mWindowManager.getAttachedView();
if (mirrorView == null) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java
new file mode 100644
index 000000000000..2f94b69b8189
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.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 com.android.systemui.accessibility;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.annotation.IdRes;
+import android.content.Context;
+import android.provider.Settings;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
+import android.widget.CompoundButton;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+public class WindowMagnificationSettingsTest extends SysuiTestCase {
+
+ private static final int MAGNIFICATION_SIZE_SMALL = 1;
+ private static final int MAGNIFICATION_SIZE_MEDIUM = 2;
+ private static final int MAGNIFICATION_SIZE_LARGE = 3;
+
+ private ViewGroup mSettingView;
+ @Mock
+ private AccessibilityManager mAccessibilityManager;
+ @Mock
+ private SfVsyncFrameCallbackProvider mSfVsyncFrameProvider;
+ @Mock
+ private WindowMagnificationSettingsCallback mWindowMagnificationSettingsCallback;
+ private TestableWindowManager mWindowManager;
+ private WindowMagnificationSettings mWindowMagnificationSettings;
+ private MotionEventHelper mMotionEventHelper = new MotionEventHelper();
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mContext = getContext();
+ mContext.setTheme(android.R.style.Theme_DeviceDefault_DayNight);
+ final WindowManager wm = mContext.getSystemService(WindowManager.class);
+ mWindowManager = spy(new TestableWindowManager(wm));
+ mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager);
+ mContext.addMockSystemService(Context.ACCESSIBILITY_SERVICE, mAccessibilityManager);
+
+ mWindowMagnificationSettings = new WindowMagnificationSettings(mContext,
+ mWindowMagnificationSettingsCallback, mSfVsyncFrameProvider);
+
+ mSettingView = mWindowMagnificationSettings.getSettingView();
+ }
+
+ @After
+ public void tearDown() {
+ mMotionEventHelper.recycleEvents();
+ mWindowMagnificationSettings.hideSettingPanel();
+ }
+
+ @Test
+ public void showSettingPanel_hasAccessibilityWindowTitle() {
+ mWindowMagnificationSettings.showSettingPanel();
+
+ final WindowManager.LayoutParams layoutPrams =
+ mWindowManager.getLayoutParamsFromAttachedView();
+ assertNotNull(layoutPrams);
+ assertEquals(getContext().getResources()
+ .getString(com.android.internal.R.string.android_system_label),
+ layoutPrams.accessibilityTitle.toString());
+ }
+
+ @Test
+ public void performClick_smallSizeButton_changeMagnifierSizeSmall() {
+ // Open view
+ mWindowMagnificationSettings.showSettingPanel();
+
+ verifyOnSetMagnifierSize(R.id.magnifier_small_button, MAGNIFICATION_SIZE_SMALL);
+ }
+
+ @Test
+ public void performClick_mediumSizeButton_changeMagnifierSizeMedium() {
+ // Open view
+ mWindowMagnificationSettings.showSettingPanel();
+
+ verifyOnSetMagnifierSize(R.id.magnifier_medium_button, MAGNIFICATION_SIZE_MEDIUM);
+ }
+
+ @Test
+ public void performClick_largeSizeButton_changeMagnifierSizeLarge() {
+ // Open view
+ mWindowMagnificationSettings.showSettingPanel();
+
+ verifyOnSetMagnifierSize(R.id.magnifier_large_button, MAGNIFICATION_SIZE_LARGE);
+ }
+
+ private void verifyOnSetMagnifierSize(@IdRes int viewId, int expectedSizeIndex) {
+ View changeSizeButton = getInternalView(viewId);
+
+ // Perform click
+ changeSizeButton.performClick();
+
+ verify(mWindowMagnificationSettingsCallback).onSetMagnifierSize(expectedSizeIndex);
+ }
+
+
+ @Test
+ public void performClick_fullScreenModeButton_setEditMagnifierSizeMode() {
+ View fullScreenModeButton = getInternalView(R.id.magnifier_full_button);
+ getInternalView(R.id.magnifier_panel_view);
+
+ // Open view
+ mWindowMagnificationSettings.showSettingPanel();
+
+ // Perform click
+ fullScreenModeButton.performClick();
+
+ verify(mWindowManager).removeView(mSettingView);
+ verify(mWindowMagnificationSettingsCallback)
+ .onModeSwitch(Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
+ }
+
+ @Test
+ public void performClick_editButton_setEditMagnifierSizeMode() {
+ View editButton = getInternalView(R.id.magnifier_edit_button);
+
+ // Open view
+ mWindowMagnificationSettings.showSettingPanel();
+
+ // Perform click
+ editButton.performClick();
+
+ verify(mWindowMagnificationSettingsCallback).onEditMagnifierSizeMode(true);
+ verify(mWindowManager).removeView(mSettingView);
+ }
+
+ @Test
+ public void performClick_setDiagonalScrollingSwitch_toggleDiagonalScrollingSwitchMode() {
+ CompoundButton diagonalScrollingSwitch =
+ getInternalView(R.id.magnifier_horizontal_lock_switch);
+ final boolean currentCheckedState = diagonalScrollingSwitch.isChecked();
+
+ // Open view
+ mWindowMagnificationSettings.showSettingPanel();
+
+ // Perform click
+ diagonalScrollingSwitch.performClick();
+
+ verify(mWindowMagnificationSettingsCallback).onSetDiagonalScrolling(!currentCheckedState);
+ }
+
+ private <T extends View> T getInternalView(@IdRes int idRes) {
+ T view = mSettingView.findViewById(idRes);
+ assertNotNull(view);
+ return view;
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java
index ccf2f8b16f8a..e1bd25bd17e7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java
@@ -158,6 +158,18 @@ public class WindowMagnificationTest extends SysuiTestCase {
}
@Test
+ public void onModeSwitch_enabled_notifyCallback() throws RemoteException {
+ final int magnificationModeFullScreen = 1;
+ mCommandQueue.requestWindowMagnificationConnection(true);
+ waitForIdleSync();
+
+ mWindowMagnification.onModeSwitch(TEST_DISPLAY, magnificationModeFullScreen);
+
+ verify(mConnectionCallback).onChangeMagnificationMode(TEST_DISPLAY,
+ magnificationModeFullScreen);
+ }
+
+ @Test
public void overviewProxyIsConnected_noController_resetFlag() {
mOverviewProxyListener.onConnectionChanged(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
index 273786d74782..8fc048920ea7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
@@ -664,6 +664,60 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
}
@Test
+ fun animateAddition_runnableRunsWhenAnimationEnds() {
+ var runnableRun = false
+ val onAnimationEndRunnable = { runnableRun = true }
+
+ ViewHierarchyAnimator.animateAddition(
+ rootView,
+ origin = ViewHierarchyAnimator.Hotspot.CENTER,
+ includeMargins = true,
+ onAnimationEnd = onAnimationEndRunnable
+ )
+ rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
+
+ endAnimation(rootView)
+
+ assertEquals(true, runnableRun)
+ }
+
+ @Test
+ fun animateAddition_runnableDoesNotRunWhenAnimationCancelled() {
+ var runnableRun = false
+ val onAnimationEndRunnable = { runnableRun = true }
+
+ ViewHierarchyAnimator.animateAddition(
+ rootView,
+ origin = ViewHierarchyAnimator.Hotspot.CENTER,
+ includeMargins = true,
+ onAnimationEnd = onAnimationEndRunnable
+ )
+ rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
+
+ cancelAnimation(rootView)
+
+ assertEquals(false, runnableRun)
+ }
+
+ @Test
+ fun animationAddition_runnableDoesNotRunWhenOnlyPartwayThroughAnimation() {
+ var runnableRun = false
+ val onAnimationEndRunnable = { runnableRun = true }
+
+ ViewHierarchyAnimator.animateAddition(
+ rootView,
+ origin = ViewHierarchyAnimator.Hotspot.CENTER,
+ includeMargins = true,
+ onAnimationEnd = onAnimationEndRunnable
+ )
+ rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
+
+ advanceAnimation(rootView, 0.5f)
+
+ assertEquals(false, runnableRun)
+ }
+
+ @Test
fun animatesViewRemovalFromStartToEnd() {
setUpRootWithChildren()
@@ -1158,6 +1212,16 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
}
}
+ private fun cancelAnimation(rootView: View) {
+ (rootView.getTag(R.id.tag_animator) as? ObjectAnimator)?.cancel()
+
+ if (rootView is ViewGroup) {
+ for (i in 0 until rootView.childCount) {
+ cancelAnimation(rootView.getChildAt(i))
+ }
+ }
+ }
+
private fun endFadeInAnimation(rootView: View) {
(rootView.getTag(R.id.tag_alpha_animator) as? ObjectAnimator)?.end()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 38a337563165..11eb4e3de354 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -16,11 +16,10 @@
package com.android.systemui.keyguard.data.repository
-import com.android.systemui.common.data.model.Position
+import com.android.systemui.common.shared.model.Position
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.yield
/** Fake implementation of [KeyguardRepository] */
class FakeKeyguardRepository : KeyguardRepository {
@@ -56,30 +55,15 @@ class FakeKeyguardRepository : KeyguardRepository {
_clockPosition.value = Position(x, y)
}
- suspend fun setKeyguardShowing(isShowing: Boolean) {
+ fun setKeyguardShowing(isShowing: Boolean) {
_isKeyguardShowing.value = isShowing
- // Yield to allow the test's collection coroutine to "catch up" and collect this value
- // before the test continues to the next line.
- // TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
- // https://developer.android.com/kotlin/flow/test#continuous-collection and remove this.
- yield()
}
- suspend fun setDozing(isDozing: Boolean) {
+ fun setDozing(isDozing: Boolean) {
_isDozing.value = isDozing
- // Yield to allow the test's collection coroutine to "catch up" and collect this value
- // before the test continues to the next line.
- // TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
- // https://developer.android.com/kotlin/flow/test#continuous-collection and remove this.
- yield()
}
- suspend fun setDozeAmount(dozeAmount: Float) {
+ fun setDozeAmount(dozeAmount: Float) {
_dozeAmount.value = dozeAmount
- // Yield to allow the test's collection coroutine to "catch up" and collect this value
- // before the test continues to the next line.
- // TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
- // https://developer.android.com/kotlin/flow/test#continuous-collection and remove this.
- yield()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index 3d2c51a449c7..3aa22669bbf2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -18,7 +18,7 @@ package com.android.systemui.keyguard.data.repository
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.common.data.model.Position
+import com.android.systemui.common.shared.model.Position
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.mockito.argumentCaptor
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceRegistry.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceRegistry.kt
index 1c9902b36517..e68c43f4abd7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceRegistry.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceRegistry.kt
@@ -23,18 +23,18 @@ import kotlin.reflect.KClass
/** Fake implementation of [FakeKeyguardQuickAffordanceRegistry], for tests. */
class FakeKeyguardQuickAffordanceRegistry(
private val configsByPosition:
- Map<KeyguardQuickAffordancePosition, List<KeyguardQuickAffordanceConfig>>,
-) : KeyguardQuickAffordanceRegistry {
+ Map<KeyguardQuickAffordancePosition, List<FakeKeyguardQuickAffordanceConfig>>,
+) : KeyguardQuickAffordanceRegistry<FakeKeyguardQuickAffordanceConfig> {
override fun getAll(
position: KeyguardQuickAffordancePosition
- ): List<KeyguardQuickAffordanceConfig> {
+ ): List<FakeKeyguardQuickAffordanceConfig> {
return configsByPosition.getValue(position)
}
override fun get(
- configClass: KClass<out KeyguardQuickAffordanceConfig>
- ): KeyguardQuickAffordanceConfig {
+ configClass: KClass<out FakeKeyguardQuickAffordanceConfig>
+ ): FakeKeyguardQuickAffordanceConfig {
return configsByPosition.values
.flatten()
.associateBy { config -> config::class }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/FakeLaunchKeyguardQuickAffordanceUseCase.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/FakeLaunchKeyguardQuickAffordanceUseCase.kt
deleted file mode 100644
index ba0c31ffa9c9..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/FakeLaunchKeyguardQuickAffordanceUseCase.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.domain.usecase
-
-import android.content.Intent
-import com.android.systemui.animation.ActivityLaunchAnimator
-
-/** Fake implementation of [LaunchKeyguardQuickAffordanceUseCase], for tests. */
-class FakeLaunchKeyguardQuickAffordanceUseCase : LaunchKeyguardQuickAffordanceUseCase {
-
- data class Invocation(
- val intent: Intent,
- val canShowWhileLocked: Boolean,
- val animationController: ActivityLaunchAnimator.Controller?
- )
-
- private val _invocations = mutableListOf<Invocation>()
- val invocations: List<Invocation> = _invocations
-
- override fun invoke(
- intent: Intent,
- canShowWhileLocked: Boolean,
- animationController: ActivityLaunchAnimator.Controller?
- ) {
- _invocations.add(
- Invocation(
- intent = intent,
- canShowWhileLocked = canShowWhileLocked,
- animationController = animationController,
- )
- )
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/FakeObserveKeyguardQuickAffordanceUseCase.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/FakeObserveKeyguardQuickAffordanceUseCase.kt
deleted file mode 100644
index 8982752c9fcc..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/FakeObserveKeyguardQuickAffordanceUseCase.kt
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.keyguard.domain.usecase
-
-import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
-import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-
-class FakeObserveKeyguardQuickAffordanceUseCase : ObserveKeyguardQuickAffordanceUseCase {
-
- private val affordanceByPosition =
- mutableMapOf<
- KeyguardQuickAffordancePosition, MutableStateFlow<KeyguardQuickAffordanceModel>>()
-
- init {
- KeyguardQuickAffordancePosition.values().forEach { position ->
- affordanceByPosition[position] = MutableStateFlow(KeyguardQuickAffordanceModel.Hidden)
- }
- }
-
- override fun invoke(
- position: KeyguardQuickAffordancePosition
- ): Flow<KeyguardQuickAffordanceModel> {
- return affordanceByPosition[position] ?: error("Flow unexpectedly missing!")
- }
-
- fun setModel(position: KeyguardQuickAffordancePosition, model: KeyguardQuickAffordanceModel) {
- affordanceByPosition[position]?.value = model
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorParameterizedTest.kt
new file mode 100644
index 000000000000..c5e828eadf9b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorParameterizedTest.kt
@@ -0,0 +1,294 @@
+/*
+ * 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.keyguard.domain.usecase
+
+import android.content.Intent
+import androidx.test.filters.SmallTest
+import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.containeddrawable.ContainedDrawable
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
+import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
+import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
+import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import kotlinx.coroutines.test.runBlockingTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.junit.runners.Parameterized.Parameter
+import org.junit.runners.Parameterized.Parameters
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.ArgumentMatchers.same
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(Parameterized::class)
+class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() {
+
+ companion object {
+ private val INTENT = Intent("some.intent.action")
+ private val DRAWABLE = mock<ContainedDrawable>()
+ private const val CONTENT_DESCRIPTION_RESOURCE_ID = 1337
+
+ @Parameters(
+ name =
+ "needStrongAuthAfterBoot={0}, canShowWhileLocked={1}," +
+ " keyguardIsUnlocked={2}, needsToUnlockFirst={3}, startActivity={4}"
+ )
+ @JvmStatic
+ fun data() =
+ listOf(
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ false,
+ /* canShowWhileLocked= */ false,
+ /* keyguardIsUnlocked= */ false,
+ /* needsToUnlockFirst= */ true,
+ /* startActivity= */ false,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ false,
+ /* canShowWhileLocked= */ false,
+ /* keyguardIsUnlocked= */ true,
+ /* needsToUnlockFirst= */ false,
+ /* startActivity= */ false,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ false,
+ /* canShowWhileLocked= */ true,
+ /* keyguardIsUnlocked= */ false,
+ /* needsToUnlockFirst= */ false,
+ /* startActivity= */ false,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ false,
+ /* canShowWhileLocked= */ true,
+ /* keyguardIsUnlocked= */ true,
+ /* needsToUnlockFirst= */ false,
+ /* startActivity= */ false,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ true,
+ /* canShowWhileLocked= */ false,
+ /* keyguardIsUnlocked= */ false,
+ /* needsToUnlockFirst= */ true,
+ /* startActivity= */ false,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ true,
+ /* canShowWhileLocked= */ false,
+ /* keyguardIsUnlocked= */ true,
+ /* needsToUnlockFirst= */ true,
+ /* startActivity= */ false,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ true,
+ /* canShowWhileLocked= */ true,
+ /* keyguardIsUnlocked= */ false,
+ /* needsToUnlockFirst= */ true,
+ /* startActivity= */ false,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ true,
+ /* canShowWhileLocked= */ true,
+ /* keyguardIsUnlocked= */ true,
+ /* needsToUnlockFirst= */ true,
+ /* startActivity= */ false,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ false,
+ /* canShowWhileLocked= */ false,
+ /* keyguardIsUnlocked= */ false,
+ /* needsToUnlockFirst= */ true,
+ /* startActivity= */ true,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ false,
+ /* canShowWhileLocked= */ false,
+ /* keyguardIsUnlocked= */ true,
+ /* needsToUnlockFirst= */ false,
+ /* startActivity= */ true,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ false,
+ /* canShowWhileLocked= */ true,
+ /* keyguardIsUnlocked= */ false,
+ /* needsToUnlockFirst= */ false,
+ /* startActivity= */ true,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ false,
+ /* canShowWhileLocked= */ true,
+ /* keyguardIsUnlocked= */ true,
+ /* needsToUnlockFirst= */ false,
+ /* startActivity= */ true,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ true,
+ /* canShowWhileLocked= */ false,
+ /* keyguardIsUnlocked= */ false,
+ /* needsToUnlockFirst= */ true,
+ /* startActivity= */ true,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ true,
+ /* canShowWhileLocked= */ false,
+ /* keyguardIsUnlocked= */ true,
+ /* needsToUnlockFirst= */ true,
+ /* startActivity= */ true,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ true,
+ /* canShowWhileLocked= */ true,
+ /* keyguardIsUnlocked= */ false,
+ /* needsToUnlockFirst= */ true,
+ /* startActivity= */ true,
+ ),
+ arrayOf(
+ /* needStrongAuthAfterBoot= */ true,
+ /* canShowWhileLocked= */ true,
+ /* keyguardIsUnlocked= */ true,
+ /* needsToUnlockFirst= */ true,
+ /* startActivity= */ true,
+ ),
+ )
+ }
+
+ @Mock private lateinit var lockPatternUtils: LockPatternUtils
+ @Mock private lateinit var keyguardStateController: KeyguardStateController
+ @Mock private lateinit var userTracker: UserTracker
+ @Mock private lateinit var activityStarter: ActivityStarter
+ @Mock private lateinit var animationController: ActivityLaunchAnimator.Controller
+
+ private lateinit var underTest: KeyguardQuickAffordanceInteractor
+
+ @JvmField @Parameter(0) var needStrongAuthAfterBoot: Boolean = false
+ @JvmField @Parameter(1) var canShowWhileLocked: Boolean = false
+ @JvmField @Parameter(2) var keyguardIsUnlocked: Boolean = false
+ @JvmField @Parameter(3) var needsToUnlockFirst: Boolean = false
+ @JvmField @Parameter(4) var startActivity: Boolean = false
+ private lateinit var homeControls: FakeKeyguardQuickAffordanceConfig
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ homeControls = object : FakeKeyguardQuickAffordanceConfig() {}
+ underTest =
+ KeyguardQuickAffordanceInteractor(
+ keyguardInteractor = KeyguardInteractor(repository = FakeKeyguardRepository()),
+ registry =
+ FakeKeyguardQuickAffordanceRegistry(
+ mapOf(
+ KeyguardQuickAffordancePosition.BOTTOM_START to
+ listOf(
+ homeControls,
+ ),
+ KeyguardQuickAffordancePosition.BOTTOM_END to
+ listOf(
+ object : FakeKeyguardQuickAffordanceConfig() {},
+ object : FakeKeyguardQuickAffordanceConfig() {},
+ ),
+ ),
+ ),
+ lockPatternUtils = lockPatternUtils,
+ keyguardStateController = keyguardStateController,
+ userTracker = userTracker,
+ activityStarter = activityStarter,
+ )
+ }
+
+ @Test
+ fun onQuickAffordanceClicked() = runBlockingTest {
+ setUpMocks(
+ needStrongAuthAfterBoot = needStrongAuthAfterBoot,
+ keyguardIsUnlocked = keyguardIsUnlocked,
+ )
+
+ homeControls.setState(
+ state =
+ KeyguardQuickAffordanceConfig.State.Visible(
+ icon = DRAWABLE,
+ contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
+ )
+ )
+ homeControls.onClickedResult =
+ if (startActivity) {
+ KeyguardQuickAffordanceConfig.OnClickedResult.StartActivity(
+ intent = INTENT,
+ canShowWhileLocked = canShowWhileLocked,
+ )
+ } else {
+ KeyguardQuickAffordanceConfig.OnClickedResult.Handled
+ }
+
+ underTest.onQuickAffordanceClicked(
+ configKey = homeControls::class,
+ animationController = animationController,
+ )
+
+ if (startActivity) {
+ if (needsToUnlockFirst) {
+ verify(activityStarter)
+ .postStartActivityDismissingKeyguard(
+ any(),
+ /* delay= */ eq(0),
+ same(animationController),
+ )
+ } else {
+ verify(activityStarter)
+ .startActivity(
+ any(),
+ /* dismissShade= */ eq(true),
+ same(animationController),
+ /* showOverLockscreenWhenLocked= */ eq(true),
+ )
+ }
+ } else {
+ verifyZeroInteractions(activityStarter)
+ }
+ }
+
+ private fun setUpMocks(
+ needStrongAuthAfterBoot: Boolean = true,
+ keyguardIsUnlocked: Boolean = false,
+ ) {
+ whenever(userTracker.userHandle).thenReturn(mock())
+ whenever(lockPatternUtils.getStrongAuthForUser(any()))
+ .thenReturn(
+ if (needStrongAuthAfterBoot) {
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
+ } else {
+ LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED
+ }
+ )
+ whenever(keyguardStateController.isUnlocked).thenReturn(keyguardIsUnlocked)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCaseImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorTest.kt
index 63eb68f423ee..d3fc29f1a0f1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/ObserveKeyguardQuickAffordanceUseCaseImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/KeyguardQuickAffordanceInteractorTest.kt
@@ -17,14 +17,20 @@
package com.android.systemui.keyguard.domain.usecase
import androidx.test.filters.SmallTest
+import com.android.internal.widget.LockPatternUtils
import com.android.systemui.SysuiTestCase
import com.android.systemui.containeddrawable.ContainedDrawable
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.launchIn
@@ -34,33 +40,39 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(JUnit4::class)
-class ObserveKeyguardQuickAffordanceUseCaseImplTest : SysuiTestCase() {
+class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
- private lateinit var underTest: ObserveKeyguardQuickAffordanceUseCase
+ @Mock private lateinit var lockPatternUtils: LockPatternUtils
+ @Mock private lateinit var keyguardStateController: KeyguardStateController
+ @Mock private lateinit var userTracker: UserTracker
+ @Mock private lateinit var activityStarter: ActivityStarter
+
+ private lateinit var underTest: KeyguardQuickAffordanceInteractor
private lateinit var repository: FakeKeyguardRepository
- private lateinit var isDozingUseCase: ObserveIsDozingUseCase
- private lateinit var isKeyguardShowingUseCase: ObserveIsKeyguardShowingUseCase
private lateinit var homeControls: FakeKeyguardQuickAffordanceConfig
private lateinit var quickAccessWallet: FakeKeyguardQuickAffordanceConfig
private lateinit var qrCodeScanner: FakeKeyguardQuickAffordanceConfig
@Before
- fun setUp() = runBlockingTest {
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
repository = FakeKeyguardRepository()
repository.setKeyguardShowing(true)
- isDozingUseCase = ObserveIsDozingUseCase(repository)
- isKeyguardShowingUseCase = ObserveIsKeyguardShowingUseCase(repository)
homeControls = object : FakeKeyguardQuickAffordanceConfig() {}
quickAccessWallet = object : FakeKeyguardQuickAffordanceConfig() {}
qrCodeScanner = object : FakeKeyguardQuickAffordanceConfig() {}
underTest =
- ObserveKeyguardQuickAffordanceUseCaseImpl(
+ KeyguardQuickAffordanceInteractor(
+ keyguardInteractor = KeyguardInteractor(repository = repository),
registry =
FakeKeyguardQuickAffordanceRegistry(
mapOf(
@@ -75,13 +87,15 @@ class ObserveKeyguardQuickAffordanceUseCaseImplTest : SysuiTestCase() {
),
),
),
- isDozingUseCase = isDozingUseCase,
- isKeyguardShowingUseCase = isKeyguardShowingUseCase,
+ lockPatternUtils = lockPatternUtils,
+ keyguardStateController = keyguardStateController,
+ userTracker = userTracker,
+ activityStarter = activityStarter,
)
}
@Test
- fun `invoke - bottom start affordance is visible`() = runBlockingTest {
+ fun `quickAffordance - bottom start affordance is visible`() = runBlockingTest {
val configKey = homeControls::class
homeControls.setState(
KeyguardQuickAffordanceConfig.State.Visible(
@@ -92,7 +106,8 @@ class ObserveKeyguardQuickAffordanceUseCaseImplTest : SysuiTestCase() {
var latest: KeyguardQuickAffordanceModel? = null
val job =
- underTest(KeyguardQuickAffordancePosition.BOTTOM_START)
+ underTest
+ .quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_START)
.onEach { latest = it }
.launchIn(this)
@@ -106,7 +121,7 @@ class ObserveKeyguardQuickAffordanceUseCaseImplTest : SysuiTestCase() {
}
@Test
- fun `invoke - bottom end affordance is visible`() = runBlockingTest {
+ fun `quickAffordance - bottom end affordance is visible`() = runBlockingTest {
val configKey = quickAccessWallet::class
quickAccessWallet.setState(
KeyguardQuickAffordanceConfig.State.Visible(
@@ -117,7 +132,8 @@ class ObserveKeyguardQuickAffordanceUseCaseImplTest : SysuiTestCase() {
var latest: KeyguardQuickAffordanceModel? = null
val job =
- underTest(KeyguardQuickAffordancePosition.BOTTOM_END)
+ underTest
+ .quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_END)
.onEach { latest = it }
.launchIn(this)
@@ -131,7 +147,7 @@ class ObserveKeyguardQuickAffordanceUseCaseImplTest : SysuiTestCase() {
}
@Test
- fun `invoke - bottom start affordance hidden while dozing`() = runBlockingTest {
+ fun `quickAffordance - bottom start affordance hidden while dozing`() = runBlockingTest {
repository.setDozing(true)
homeControls.setState(
KeyguardQuickAffordanceConfig.State.Visible(
@@ -142,7 +158,8 @@ class ObserveKeyguardQuickAffordanceUseCaseImplTest : SysuiTestCase() {
var latest: KeyguardQuickAffordanceModel? = null
val job =
- underTest(KeyguardQuickAffordancePosition.BOTTOM_START)
+ underTest
+ .quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_START)
.onEach { latest = it }
.launchIn(this)
assertThat(latest).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
@@ -150,7 +167,7 @@ class ObserveKeyguardQuickAffordanceUseCaseImplTest : SysuiTestCase() {
}
@Test
- fun `invoke - bottom start affordance hidden when lockscreen is not showing`() =
+ fun `quickAffordance - bottom start affordance hidden when lockscreen is not showing`() =
runBlockingTest {
repository.setKeyguardShowing(false)
homeControls.setState(
@@ -162,7 +179,8 @@ class ObserveKeyguardQuickAffordanceUseCaseImplTest : SysuiTestCase() {
var latest: KeyguardQuickAffordanceModel? = null
val job =
- underTest(KeyguardQuickAffordancePosition.BOTTOM_START)
+ underTest
+ .quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_START)
.onEach { latest = it }
.launchIn(this)
assertThat(latest).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/LaunchKeyguardQuickAffordanceUseCaseImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/LaunchKeyguardQuickAffordanceUseCaseImplTest.kt
deleted file mode 100644
index b3c1ae0106cf..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/usecase/LaunchKeyguardQuickAffordanceUseCaseImplTest.kt
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.domain.usecase
-
-import android.content.Intent
-import androidx.test.filters.SmallTest
-import com.android.internal.widget.LockPatternUtils
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.settings.UserTracker
-import com.android.systemui.statusbar.policy.KeyguardStateController
-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.junit.runners.Parameterized
-import org.junit.runners.Parameterized.Parameter
-import org.junit.runners.Parameterized.Parameters
-import org.mockito.Mock
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWith(Parameterized::class)
-class LaunchKeyguardQuickAffordanceUseCaseImplTest : SysuiTestCase() {
-
- companion object {
- private val INTENT = Intent("some.intent.action")
-
- @Parameters(
- name =
- "needStrongAuthAfterBoot={0}, canShowWhileLocked={1}," +
- " keyguardIsUnlocked={2}, needsToUnlockFirst={3}"
- )
- @JvmStatic
- fun data() =
- listOf(
- arrayOf(
- /* needStrongAuthAfterBoot= */ false,
- /* canShowWhileLocked= */ false,
- /* keyguardIsUnlocked= */ false,
- /* needsToUnlockFirst= */ true,
- ),
- arrayOf(
- /* needStrongAuthAfterBoot= */ false,
- /* canShowWhileLocked= */ false,
- /* keyguardIsUnlocked= */ true,
- /* needsToUnlockFirst= */ false,
- ),
- arrayOf(
- /* needStrongAuthAfterBoot= */ false,
- /* canShowWhileLocked= */ true,
- /* keyguardIsUnlocked= */ false,
- /* needsToUnlockFirst= */ false,
- ),
- arrayOf(
- /* needStrongAuthAfterBoot= */ false,
- /* canShowWhileLocked= */ true,
- /* keyguardIsUnlocked= */ true,
- /* needsToUnlockFirst= */ false,
- ),
- arrayOf(
- /* needStrongAuthAfterBoot= */ true,
- /* canShowWhileLocked= */ false,
- /* keyguardIsUnlocked= */ false,
- /* needsToUnlockFirst= */ true,
- ),
- arrayOf(
- /* needStrongAuthAfterBoot= */ true,
- /* canShowWhileLocked= */ false,
- /* keyguardIsUnlocked= */ true,
- /* needsToUnlockFirst= */ true,
- ),
- arrayOf(
- /* needStrongAuthAfterBoot= */ true,
- /* canShowWhileLocked= */ true,
- /* keyguardIsUnlocked= */ false,
- /* needsToUnlockFirst= */ true,
- ),
- arrayOf(
- /* needStrongAuthAfterBoot= */ true,
- /* canShowWhileLocked= */ true,
- /* keyguardIsUnlocked= */ true,
- /* needsToUnlockFirst= */ true,
- ),
- )
- }
-
- @Mock private lateinit var lockPatternUtils: LockPatternUtils
- @Mock private lateinit var keyguardStateController: KeyguardStateController
- @Mock private lateinit var userTracker: UserTracker
- @Mock private lateinit var activityStarter: ActivityStarter
- @Mock private lateinit var animationController: ActivityLaunchAnimator.Controller
-
- private lateinit var underTest: LaunchKeyguardQuickAffordanceUseCase
-
- @JvmField @Parameter(0) var needStrongAuthAfterBoot: Boolean = false
- @JvmField @Parameter(1) var canShowWhileLocked: Boolean = false
- @JvmField @Parameter(2) var keyguardIsUnlocked: Boolean = false
- @JvmField @Parameter(3) var needsToUnlockFirst: Boolean = false
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
-
- underTest =
- LaunchKeyguardQuickAffordanceUseCaseImpl(
- lockPatternUtils = lockPatternUtils,
- keyguardStateController = keyguardStateController,
- userTracker = userTracker,
- activityStarter = activityStarter,
- )
- }
-
- @Test
- fun invoke() {
- setUpMocks(
- needStrongAuthAfterBoot = needStrongAuthAfterBoot,
- keyguardIsUnlocked = keyguardIsUnlocked,
- )
-
- underTest(
- intent = INTENT,
- canShowWhileLocked = canShowWhileLocked,
- animationController = animationController,
- )
-
- if (needsToUnlockFirst) {
- verify(activityStarter)
- .postStartActivityDismissingKeyguard(
- INTENT,
- /* delay= */ 0,
- animationController,
- )
- } else {
- verify(activityStarter)
- .startActivity(
- INTENT,
- /* dismissShade= */ true,
- animationController,
- /* showOverLockscreenWhenLocked= */ true,
- )
- }
- }
-
- private fun setUpMocks(
- needStrongAuthAfterBoot: Boolean = true,
- keyguardIsUnlocked: Boolean = false,
- ) {
- whenever(userTracker.userHandle).thenReturn(mock())
- whenever(lockPatternUtils.getStrongAuthForUser(any()))
- .thenReturn(
- if (needStrongAuthAfterBoot) {
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
- } else {
- LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED
- }
- )
- whenever(keyguardStateController.isUnlocked).thenReturn(keyguardIsUnlocked)
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index 8758ce5eade6..19491f41a0c1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -18,24 +18,22 @@ package com.android.systemui.keyguard.ui.viewmodel
import android.content.Intent
import androidx.test.filters.SmallTest
+import com.android.internal.widget.LockPatternUtils
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.containeddrawable.ContainedDrawable
import com.android.systemui.doze.util.BurnInHelperWrapper
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
+import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.domain.usecase.FakeLaunchKeyguardQuickAffordanceUseCase
-import com.android.systemui.keyguard.domain.usecase.FakeObserveKeyguardQuickAffordanceUseCase
-import com.android.systemui.keyguard.domain.usecase.ObserveAnimateBottomAreaTransitionsUseCase
-import com.android.systemui.keyguard.domain.usecase.ObserveBottomAreaAlphaUseCase
-import com.android.systemui.keyguard.domain.usecase.ObserveClockPositionUseCase
-import com.android.systemui.keyguard.domain.usecase.ObserveDozeAmountUseCase
-import com.android.systemui.keyguard.domain.usecase.ObserveIsDozingUseCase
-import com.android.systemui.keyguard.domain.usecase.OnKeyguardQuickAffordanceClickedUseCase
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
@@ -43,12 +41,15 @@ import kotlin.reflect.KClass
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.test.runBlockingTest
+import kotlinx.coroutines.yield
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.verifyZeroInteractions
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@@ -58,17 +59,18 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
@Mock private lateinit var animationController: ActivityLaunchAnimator.Controller
@Mock private lateinit var burnInHelperWrapper: BurnInHelperWrapper
+ @Mock private lateinit var lockPatternUtils: LockPatternUtils
+ @Mock private lateinit var keyguardStateController: KeyguardStateController
+ @Mock private lateinit var userTracker: UserTracker
+ @Mock private lateinit var activityStarter: ActivityStarter
private lateinit var underTest: KeyguardBottomAreaViewModel
private lateinit var repository: FakeKeyguardRepository
private lateinit var registry: FakeKeyguardQuickAffordanceRegistry
- private lateinit var isDozingUseCase: ObserveIsDozingUseCase
- private lateinit var launchQuickAffordanceUseCase: FakeLaunchKeyguardQuickAffordanceUseCase
private lateinit var homeControlsQuickAffordanceConfig: FakeKeyguardQuickAffordanceConfig
private lateinit var quickAccessWalletAffordanceConfig: FakeKeyguardQuickAffordanceConfig
private lateinit var qrCodeScannerAffordanceConfig: FakeKeyguardQuickAffordanceConfig
- private lateinit var observeQuickAffordanceUseCase: FakeObserveKeyguardQuickAffordanceUseCase
@Before
fun setUp() {
@@ -94,57 +96,31 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
),
)
repository = FakeKeyguardRepository()
- isDozingUseCase =
- ObserveIsDozingUseCase(
- repository = repository,
- )
- launchQuickAffordanceUseCase = FakeLaunchKeyguardQuickAffordanceUseCase()
- observeQuickAffordanceUseCase = FakeObserveKeyguardQuickAffordanceUseCase()
+ val keyguardInteractor = KeyguardInteractor(repository = repository)
+ whenever(userTracker.userHandle).thenReturn(mock())
+ whenever(lockPatternUtils.getStrongAuthForUser(anyInt()))
+ .thenReturn(LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED)
underTest =
KeyguardBottomAreaViewModel(
- observeQuickAffordanceUseCase = observeQuickAffordanceUseCase,
- onQuickAffordanceClickedUseCase =
- OnKeyguardQuickAffordanceClickedUseCase(
- registry =
- FakeKeyguardQuickAffordanceRegistry(
- mapOf(
- KeyguardQuickAffordancePosition.BOTTOM_START to
- listOf(
- homeControlsQuickAffordanceConfig,
- ),
- KeyguardQuickAffordancePosition.BOTTOM_END to
- listOf(
- quickAccessWalletAffordanceConfig,
- qrCodeScannerAffordanceConfig,
- ),
- ),
- ),
- launchAffordanceUseCase = launchQuickAffordanceUseCase,
- ),
- observeBottomAreaAlphaUseCase =
- ObserveBottomAreaAlphaUseCase(
- repository = repository,
- ),
- observeIsDozingUseCase = isDozingUseCase,
- observeAnimateBottomAreaTransitionsUseCase =
- ObserveAnimateBottomAreaTransitionsUseCase(
- repository = repository,
- ),
- observeDozeAmountUseCase =
- ObserveDozeAmountUseCase(
- repository = repository,
- ),
- observeClockPositionUseCase =
- ObserveClockPositionUseCase(
- repository = repository,
+ keyguardInteractor = keyguardInteractor,
+ quickAffordanceInteractor =
+ KeyguardQuickAffordanceInteractor(
+ keyguardInteractor = keyguardInteractor,
+ registry = registry,
+ lockPatternUtils = lockPatternUtils,
+ keyguardStateController = keyguardStateController,
+ userTracker = userTracker,
+ activityStarter = activityStarter,
),
+ bottomAreaInteractor = KeyguardBottomAreaInteractor(repository = repository),
burnInHelperWrapper = burnInHelperWrapper,
)
}
@Test
fun `startButton - present - visible model - starts activity on click`() = runBlockingTest {
+ repository.setKeyguardShowing(true)
var latest: KeyguardQuickAffordanceViewModel? = null
val job = underTest.startButton.onEach { latest = it }.launchIn(this)
@@ -171,6 +147,7 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
@Test
fun `endButton - present - visible model - do nothing on click`() = runBlockingTest {
+ repository.setKeyguardShowing(true)
var latest: KeyguardQuickAffordanceViewModel? = null
val job = underTest.endButton.onEach { latest = it }.launchIn(this)
@@ -220,11 +197,27 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
@Test
fun animateButtonReveal() = runBlockingTest {
+ repository.setKeyguardShowing(true)
+ val testConfig =
+ TestConfig(
+ isVisible = true,
+ icon = mock(),
+ canShowWhileLocked = false,
+ intent = Intent("action"),
+ )
+
+ setUpQuickAffordanceModel(
+ position = KeyguardQuickAffordancePosition.BOTTOM_START,
+ testConfig = testConfig,
+ )
+
val values = mutableListOf<Boolean>()
- val job = underTest.animateButtonReveal.onEach(values::add).launchIn(this)
+ val job = underTest.startButton.onEach { values.add(it.animateReveal) }.launchIn(this)
repository.setAnimateDozingTransitions(true)
+ yield()
repository.setAnimateDozingTransitions(false)
+ yield()
assertThat(values).isEqualTo(listOf(false, true, false))
job.cancel()
@@ -357,7 +350,7 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
private suspend fun setUpQuickAffordanceModel(
position: KeyguardQuickAffordancePosition,
testConfig: TestConfig,
- ): KClass<*> {
+ ): KClass<out FakeKeyguardQuickAffordanceConfig> {
val config =
when (position) {
KeyguardQuickAffordancePosition.BOTTOM_START -> homeControlsQuickAffordanceConfig
@@ -381,20 +374,13 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
KeyguardQuickAffordanceConfig.State.Hidden
}
config.setState(state)
-
- val configKey = config::class
- observeQuickAffordanceUseCase.setModel(
- position,
- KeyguardQuickAffordanceModel.from(state, configKey)
- )
-
- return configKey
+ return config::class
}
private fun assertQuickAffordanceViewModel(
viewModel: KeyguardQuickAffordanceViewModel?,
testConfig: TestConfig,
- configKey: KClass<*>,
+ configKey: KClass<out FakeKeyguardQuickAffordanceConfig>,
) {
checkNotNull(viewModel)
assertThat(viewModel.isVisible).isEqualTo(testConfig.isVisible)
@@ -406,19 +392,11 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
animationController = animationController,
)
)
- testConfig.intent?.let { intent ->
- assertThat(launchQuickAffordanceUseCase.invocations)
- .isEqualTo(
- listOf(
- FakeLaunchKeyguardQuickAffordanceUseCase.Invocation(
- intent = intent,
- canShowWhileLocked = testConfig.canShowWhileLocked,
- animationController = animationController,
- )
- )
- )
+ if (testConfig.intent != null) {
+ assertThat(Mockito.mockingDetails(activityStarter).invocations).hasSize(1)
+ } else {
+ verifyZeroInteractions(activityStarter)
}
- ?: run { assertThat(launchQuickAffordanceUseCase.invocations).isEmpty() }
} else {
assertThat(viewModel.isVisible).isFalse()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
index d65b6b31a26d..369913d1ea73 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
@@ -16,7 +16,6 @@
package com.android.systemui.media
-import org.mockito.Mockito.`when` as whenever
import android.graphics.Rect
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
@@ -30,6 +29,7 @@ import com.android.systemui.controls.controller.ControlsControllerImplTest.Compa
import com.android.systemui.dreams.DreamOverlayStateController
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.testing.FakeNotifPanelEvents
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.SysuiStatusBarStateController
@@ -50,10 +50,11 @@ import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyLong
import org.mockito.Captor
import org.mockito.Mock
-import org.mockito.Mockito.`when`
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.`when` as whenever
import org.mockito.junit.MockitoJUnit
@SmallTest
@@ -61,32 +62,19 @@ import org.mockito.junit.MockitoJUnit
@TestableLooper.RunWithLooper
class MediaHierarchyManagerTest : SysuiTestCase() {
- @Mock
- private lateinit var lockHost: MediaHost
- @Mock
- private lateinit var qsHost: MediaHost
- @Mock
- private lateinit var qqsHost: MediaHost
- @Mock
- private lateinit var bypassController: KeyguardBypassController
- @Mock
- private lateinit var keyguardStateController: KeyguardStateController
- @Mock
- private lateinit var statusBarStateController: SysuiStatusBarStateController
- @Mock
- private lateinit var notificationLockscreenUserManager: NotificationLockscreenUserManager
- @Mock
- private lateinit var mediaCarouselController: MediaCarouselController
- @Mock
- private lateinit var mediaCarouselScrollHandler: MediaCarouselScrollHandler
- @Mock
- private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
- @Mock
- private lateinit var keyguardViewController: KeyguardViewController
- @Mock
- private lateinit var uniqueObjectHostView: UniqueObjectHostView
- @Mock
- private lateinit var dreamOverlayStateController: DreamOverlayStateController
+ @Mock private lateinit var lockHost: MediaHost
+ @Mock private lateinit var qsHost: MediaHost
+ @Mock private lateinit var qqsHost: MediaHost
+ @Mock private lateinit var bypassController: KeyguardBypassController
+ @Mock private lateinit var keyguardStateController: KeyguardStateController
+ @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
+ @Mock private lateinit var notificationLockscreenUserManager: NotificationLockscreenUserManager
+ @Mock private lateinit var mediaCarouselController: MediaCarouselController
+ @Mock private lateinit var mediaCarouselScrollHandler: MediaCarouselScrollHandler
+ @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
+ @Mock private lateinit var keyguardViewController: KeyguardViewController
+ @Mock private lateinit var uniqueObjectHostView: UniqueObjectHostView
+ @Mock private lateinit var dreamOverlayStateController: DreamOverlayStateController
@Captor
private lateinit var wakefullnessObserver: ArgumentCaptor<(WakefulnessLifecycle.Observer)>
@Captor
@@ -97,6 +85,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() {
private lateinit var mediaHiearchyManager: MediaHierarchyManager
private lateinit var mediaFrame: ViewGroup
private val configurationController = FakeConfigurationController()
+ private val notifPanelEvents = FakeNotifPanelEvents()
@Before
fun setup() {
@@ -111,10 +100,12 @@ class MediaHierarchyManagerTest : SysuiTestCase() {
bypassController,
mediaCarouselController,
notificationLockscreenUserManager,
+ keyguardViewController,
+ dreamOverlayStateController,
configurationController,
wakefulnessLifecycle,
- keyguardViewController,
- dreamOverlayStateController)
+ notifPanelEvents,
+ )
verify(wakefulnessLifecycle).addObserver(wakefullnessObserver.capture())
verify(statusBarStateController).addCallback(statusBarCallback.capture())
setupHost(lockHost, MediaHierarchyManager.LOCATION_LOCKSCREEN, LOCKSCREEN_TOP)
@@ -212,6 +203,25 @@ class MediaHierarchyManagerTest : SysuiTestCase() {
}
@Test
+ fun calculateTransformationType_notOnLockscreen_returnsTransition() {
+ expandQS()
+
+ val transformType = mediaHiearchyManager.calculateTransformationType()
+
+ assertThat(transformType).isEqualTo(MediaHierarchyManager.TRANSFORMATION_TYPE_TRANSITION)
+ }
+
+ @Test
+ fun calculateTransformationType_onLockscreen_returnsTransition() {
+ goToLockscreen()
+ expandQS()
+
+ val transformType = mediaHiearchyManager.calculateTransformationType()
+
+ assertThat(transformType).isEqualTo(MediaHierarchyManager.TRANSFORMATION_TYPE_FADE)
+ }
+
+ @Test
fun calculateTransformationType_onLockShade_inSplitShade_goingToFullShade_returnsTransition() {
enableSplitShade()
goToLockscreen()
@@ -295,6 +305,18 @@ class MediaHierarchyManagerTest : SysuiTestCase() {
}
@Test
+ fun isCurrentlyInGuidedTransformation_hostsVisible_expandImmediateEnabled_returnsFalse() {
+ notifPanelEvents.changeExpandImmediate(expandImmediate = true)
+ goToLockscreen()
+ enterGuidedTransformation()
+ whenever(lockHost.visible).thenReturn(true)
+ whenever(qsHost.visible).thenReturn(true)
+ whenever(qqsHost.visible).thenReturn(true)
+
+ assertThat(mediaHiearchyManager.isCurrentlyInGuidedTransformation()).isFalse()
+ }
+
+ @Test
fun isCurrentlyInGuidedTransformation_hostNotVisible_returnsTrue() {
goToLockscreen()
enterGuidedTransformation()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
index 314997d8d38a..61736923fb82 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
@@ -24,9 +24,9 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.KeyguardManager;
import android.content.Context;
import android.graphics.Bitmap;
-import android.graphics.drawable.Drawable;
import android.media.AudioManager;
import android.media.session.MediaController;
import android.media.session.MediaSessionManager;
@@ -88,6 +88,7 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase {
private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class);
private final AudioManager mAudioManager = mock(AudioManager.class);
private PowerExemptionManager mPowerExemptionManager = mock(PowerExemptionManager.class);
+ private KeyguardManager mKeyguardManager = mock(KeyguardManager.class);
private List<MediaController> mMediaControllers = new ArrayList<>();
private MediaOutputBaseDialogImpl mMediaOutputBaseDialogImpl;
@@ -119,7 +120,8 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase {
mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE,
mMediaSessionManager, mLocalBluetoothManager, mStarter,
mNotifCollection, mDialogLaunchAnimator,
- Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager);
+ Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+ mKeyguardManager);
mMediaOutputBaseDialogImpl = new MediaOutputBaseDialogImpl(mContext, mBroadcastSender,
mMediaOutputController);
mMediaOutputBaseDialogImpl.onCreate(new Bundle());
@@ -276,7 +278,7 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase {
}
@Override
- Drawable getAppSourceIcon() {
+ IconCompat getAppSourceIcon() {
return null;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
index 751c8951859c..6dcf8024eca8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
@@ -31,6 +31,7 @@ import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.KeyguardManager;
import android.app.Notification;
import android.content.Context;
import android.graphics.drawable.Icon;
@@ -47,6 +48,7 @@ import android.os.RemoteException;
import android.service.notification.StatusBarNotification;
import android.testing.AndroidTestingRunner;
import android.text.TextUtils;
+import android.view.View;
import androidx.core.graphics.drawable.IconCompat;
import androidx.test.filters.SmallTest;
@@ -57,6 +59,7 @@ import com.android.settingslib.media.LocalMediaManager;
import com.android.settingslib.media.MediaDevice;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
import com.android.systemui.plugins.ActivityStarter;
@@ -102,11 +105,16 @@ public class MediaOutputControllerTest extends SysuiTestCase {
private RoutingSessionInfo mRemoteSessionInfo = mock(RoutingSessionInfo.class);
private ActivityStarter mStarter = mock(ActivityStarter.class);
private AudioManager mAudioManager = mock(AudioManager.class);
+ private KeyguardManager mKeyguardManager = mock(KeyguardManager.class);
private PowerExemptionManager mPowerExemptionManager = mock(PowerExemptionManager.class);
private CommonNotifCollection mNotifCollection = mock(CommonNotifCollection.class);
private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class);
+ private final ActivityLaunchAnimator.Controller mActivityLaunchAnimatorController = mock(
+ ActivityLaunchAnimator.Controller.class);
private final NearbyMediaDevicesManager mNearbyMediaDevicesManager = mock(
NearbyMediaDevicesManager.class);
+ private View mDialogLaunchView = mock(View.class);
+ private MediaOutputController.Callback mCallback = mock(MediaOutputController.Callback.class);
private Context mSpyContext;
private MediaOutputController mMediaOutputController;
@@ -131,7 +139,8 @@ public class MediaOutputControllerTest extends SysuiTestCase {
mMediaOutputController = new MediaOutputController(mSpyContext, TEST_PACKAGE_NAME,
mMediaSessionManager, mLocalBluetoothManager, mStarter,
mNotifCollection, mDialogLaunchAnimator,
- Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager);
+ Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+ mKeyguardManager);
mLocalMediaManager = spy(mMediaOutputController.mLocalMediaManager);
mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
MediaDescription.Builder builder = new MediaDescription.Builder();
@@ -183,7 +192,8 @@ public class MediaOutputControllerTest extends SysuiTestCase {
mMediaOutputController = new MediaOutputController(mSpyContext, null,
mMediaSessionManager, mLocalBluetoothManager, mStarter,
mNotifCollection, mDialogLaunchAnimator,
- Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager);
+ Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+ mKeyguardManager);
mMediaOutputController.start(mCb);
@@ -212,7 +222,8 @@ public class MediaOutputControllerTest extends SysuiTestCase {
mMediaOutputController = new MediaOutputController(mSpyContext, null,
mMediaSessionManager, mLocalBluetoothManager, mStarter,
mNotifCollection, mDialogLaunchAnimator,
- Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager);
+ Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+ mKeyguardManager);
mMediaOutputController.start(mCb);
@@ -461,7 +472,8 @@ public class MediaOutputControllerTest extends SysuiTestCase {
mMediaOutputController = new MediaOutputController(mSpyContext, null,
mMediaSessionManager, mLocalBluetoothManager, mStarter,
mNotifCollection, mDialogLaunchAnimator,
- Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager);
+ Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+ mKeyguardManager);
assertThat(mMediaOutputController.getNotificationIcon()).isNull();
}
@@ -557,4 +569,16 @@ public class MediaOutputControllerTest extends SysuiTestCase {
verify(mPowerExemptionManager).addToTemporaryAllowList(anyString(), anyInt(), anyString(),
anyLong());
}
+
+ @Test
+ public void launchBluetoothPairing_isKeyguardLocked_dismissDialog() {
+ when(mDialogLaunchAnimator.createActivityLaunchController(mDialogLaunchView)).thenReturn(
+ mActivityLaunchAnimatorController);
+ when(mKeyguardManager.isKeyguardLocked()).thenReturn(true);
+ mMediaOutputController.mCallback = this.mCallback;
+
+ mMediaOutputController.launchBluetoothPairing(mDialogLaunchView);
+
+ verify(mCallback).dismissDialog();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
index 4779d322a90e..9557513775f8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
@@ -24,6 +24,7 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.KeyguardManager;
import android.media.AudioManager;
import android.media.MediaRoute2Info;
import android.media.session.MediaController;
@@ -85,6 +86,7 @@ public class MediaOutputDialogTest extends SysuiTestCase {
NearbyMediaDevicesManager.class);
private final AudioManager mAudioManager = mock(AudioManager.class);
private PowerExemptionManager mPowerExemptionManager = mock(PowerExemptionManager.class);
+ private KeyguardManager mKeyguardManager = mock(KeyguardManager.class);
private List<MediaController> mMediaControllers = new ArrayList<>();
private MediaOutputDialog mMediaOutputDialog;
@@ -104,7 +106,8 @@ public class MediaOutputDialogTest extends SysuiTestCase {
mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE,
mMediaSessionManager, mLocalBluetoothManager, mStarter,
mNotifCollection, mDialogLaunchAnimator,
- Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager);
+ Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+ mKeyguardManager);
mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
mMediaOutputDialog = new MediaOutputDialog(mContext, false, mBroadcastSender,
mMediaOutputController, mUiEventLogger);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
index dbc5f7c7041e..171d893640d6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
@@ -241,5 +241,5 @@ private const val PACKAGE_NAME = "com.android.systemui"
private val routeInfo = MediaRoute2Info.Builder("id", "Test route name")
.addFeature("feature")
- .setPackageName(PACKAGE_NAME)
+ .setClientPackageName(PACKAGE_NAME)
.build()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
index cd8ee732e113..1061e3c6b0d5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
@@ -686,5 +686,5 @@ private const val TIMEOUT = 10000
private val routeInfo = MediaRoute2Info.Builder("id", OTHER_DEVICE_NAME)
.addFeature("feature")
- .setPackageName(PACKAGE_NAME)
+ .setClientPackageName(PACKAGE_NAME)
.build()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
index 1f28210acc64..e4f47fd16be7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
@@ -17,8 +17,8 @@
package com.android.systemui.qs
import android.content.res.Configuration
-import android.test.suitebuilder.annotation.SmallTest
import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
import com.android.internal.logging.MetricsLogger
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.systemui.SysuiTestCase
@@ -38,38 +38,32 @@ import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.Captor
import org.mockito.Mock
-import org.mockito.Mockito.`when`
import org.mockito.Mockito.any
+import org.mockito.Mockito.reset
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidTestingRunner::class)
class QuickQSPanelControllerTest : SysuiTestCase() {
- @Mock
- private lateinit var quickQSPanel: QuickQSPanel
- @Mock
- private lateinit var qsTileHost: QSTileHost
- @Mock
- private lateinit var qsCustomizerController: QSCustomizerController
- @Mock
- private lateinit var mediaHost: MediaHost
- @Mock
- private lateinit var metricsLogger: MetricsLogger
+ @Mock private lateinit var quickQSPanel: QuickQSPanel
+ @Mock private lateinit var qsTileHost: QSTileHost
+ @Mock private lateinit var qsCustomizerController: QSCustomizerController
+ @Mock private lateinit var mediaHost: MediaHost
+ @Mock private lateinit var metricsLogger: MetricsLogger
+ @Mock private lateinit var qsLogger: QSLogger
+ @Mock private lateinit var tile: QSTile
+ @Mock private lateinit var tileLayout: TileLayout
+ @Mock private lateinit var tileView: QSTileView
+ @Captor private lateinit var captor: ArgumentCaptor<QSPanel.OnConfigurationChangedListener>
+
private val uiEventLogger = UiEventLoggerFake()
- @Mock
- private lateinit var qsLogger: QSLogger
private val dumpManager = DumpManager()
- @Mock
- private lateinit var tile: QSTile
- @Mock
- private lateinit var tileLayout: TileLayout
- @Mock
- private lateinit var tileView: QSTileView
- @Captor
- private lateinit var captor: ArgumentCaptor<QSPanel.OnConfigurationChangedListener>
+
+ private var usingCollapsedLandscapeMedia = true
private lateinit var controller: TestQuickQSPanelController
@@ -77,24 +71,24 @@ class QuickQSPanelControllerTest : SysuiTestCase() {
fun setUp() {
MockitoAnnotations.initMocks(this)
- `when`(quickQSPanel.tileLayout).thenReturn(tileLayout)
- `when`(quickQSPanel.isAttachedToWindow).thenReturn(true)
- `when`(quickQSPanel.dumpableTag).thenReturn("")
- `when`(quickQSPanel.resources).thenReturn(mContext.resources)
- `when`(qsTileHost.createTileView(any(), any(), anyBoolean())).thenReturn(tileView)
+ whenever(quickQSPanel.tileLayout).thenReturn(tileLayout)
+ whenever(quickQSPanel.isAttachedToWindow).thenReturn(true)
+ whenever(quickQSPanel.dumpableTag).thenReturn("")
+ whenever(quickQSPanel.resources).thenReturn(mContext.resources)
+ whenever(qsTileHost.createTileView(any(), any(), anyBoolean())).thenReturn(tileView)
- controller = TestQuickQSPanelController(
+ controller =
+ TestQuickQSPanelController(
quickQSPanel,
qsTileHost,
qsCustomizerController,
- false,
+ /* usingMediaPlayer = */ false,
mediaHost,
- true,
+ { usingCollapsedLandscapeMedia },
metricsLogger,
uiEventLogger,
qsLogger,
- dumpManager
- )
+ dumpManager)
controller.init()
}
@@ -106,9 +100,9 @@ class QuickQSPanelControllerTest : SysuiTestCase() {
@Test
fun testTileSublistWithFewerTiles_noCrash() {
- `when`(quickQSPanel.numQuickTiles).thenReturn(3)
+ whenever(quickQSPanel.numQuickTiles).thenReturn(3)
- `when`(qsTileHost.tiles).thenReturn(listOf(tile, tile))
+ whenever(qsTileHost.tiles).thenReturn(listOf(tile, tile))
controller.setTiles()
}
@@ -116,8 +110,8 @@ class QuickQSPanelControllerTest : SysuiTestCase() {
@Test
fun testTileSublistWithTooManyTiles() {
val limit = 3
- `when`(quickQSPanel.numQuickTiles).thenReturn(limit)
- `when`(qsTileHost.tiles).thenReturn(listOf(tile, tile, tile, tile))
+ whenever(quickQSPanel.numQuickTiles).thenReturn(limit)
+ whenever(qsTileHost.tiles).thenReturn(listOf(tile, tile, tile, tile))
controller.setTiles()
@@ -125,39 +119,61 @@ class QuickQSPanelControllerTest : SysuiTestCase() {
}
@Test
- fun testMediaExpansionUpdatedWhenConfigurationChanged() {
+ fun mediaExpansion_afterConfigChange_inLandscape_collapsedInLandscapeTrue_updatesToCollapsed() {
// times(2) because both controller and base controller are registering their listeners
verify(quickQSPanel, times(2)).addOnConfigurationChangedListener(captor.capture())
// verify that media starts in the expanded state by default
verify(mediaHost).expansion = MediaHostState.EXPANDED
- // Rotate device, verify media size updated
+ // Rotate device, verify media size updated to collapsed
+ usingCollapsedLandscapeMedia = true
controller.setRotation(RotationUtils.ROTATION_LANDSCAPE)
captor.allValues.forEach { it.onConfigurationChange(Configuration.EMPTY) }
verify(mediaHost).expansion = MediaHostState.COLLAPSED
}
+ @Test
+ fun mediaExpansion_afterConfigChange_landscape_collapsedInLandscapeFalse_remainsExpanded() {
+ // times(2) because both controller and base controller are registering their listeners
+ verify(quickQSPanel, times(2)).addOnConfigurationChangedListener(captor.capture())
+ reset(mediaHost)
+
+ usingCollapsedLandscapeMedia = false
+ controller.setRotation(RotationUtils.ROTATION_LANDSCAPE)
+ captor.allValues.forEach { it.onConfigurationChange(Configuration.EMPTY) }
+
+ verify(mediaHost).expansion = MediaHostState.EXPANDED
+ }
+
class TestQuickQSPanelController(
view: QuickQSPanel,
qsTileHost: QSTileHost,
qsCustomizerController: QSCustomizerController,
usingMediaPlayer: Boolean,
mediaHost: MediaHost,
- usingCollapsedLandscapeMedia: Boolean,
+ usingCollapsedLandscapeMedia: () -> Boolean,
metricsLogger: MetricsLogger,
uiEventLogger: UiEventLoggerFake,
qsLogger: QSLogger,
dumpManager: DumpManager
- ) : QuickQSPanelController(view, qsTileHost, qsCustomizerController, usingMediaPlayer,
- mediaHost, usingCollapsedLandscapeMedia, metricsLogger, uiEventLogger, qsLogger,
- dumpManager) {
+ ) :
+ QuickQSPanelController(
+ view,
+ qsTileHost,
+ qsCustomizerController,
+ usingMediaPlayer,
+ mediaHost,
+ usingCollapsedLandscapeMedia,
+ metricsLogger,
+ uiEventLogger,
+ qsLogger,
+ dumpManager) {
private var rotation = RotationUtils.ROTATION_NONE
- @Override
- override fun getRotation(): Int = rotation
+ @Override override fun getRotation(): Int = rotation
fun setRotation(newRotation: Int) {
rotation = newRotation
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index fc28349e1e57..95211c0c8ec8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -98,9 +98,7 @@ import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.fragments.FragmentHostManager;
import com.android.systemui.fragments.FragmentService;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
-import com.android.systemui.keyguard.domain.usecase.SetClockPositionUseCase;
-import com.android.systemui.keyguard.domain.usecase.SetKeyguardBottomAreaAlphaUseCase;
-import com.android.systemui.keyguard.domain.usecase.SetKeyguardBottomAreaAnimateDozingTransitionsUseCase;
+import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor;
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel;
import com.android.systemui.media.KeyguardMediaController;
import com.android.systemui.media.MediaDataManager;
@@ -240,6 +238,8 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
@Mock
private DozeLog mDozeLog;
@Mock
+ private ShadeLogger mShadeLog;
+ @Mock
private CommandQueue mCommandQueue;
@Mock
private VibratorHelper mVibratorHelper;
@@ -379,10 +379,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
@Mock
private ViewTreeObserver mViewTreeObserver;
@Mock private KeyguardBottomAreaViewModel mKeyguardBottomAreaViewModel;
- @Mock private SetClockPositionUseCase mSetClockPositionUseCase;
- @Mock private SetKeyguardBottomAreaAlphaUseCase mSetKeyguardBottomAreaAlphaUseCase;
- @Mock private SetKeyguardBottomAreaAnimateDozingTransitionsUseCase
- mSetKeyguardBottomAreaAnimateDozingTransitionsUseCase;
+ @Mock private KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor;
private NotificationPanelViewController.PanelEventsEmitter mPanelEventsEmitter;
private Optional<SysUIUnfoldComponent> mSysUIUnfoldComponent = Optional.empty();
private SysuiStatusBarStateController mStatusBarStateController;
@@ -529,7 +526,9 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
mNotificationShadeWindowController,
mDozeLog, mDozeParameters, mCommandQueue, mVibratorHelper,
mLatencyTracker, mPowerManager, mAccessibilityManager, 0, mUpdateMonitor,
- mMetricsLogger, mConfigurationController,
+ mMetricsLogger,
+ mShadeLog,
+ mConfigurationController,
() -> flingAnimationUtilsBuilder, mStatusBarTouchableRegionManager,
mConversationNotificationManager, mMediaHiearchyManager,
mStatusBarKeyguardViewManager,
@@ -577,9 +576,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
mSystemClock,
mock(CameraGestureHelper.class),
() -> mKeyguardBottomAreaViewModel,
- () -> mSetClockPositionUseCase,
- () -> mSetKeyguardBottomAreaAlphaUseCase,
- () -> mSetKeyguardBottomAreaAnimateDozingTransitionsUseCase);
+ () -> mKeyguardBottomAreaInteractor);
mNotificationPanelViewController.initDependencies(
mCentralSurfaces,
() -> {},
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/testing/FakeNotifPanelEvents.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/testing/FakeNotifPanelEvents.kt
new file mode 100644
index 000000000000..d05213877232
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/testing/FakeNotifPanelEvents.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.testing
+
+import com.android.systemui.shade.NotifPanelEvents
+
+/** Fake implementation of [NotifPanelEvents] for testing. */
+class FakeNotifPanelEvents : NotifPanelEvents {
+
+ private val listeners = mutableListOf<NotifPanelEvents.Listener>()
+
+ override fun registerListener(listener: NotifPanelEvents.Listener) {
+ listeners.add(listener)
+ }
+
+ override fun unregisterListener(listener: NotifPanelEvents.Listener) {
+ listeners.remove(listener)
+ }
+
+ fun changeExpandImmediate(expandImmediate: Boolean) {
+ listeners.forEach { it.onExpandImmediateChanged(expandImmediate) }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolverTest.kt
new file mode 100644
index 000000000000..8275c0c24339
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolverTest.kt
@@ -0,0 +1,132 @@
+/*
+ * 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.collection
+
+import android.app.Notification
+import android.app.NotificationManager
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.os.Bundle
+import android.os.UserHandle
+import android.service.notification.NotificationListenerService.Ranking
+import android.service.notification.StatusBarNotification
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.withArgCaptor
+import junit.framework.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.anyString
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.Mockito.`when` as whenever
+
+private const val SDK_VERSION = 33
+private const val PACKAGE = "pkg"
+private const val USER_ID = -1
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class TargetSdkResolverTest : SysuiTestCase() {
+ private val packageManager: PackageManager = mock()
+ private val applicationInfo = ApplicationInfo().apply { targetSdkVersion = SDK_VERSION }
+
+ private lateinit var targetSdkResolver: TargetSdkResolver
+ private lateinit var notifListener: NotifCollectionListener
+
+ @Before
+ fun setUp() {
+ targetSdkResolver = TargetSdkResolver(mContext)
+ mContext.setMockPackageManager(packageManager)
+
+ val notifCollection: CommonNotifCollection = mock()
+ targetSdkResolver.initialize(notifCollection)
+ notifListener = withArgCaptor {
+ verify(notifCollection).addCollectionListener(capture())
+ }
+ }
+
+ @Test
+ fun resolveFromNotificationExtras() {
+ val extras = Bundle().apply {
+ putParcelable(Notification.EXTRA_BUILDER_APPLICATION_INFO, applicationInfo)
+ }
+ val notification = Notification().apply { this.extras = extras }
+ val sbn = createSbn(notification)
+ val entry = createNotificationEntry(sbn)
+
+ notifListener.onEntryBind(entry, sbn)
+
+ assertEquals(SDK_VERSION, entry.targetSdk)
+ verifyZeroInteractions(packageManager)
+ }
+
+ @Test
+ fun resolveFromPackageManager() {
+ val sbn = createSbn(Notification())
+ val entry = createNotificationEntry(sbn)
+ whenever(packageManager.getApplicationInfo(anyString(), anyInt()))
+ .thenReturn(applicationInfo)
+
+ notifListener.onEntryBind(entry, sbn)
+
+ assertEquals(SDK_VERSION, entry.targetSdk)
+ verify(packageManager).getApplicationInfo(eq(PACKAGE), anyInt())
+ }
+
+ @Test
+ fun resolveFromPackageManager_andPackageManagerCrashes() {
+ val sbn = createSbn(Notification())
+ val entry = createNotificationEntry(sbn)
+ whenever(packageManager.getApplicationInfo(anyString(), anyInt()))
+ .thenThrow(PackageManager.NameNotFoundException())
+
+ notifListener.onEntryBind(entry, sbn)
+
+ assertEquals(0, entry.targetSdk)
+ verify(packageManager).getApplicationInfo(eq(PACKAGE), anyInt())
+ }
+
+ private fun createSbn(notification: Notification) = StatusBarNotification(
+ PACKAGE, "opPkg", 0, "tag", 0, 0,
+ notification, UserHandle(USER_ID), "", 0
+ )
+
+ private fun createNotificationEntry(sbn: StatusBarNotification) =
+ NotificationEntry(sbn, createRanking(sbn.key), 0)
+
+ private fun createRanking(key: String) = Ranking().apply {
+ populate(
+ key,
+ 0,
+ false,
+ 0,
+ 0,
+ NotificationManager.IMPORTANCE_DEFAULT,
+ null, null,
+ null, null, null, true, 0, false, -1, false, null, null, false, false,
+ false, null, 0, false)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
index 541749b49474..d59cc54dfe98 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.interruption;
+import static android.app.Notification.VISIBILITY_PRIVATE;
import static android.app.Notification.VISIBILITY_PUBLIC;
import static android.app.Notification.VISIBILITY_SECRET;
import static android.app.NotificationManager.IMPORTANCE_HIGH;
@@ -449,6 +450,54 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase {
}
@Test
+ public void notificationVisibilityPublic() {
+ // GIVEN a VISIBILITY_PUBLIC notification
+ NotificationEntryBuilder entryBuilder = new NotificationEntryBuilder()
+ .setUser(new UserHandle(NOTIF_USER_ID));
+ entryBuilder.modifyNotification(mContext)
+ .setVisibility(VISIBILITY_PUBLIC);
+ mEntry = entryBuilder.build();
+
+ // WHEN we're in an 'unfiltered-keyguard-showing' state
+ setupUnfilteredState(mEntry);
+
+ // THEN don't hide the entry based on visibility.
+ assertFalse(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry));
+ }
+
+ @Test
+ public void notificationVisibilityPrivate() {
+ // GIVEN a VISIBILITY_PRIVATE notification
+ NotificationEntryBuilder entryBuilder = new NotificationEntryBuilder()
+ .setUser(new UserHandle(NOTIF_USER_ID));
+ entryBuilder.modifyNotification(mContext)
+ .setVisibility(VISIBILITY_PRIVATE);
+ mEntry = entryBuilder.build();
+
+ // WHEN we're in an 'unfiltered-keyguard-showing' state
+ setupUnfilteredState(mEntry);
+
+ // THEN don't hide the entry based on visibility. (Redaction is handled elsewhere.)
+ assertFalse(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry));
+ }
+
+ @Test
+ public void notificationVisibilitySecret() {
+ // GIVEN a VISIBILITY_SECRET notification
+ NotificationEntryBuilder entryBuilder = new NotificationEntryBuilder()
+ .setUser(new UserHandle(NOTIF_USER_ID));
+ entryBuilder.modifyNotification(mContext)
+ .setVisibility(VISIBILITY_SECRET);
+ mEntry = entryBuilder.build();
+
+ // WHEN we're in an 'unfiltered-keyguard-showing' state
+ setupUnfilteredState(mEntry);
+
+ // THEN hide the entry based on visibility.
+ assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry));
+ }
+
+ @Test
public void summaryExceedsThresholdToShow() {
// GIVEN the notification doesn't exceed the threshold to show on the lockscreen
// but it's part of a group (has a parent)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
index 381d72f53d5f..90adabfadd5d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
@@ -236,7 +236,6 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
@Test
public void testBindNotification_SetsShortcutIcon() {
mNotificationInfo.bindNotification(
- -1,
mShortcutManager,
mMockPackageManager,
mPeopleSpaceWidgetManager,
@@ -261,7 +260,6 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
public void testBindNotification_SetsTextApplicationName() {
when(mMockPackageManager.getApplicationLabel(any())).thenReturn("App Name");
mNotificationInfo.bindNotification(
- -1,
mShortcutManager,
mMockPackageManager,
mPeopleSpaceWidgetManager,
@@ -314,7 +312,6 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
mConversationChannel.setGroup(group.getId());
mNotificationInfo.bindNotification(
- -1,
mShortcutManager,
mMockPackageManager,
mPeopleSpaceWidgetManager,
@@ -340,7 +337,6 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
@Test
public void testBindNotification_GroupNameHiddenIfNoGroup() {
mNotificationInfo.bindNotification(
- -1,
mShortcutManager,
mMockPackageManager,
mPeopleSpaceWidgetManager,
@@ -365,7 +361,6 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
@Test
public void testBindNotification_noDelegate() {
mNotificationInfo.bindNotification(
- -1,
mShortcutManager,
mMockPackageManager,
mPeopleSpaceWidgetManager,
@@ -401,7 +396,6 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
.setShortcutInfo(mShortcutInfo)
.build();
mNotificationInfo.bindNotification(
- -1,
mShortcutManager,
mMockPackageManager,
mPeopleSpaceWidgetManager,
@@ -427,7 +421,6 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
public void testBindNotification_SetsOnClickListenerForSettings() {
final CountDownLatch latch = new CountDownLatch(1);
mNotificationInfo.bindNotification(
- -1,
mShortcutManager,
mMockPackageManager,
mPeopleSpaceWidgetManager,
@@ -457,7 +450,6 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
@Test
public void testBindNotification_SettingsButtonInvisibleWhenNoClickListener() {
mNotificationInfo.bindNotification(
- -1,
mShortcutManager,
mMockPackageManager,
mPeopleSpaceWidgetManager,
@@ -482,7 +474,6 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
public void testBindNotification_SettingsButtonInvisibleWhenDeviceUnprovisioned() {
final CountDownLatch latch = new CountDownLatch(1);
mNotificationInfo.bindNotification(
- -1,
mShortcutManager,
mMockPackageManager,
mPeopleSpaceWidgetManager,
@@ -511,7 +502,6 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
mConversationChannel.setImportance(IMPORTANCE_LOW);
mConversationChannel.setImportantConversation(true);
mNotificationInfo.bindNotification(
- -1,
mShortcutManager,
mMockPackageManager,
mPeopleSpaceWidgetManager,
@@ -540,7 +530,6 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
mConversationChannel.setImportantConversation(false);
mConversationChannel.setAllowBubbles(true);
mNotificationInfo.bindNotification(
- -1,
mShortcutManager,
mMockPackageManager,
mPeopleSpaceWidgetManager,
@@ -572,7 +561,6 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
mConversationChannel.setImportantConversation(false);
mConversationChannel.setAllowBubbles(true);
mNotificationInfo.bindNotification(
- -1,
mShortcutManager,
mMockPackageManager,
mPeopleSpaceWidgetManager,
@@ -610,7 +598,6 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
mConversationChannel.setImportantConversation(false);
mConversationChannel.setAllowBubbles(false);
mNotificationInfo.bindNotification(
- -1,
mShortcutManager,
mMockPackageManager,
mPeopleSpaceWidgetManager,
@@ -639,7 +626,6 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
mConversationChannel.setImportantConversation(false);
mConversationChannel.setAllowBubbles(false);
mNotificationInfo.bindNotification(
- -1,
mShortcutManager,
mMockPackageManager,
mPeopleSpaceWidgetManager,
@@ -675,7 +661,6 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
mConversationChannel.setImportantConversation(false);
mConversationChannel.setAllowBubbles(true);
mNotificationInfo.bindNotification(
- -1,
mShortcutManager,
mMockPackageManager,
mPeopleSpaceWidgetManager,
@@ -704,7 +689,6 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
mConversationChannel.setImportantConversation(false);
mNotificationInfo.bindNotification(
- -1,
mShortcutManager,
mMockPackageManager,
mPeopleSpaceWidgetManager,
@@ -735,7 +719,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
.isEqualTo(GONE);
// no changes until hit done
- assertFalse(mNotificationInfo.shouldBeSaved());
+ assertFalse(mNotificationInfo.shouldBeSavedOnClose());
verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
anyString(), anyInt(), any());
assertFalse(mConversationChannel.isImportantConversation());
@@ -749,7 +733,6 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
mConversationChannel.setImportance(IMPORTANCE_LOW);
mConversationChannel.setImportantConversation(false);
mNotificationInfo.bindNotification(
- -1,
mShortcutManager,
mMockPackageManager,
mPeopleSpaceWidgetManager,
@@ -779,7 +762,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
.isEqualTo(GONE);
// no changes until hit done
- assertFalse(mNotificationInfo.shouldBeSaved());
+ assertFalse(mNotificationInfo.shouldBeSavedOnClose());
verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
anyString(), anyInt(), any());
assertFalse(mConversationChannel.isImportantConversation());
@@ -793,7 +776,6 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
mConversationChannel.setImportantConversation(false);
mNotificationInfo.bindNotification(
- -1,
mShortcutManager,
mMockPackageManager,
mPeopleSpaceWidgetManager,
@@ -825,7 +807,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
.isEqualTo(VISIBLE);
// no changes until save
- assertFalse(mNotificationInfo.shouldBeSaved());
+ assertFalse(mNotificationInfo.shouldBeSavedOnClose());
verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
anyString(), anyInt(), any());
assertEquals(IMPORTANCE_DEFAULT, mConversationChannel.getImportance());
@@ -838,7 +820,6 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
mConversationChannel.setImportantConversation(false);
mNotificationInfo.bindNotification(
- -1,
mShortcutManager,
mMockPackageManager,
mPeopleSpaceWidgetManager,
@@ -868,6 +849,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
assertTrue(captor.getValue().isImportantConversation());
assertTrue(captor.getValue().canBubble());
assertEquals(IMPORTANCE_DEFAULT, captor.getValue().getImportance());
+ assertFalse(mNotificationInfo.shouldBeSavedOnClose());
}
@Test
@@ -876,7 +858,6 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
mConversationChannel.setImportance(9);
mNotificationInfo.bindNotification(
- -1,
mShortcutManager,
mMockPackageManager,
mPeopleSpaceWidgetManager,
@@ -913,7 +894,6 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
mConversationChannel.setImportantConversation(true);
mNotificationInfo.bindNotification(
- -1,
mShortcutManager,
mMockPackageManager,
mPeopleSpaceWidgetManager,
@@ -954,7 +934,6 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
// WHEN we indicate no selected action
mNotificationInfo.bindNotification(
- -1, // no action selected by default
mShortcutManager,
mMockPackageManager,
mPeopleSpaceWidgetManager,
@@ -984,8 +963,8 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
mConversationChannel.setImportantConversation(false);
// WHEN we indicate the selected action should be "Favorite"
+ mNotificationInfo.setSelectedAction(NotificationConversationInfo.ACTION_FAVORITE);
mNotificationInfo.bindNotification(
- NotificationConversationInfo.ACTION_FAVORITE, // "Favorite" selected by default
mShortcutManager,
mMockPackageManager,
mPeopleSpaceWidgetManager,
@@ -1015,7 +994,6 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
mConversationChannel.setOriginalImportance(IMPORTANCE_HIGH);
mConversationChannel.setImportantConversation(true);
mNotificationInfo.bindNotification(
- -1,
mShortcutManager,
mMockPackageManager,
mPeopleSpaceWidgetManager,
@@ -1044,6 +1022,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
assertFalse(captor.getValue().isImportantConversation());
assertFalse(captor.getValue().canBubble());
assertEquals(IMPORTANCE_HIGH, captor.getValue().getImportance());
+ assertFalse(mNotificationInfo.shouldBeSavedOnClose());
}
@Test
@@ -1052,7 +1031,6 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
mConversationChannel.setOriginalImportance(IMPORTANCE_HIGH);
mConversationChannel.setImportantConversation(false);
mNotificationInfo.bindNotification(
- -1,
mShortcutManager,
mMockPackageManager,
mPeopleSpaceWidgetManager,
@@ -1089,7 +1067,6 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
mConversationChannel.setOriginalImportance(IMPORTANCE_HIGH);
mNotificationInfo.bindNotification(
- -1,
mShortcutManager,
mMockPackageManager,
mPeopleSpaceWidgetManager,
@@ -1125,7 +1102,6 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
mConversationChannel.setAllowBubbles(true);
mNotificationInfo.bindNotification(
- -1,
mShortcutManager,
mMockPackageManager,
mPeopleSpaceWidgetManager,
@@ -1155,12 +1131,46 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
assertFalse(captor.getValue().isImportantConversation());
assertFalse(captor.getValue().canBubble());
assertEquals(IMPORTANCE_LOW, captor.getValue().getImportance());
+ assertFalse(mNotificationInfo.shouldBeSavedOnClose());
+ }
+
+ @Test
+ public void testSilence_closeGutsThenTryToSave() {
+ mConversationChannel.setImportance(IMPORTANCE_DEFAULT);
+ mConversationChannel.setImportantConversation(true);
+ mConversationChannel.setAllowBubbles(true);
+
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mMockPackageManager,
+ mPeopleSpaceWidgetManager,
+ mMockINotificationManager,
+ mOnUserInteractionCallback,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ mBubbleMetadata,
+ null,
+ mIconFactory,
+ mContext,
+ true,
+ mTestHandler,
+ mTestHandler, null, Optional.of(mBubblesManager),
+ mShadeController);
+
+ mNotificationInfo.findViewById(R.id.silence).performClick();
+ mNotificationInfo.handleCloseControls(false, false);
+ mNotificationInfo.findViewById(R.id.done).performClick();
+
+ mTestableLooper.processAllMessages();
+
+ assertEquals(IMPORTANCE_DEFAULT, mConversationChannel.getImportance());
+ assertFalse(mNotificationInfo.shouldBeSavedOnClose());
}
@Test
public void testBindNotification_createsNewChannel() throws Exception {
mNotificationInfo.bindNotification(
- -1,
mShortcutManager,
mMockPackageManager,
mPeopleSpaceWidgetManager,
@@ -1186,7 +1196,6 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
public void testBindNotification_doesNotCreateNewChannelIfExists() throws Exception {
mNotificationChannel.setConversationId("", CONVERSATION_ID);
mNotificationInfo.bindNotification(
- -1,
mShortcutManager,
mMockPackageManager,
mPeopleSpaceWidgetManager,
@@ -1213,7 +1222,6 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
//WHEN channel is default importance
mNotificationChannel.setImportantConversation(false);
mNotificationInfo.bindNotification(
- -1,
mShortcutManager,
mMockPackageManager,
mPeopleSpaceWidgetManager,
@@ -1244,7 +1252,6 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
@Test
public void testSelectDefaultDoesNotRequestPinPeopleTile() {
mNotificationInfo.bindNotification(
- -1,
mShortcutManager,
mMockPackageManager,
mPeopleSpaceWidgetManager,
@@ -1279,7 +1286,6 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
mConversationChannel.setImportantConversation(true);
mNotificationInfo.bindNotification(
- -1,
mShortcutManager,
mMockPackageManager,
mPeopleSpaceWidgetManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsTest.kt
new file mode 100644
index 000000000000..e696c8738d72
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsTest.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
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.testing.ViewUtils
+import android.view.LayoutInflater
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class NotificationGutsTest : SysuiTestCase() {
+
+ private lateinit var guts: NotificationGuts
+ private lateinit var gutsContentView: View
+
+ @Mock
+ private lateinit var gutsContent: NotificationGuts.GutsContent
+
+ @Mock
+ private lateinit var gutsClosedListener: NotificationGuts.OnGutsClosedListener
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ val layoutInflater = LayoutInflater.from(mContext)
+ guts = layoutInflater.inflate(R.layout.notification_guts, null) as NotificationGuts
+ gutsContentView = View(mContext)
+
+ whenever(gutsContent.contentView).thenReturn(gutsContentView)
+
+ ViewUtils.attachView(guts)
+ }
+
+ @After
+ fun tearDown() {
+ ViewUtils.detachView(guts)
+ }
+
+ @Test
+ fun setGutsContent() {
+ guts.gutsContent = gutsContent
+
+ verify(gutsContent).setGutsParent(guts)
+ }
+
+ @Test
+ fun openControls() {
+ guts.gutsContent = gutsContent
+
+ guts.openControls(true, 0, 0, false, null)
+ }
+
+ @Test
+ fun closeControlsWithSave() {
+ guts.gutsContent = gutsContent
+ guts.setClosedListener(gutsClosedListener)
+
+ guts.closeControls(gutsContentView, true)
+
+ verify(gutsContent).handleCloseControls(true, false)
+ verify(gutsClosedListener).onGutsClosed(guts)
+ }
+
+ @Test
+ fun closeControlsWithoutSave() {
+ guts.gutsContent = gutsContent
+ guts.setClosedListener(gutsClosedListener)
+
+ guts.closeControls(gutsContentView, false)
+
+ verify(gutsContent).handleCloseControls(false, false)
+ verify(gutsClosedListener).onGutsClosed(guts)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
index b1f10751119e..80a81a592049 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
@@ -50,6 +50,7 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
import android.telecom.TelecomManager;
@@ -1090,6 +1091,7 @@ public class NotificationInfoTest extends SysuiTestCase {
mUiEventLogger.eventId(0));
assertEquals(NotificationControlsEvent.NOTIFICATION_CONTROLS_SAVE_IMPORTANCE.getId(),
mUiEventLogger.eventId(1));
+ assertFalse(mNotificationInfo.shouldBeSavedOnClose());
}
@Test
@@ -1124,6 +1126,7 @@ public class NotificationInfoTest extends SysuiTestCase {
assertTrue((updated.getValue().getUserLockedFields()
& USER_LOCKED_IMPORTANCE) != 0);
assertEquals(IMPORTANCE_DEFAULT, updated.getValue().getImportance());
+ assertFalse(mNotificationInfo.shouldBeSavedOnClose());
}
@Test
@@ -1156,6 +1159,7 @@ public class NotificationInfoTest extends SysuiTestCase {
verify(mMockINotificationManager, times(1)).unlockNotificationChannel(
anyString(), eq(TEST_UID), any());
assertEquals(IMPORTANCE_DEFAULT, mNotificationChannel.getImportance());
+ assertFalse(mNotificationInfo.shouldBeSavedOnClose());
}
@Test
@@ -1191,6 +1195,7 @@ public class NotificationInfoTest extends SysuiTestCase {
assertTrue((updated.getValue().getUserLockedFields()
& USER_LOCKED_IMPORTANCE) != 0);
assertEquals(IMPORTANCE_LOW, updated.getValue().getImportance());
+ assertFalse(mNotificationInfo.shouldBeSavedOnClose());
}
@Test
@@ -1229,6 +1234,37 @@ public class NotificationInfoTest extends SysuiTestCase {
anyString(), eq(TEST_UID), updated.capture());
assertTrue((updated.getValue().getUserLockedFields() & USER_LOCKED_IMPORTANCE) != 0);
assertEquals(IMPORTANCE_MIN, updated.getValue().getImportance());
+ assertFalse(mNotificationInfo.shouldBeSavedOnClose());
+ }
+
+ @Test
+ public void testSilence_closeGutsThenTryToSave() throws RemoteException {
+ mNotificationChannel.setImportance(IMPORTANCE_DEFAULT);
+ mNotificationInfo.bindNotification(
+ mMockPackageManager,
+ mMockINotificationManager,
+ mOnUserInteractionCallback,
+ mChannelEditorDialogController,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mNotificationChannelSet,
+ mEntry,
+ null,
+ null,
+ mUiEventLogger,
+ true,
+ false,
+ false,
+ mAssistantFeedbackController);
+
+ mNotificationInfo.findViewById(R.id.silence).performClick();
+ mNotificationInfo.handleCloseControls(false, false);
+ mNotificationInfo.handleCloseControls(true, false);
+
+ mTestableLooper.processAllMessages();
+
+ assertEquals(IMPORTANCE_DEFAULT, mNotificationChannel.getImportance());
+ assertFalse(mNotificationInfo.shouldBeSavedOnClose());
}
@Test
@@ -1267,6 +1303,7 @@ public class NotificationInfoTest extends SysuiTestCase {
anyString(), eq(TEST_UID), updated.capture());
assertTrue((updated.getValue().getUserLockedFields() & USER_LOCKED_IMPORTANCE) != 0);
assertEquals(IMPORTANCE_DEFAULT, updated.getValue().getImportance());
+ assertFalse(mNotificationInfo.shouldBeSavedOnClose());
}
@Test
@@ -1294,6 +1331,7 @@ public class NotificationInfoTest extends SysuiTestCase {
mNotificationInfo.handleCloseControls(true, false);
verify(mOnUserInteractionCallback).onImportanceChanged(mEntry);
+ assertFalse(mNotificationInfo.shouldBeSavedOnClose());
}
@Test
@@ -1360,6 +1398,7 @@ public class NotificationInfoTest extends SysuiTestCase {
assertTrue((updated.getValue().getUserLockedFields()
& USER_LOCKED_IMPORTANCE) != 0);
assertEquals(IMPORTANCE_DEFAULT, updated.getValue().getImportance());
+ assertFalse(mNotificationInfo.shouldBeSavedOnClose());
}
@Test
@@ -1450,7 +1489,7 @@ public class NotificationInfoTest extends SysuiTestCase {
mNotificationInfo.findViewById(R.id.alert).performClick();
- assertFalse(mNotificationInfo.shouldBeSaved());
+ assertFalse(mNotificationInfo.shouldBeSavedOnClose());
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java
index 43aa8fef76a9..12c8fd5db751 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.notification.row;
-import static android.app.Notification.EXTRA_IS_GROUP_CONVERSATION;
import static android.app.NotificationManager.IMPORTANCE_LOW;
import static android.print.PrintManager.PRINT_SPOOLER_PACKAGE_NAME;
import static android.view.View.GONE;
@@ -25,7 +24,6 @@ import static android.view.View.VISIBLE;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.anyObject;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyBoolean;
import static org.mockito.Mockito.anyInt;
@@ -36,8 +34,6 @@ import static org.mockito.Mockito.when;
import android.app.INotificationManager;
import android.app.Notification;
import android.app.NotificationChannel;
-import android.app.NotificationChannelGroup;
-import android.app.Person;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessorTest.kt
index 515a7c936f4d..7b492cb7ddd1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessorTest.kt
@@ -20,7 +20,7 @@ import android.net.NetworkCapabilities
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.statusbar.pipeline.repository.NetworkCapabilityInfo
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.NetworkCapabilityInfo
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
@@ -59,6 +59,7 @@ class ConnectivityInfoProcessorTest : SysuiTestCase() {
context,
scope,
statusBarPipelineFlags,
+ mock(),
)
var mostRecentValue: ProcessedConnectivityInfo? = null
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ConnectivityPipelineLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt
index 2915ae8dea88..36be1be309d6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/ConnectivityPipelineLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.pipeline
+package com.android.systemui.statusbar.pipeline.shared
import android.net.Network
import android.net.NetworkCapabilities
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
new file mode 100644
index 000000000000..df389bc52664
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.pipeline.wifi.data.repository
+
+import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiActivityModel
+import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiModel
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl.Companion.ACTIVITY_DEFAULT
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+
+/** Fake implementation of [WifiRepository] exposing set methods for all the flows. */
+class FakeWifiRepository : WifiRepository {
+ private val _wifiModel: MutableStateFlow<WifiModel?> = MutableStateFlow(null)
+ override val wifiModel: Flow<WifiModel?> = _wifiModel
+
+ private val _wifiActivity = MutableStateFlow(ACTIVITY_DEFAULT)
+ override val wifiActivity: Flow<WifiActivityModel> = _wifiActivity
+
+ fun setWifiModel(wifiModel: WifiModel?) {
+ _wifiModel.value = wifiModel
+ }
+
+ fun setWifiActivity(activity: WifiActivityModel) {
+ _wifiActivity.value = activity
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/repository/NetworkCapabilitiesRepoTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/NetworkCapabilitiesRepoTest.kt
index 40f8fbfd9af2..6edf76ce77c9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/repository/NetworkCapabilitiesRepoTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/NetworkCapabilitiesRepoTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.pipeline.repository
+package com.android.systemui.statusbar.pipeline.wifi.data.repository
import android.net.ConnectivityManager
import android.net.ConnectivityManager.NetworkCallback
@@ -28,7 +28,7 @@ import android.net.NetworkRequest
import android.test.suitebuilder.annotation.SmallTest
import android.testing.AndroidTestingRunner
import com.android.systemui.SysuiTestCase
-import com.android.systemui.statusbar.pipeline.ConnectivityPipelineLogger
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.withArgCaptor
import com.google.common.truth.Truth.assertThat
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt
new file mode 100644
index 000000000000..8b61364a2ac9
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt
@@ -0,0 +1,175 @@
+/*
+ * 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.pipeline.wifi.data.repository
+
+import android.net.wifi.WifiManager
+import android.net.wifi.WifiManager.TrafficStateCallback
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiActivityModel
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl.Companion.ACTIVITY_DEFAULT
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.Executor
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+class WifiRepositoryImplTest : SysuiTestCase() {
+
+ private lateinit var underTest: WifiRepositoryImpl
+
+ @Mock private lateinit var logger: ConnectivityPipelineLogger
+ @Mock private lateinit var wifiManager: WifiManager
+ private lateinit var executor: Executor
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ executor = FakeExecutor(FakeSystemClock())
+ }
+
+ @Test
+ fun wifiActivity_nullWifiManager_receivesDefault() = runBlocking(IMMEDIATE) {
+ underTest = WifiRepositoryImpl(
+ wifiManager = null,
+ executor,
+ logger,
+ )
+
+ var latest: WifiActivityModel? = null
+ val job = underTest
+ .wifiActivity
+ .onEach { latest = it }
+ .launchIn(this)
+
+ assertThat(latest).isEqualTo(ACTIVITY_DEFAULT)
+
+ job.cancel()
+ }
+
+ @Test
+ fun wifiActivity_callbackGivesNone_activityFlowHasNone() = runBlocking(IMMEDIATE) {
+ underTest = WifiRepositoryImpl(
+ wifiManager,
+ executor,
+ logger,
+ )
+
+ var latest: WifiActivityModel? = null
+ val job = underTest
+ .wifiActivity
+ .onEach { latest = it }
+ .launchIn(this)
+
+ getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_NONE)
+
+ assertThat(latest).isEqualTo(
+ WifiActivityModel(hasActivityIn = false, hasActivityOut = false)
+ )
+
+ job.cancel()
+ }
+
+ @Test
+ fun wifiActivity_callbackGivesIn_activityFlowHasIn() = runBlocking(IMMEDIATE) {
+ underTest = WifiRepositoryImpl(
+ wifiManager,
+ executor,
+ logger,
+ )
+
+ var latest: WifiActivityModel? = null
+ val job = underTest
+ .wifiActivity
+ .onEach { latest = it }
+ .launchIn(this)
+
+ getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_IN)
+
+ assertThat(latest).isEqualTo(
+ WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
+ )
+
+ job.cancel()
+ }
+
+ @Test
+ fun wifiActivity_callbackGivesOut_activityFlowHasOut() = runBlocking(IMMEDIATE) {
+ underTest = WifiRepositoryImpl(
+ wifiManager,
+ executor,
+ logger,
+ )
+
+ var latest: WifiActivityModel? = null
+ val job = underTest
+ .wifiActivity
+ .onEach { latest = it }
+ .launchIn(this)
+
+ getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_OUT)
+
+ assertThat(latest).isEqualTo(
+ WifiActivityModel(hasActivityIn = false, hasActivityOut = true)
+ )
+
+ job.cancel()
+ }
+
+ @Test
+ fun wifiActivity_callbackGivesInout_activityFlowHasInAndOut() = runBlocking(IMMEDIATE) {
+ underTest = WifiRepositoryImpl(
+ wifiManager,
+ executor,
+ logger,
+ )
+
+ var latest: WifiActivityModel? = null
+ val job = underTest
+ .wifiActivity
+ .onEach { latest = it }
+ .launchIn(this)
+
+ getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_INOUT)
+
+ assertThat(latest).isEqualTo(WifiActivityModel(hasActivityIn = true, hasActivityOut = true))
+
+ job.cancel()
+ }
+
+ private fun getTrafficStateCallback(): TrafficStateCallback {
+ val callbackCaptor = argumentCaptor<TrafficStateCallback>()
+ verify(wifiManager).registerTrafficStateCallback(any(), callbackCaptor.capture())
+ return callbackCaptor.value!!
+ }
+}
+
+private val IMMEDIATE = Dispatchers.Main.immediate
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt
new file mode 100644
index 000000000000..c52f347a605a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt
@@ -0,0 +1,163 @@
+/*
+ * 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.pipeline.wifi.domain.interactor
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiActivityModel
+import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiModel
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.yield
+import org.junit.Before
+import org.junit.Test
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+class WifiInteractorTest : SysuiTestCase() {
+
+ private lateinit var underTest: WifiInteractor
+
+ private lateinit var repository: FakeWifiRepository
+
+ @Before
+ fun setUp() {
+ repository = FakeWifiRepository()
+ underTest = WifiInteractor(repository)
+ }
+
+ @Test
+ fun hasActivityIn_noInOrOut_outputsFalse() = runBlocking(IMMEDIATE) {
+ repository.setWifiModel(WifiModel(ssid = "AB"))
+ repository.setWifiActivity(WifiActivityModel(hasActivityIn = false, hasActivityOut = false))
+
+ var latest: Boolean? = null
+ val job = underTest
+ .hasActivityIn
+ .onEach { latest = it }
+ .launchIn(this)
+
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun hasActivityIn_onlyOut_outputsFalse() = runBlocking(IMMEDIATE) {
+ repository.setWifiModel(WifiModel(ssid = "AB"))
+ repository.setWifiActivity(WifiActivityModel(hasActivityIn = false, hasActivityOut = true))
+
+ var latest: Boolean? = null
+ val job = underTest
+ .hasActivityIn
+ .onEach { latest = it }
+ .launchIn(this)
+
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun hasActivityIn_onlyIn_outputsTrue() = runBlocking(IMMEDIATE) {
+ repository.setWifiModel(WifiModel(ssid = "AB"))
+ repository.setWifiActivity(WifiActivityModel(hasActivityIn = true, hasActivityOut = false))
+
+ var latest: Boolean? = null
+ val job = underTest
+ .hasActivityIn
+ .onEach { latest = it }
+ .launchIn(this)
+
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun hasActivityIn_inAndOut_outputsTrue() = runBlocking(IMMEDIATE) {
+ repository.setWifiModel(WifiModel(ssid = "AB"))
+ repository.setWifiActivity(WifiActivityModel(hasActivityIn = true, hasActivityOut = true))
+
+ var latest: Boolean? = null
+ val job = underTest
+ .hasActivityIn
+ .onEach { latest = it }
+ .launchIn(this)
+
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun hasActivityIn_ssidNull_outputsFalse() = runBlocking(IMMEDIATE) {
+ repository.setWifiModel(WifiModel(ssid = null))
+ repository.setWifiActivity(WifiActivityModel(hasActivityIn = true, hasActivityOut = true))
+
+ var latest: Boolean? = null
+ val job = underTest
+ .hasActivityIn
+ .onEach { latest = it }
+ .launchIn(this)
+
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun hasActivityIn_multipleChanges_multipleOutputChanges() = runBlocking(IMMEDIATE) {
+ repository.setWifiModel(WifiModel(ssid = "AB"))
+
+ var latest: Boolean? = null
+ val job = underTest
+ .hasActivityIn
+ .onEach { latest = it }
+ .launchIn(this)
+
+ // Conduct a series of changes and verify we catch each of them in succession
+ repository.setWifiActivity(WifiActivityModel(hasActivityIn = true, hasActivityOut = false))
+ yield()
+ assertThat(latest).isTrue()
+
+ repository.setWifiActivity(WifiActivityModel(hasActivityIn = false, hasActivityOut = true))
+ yield()
+ assertThat(latest).isFalse()
+
+ repository.setWifiActivity(WifiActivityModel(hasActivityIn = true, hasActivityOut = true))
+ yield()
+ assertThat(latest).isTrue()
+
+ repository.setWifiActivity(WifiActivityModel(hasActivityIn = true, hasActivityOut = false))
+ yield()
+ assertThat(latest).isTrue()
+
+ repository.setWifiActivity(WifiActivityModel(hasActivityIn = false, hasActivityOut = false))
+ yield()
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+}
+
+private val IMMEDIATE = Dispatchers.Main.immediate
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
new file mode 100644
index 000000000000..e9259b071e0b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
@@ -0,0 +1,124 @@
+/*
+ * 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.pipeline.wifi.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiActivityModel
+import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiModel
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
+import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
+import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.yield
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+class WifiViewModelTest : SysuiTestCase() {
+
+ private lateinit var underTest: WifiViewModel
+
+ @Mock private lateinit var logger: ConnectivityPipelineLogger
+ @Mock private lateinit var constants: WifiConstants
+ private lateinit var repository: FakeWifiRepository
+ private lateinit var interactor: WifiInteractor
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ repository = FakeWifiRepository()
+ interactor = WifiInteractor(repository)
+
+ underTest = WifiViewModel(
+ constants,
+ logger,
+ interactor
+ )
+
+ // Set up with a valid SSID
+ repository.setWifiModel(WifiModel(ssid = "AB"))
+ }
+
+ @Test
+ fun activityInVisible_showActivityConfigFalse_receivesFalse() = runBlocking(IMMEDIATE) {
+ whenever(constants.shouldShowActivityConfig).thenReturn(false)
+
+ var latest: Boolean? = null
+ val job = underTest
+ .isActivityInVisible
+ .onEach { latest = it }
+ .launchIn(this)
+
+ // Verify that on launch, we receive a false.
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun activityInVisible_showActivityConfigFalse_noUpdatesReceived() = runBlocking(IMMEDIATE) {
+ whenever(constants.shouldShowActivityConfig).thenReturn(false)
+
+ var latest: Boolean? = null
+ val job = underTest
+ .isActivityInVisible
+ .onEach { latest = it }
+ .launchIn(this)
+
+ // Update the repo to have activityIn
+ repository.setWifiActivity(WifiActivityModel(hasActivityIn = true, hasActivityOut = false))
+ yield()
+
+ // Verify that we didn't update to activityIn=true (because our config is false)
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun activityInVisible_showActivityConfigTrue_receivesUpdate() = runBlocking(IMMEDIATE) {
+ whenever(constants.shouldShowActivityConfig).thenReturn(true)
+
+ var latest: Boolean? = null
+ val job = underTest
+ .isActivityInVisible
+ .onEach { latest = it }
+ .launchIn(this)
+
+ // Update the repo to have activityIn
+ repository.setWifiActivity(WifiActivityModel(hasActivityIn = true, hasActivityOut = false))
+ yield()
+
+ // Verify that we updated to activityIn=true
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+}
+
+private val IMMEDIATE = Dispatchers.Main.immediate
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt
index 309acdf13a5d..f539dbdf76a0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt
@@ -91,6 +91,8 @@ class KotlinArgumentCaptor<T> constructor(clazz: Class<T>) {
fun capture(): T = wrapped.capture()
val value: T
get() = wrapped.value
+ val allValues: List<T>
+ get() = wrapped.allValues
}
/**
diff --git a/proto/src/OWNERS b/proto/src/OWNERS
index b456ba60d086..abd08deced79 100644
--- a/proto/src/OWNERS
+++ b/proto/src/OWNERS
@@ -1,3 +1,4 @@
per-file gnss.proto = file:/services/core/java/com/android/server/location/OWNERS
per-file wifi.proto = file:/wifi/OWNERS
per-file camera.proto = file:/services/core/java/com/android/server/camera/OWNERS
+per-file system_messages.proto = file:/core/res/OWNERS
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index f903c2005ae3..a94bfe281be1 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -302,6 +302,10 @@ message SystemMessage {
// Package: android
NOTE_WIFI_APM_NOTIFICATION = 73;
+ // Inform the user of bluetooth apm state changes.
+ // Package: android
+ NOTE_BT_APM_NOTIFICATION = 74;
+
// ADD_NEW_IDS_ABOVE_THIS_LINE
// Legacy IDs with arbitrary values appear below
// Legacy IDs existed as stable non-conflicting constants prior to the O release
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index 3324c526ecc2..b34482f0964f 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -354,24 +354,16 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
if (supportsFlagForNotImportantViews(info)) {
if ((info.flags & AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) != 0) {
- mFetchFlags |=
- AccessibilityNodeInfo.FLAG_SERVICE_REQUESTS_INCLUDE_NOT_IMPORTANT_VIEWS;
+ mFetchFlags |= AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
} else {
- mFetchFlags &=
- ~AccessibilityNodeInfo.FLAG_SERVICE_REQUESTS_INCLUDE_NOT_IMPORTANT_VIEWS;
+ mFetchFlags &= ~AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
}
}
if ((info.flags & AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS) != 0) {
- mFetchFlags |= AccessibilityNodeInfo.FLAG_SERVICE_REQUESTS_REPORT_VIEW_IDS;
+ mFetchFlags |= AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS;
} else {
- mFetchFlags &= ~AccessibilityNodeInfo.FLAG_SERVICE_REQUESTS_REPORT_VIEW_IDS;
- }
-
- if (mAccessibilityServiceInfo.isAccessibilityTool()) {
- mFetchFlags |= AccessibilityNodeInfo.FLAG_SERVICE_IS_ACCESSIBILITY_TOOL;
- } else {
- mFetchFlags &= ~AccessibilityNodeInfo.FLAG_SERVICE_IS_ACCESSIBILITY_TOOL;
+ mFetchFlags &= ~AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS;
}
mRequestTouchExplorationMode = (info.flags
@@ -1530,16 +1522,9 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
return false;
}
- final boolean includeNotImportantViews = (mFetchFlags
- & AccessibilityNodeInfo.FLAG_SERVICE_REQUESTS_INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
if ((event.getWindowId() != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID)
&& !event.isImportantForAccessibility()
- && !includeNotImportantViews) {
- return false;
- }
-
- if (event.isAccessibilityDataPrivate()
- && (mFetchFlags & AccessibilityNodeInfo.FLAG_SERVICE_IS_ACCESSIBILITY_TOOL) == 0) {
+ && (mFetchFlags & AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) == 0) {
return false;
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 6a6d2bb44d48..6eabc981e9fe 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -3693,7 +3693,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
info.setCapabilities(AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT);
info.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
info.flags |= AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
- info.setAccessibilityTool(true);
final AccessibilityUserState userState;
synchronized (mLock) {
userState = getCurrentUserStateLocked();
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index fe85db286fa8..5a35474207f7 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -231,7 +231,7 @@ final class AutofillManagerServiceImpl
sendStateToClients(/* resetClient= */ false);
}
updateRemoteAugmentedAutofillService();
- updateRemoteInlineSuggestionRenderServiceLocked();
+ getRemoteInlineSuggestionRenderServiceLocked();
return enabledChanged;
}
@@ -685,8 +685,12 @@ final class AutofillManagerServiceImpl
@GuardedBy("mLock")
void resetExtServiceLocked() {
- if (sVerbose) Slog.v(TAG, "reset autofill service.");
+ if (sVerbose) Slog.v(TAG, "reset autofill service in ExtServices.");
mFieldClassificationStrategy.reset();
+ if (mRemoteInlineSuggestionRenderService != null) {
+ mRemoteInlineSuggestionRenderService.destroy();
+ mRemoteInlineSuggestionRenderService = null;
+ }
}
@GuardedBy("mLock")
@@ -1583,18 +1587,6 @@ final class AutofillManagerServiceImpl
return mFieldClassificationStrategy.getDefaultAlgorithm();
}
- private void updateRemoteInlineSuggestionRenderServiceLocked() {
- if (mRemoteInlineSuggestionRenderService != null) {
- if (sVerbose) {
- Slog.v(TAG, "updateRemoteInlineSuggestionRenderService(): "
- + "destroying old remote service");
- }
- mRemoteInlineSuggestionRenderService = null;
- }
-
- mRemoteInlineSuggestionRenderService = getRemoteInlineSuggestionRenderServiceLocked();
- }
-
@Nullable RemoteInlineSuggestionRenderService getRemoteInlineSuggestionRenderServiceLocked() {
if (mRemoteInlineSuggestionRenderService == null) {
final ComponentName componentName = RemoteInlineSuggestionRenderService
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index 243a7e0ed2ad..907daa34826f 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -160,7 +160,6 @@ import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
-import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.Queue;
@@ -3401,7 +3400,8 @@ public class UserBackupManagerService {
}
/**
- * Selects transport {@code transportName} and returns previously selected transport.
+ * Selects transport {@code transportName}, if it is already registered, and returns previously
+ * selected transport. Returns {@code null} if the transport is not registered.
*
* @deprecated Use {@link #selectBackupTransportAsync(ComponentName,
* ISelectBackupTransportCallback)} instead.
@@ -3414,6 +3414,17 @@ public class UserBackupManagerService {
final long oldId = Binder.clearCallingIdentity();
try {
+ if (!mTransportManager.isTransportRegistered(transportName)) {
+ Slog.v(
+ TAG,
+ addUserIdToLogMessage(
+ mUserId,
+ "Could not select transport "
+ + transportName
+ + ", as the transport is not registered."));
+ return null;
+ }
+
String previousTransportName = mTransportManager.selectTransport(transportName);
updateStateForTransport(transportName);
Slog.v(
diff --git a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
index 43e2b881927c..593a63c2f0c9 100644
--- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
+++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
@@ -77,6 +77,14 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController
new ComponentName("android", BlockedAppStreamingActivity.class.getName());
/**
+ * For communicating when a secure window shows on the virtual display.
+ */
+ public interface SecureWindowCallback {
+ /** Called when a secure window shows on the virtual display. */
+ void onSecureWindowShown(int displayId, int uid);
+ }
+
+ /**
* If required, allow the secure activity to display on remote device since
* {@link android.os.Build.VERSION_CODES#TIRAMISU}.
*/
@@ -108,6 +116,7 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController
new ArraySet<>();
@Nullable
private final @AssociationRequest.DeviceProfile String mDeviceProfile;
+ @Nullable private final SecureWindowCallback mSecureWindowCallback;
/**
* Creates a window policy controller that is generic to the different use cases of virtual
@@ -131,6 +140,8 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController
* @param activityListener Activity listener to listen for activity changes.
* @param activityBlockedCallback Callback that is called when an activity is blocked from
* launching.
+ * @param secureWindowCallback Callback that is called when a secure window shows on the
+ * virtual display.
* @param deviceProfile The {@link AssociationRequest.DeviceProfile} of this virtual device.
*/
public GenericWindowPolicyController(int windowFlags, int systemWindowFlags,
@@ -142,6 +153,7 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController
@ActivityPolicy int defaultActivityPolicy,
@NonNull ActivityListener activityListener,
@NonNull ActivityBlockedCallback activityBlockedCallback,
+ @NonNull SecureWindowCallback secureWindowCallback,
@AssociationRequest.DeviceProfile String deviceProfile) {
super();
mAllowedUsers = allowedUsers;
@@ -154,6 +166,7 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController
setInterestedWindowFlags(windowFlags, systemWindowFlags);
mActivityListener = activityListener;
mDeviceProfile = deviceProfile;
+ mSecureWindowCallback = secureWindowCallback;
}
/**
@@ -234,6 +247,12 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController
@Override
public boolean keepActivityOnWindowFlagsChanged(ActivityInfo activityInfo, int windowFlags,
int systemWindowFlags) {
+ // The callback is fired only when windowFlags are changed. To let VirtualDevice owner
+ // aware that the virtual display has a secure window on top.
+ if ((windowFlags & FLAG_SECURE) != 0) {
+ mSecureWindowCallback.onSecureWindowShown(mDisplayId, activityInfo.applicationInfo.uid);
+ }
+
if (!canContainActivity(activityInfo, windowFlags, systemWindowFlags)) {
mActivityBlockedCallback.onActivityBlocked(mDisplayId, activityInfo);
return false;
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 5f337ab9bae3..cca3212703f0 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -52,6 +52,7 @@ import android.hardware.input.VirtualMouseScrollEvent;
import android.hardware.input.VirtualTouchEvent;
import android.os.Binder;
import android.os.IBinder;
+import android.os.Looper;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.ResultReceiver;
@@ -542,6 +543,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
mParams.getDefaultActivityPolicy(),
createListenerAdapter(),
this::onActivityBlocked,
+ this::onSecureWindowShown,
mAssociationInfo.getDeviceProfile());
gwpc.registerRunningAppsChangedListener(/* listener= */ this);
return gwpc;
@@ -591,6 +593,21 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
mContext.getUser());
}
+ private void onSecureWindowShown(int displayId, int uid) {
+ if (!mVirtualDisplayIds.contains(displayId)) {
+ return;
+ }
+
+ // If a virtual display isn't secure, the screen can't be captured. Show a warning toast
+ // if the secure window is shown on a non-secure virtual display.
+ DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
+ Display display = displayManager.getDisplay(displayId);
+ if ((display.getFlags() & FLAG_SECURE) == 0) {
+ showToastWhereUidIsRunning(uid, com.android.internal.R.string.vdm_secure_window,
+ Toast.LENGTH_LONG, mContext.getMainLooper());
+ }
+ }
+
private ArraySet<UserHandle> getAllowedUserHandles() {
ArraySet<UserHandle> result = new ArraySet<>();
DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
@@ -650,14 +667,16 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
/**
* Shows a toast on virtual displays owned by this device which have a given uid running.
*/
- void showToastWhereUidIsRunning(int uid, @StringRes int resId, @Toast.Duration int duration) {
- showToastWhereUidIsRunning(uid, mContext.getString(resId), duration);
+ void showToastWhereUidIsRunning(int uid, @StringRes int resId, @Toast.Duration int duration,
+ Looper looper) {
+ showToastWhereUidIsRunning(uid, mContext.getString(resId), duration, looper);
}
/**
* Shows a toast on virtual displays owned by this device which have a given uid running.
*/
- void showToastWhereUidIsRunning(int uid, String text, @Toast.Duration int duration) {
+ void showToastWhereUidIsRunning(int uid, String text, @Toast.Duration int duration,
+ Looper looper) {
synchronized (mVirtualDeviceLock) {
DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
final int size = mWindowPolicyControllers.size();
@@ -666,7 +685,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
int displayId = mWindowPolicyControllers.keyAt(i);
Display display = displayManager.getDisplay(displayId);
if (display != null && display.isValid()) {
- Toast.makeText(mContext.createDisplayContext(display), text,
+ Toast.makeText(mContext.createDisplayContext(display), looper, text,
duration).show();
}
}
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 b25518875cf0..cdddc1dea82b 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -203,7 +203,7 @@ public class VirtualDeviceManagerService extends SystemService {
getContext().getString(
com.android.internal.R.string.vdm_camera_access_denied,
deviceName),
- Toast.LENGTH_LONG);
+ Toast.LENGTH_LONG, Looper.myLooper());
}
}
}
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index dac23a775d56..3d8dc148b7e2 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -975,11 +975,6 @@ public abstract class PackageManagerInternal {
*/
public abstract void setEnableRollbackCode(int token, int enableRollbackCode);
- /**
- * Ask the package manager to compile layouts in the given package.
- */
- public abstract boolean compileLayouts(String packageName);
-
/*
* Inform the package manager that the pending package install identified by
* {@code token} can be completed.
diff --git a/services/core/java/com/android/server/DropBoxManagerService.java b/services/core/java/com/android/server/DropBoxManagerService.java
index 1d457aa933d7..02c6ca20d34e 100644
--- a/services/core/java/com/android/server/DropBoxManagerService.java
+++ b/services/core/java/com/android/server/DropBoxManagerService.java
@@ -479,6 +479,10 @@ public final class DropBoxManagerService extends SystemService {
if (length > max) {
// Log and fall through to create empty tombstone below
Slog.w(TAG, "Dropping: " + tag + " (" + length + " > " + max + " bytes)");
+ logDropboxDropped(
+ FrameworkStatsLog.DROPBOX_ENTRY_DROPPED__DROP_REASON__ENTRY_TOO_LARGE,
+ tag,
+ 0);
} else {
temp = new File(mDropBoxDir, "drop" + Thread.currentThread().getId() + ".tmp");
try (FileOutputStream out = new FileOutputStream(temp)) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index e46639b83996..c8519c104222 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -459,6 +459,7 @@ import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.BiFunction;
public class ActivityManagerService extends IActivityManager.Stub
implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback, ActivityManagerGlobalLock {
@@ -4304,7 +4305,8 @@ public class ActivityManagerService extends IActivityManager.Stub
null /* excludedPackages */, OP_NONE, null /* bOptions */, false /* ordered */,
false /* sticky */, MY_PID, SYSTEM_UID, Binder.getCallingUid(),
Binder.getCallingPid(), userId, false /* allowBackgroundActivityStarts */,
- null /* backgroundActivityStartsToken */, broadcastAllowList);
+ null /* backgroundActivityStartsToken */,
+ broadcastAllowList, null /* filterExtrasForReceiver */);
}
private void cleanupDisabledPackageComponentsLocked(
@@ -13357,7 +13359,8 @@ public class ActivityManagerService extends IActivityManager.Stub
BroadcastRecord r = new BroadcastRecord(queue, intent, null,
null, null, -1, -1, false, null, null, null, null, OP_NONE, null,
receivers, null, 0, null, null, false, true, true, -1, false, null,
- false /* only PRE_BOOT_COMPLETED should be exempt, no stickies */);
+ false /* only PRE_BOOT_COMPLETED should be exempt, no stickies */,
+ null /* filterExtrasForReceiver */);
queue.enqueueParallelBroadcastLocked(r);
queue.scheduleBroadcastsLocked();
}
@@ -13620,7 +13623,8 @@ public class ActivityManagerService extends IActivityManager.Stub
excludedPermissions, excludedPackages, appOp, bOptions, ordered, sticky, callingPid,
callingUid, realCallingUid, realCallingPid, userId,
false /* allowBackgroundActivityStarts */,
- null /* tokenNeededForBackgroundActivityStarts */, null /* broadcastAllowList */);
+ null /* tokenNeededForBackgroundActivityStarts */,
+ null /* broadcastAllowList */, null /* filterExtrasForReceiver */);
}
@GuardedBy("this")
@@ -13633,7 +13637,8 @@ public class ActivityManagerService extends IActivityManager.Stub
int realCallingUid, int realCallingPid, int userId,
boolean allowBackgroundActivityStarts,
@Nullable IBinder backgroundActivityStartsToken,
- @Nullable int[] broadcastAllowList) {
+ @Nullable int[] broadcastAllowList,
+ @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver) {
intent = new Intent(intent);
final boolean callerInstantApp = isInstantApp(callerApp, callerPackage, callingUid);
@@ -14233,7 +14238,7 @@ public class ActivityManagerService extends IActivityManager.Stub
requiredPermissions, excludedPermissions, excludedPackages, appOp, brOptions,
registeredReceivers, resultTo, resultCode, resultData, resultExtras, ordered,
sticky, false, userId, allowBackgroundActivityStarts,
- backgroundActivityStartsToken, timeoutExempt);
+ backgroundActivityStartsToken, timeoutExempt, filterExtrasForReceiver);
if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Enqueueing parallel broadcast " + r);
final boolean replaced = replacePending
&& (queue.replaceParallelBroadcastLocked(r) != null);
@@ -14331,7 +14336,7 @@ public class ActivityManagerService extends IActivityManager.Stub
requiredPermissions, excludedPermissions, excludedPackages, appOp, brOptions,
receivers, resultTo, resultCode, resultData, resultExtras,
ordered, sticky, false, userId, allowBackgroundActivityStarts,
- backgroundActivityStartsToken, timeoutExempt);
+ backgroundActivityStartsToken, timeoutExempt, filterExtrasForReceiver);
if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Enqueueing ordered broadcast " + r);
@@ -14523,7 +14528,8 @@ public class ActivityManagerService extends IActivityManager.Stub
resultTo, resultCode, resultData, resultExtras, requiredPermissions, null,
null, OP_NONE, bOptions, serialized, sticky, -1, uid, realCallingUid,
realCallingPid, userId, allowBackgroundActivityStarts,
- backgroundActivityStartsToken, broadcastAllowList);
+ backgroundActivityStartsToken, broadcastAllowList,
+ null /* filterExtrasForReceiver */);
} finally {
Binder.restoreCallingIdentity(origId);
}
@@ -16201,6 +16207,13 @@ public class ActivityManagerService extends IActivityManager.Stub
return mUserController.startUser(userId, /* foreground */ true, unlockListener);
}
+ @Override
+ public boolean startUserInBackgroundOnSecondaryDisplay(int userId, int displayId) {
+ // Permission check done inside UserController.
+ return mUserController.startUserOnSecondaryDisplay(userId, displayId,
+ /* unlockListener= */ null);
+ }
+
/**
* Unlocks the given user.
*
@@ -17058,7 +17071,9 @@ public class ActivityManagerService extends IActivityManager.Stub
public int broadcastIntent(Intent intent,
IIntentReceiver resultTo,
String[] requiredPermissions,
- boolean serialized, int userId, int[] appIdAllowList, @Nullable Bundle bOptions) {
+ boolean serialized, int userId, int[] appIdAllowList,
+ @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
+ @Nullable Bundle bOptions) {
synchronized (ActivityManagerService.this) {
intent = verifyBroadcastLocked(intent);
@@ -17074,7 +17089,8 @@ public class ActivityManagerService extends IActivityManager.Stub
AppOpsManager.OP_NONE, bOptions /*options*/, serialized,
false /*sticky*/, callingPid, callingUid, callingUid, callingPid,
userId, false /*allowBackgroundStarts*/,
- null /*tokenNeededForBackgroundActivityStarts*/, appIdAllowList);
+ null /*tokenNeededForBackgroundActivityStarts*/,
+ appIdAllowList, filterExtrasForReceiver);
} finally {
Binder.restoreCallingIdentity(origId);
}
@@ -17507,7 +17523,8 @@ public class ActivityManagerService extends IActivityManager.Stub
// sends to the activity. After this race issue between WM/ATMS and AMS is solved, this
// workaround can be removed. (b/213288355)
if (isNewPending) {
- mOomAdjuster.mCachedAppOptimizer.unfreezeProcess(pid);
+ mOomAdjuster.mCachedAppOptimizer.unfreezeProcess(pid,
+ OomAdjuster.OOM_ADJ_REASON_ACTIVITY);
}
// We need to update the network rules for the app coming to the top state so that
// it can access network when the device or the app is in a restricted state
@@ -18397,11 +18414,11 @@ public class ActivityManagerService extends IActivityManager.Stub
attributionSource.getUid()), Process.SHELL_UID);
final long identity = Binder.clearCallingIdentity();
try {
- return superImpl.apply(code, new AttributionSource(shellUid,
+ return superImpl.apply(code, new AttributionSource(shellUid, /*pid*/ -1,
"com.android.shell", attributionSource.getAttributionTag(),
- attributionSource.getToken(), attributionSource.getNext()),
- shouldCollectAsyncNotedOp, message, shouldCollectMessage,
- skiProxyOperation);
+ attributionSource.getToken(), /*renouncedPermissions*/ null,
+ attributionSource.getNext()), shouldCollectAsyncNotedOp, message,
+ shouldCollectMessage, skiProxyOperation);
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -18448,12 +18465,13 @@ public class ActivityManagerService extends IActivityManager.Stub
attributionSource.getUid()), Process.SHELL_UID);
final long identity = Binder.clearCallingIdentity();
try {
- return superImpl.apply(code, new AttributionSource(shellUid,
+ return superImpl.apply(code, new AttributionSource(shellUid, /*pid*/ -1,
"com.android.shell", attributionSource.getAttributionTag(),
- attributionSource.getToken(), attributionSource.getNext()),
- startIfModeDefault, shouldCollectAsyncNotedOp, message,
- shouldCollectMessage, skipProxyOperation, proxyAttributionFlags,
- proxiedAttributionFlags, attributionChainId);
+ attributionSource.getToken(), /*renouncedPermissions*/ null,
+ attributionSource.getNext()), startIfModeDefault,
+ shouldCollectAsyncNotedOp, message, shouldCollectMessage,
+ skipProxyOperation, proxyAttributionFlags, proxiedAttributionFlags,
+ attributionChainId);
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -18472,10 +18490,10 @@ public class ActivityManagerService extends IActivityManager.Stub
attributionSource.getUid()), Process.SHELL_UID);
final long identity = Binder.clearCallingIdentity();
try {
- superImpl.apply(code, new AttributionSource(shellUid,
+ superImpl.apply(code, new AttributionSource(shellUid, /*pid*/ -1,
"com.android.shell", attributionSource.getAttributionTag(),
- attributionSource.getToken(), attributionSource.getNext()),
- skipProxyOperation);
+ attributionSource.getToken(), /*renouncedPermissions*/ null,
+ attributionSource.getNext()), skipProxyOperation);
} finally {
Binder.restoreCallingIdentity(identity);
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 4bbfa3eec67d..5cb25d36d94b 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -892,6 +892,7 @@ final class ActivityManagerShellCommand extends ShellCommand {
}
}
+ // TODO(b/239982558): might need to support --displayId as well
private int runProfile(PrintWriter pw) throws RemoteException {
final PrintWriter err = getErrPrintWriter();
String profileFile = null;
@@ -2034,26 +2035,42 @@ final class ActivityManagerShellCommand extends ShellCommand {
int runStartUser(PrintWriter pw) throws RemoteException {
boolean wait = false;
String opt;
+ int displayId = Display.INVALID_DISPLAY;
while ((opt = getNextOption()) != null) {
- if ("-w".equals(opt)) {
- wait = true;
- } else {
- getErrPrintWriter().println("Error: unknown option: " + opt);
- return -1;
+ switch(opt) {
+ case "-w":
+ wait = true;
+ break;
+ case "--display":
+ displayId = getDisplayIdFromNextArg();
+ break;
+ default:
+ getErrPrintWriter().println("Error: unknown option: " + opt);
+ return -1;
}
}
int userId = Integer.parseInt(getNextArgRequired());
final ProgressWaiter waiter = wait ? new ProgressWaiter() : null;
- boolean success = mInterface.startUserInBackgroundWithListener(userId, waiter);
+
+ boolean success;
+ String displaySuffix;
+
+ if (displayId == Display.INVALID_DISPLAY) {
+ success = mInterface.startUserInBackgroundWithListener(userId, waiter);
+ displaySuffix = "";
+ } else {
+ success = mInterface.startUserInBackgroundOnSecondaryDisplay(userId, displayId);
+ displaySuffix = " on display " + displayId;
+ }
if (wait && success) {
success = waiter.waitForFinish(USER_OPERATION_TIMEOUT_MS);
}
if (success) {
- pw.println("Success: user started");
+ pw.println("Success: user started" + displaySuffix);
} else {
- getErrPrintWriter().println("Error: could not start user");
+ getErrPrintWriter().println("Error: could not start user" + displaySuffix);
}
return 0;
}
@@ -2506,6 +2523,14 @@ final class ActivityManagerShellCommand extends ShellCommand {
}
}
+ private int getDisplayIdFromNextArg() {
+ int displayId = Integer.parseInt(getNextArgRequired());
+ if (displayId < 0) {
+ throw new IllegalArgumentException("--display must be a non-negative integer");
+ }
+ return displayId;
+ }
+
int runGetConfig(PrintWriter pw) throws RemoteException {
int days = -1;
int displayId = Display.DEFAULT_DISPLAY;
@@ -2524,10 +2549,7 @@ final class ActivityManagerShellCommand extends ShellCommand {
} else if (opt.equals("--device")) {
inclDevice = true;
} else if (opt.equals("--display")) {
- displayId = Integer.parseInt(getNextArgRequired());
- if (displayId < 0) {
- throw new IllegalArgumentException("--display must be a non-negative integer");
- }
+ displayId = getDisplayIdFromNextArg();
} else {
getErrPrintWriter().println("Error: Unknown option: " + opt);
return -1;
@@ -3714,10 +3736,13 @@ final class ActivityManagerShellCommand extends ShellCommand {
pw.println(" execution of that user if it is currently stopped.");
pw.println(" get-current-user");
pw.println(" Returns id of the current foreground user.");
- pw.println(" start-user [-w] <USER_ID>");
+ pw.println(" start-user [-w] [--display DISPLAY_ID] <USER_ID>");
pw.println(" Start USER_ID in background if it is currently stopped;");
pw.println(" use switch-user if you want to start the user in foreground.");
pw.println(" -w: wait for start-user to complete and the user to be unlocked.");
+ pw.println(" --display <DISPLAY_ID>: allows the user to launch activities in the");
+ pw.println(" given display, when supported (typically on automotive builds");
+ pw.println(" wherethe vehicle has multiple displays)");
pw.println(" unlock-user <USER_ID>");
pw.println(" Unlock the given user. This will only work if the user doesn't");
pw.println(" have an LSKF (PIN/pattern/password).");
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index aaaacef33696..31d9f96d46e4 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -36,6 +36,8 @@ import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PERMISSIONS_REVIEW;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BROADCAST;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_MU;
+import static com.android.server.am.OomAdjuster.OOM_ADJ_REASON_FINISH_RECEIVER;
+import static com.android.server.am.OomAdjuster.OOM_ADJ_REASON_START_RECEIVER;
import android.annotation.NonNull;
@@ -348,7 +350,7 @@ public final class BroadcastQueue {
// Force an update, even if there are other pending requests, overall it still saves time,
// because time(updateOomAdj(N apps)) <= N * time(updateOomAdj(1 app)).
mService.enqueueOomAdjTargetLocked(app);
- mService.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_START_RECEIVER);
+ mService.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_RECEIVER);
// Tell the application to launch this receiver.
maybeReportBroadcastDispatchedEventLocked(r, r.curReceiver.applicationInfo.uid);
@@ -361,8 +363,8 @@ public final class BroadcastQueue {
+ ": " + r);
mService.notifyPackageUse(r.intent.getComponent().getPackageName(),
PackageManager.NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER);
- thread.scheduleReceiver(new Intent(r.intent), r.curReceiver,
- null /* compatInfo (unused but need to keep method signature) */,
+ thread.scheduleReceiver(prepareReceiverIntent(r.intent, r.curFilteredExtras),
+ r.curReceiver, null /* compatInfo (unused but need to keep method signature) */,
r.resultCode, r.resultData, r.resultExtras, r.ordered, r.userId,
app.mState.getReportedProcState());
if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
@@ -590,6 +592,7 @@ public final class BroadcastQueue {
r.curFilter = null;
r.curReceiver = null;
r.curApp = null;
+ r.curFilteredExtras = null;
mPendingBroadcast = null;
r.resultCode = resultCode;
@@ -941,6 +944,24 @@ public final class BroadcastQueue {
skip = true;
}
+ // Filter packages in the intent extras, skipping delivery if none of the packages is
+ // visible to the receiver.
+ Bundle filteredExtras = null;
+ if (!skip && r.filterExtrasForReceiver != null) {
+ final Bundle extras = r.intent.getExtras();
+ if (extras != null) {
+ filteredExtras = r.filterExtrasForReceiver.apply(filter.receiverList.uid, extras);
+ if (filteredExtras == null) {
+ if (DEBUG_BROADCAST) {
+ Slog.v(TAG, "Skipping delivery to "
+ + filter.receiverList.app
+ + " : receiver is filtered by the package visibility");
+ }
+ skip = true;
+ }
+ }
+ }
+
if (skip) {
r.delivery[index] = BroadcastRecord.DELIVERY_SKIPPED;
return;
@@ -976,10 +997,11 @@ public final class BroadcastQueue {
filter.receiverList.app.mReceivers.addCurReceiver(r);
mService.enqueueOomAdjTargetLocked(r.curApp);
mService.updateOomAdjPendingTargetsLocked(
- OomAdjuster.OOM_ADJ_REASON_START_RECEIVER);
+ OOM_ADJ_REASON_START_RECEIVER);
}
} else if (filter.receiverList.app != null) {
- mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(filter.receiverList.app);
+ mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(filter.receiverList.app,
+ OOM_ADJ_REASON_START_RECEIVER);
}
try {
@@ -997,7 +1019,7 @@ public final class BroadcastQueue {
maybeScheduleTempAllowlistLocked(filter.owningUid, r, r.options);
maybeReportBroadcastDispatchedEventLocked(r, filter.owningUid);
performReceiveLocked(filter.receiverList.app, filter.receiverList.receiver,
- new Intent(r.intent), r.resultCode, r.resultData,
+ prepareReceiverIntent(r.intent, filteredExtras), r.resultCode, r.resultData,
r.resultExtras, r.ordered, r.initialSticky, r.userId,
filter.receiverList.uid, r.callingUid,
r.dispatchTime - r.enqueueTime,
@@ -1164,6 +1186,15 @@ public final class BroadcastQueue {
+ ", uid=" + r.callingUid + ") to " + component.flattenToShortString();
}
+ private static Intent prepareReceiverIntent(@NonNull Intent originalIntent,
+ @Nullable Bundle filteredExtras) {
+ final Intent intent = new Intent(originalIntent);
+ if (filteredExtras != null) {
+ intent.replaceExtras(filteredExtras);
+ }
+ return intent;
+ }
+
final void processNextBroadcastLocked(boolean fromMsg, boolean skipOomAdj) {
BroadcastRecord r;
@@ -1263,7 +1294,7 @@ public final class BroadcastQueue {
// make sure all processes have correct oom and sched
// adjustments.
mService.updateOomAdjPendingTargetsLocked(
- OomAdjuster.OOM_ADJ_REASON_START_RECEIVER);
+ OOM_ADJ_REASON_START_RECEIVER);
}
// when we have no more ordered broadcast on this queue, stop logging
@@ -1345,7 +1376,7 @@ public final class BroadcastQueue {
if (sendResult) {
if (r.callerApp != null) {
mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(
- r.callerApp);
+ r.callerApp, OOM_ADJ_REASON_FINISH_RECEIVER);
}
try {
if (DEBUG_BROADCAST) {
@@ -1834,6 +1865,24 @@ public final class BroadcastQueue {
}
}
+ // Filter packages in the intent extras, skipping delivery if none of the packages is
+ // visible to the receiver.
+ Bundle filteredExtras = null;
+ if (!skip && r.filterExtrasForReceiver != null) {
+ final Bundle extras = r.intent.getExtras();
+ if (extras != null) {
+ filteredExtras = r.filterExtrasForReceiver.apply(receiverUid, extras);
+ if (filteredExtras == null) {
+ if (DEBUG_BROADCAST) {
+ Slog.v(TAG, "Skipping delivery to "
+ + info.activityInfo.packageName + " / " + receiverUid
+ + " : receiver is filtered by the package visibility");
+ }
+ skip = true;
+ }
+ }
+ }
+
if (skip) {
if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
"Skipping delivery of ordered [" + mQueueName + "] "
@@ -1852,6 +1901,7 @@ public final class BroadcastQueue {
r.state = BroadcastRecord.APP_RECEIVE;
r.curComponent = component;
r.curReceiver = info.activityInfo;
+ r.curFilteredExtras = filteredExtras;
if (DEBUG_MU && r.callingUid > UserHandle.PER_USER_RANGE) {
Slog.v(TAG_MU, "Updated broadcast record activity info for secondary user, "
+ info.activityInfo + ", callingUid = " + r.callingUid + ", uid = "
@@ -1919,8 +1969,7 @@ public final class BroadcastQueue {
info.activityInfo.applicationInfo, true,
r.intent.getFlags() | Intent.FLAG_FROM_BACKGROUND,
new HostingRecord(HostingRecord.HOSTING_TYPE_BROADCAST, r.curComponent,
- r.intent.getAction(), (r.alarm ? HostingRecord.TRIGGER_TYPE_ALARM
- : HostingRecord.TRIGGER_TYPE_UNKNOWN)),
+ r.intent.getAction(), getHostingRecordTriggerType(r)),
isActivityCapable ? ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE : ZYGOTE_POLICY_FLAG_EMPTY,
(r.intent.getFlags() & Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0, false);
if (r.curApp == null) {
@@ -1943,6 +1992,16 @@ public final class BroadcastQueue {
mPendingBroadcastRecvIndex = recIdx;
}
+ private String getHostingRecordTriggerType(BroadcastRecord r) {
+ if (r.alarm) {
+ return HostingRecord.TRIGGER_TYPE_ALARM;
+ } else if (r.pushMessage) {
+ return HostingRecord.TRIGGER_TYPE_PUSH_MESSAGE;
+ } else if (r.pushMessageOverQuota) {
+ return HostingRecord.TRIGGER_TYPE_PUSH_MESSAGE_OVER_QUOTA;
+ }
+ return HostingRecord.TRIGGER_TYPE_UNKNOWN;
+ }
@Nullable
private String getTargetPackage(BroadcastRecord r) {
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index ce4528bca887..18fbfdeb832c 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -55,6 +55,7 @@ import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.BiFunction;
/**
* An active intent broadcast.
@@ -71,6 +72,8 @@ final class BroadcastRecord extends Binder {
final boolean ordered; // serialize the send to receivers?
final boolean sticky; // originated from existing sticky data?
final boolean alarm; // originated from an alarm triggering?
+ final boolean pushMessage; // originated from a push message?
+ final boolean pushMessageOverQuota; // originated from a push message which was over quota?
final boolean initialSticky; // initial broadcast from register to sticky?
final int userId; // user id this broadcast was for
final String resolvedType; // the resolved data type
@@ -114,6 +117,11 @@ final class BroadcastRecord extends Binder {
@Nullable
final IBinder mBackgroundActivityStartsToken;
+ // Filter the intent extras by using the rules of the package visibility before broadcasting
+ // the intent to the receiver.
+ @Nullable
+ final BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver;
+
static final int IDLE = 0;
static final int APP_RECEIVE = 1;
static final int CALL_IN_RECEIVE = 2;
@@ -134,6 +142,7 @@ final class BroadcastRecord extends Binder {
ProcessRecord curApp; // hosting application of current receiver.
ComponentName curComponent; // the receiver class that is currently running.
ActivityInfo curReceiver; // info about the receiver that is currently running.
+ Bundle curFilteredExtras; // the bundle that has been filtered by the package visibility rules
boolean mIsReceiverAppRunning; // Was the receiver's app already running.
@@ -227,6 +236,9 @@ final class BroadcastRecord extends Binder {
pw.println(curReceiver.applicationInfo.sourceDir);
}
}
+ if (curFilteredExtras != null) {
+ pw.print(" filtered extras: "); pw.println(curFilteredExtras);
+ }
if (state != IDLE) {
String stateStr = " (?)";
switch (state) {
@@ -273,7 +285,8 @@ final class BroadcastRecord extends Binder {
BroadcastOptions _options, List _receivers, IIntentReceiver _resultTo, int _resultCode,
String _resultData, Bundle _resultExtras, boolean _serialized, boolean _sticky,
boolean _initialSticky, int _userId, boolean allowBackgroundActivityStarts,
- @Nullable IBinder backgroundActivityStartsToken, boolean timeoutExempt) {
+ @Nullable IBinder backgroundActivityStartsToken, boolean timeoutExempt,
+ @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver) {
if (_intent == null) {
throw new NullPointerException("Can't construct with a null intent");
}
@@ -309,6 +322,9 @@ final class BroadcastRecord extends Binder {
mBackgroundActivityStartsToken = backgroundActivityStartsToken;
this.timeoutExempt = timeoutExempt;
alarm = options != null && options.isAlarmBroadcast();
+ pushMessage = options != null && options.isPushMessagingBroadcast();
+ pushMessageOverQuota = options != null && options.isPushMessagingOverQuotaBroadcast();
+ this.filterExtrasForReceiver = filterExtrasForReceiver;
}
/**
@@ -362,6 +378,9 @@ final class BroadcastRecord extends Binder {
mBackgroundActivityStartsToken = from.mBackgroundActivityStartsToken;
timeoutExempt = from.timeoutExempt;
alarm = from.alarm;
+ pushMessage = from.pushMessage;
+ pushMessageOverQuota = from.pushMessageOverQuota;
+ filterExtrasForReceiver = from.filterExtrasForReceiver;
}
/**
@@ -397,7 +416,7 @@ final class BroadcastRecord extends Binder {
requiredPermissions, excludedPermissions, excludedPackages, appOp, options,
splitReceivers, resultTo, resultCode, resultData, resultExtras, ordered, sticky,
initialSticky, userId, allowBackgroundActivityStarts,
- mBackgroundActivityStartsToken, timeoutExempt);
+ mBackgroundActivityStartsToken, timeoutExempt, filterExtrasForReceiver);
split.enqueueTime = this.enqueueTime;
split.enqueueRealTime = this.enqueueRealTime;
split.enqueueClockTime = this.enqueueClockTime;
@@ -476,7 +495,8 @@ final class BroadcastRecord extends Binder {
requiredPermissions, excludedPermissions, excludedPackages, appOp, options,
uid2receiverList.valueAt(i), null /* _resultTo */,
resultCode, resultData, resultExtras, ordered, sticky, initialSticky, userId,
- allowBackgroundActivityStarts, mBackgroundActivityStartsToken, timeoutExempt);
+ allowBackgroundActivityStarts, mBackgroundActivityStartsToken, timeoutExempt,
+ filterExtrasForReceiver);
br.enqueueTime = this.enqueueTime;
br.enqueueRealTime = this.enqueueRealTime;
br.enqueueClockTime = this.enqueueClockTime;
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index 363c9d0a963a..653b6026944b 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -37,6 +37,7 @@ import android.provider.DeviceConfig.Properties;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.EventLog;
+import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
@@ -904,7 +905,7 @@ public final class CachedAppOptimizer {
}
if (!enable && opt.isFrozen()) {
- unfreezeAppLSP(process);
+ unfreezeAppLSP(process, OomAdjuster.OOM_ADJ_REASON_NONE);
// Set freezerOverride *after* calling unfreezeAppLSP (it resets the flag)
opt.setFreezerOverride(true);
@@ -1214,11 +1215,11 @@ public final class CachedAppOptimizer {
// This will ensure app will be out of the freezer for at least mFreezerDebounceTimeout.
@GuardedBy("mAm")
- void unfreezeTemporarily(ProcessRecord app) {
+ void unfreezeTemporarily(ProcessRecord app, @OomAdjuster.OomAdjReason int reason) {
if (mUseFreezer) {
synchronized (mProcLock) {
if (app.mOptRecord.isFrozen() || app.mOptRecord.isPendingFreeze()) {
- unfreezeAppLSP(app);
+ unfreezeAppLSP(app, reason);
freezeAppAsyncLSP(app);
}
}
@@ -1244,7 +1245,7 @@ public final class CachedAppOptimizer {
}
@GuardedBy({"mAm", "mProcLock", "mFreezerLock"})
- void unfreezeAppInternalLSP(ProcessRecord app) {
+ void unfreezeAppInternalLSP(ProcessRecord app, @OomAdjuster.OomAdjReason int reason) {
final int pid = app.getPid();
final ProcessCachedOptimizerRecord opt = app.mOptRecord;
if (opt.isPendingFreeze()) {
@@ -1325,14 +1326,14 @@ public final class CachedAppOptimizer {
mFreezeHandler.obtainMessage(REPORT_UNFREEZE_MSG,
pid,
(int) Math.min(opt.getFreezeUnfreezeTime() - freezeTime, Integer.MAX_VALUE),
- app.processName));
+ new Pair<String, Integer>(app.processName, reason)));
}
}
@GuardedBy({"mAm", "mProcLock"})
- void unfreezeAppLSP(ProcessRecord app) {
+ void unfreezeAppLSP(ProcessRecord app, @OomAdjuster.OomAdjReason int reason) {
synchronized (mFreezerLock) {
- unfreezeAppInternalLSP(app);
+ unfreezeAppInternalLSP(app, reason);
}
}
@@ -1343,25 +1344,14 @@ public final class CachedAppOptimizer {
* The caller of this function should still trigger updateOomAdj for AMS to unfreeze the app.
* @param pid pid of the process to be unfrozen
*/
- void unfreezeProcess(int pid) {
+ void unfreezeProcess(int pid, @OomAdjuster.OomAdjReason int reason) {
synchronized (mFreezerLock) {
ProcessRecord app = mFrozenProcesses.get(pid);
if (app == null) {
return;
}
Slog.d(TAG_AM, "quick sync unfreeze " + pid);
- try {
- freezeBinder(pid, false);
- } catch (RuntimeException e) {
- Slog.e(TAG_AM, "Unable to quick unfreeze binder for " + pid);
- return;
- }
-
- try {
- Process.setProcessFrozen(pid, app.uid, false);
- } catch (Exception e) {
- Slog.e(TAG_AM, "Unable to quick unfreeze " + pid);
- }
+ unfreezeAppLSP(app, reason);
}
}
@@ -1880,9 +1870,11 @@ public final class CachedAppOptimizer {
case REPORT_UNFREEZE_MSG:
int pid = msg.arg1;
int frozenDuration = msg.arg2;
- String processName = (String) msg.obj;
+ Pair<String, Integer> obj = (Pair<String, Integer>) msg.obj;
+ String processName = obj.first;
+ int reason = obj.second;
- reportUnfreeze(pid, frozenDuration, processName);
+ reportUnfreeze(pid, frozenDuration, processName, reason);
break;
default:
return;
@@ -1893,7 +1885,7 @@ public final class CachedAppOptimizer {
private void rescheduleFreeze(final ProcessRecord proc, final String reason) {
Slog.d(TAG_AM, "Reschedule freeze for process " + proc.getPid()
+ " " + proc.processName + " (" + reason + ")");
- unfreezeAppLSP(proc);
+ unfreezeAppLSP(proc, OomAdjuster.OOM_ADJ_REASON_NONE);
freezeAppAsyncLSP(proc);
}
@@ -1981,7 +1973,8 @@ public final class CachedAppOptimizer {
FrameworkStatsLog.APP_FREEZE_CHANGED__ACTION__FREEZE_APP,
pid,
name,
- unfrozenDuration);
+ unfrozenDuration,
+ FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON__NONE);
}
try {
@@ -2011,12 +2004,13 @@ public final class CachedAppOptimizer {
} catch (Exception e) {
Slog.e(TAG_AM, "Unable to check file locks for " + name + "(" + pid + "): " + e);
synchronized (mProcLock) {
- unfreezeAppLSP(proc);
+ unfreezeAppLSP(proc, OomAdjuster.OOM_ADJ_REASON_NONE);
}
}
}
- private void reportUnfreeze(int pid, int frozenDuration, String processName) {
+ private void reportUnfreeze(int pid, int frozenDuration, String processName,
+ @OomAdjuster.OomAdjReason int reason) {
EventLog.writeEvent(EventLogTags.AM_UNFREEZE, pid, processName);
@@ -2027,7 +2021,39 @@ public final class CachedAppOptimizer {
FrameworkStatsLog.APP_FREEZE_CHANGED__ACTION__UNFREEZE_APP,
pid,
processName,
- frozenDuration);
+ frozenDuration,
+ getUnfreezeReasonCode(reason));
+ }
+ }
+
+ private int getUnfreezeReasonCode(@OomAdjuster.OomAdjReason int oomAdjReason) {
+ switch (oomAdjReason) {
+ case OomAdjuster.OOM_ADJ_REASON_ACTIVITY:
+ return FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON__ACTIVITY;
+ case OomAdjuster.OOM_ADJ_REASON_FINISH_RECEIVER:
+ return FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON__FINISH_RECEIVER;
+ case OomAdjuster.OOM_ADJ_REASON_START_RECEIVER:
+ return FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON__START_RECEIVER;
+ case OomAdjuster.OOM_ADJ_REASON_BIND_SERVICE:
+ return FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON__BIND_SERVICE;
+ case OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE:
+ return FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON__UNBIND_SERVICE;
+ case OomAdjuster.OOM_ADJ_REASON_START_SERVICE:
+ return FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON__START_SERVICE;
+ case OomAdjuster.OOM_ADJ_REASON_GET_PROVIDER:
+ return FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON__GET_PROVIDER;
+ case OomAdjuster.OOM_ADJ_REASON_REMOVE_PROVIDER:
+ return FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON__REMOVE_PROVIDER;
+ case OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY:
+ return FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON__UI_VISIBILITY;
+ case OomAdjuster.OOM_ADJ_REASON_ALLOWLIST:
+ return FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON__ALLOWLIST;
+ case OomAdjuster.OOM_ADJ_REASON_PROCESS_BEGIN:
+ return FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON__PROCESS_BEGIN;
+ case OomAdjuster.OOM_ADJ_REASON_PROCESS_END:
+ return FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON__PROCESS_END;
+ default:
+ return FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON__NONE;
}
}
@@ -2041,7 +2067,7 @@ public final class CachedAppOptimizer {
ProcessRecord app = mFrozenProcesses.get(pid);
if (app != null) {
Slog.i(TAG_AM, app.processName + " (" + pid + ") holds blocking file lock");
- unfreezeAppLSP(app);
+ unfreezeAppLSP(app, OomAdjuster.OOM_ADJ_REASON_NONE);
}
}
}
diff --git a/services/core/java/com/android/server/am/HostingRecord.java b/services/core/java/com/android/server/am/HostingRecord.java
index efc2a2719dad..2498f763ff77 100644
--- a/services/core/java/com/android/server/am/HostingRecord.java
+++ b/services/core/java/com/android/server/am/HostingRecord.java
@@ -30,6 +30,8 @@ import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HO
import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_SERVICE;
import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_SYSTEM;
import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_TOP_ACTIVITY;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_PUSH_MESSAGE;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_PUSH_MESSAGE_OVER_QUOTA;
import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_TYPE_ALARM;
import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_TYPE_UNKNOWN;
import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__TYPE__UNKNOWN;
@@ -93,6 +95,8 @@ public final class HostingRecord {
public static final String TRIGGER_TYPE_UNKNOWN = "unknown";
public static final String TRIGGER_TYPE_ALARM = "alarm";
+ public static final String TRIGGER_TYPE_PUSH_MESSAGE = "push_message";
+ public static final String TRIGGER_TYPE_PUSH_MESSAGE_OVER_QUOTA = "push_message_over_quota";
@NonNull private final String mHostingType;
private final String mHostingName;
@@ -308,6 +312,10 @@ public final class HostingRecord {
switch(triggerType) {
case TRIGGER_TYPE_ALARM:
return PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_TYPE_ALARM;
+ case TRIGGER_TYPE_PUSH_MESSAGE:
+ return PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_PUSH_MESSAGE;
+ case TRIGGER_TYPE_PUSH_MESSAGE_OVER_QUOTA:
+ return PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_PUSH_MESSAGE_OVER_QUOTA;
default:
return PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_TYPE_UNKNOWN;
}
diff --git a/services/core/java/com/android/server/am/LmkdStatsReporter.java b/services/core/java/com/android/server/am/LmkdStatsReporter.java
index 91588913c33c..4380b42ee54c 100644
--- a/services/core/java/com/android/server/am/LmkdStatsReporter.java
+++ b/services/core/java/com/android/server/am/LmkdStatsReporter.java
@@ -50,7 +50,8 @@ public final class LmkdStatsReporter {
* Logs the event when LMKD kills a process to reduce memory pressure.
* Code: LMK_KILL_OCCURRED = 51
*/
- public static void logKillOccurred(DataInputStream inputData) {
+ public static void logKillOccurred(DataInputStream inputData, int totalForegroundServices,
+ int procsWithForegroundServices) {
try {
final long pgFault = inputData.readLong();
final long pgMajFault = inputData.readLong();
@@ -67,11 +68,10 @@ public final class LmkdStatsReporter {
final int thrashing = inputData.readInt();
final int maxThrashing = inputData.readInt();
final String procName = inputData.readUTF();
-
FrameworkStatsLog.write(FrameworkStatsLog.LMK_KILL_OCCURRED, uid, procName, oomScore,
pgFault, pgMajFault, rssInBytes, cacheInBytes, swapInBytes, processStartTimeNS,
minOomScore, freeMemKb, freeSwapKb, mapKillReason(killReason), thrashing,
- maxThrashing);
+ maxThrashing, totalForegroundServices, procsWithForegroundServices);
} catch (IOException e) {
Slog.e(TAG, "Invalid buffer data. Failed to log LMK_KILL_OCCURRED");
return;
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 8759f23065f0..12aa66b84d85 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -448,7 +448,7 @@ public class OomAdjuster {
*/
@GuardedBy({"mService", "mProcLock"})
private boolean performUpdateOomAdjLSP(ProcessRecord app, int cachedAdj,
- ProcessRecord topApp, long now) {
+ ProcessRecord topApp, long now, @OomAdjReason int oomAdjReason) {
if (app.getThread() == null) {
return false;
}
@@ -492,7 +492,7 @@ public class OomAdjuster {
}
}
- return applyOomAdjLSP(app, false, now, SystemClock.elapsedRealtime());
+ return applyOomAdjLSP(app, false, now, SystemClock.elapsedRealtime(), oomAdjReason);
}
/**
@@ -592,7 +592,7 @@ public class OomAdjuster {
mPendingProcessSet.remove(app);
app.mOptRecord.setLastOomAdjChangeReason(oomAdjReason);
boolean success = performUpdateOomAdjLSP(app, cachedAdj, topApp,
- SystemClock.uptimeMillis());
+ SystemClock.uptimeMillis(), oomAdjReason);
// The 'app' here itself might or might not be in the cycle, for example,
// the case A <=> B vs. A -> B <=> C; anyway, if we spot a cycle here, re-compute them.
if (!success || (wasCached == state.isCached() && oldAdj != ProcessList.INVALID_ADJ
@@ -645,7 +645,7 @@ public class OomAdjuster {
processes.add(app);
assignCachedAdjIfNecessary(processes);
applyOomAdjLSP(app, false, SystemClock.uptimeMillis(),
- SystemClock.elapsedRealtime());
+ SystemClock.elapsedRealtime(), oomAdjReason);
}
mTmpProcessList.clear();
mService.mOomAdjProfiler.oomAdjEnded();
@@ -941,7 +941,8 @@ public class OomAdjuster {
mNumNonCachedProcs = 0;
mNumCachedHiddenProcs = 0;
- boolean allChanged = updateAndTrimProcessLSP(now, nowElapsed, oldTime, activeUids);
+ boolean allChanged = updateAndTrimProcessLSP(now, nowElapsed, oldTime, activeUids,
+ oomAdjReason);
mNumServiceProcs = mNewNumServiceProcs;
if (mService.mAlwaysFinishActivities) {
@@ -1119,7 +1120,7 @@ public class OomAdjuster {
@GuardedBy({"mService", "mProcLock"})
private boolean updateAndTrimProcessLSP(final long now, final long nowElapsed,
- final long oldTime, final ActiveUids activeUids) {
+ final long oldTime, final ActiveUids activeUids, @OomAdjReason int oomAdjReason) {
ArrayList<ProcessRecord> lruList = mProcessList.getLruProcessesLOSP();
final int numLru = lruList.size();
@@ -1147,7 +1148,7 @@ public class OomAdjuster {
if (!app.isKilledByAm() && app.getThread() != null) {
// We don't need to apply the update for the process which didn't get computed
if (state.getCompletedAdjSeq() == mAdjSeq) {
- applyOomAdjLSP(app, true, now, nowElapsed);
+ applyOomAdjLSP(app, true, now, nowElapsed, oomAdjReason);
}
final ProcessServiceRecord psr = app.mServices;
@@ -2640,7 +2641,7 @@ public class OomAdjuster {
/** Applies the computed oomadj, procstate and sched group values and freezes them in set* */
@GuardedBy({"mService", "mProcLock"})
private boolean applyOomAdjLSP(ProcessRecord app, boolean doingAll, long now,
- long nowElapsed) {
+ long nowElapsed, @OomAdjReason int oomAdjReson) {
boolean success = true;
final ProcessStateRecord state = app.mState;
final UidRecord uidRec = app.getUidRecord();
@@ -2799,7 +2800,7 @@ public class OomAdjuster {
changes |= ActivityManagerService.ProcessChangeItem.CHANGE_ACTIVITIES;
}
- updateAppFreezeStateLSP(app);
+ updateAppFreezeStateLSP(app, oomAdjReson);
if (state.getReportedProcState() != state.getCurProcState()) {
state.setReportedProcState(state.getCurProcState());
@@ -3160,7 +3161,7 @@ public class OomAdjuster {
}
@GuardedBy({"mService", "mProcLock"})
- private void updateAppFreezeStateLSP(ProcessRecord app) {
+ private void updateAppFreezeStateLSP(ProcessRecord app, @OomAdjReason int oomAdjReason) {
if (!mCachedAppOptimizer.useFreezer()) {
return;
}
@@ -3172,7 +3173,7 @@ public class OomAdjuster {
final ProcessCachedOptimizerRecord opt = app.mOptRecord;
// if an app is already frozen and shouldNotFreeze becomes true, immediately unfreeze
if (opt.isFrozen() && opt.shouldNotFreeze()) {
- mCachedAppOptimizer.unfreezeAppLSP(app);
+ mCachedAppOptimizer.unfreezeAppLSP(app, oomAdjReason);
return;
}
@@ -3182,7 +3183,7 @@ public class OomAdjuster {
&& !opt.shouldNotFreeze()) {
mCachedAppOptimizer.freezeAppAsyncLSP(app);
} else if (state.getSetAdj() < ProcessList.CACHED_APP_MIN_ADJ) {
- mCachedAppOptimizer.unfreezeAppLSP(app);
+ mCachedAppOptimizer.unfreezeAppLSP(app, oomAdjReason);
}
}
}
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 42792bf64f44..ccbca76d1868 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -814,7 +814,12 @@ public final class ProcessList {
< LmkdStatsReporter.KILL_OCCURRED_MSG_SIZE) {
return false;
}
- LmkdStatsReporter.logKillOccurred(inputData);
+ Pair<Integer, Integer> temp = getNumForegroundServices();
+ final int totalForegroundServices = temp.first;
+ final int procsWithForegroundServices = temp.second;
+ LmkdStatsReporter.logKillOccurred(inputData,
+ totalForegroundServices,
+ procsWithForegroundServices);
return true;
case LMK_STATE_CHANGED:
if (receivedLen
@@ -5123,6 +5128,26 @@ public final class ProcessList {
}
}
+ /**
+ * Get the number of foreground services in all processes and number of processes that have
+ * foreground service within.
+ */
+ Pair<Integer, Integer> getNumForegroundServices() {
+ int numForegroundServices = 0;
+ int procs = 0;
+ synchronized (mService) {
+ for (int i = 0, size = mLruProcesses.size(); i < size; i++) {
+ ProcessRecord pr = mLruProcesses.get(i);
+ int numFgs = pr.mServices.getNumForegroundServices();
+ if (numFgs > 0) {
+ numForegroundServices += numFgs;
+ procs++;
+ }
+ }
+ }
+ return new Pair<>(numForegroundServices, procs);
+ }
+
private final class ImperceptibleKillRunner extends IUidObserver.Stub {
private static final String EXTRA_PID = "pid";
private static final String EXTRA_UID = "uid";
diff --git a/services/core/java/com/android/server/am/ProcessServiceRecord.java b/services/core/java/com/android/server/am/ProcessServiceRecord.java
index 9951e983a752..67eb675503ad 100644
--- a/services/core/java/com/android/server/am/ProcessServiceRecord.java
+++ b/services/core/java/com/android/server/am/ProcessServiceRecord.java
@@ -180,6 +180,16 @@ final class ProcessServiceRecord {
mRepFgServiceTypes = foregroundServiceTypes;
}
+ int getNumForegroundServices() {
+ int count = 0;
+ for (int i = 0, serviceCount = mServices.size(); i < serviceCount; i++) {
+ if (mServices.valueAt(i).isForeground) {
+ count++;
+ }
+ }
+ return count;
+ }
+
void updateHasTopStartedAlmostPerceptibleServices() {
mHasTopStartedAlmostPerceptibleServices = false;
mLastTopStartedAlmostPerceptibleBindRequestUptimeMs = 0;
diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java
index 262436d693ec..eb1fd3aa49be 100644
--- a/services/core/java/com/android/server/am/ProcessStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessStateRecord.java
@@ -497,6 +497,7 @@ final class ProcessStateRecord {
@GuardedBy({"mService", "mProcLock"})
void setCurAdj(int curAdj) {
mCurAdj = curAdj;
+ mApp.getWindowProcessController().setCurrentAdj(curAdj);
}
@GuardedBy(anyOf = {"mService", "mProcLock"})
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index d0817b078b61..470de8ce0955 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -99,6 +99,7 @@ public class SettingsToPropertiesMapper {
DeviceConfig.NAMESPACE_SWCODEC_NATIVE,
DeviceConfig.NAMESPACE_TETHERING,
DeviceConfig.NAMESPACE_VENDOR_SYSTEM_NATIVE,
+ DeviceConfig.NAMESPACE_VENDOR_SYSTEM_NATIVE_BOOT,
DeviceConfig.NAMESPACE_VIRTUALIZATION_FRAMEWORK_NATIVE,
DeviceConfig.NAMESPACE_WINDOW_MANAGER_NATIVE_BOOT,
DeviceConfig.NAMESPACE_MEMORY_SAFETY_NATIVE,
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 0c0ae7d8f934..a1f3dae39dcb 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -16,9 +16,11 @@
package com.android.server.am;
+import static android.Manifest.permission.CREATE_USERS;
import static android.Manifest.permission.INTERACT_ACROSS_PROFILES;
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+import static android.Manifest.permission.MANAGE_USERS;
import static android.app.ActivityManager.STOP_USER_ON_SWITCH_DEFAULT;
import static android.app.ActivityManager.STOP_USER_ON_SWITCH_TRUE;
import static android.app.ActivityManager.StopUserOnSwitch;
@@ -69,6 +71,7 @@ import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.PackagePartitions;
import android.content.pm.UserInfo;
+import android.content.pm.UserProperties;
import android.os.BatteryStats;
import android.os.Binder;
import android.os.Bundle;
@@ -100,6 +103,7 @@ import android.util.Pair;
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.util.proto.ProtoOutputStream;
+import android.view.Display;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
@@ -107,6 +111,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.IKeyguardDismissCallback;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.internal.util.Preconditions;
import com.android.internal.widget.LockPatternUtils;
import com.android.server.FactoryResetter;
import com.android.server.FgThread;
@@ -1071,6 +1076,12 @@ class UserController implements Handler.Callback {
Binder.getCallingPid(), UserHandle.USER_ALL);
});
}
+
+ // TODO(b/239982558): for now we're just updating the user's visibility, but most likely
+ // we'll need to remove this call and handle that as part of the user state workflow
+ // instead.
+ // TODO(b/240613396) also check if multi-display is supported
+ mInjector.getUserManagerInternal().assignUserToDisplay(userId, Display.INVALID_DISPLAY);
}
private void finishUserStopping(final int userId, final UserState uss,
@@ -1357,12 +1368,14 @@ class UserController implements Handler.Callback {
List<UserInfo> profilesToStart = new ArrayList<>(profiles.size());
for (UserInfo user : profiles) {
if ((user.flags & UserInfo.FLAG_INITIALIZED) == UserInfo.FLAG_INITIALIZED
- && user.id != currentUserId && !user.isQuietModeEnabled()) {
+ && user.id != currentUserId
+ && shouldStartWithParent(user)) {
profilesToStart.add(user);
}
}
final int profilesToStartSize = profilesToStart.size();
int i = 0;
+ // TODO(b/239982558): pass displayId
for (; i < profilesToStartSize && i < (getMaxRunningUsers() - 1); ++i) {
startUser(profilesToStart.get(i).id, /* foreground= */ false);
}
@@ -1371,6 +1384,14 @@ class UserController implements Handler.Callback {
}
}
+ private boolean shouldStartWithParent(UserInfo user) {
+ final UserProperties properties = mInjector.getUserManagerInternal()
+ .getUserProperties(user.id);
+ return (properties != null && properties.getStartWithParent())
+ && !user.isQuietModeEnabled();
+ }
+
+ // TODO(b/239982558): might need to infer the display id based on parent user
/**
* Starts a user only if it's a profile, with a more relaxed permission requirement:
* {@link android.Manifest.permission#MANAGE_USERS} or
@@ -1399,7 +1420,8 @@ class UserController implements Handler.Callback {
return false;
}
- return startUserNoChecks(userId, /* foreground= */ false, /* unlockListener= */ null);
+ return startUserNoChecks(userId, Display.DEFAULT_DISPLAY, /* foreground= */ false,
+ /* unlockListener= */ null);
}
@VisibleForTesting
@@ -1445,26 +1467,70 @@ class UserController implements Handler.Callback {
@Nullable IProgressListener unlockListener) {
checkCallingPermission(INTERACT_ACROSS_USERS_FULL, "startUser");
- return startUserNoChecks(userId, foreground, unlockListener);
+ return startUserNoChecks(userId, Display.DEFAULT_DISPLAY, foreground, unlockListener);
+ }
+
+ // TODO(b/239982558): add javadoc (need to wait until the intents / SystemService callbacks are
+ // defined
+ boolean startUserOnSecondaryDisplay(@UserIdInt int userId, int displayId,
+ @Nullable IProgressListener unlockListener) {
+ checkCallingHasOneOfThosePermissions("startUserOnSecondaryDisplay",
+ MANAGE_USERS, CREATE_USERS);
+
+ // DEFAULT_DISPLAY is used for "regular" start user operations
+ Preconditions.checkArgument(displayId != Display.DEFAULT_DISPLAY,
+ "Cannot use DEFAULT_DISPLAY");
+
+ try {
+ return startUserNoChecks(userId, displayId, /* foreground= */ false, unlockListener);
+ } catch (RuntimeException e) {
+ Slogf.w(TAG, "startUserOnSecondaryDisplay(%d, %d) failed: %s", userId, displayId, e);
+ return false;
+ }
}
- private boolean startUserNoChecks(final @UserIdInt int userId, final boolean foreground,
+ private boolean startUserNoChecks(@UserIdInt int userId, int displayId, boolean foreground,
@Nullable IProgressListener unlockListener) {
TimingsTraceAndSlog t = new TimingsTraceAndSlog();
- t.traceBegin("UserController.startUser-" + userId + "-" + (foreground ? "fg" : "bg"));
+ t.traceBegin("UserController.startUser-" + userId
+ + (displayId == Display.DEFAULT_DISPLAY ? "" : "-display-" + displayId)
+ + "-" + (foreground ? "fg" : "bg"));
try {
- return startUserInternal(userId, foreground, unlockListener, t);
+ return startUserInternal(userId, displayId, foreground, unlockListener, t);
} finally {
t.traceEnd();
}
}
- private boolean startUserInternal(@UserIdInt int userId, boolean foreground,
+ private boolean startUserInternal(@UserIdInt int userId, int displayId, boolean foreground,
@Nullable IProgressListener unlockListener, @NonNull TimingsTraceAndSlog t) {
if (DEBUG_MU) {
- Slogf.i(TAG, "Starting user %d%s", userId, foreground ? " in foreground" : "");
+ Slogf.i(TAG, "Starting user %d on display %d %s", userId, displayId,
+ foreground ? " in foreground" : "");
+ }
+
+ // TODO(b/239982558): move logic below to a different class (like DisplayAssignmentManager)
+ if (displayId != Display.DEFAULT_DISPLAY) {
+ // This is called by startUserOnSecondaryDisplay()
+ if (!UserManager.isUsersOnSecondaryDisplaysEnabled()) {
+ // TODO(b/239824814): add CTS test and/or unit test for all exceptional cases
+ throw new UnsupportedOperationException("Not supported by device");
+ }
+
+ // TODO(b/239982558): call DisplayManagerInternal to check if display is valid instead
+ Preconditions.checkArgument(displayId > 0, "Invalid display id (%d)", displayId);
+ Preconditions.checkArgument(userId != UserHandle.USER_SYSTEM, "Cannot start system user"
+ + " on secondary display (%d)", displayId);
+ Preconditions.checkArgument(!foreground, "Cannot start user %d in foreground AND "
+ + "on secondary display (%d)", userId, displayId);
+
+ // TODO(b/239982558): for now we're just updating the user's visibility, but most likely
+ // we'll need to remove this call and handle that as part of the user state workflow
+ // instead.
+ mInjector.getUserManagerInternal().assignUserToDisplay(userId, displayId);
}
+
EventLog.writeEvent(EventLogTags.UC_START_USER_INTERNAL, userId);
final int callingUid = Binder.getCallingUid();
@@ -1519,6 +1585,7 @@ class UserController implements Handler.Callback {
return false;
}
+ // TODO(b/239982558): might need something similar for bg users on secondary display
if (foreground && isUserSwitchUiEnabled()) {
t.traceBegin("startFreezingScreen");
mInjector.getWindowManager().startFreezingScreen(
@@ -2568,15 +2635,24 @@ class UserController implements Handler.Callback {
}
private void checkCallingPermission(String permission, String methodName) {
- if (mInjector.checkCallingPermission(permission)
- != PackageManager.PERMISSION_GRANTED) {
- String msg = "Permission denial: " + methodName
- + "() from pid=" + Binder.getCallingPid()
- + ", uid=" + Binder.getCallingUid()
- + " requires " + permission;
- Slogf.w(TAG, msg);
- throw new SecurityException(msg);
+ checkCallingHasOneOfThosePermissions(methodName, permission);
+ }
+
+ private void checkCallingHasOneOfThosePermissions(String methodName, String...permissions) {
+ for (String permission : permissions) {
+ if (mInjector.checkCallingPermission(permission) == PackageManager.PERMISSION_GRANTED) {
+ return;
+ }
}
+ String msg = "Permission denial: " + methodName
+ + "() from pid=" + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid()
+ + " requires "
+ + (permissions.length == 1
+ ? permissions[0]
+ : "one of " + Arrays.toString(permissions));
+ Slogf.w(TAG, msg);
+ throw new SecurityException(msg);
}
private void enforceShellRestriction(String restriction, @UserIdInt int userId) {
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index 854b818c095f..1302e226eba3 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -99,6 +99,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
/**
* Service to manage game related features.
@@ -119,7 +120,7 @@ public final class GameManagerService extends IGameManagerService.Stub {
static final int SET_GAME_STATE = 4;
static final int CANCEL_GAME_LOADING_MODE = 5;
static final int WRITE_GAME_MODE_INTERVENTION_LIST_FILE = 6;
- static final int WRITE_SETTINGS_DELAY = 10 * 1000; // 10 seconds
+ static final int WRITE_DELAY_MILLIS = 10 * 1000; // 10 seconds
static final int LOADING_BOOST_MAX_DURATION = 5 * 1000; // 5 seconds
private static final String PACKAGE_NAME_MSG_KEY = "packageName";
@@ -130,8 +131,8 @@ public final class GameManagerService extends IGameManagerService.Stub {
private final Context mContext;
private final Object mLock = new Object();
private final Object mDeviceConfigLock = new Object();
- private final Object mOverrideConfigLock = new Object();
- private final Handler mHandler;
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ final Handler mHandler;
private final PackageManager mPackageManager;
private final UserManager mUserManager;
private final PowerManagerInternal mPowerManagerInternal;
@@ -143,8 +144,6 @@ public final class GameManagerService extends IGameManagerService.Stub {
private final ArrayMap<Integer, GameManagerSettings> mSettings = new ArrayMap<>();
@GuardedBy("mDeviceConfigLock")
private final ArrayMap<String, GamePackageConfiguration> mConfigs = new ArrayMap<>();
- @GuardedBy("mOverrideConfigLock")
- private final ArrayMap<String, GamePackageConfiguration> mOverrideConfigs = new ArrayMap<>();
@Nullable
private final GameServiceController mGameServiceController;
@@ -236,7 +235,7 @@ public final class GameManagerService extends IGameManagerService.Stub {
final int userId = ActivityManager.getCurrentUser();
String[] packageList = getInstalledGamePackageNames(userId);
for (final String packageName : packageList) {
- pw.println(getInterventionList(packageName));
+ pw.println(getInterventionList(packageName, userId));
}
}
@@ -258,14 +257,13 @@ public final class GameManagerService extends IGameManagerService.Stub {
if (userId < 0) {
Slog.wtf(TAG, "Attempt to write settings for invalid user: " + userId);
synchronized (mLock) {
- removeMessages(WRITE_SETTINGS, msg.obj);
+ removeEqualMessages(WRITE_SETTINGS, msg.obj);
}
break;
}
-
Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
synchronized (mLock) {
- removeMessages(WRITE_SETTINGS, msg.obj);
+ removeEqualMessages(WRITE_SETTINGS, msg.obj);
if (mSettings.containsKey(userId)) {
GameManagerSettings userSettings = mSettings.get(userId);
userSettings.writePersistentDataLocked();
@@ -279,8 +277,8 @@ public final class GameManagerService extends IGameManagerService.Stub {
if (userId < 0) {
Slog.wtf(TAG, "Attempt to write settings for invalid user: " + userId);
synchronized (mLock) {
- removeMessages(WRITE_SETTINGS, msg.obj);
- removeMessages(REMOVE_SETTINGS, msg.obj);
+ removeEqualMessages(WRITE_SETTINGS, msg.obj);
+ removeEqualMessages(REMOVE_SETTINGS, msg.obj);
}
break;
}
@@ -288,8 +286,8 @@ public final class GameManagerService extends IGameManagerService.Stub {
synchronized (mLock) {
// Since the user was removed, ignore previous write message
// and do write here.
- removeMessages(WRITE_SETTINGS, msg.obj);
- removeMessages(REMOVE_SETTINGS, msg.obj);
+ removeEqualMessages(WRITE_SETTINGS, msg.obj);
+ removeEqualMessages(REMOVE_SETTINGS, msg.obj);
if (mSettings.containsKey(userId)) {
final GameManagerSettings userSettings = mSettings.get(userId);
mSettings.remove(userId);
@@ -299,7 +297,7 @@ public final class GameManagerService extends IGameManagerService.Stub {
break;
}
case POPULATE_GAME_MODE_SETTINGS: {
- removeMessages(POPULATE_GAME_MODE_SETTINGS, msg.obj);
+ removeEqualMessages(POPULATE_GAME_MODE_SETTINGS, msg.obj);
final int userId = (int) msg.obj;
final String[] packageNames = getInstalledGamePackageNames(userId);
updateConfigsForUser(userId, false /*checkGamePackage*/, packageNames);
@@ -345,13 +343,13 @@ public final class GameManagerService extends IGameManagerService.Stub {
if (userId < 0) {
Slog.wtf(TAG, "Attempt to write setting for invalid user: " + userId);
synchronized (mLock) {
- removeMessages(WRITE_GAME_MODE_INTERVENTION_LIST_FILE, null);
+ removeEqualMessages(WRITE_GAME_MODE_INTERVENTION_LIST_FILE, msg.obj);
}
break;
}
Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
- removeMessages(WRITE_GAME_MODE_INTERVENTION_LIST_FILE, null);
+ removeEqualMessages(WRITE_GAME_MODE_INTERVENTION_LIST_FILE, msg.obj);
writeGameModeInterventionsToFile(userId);
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
break;
@@ -446,8 +444,7 @@ public final class GameManagerService extends IGameManagerService.Stub {
/**
* GamePackageConfiguration manages all game mode config details for its associated package.
*/
- @VisibleForTesting
- public class GamePackageConfiguration {
+ public static class GamePackageConfiguration {
public static final String TAG = "GameManagerService_GamePackageConfiguration";
/**
@@ -499,12 +496,16 @@ public final class GameManagerService extends IGameManagerService.Stub {
private boolean mAllowAngle;
private boolean mAllowFpsOverride;
- GamePackageConfiguration(String packageName, int userId) {
+ GamePackageConfiguration(String packageName) {
+ mPackageName = packageName;
+ }
+
+ GamePackageConfiguration(PackageManager packageManager, String packageName, int userId) {
mPackageName = packageName;
try {
- final ApplicationInfo ai = mPackageManager.getApplicationInfoAsUser(packageName,
+ final ApplicationInfo ai = packageManager.getApplicationInfoAsUser(packageName,
PackageManager.GET_META_DATA, userId);
- if (!parseInterventionFromXml(ai, packageName)) {
+ if (!parseInterventionFromXml(packageManager, ai, packageName)) {
if (ai.metaData != null) {
mPerfModeOptedIn = ai.metaData.getBoolean(METADATA_PERFORMANCE_MODE_ENABLE);
mBatteryModeOptedIn = ai.metaData.getBoolean(METADATA_BATTERY_MODE_ENABLE);
@@ -538,16 +539,17 @@ public final class GameManagerService extends IGameManagerService.Stub {
}
}
- private boolean parseInterventionFromXml(ApplicationInfo ai, String packageName) {
+ private boolean parseInterventionFromXml(PackageManager packageManager, ApplicationInfo ai,
+ String packageName) {
boolean xmlFound = false;
- try (XmlResourceParser parser = ai.loadXmlMetaData(mPackageManager,
+ try (XmlResourceParser parser = ai.loadXmlMetaData(packageManager,
METADATA_GAME_MODE_CONFIG)) {
if (parser == null) {
Slog.v(TAG, "No " + METADATA_GAME_MODE_CONFIG
+ " meta-data found for package " + mPackageName);
} else {
xmlFound = true;
- final Resources resources = mPackageManager.getResourcesForApplication(
+ final Resources resources = packageManager.getResourcesForApplication(
packageName);
final AttributeSet attributeSet = Xml.asAttributeSet(parser);
int type;
@@ -596,7 +598,6 @@ public final class GameManagerService extends IGameManagerService.Stub {
* GameModeConfiguration contains all the values for all the interventions associated with
* a game mode.
*/
- @VisibleForTesting
public class GameModeConfiguration {
public static final String TAG = "GameManagerService_GameModeConfiguration";
public static final String MODE_KEY = "mode";
@@ -613,8 +614,8 @@ public final class GameManagerService extends IGameManagerService.Stub {
private final @GameMode int mGameMode;
private float mScaling = DEFAULT_SCALING;
private String mFps = DEFAULT_FPS;
- private final boolean mUseAngle;
- private final int mLoadingBoostDuration;
+ private boolean mUseAngle;
+ private int mLoadingBoostDuration;
GameModeConfiguration(int gameMode) {
mGameMode = gameMode;
@@ -657,11 +658,15 @@ public final class GameManagerService extends IGameManagerService.Stub {
return GameManagerService.getFpsInt(mFps);
}
- public boolean getUseAngle() {
+ synchronized String getFpsStr() {
+ return mFps;
+ }
+
+ public synchronized boolean getUseAngle() {
return mUseAngle;
}
- public int getLoadingBoostDuration() {
+ public synchronized int getLoadingBoostDuration() {
return mLoadingBoostDuration;
}
@@ -673,6 +678,14 @@ public final class GameManagerService extends IGameManagerService.Stub {
mFps = fpsStr;
}
+ public synchronized void setUseAngle(boolean useAngle) {
+ mUseAngle = useAngle;
+ }
+
+ public synchronized void setLoadingBoostDuration(int loadingBoostDuration) {
+ mLoadingBoostDuration = loadingBoostDuration;
+ }
+
public boolean isActive() {
return (mGameMode == GameManager.GAME_MODE_STANDARD
|| mGameMode == GameManager.GAME_MODE_PERFORMANCE
@@ -759,7 +772,7 @@ public final class GameManagerService extends IGameManagerService.Stub {
}
/**
- * Inserts a new GameModeConfiguration
+ * Inserts a new GameModeConfiguration.
*/
public void addModeConfig(GameModeConfiguration config) {
if (config.isActive()) {
@@ -772,6 +785,15 @@ public final class GameManagerService extends IGameManagerService.Stub {
}
}
+ /**
+ * Removes the GameModeConfiguration.
+ */
+ public void removeModeConfig(int mode) {
+ synchronized (mModeConfigLock) {
+ mModeConfigs.remove(mode);
+ }
+ }
+
public boolean isActive() {
synchronized (mModeConfigLock) {
return mModeConfigs.size() > 0 || mBatteryModeOptedIn || mPerfModeOptedIn;
@@ -823,7 +845,8 @@ public final class GameManagerService extends IGameManagerService.Stub {
@Override
public void onUserStarting(@NonNull TargetUser user) {
- mService.onUserStarting(user);
+ mService.onUserStarting(user,
+ Environment.getDataSystemDeDirectory(user.getUserIdentifier()));
}
@Override
@@ -860,7 +883,10 @@ public final class GameManagerService extends IGameManagerService.Stub {
}
private @GameMode int[] getAvailableGameModesUnchecked(String packageName) {
- final GamePackageConfiguration config = getConfig(packageName);
+ final GamePackageConfiguration config;
+ synchronized (mDeviceConfigLock) {
+ config = mConfigs.get(packageName);
+ }
if (config == null) {
return new int[]{};
}
@@ -987,18 +1013,11 @@ public final class GameManagerService extends IGameManagerService.Stub {
}
GameManagerSettings userSettings = mSettings.get(userId);
userSettings.setGameModeLocked(packageName, gameMode);
- final Message msg = mHandler.obtainMessage(WRITE_SETTINGS);
- msg.obj = userId;
- if (!mHandler.hasEqualMessages(WRITE_SETTINGS, userId)) {
- mHandler.sendMessageDelayed(msg, WRITE_SETTINGS_DELAY);
- }
}
updateInterventions(packageName, gameMode, userId);
- final Message msg = mHandler.obtainMessage(WRITE_GAME_MODE_INTERVENTION_LIST_FILE);
- msg.obj = userId;
- if (!mHandler.hasEqualMessages(WRITE_GAME_MODE_INTERVENTION_LIST_FILE, userId)) {
- mHandler.sendMessage(msg);
- }
+ sendUserMessage(userId, WRITE_SETTINGS, "SET_GAME_MODE", WRITE_DELAY_MILLIS);
+ sendUserMessage(userId, WRITE_GAME_MODE_INTERVENTION_LIST_FILE,
+ "SET_GAME_MODE", 0 /*delayMillis*/);
}
/**
@@ -1033,7 +1052,6 @@ public final class GameManagerService extends IGameManagerService.Stub {
* the boost duration. If no configuration is available for the selected package or mode, the
* default is returned.
*/
- @VisibleForTesting
public int getLoadingBoostDuration(String packageName, int userId)
throws SecurityException {
final int gameMode = getGameMode(packageName, userId);
@@ -1165,7 +1183,7 @@ public final class GameManagerService extends IGameManagerService.Stub {
}
float getResolutionScalingFactorInternal(String packageName, int gameMode, int userId) {
- final GamePackageConfiguration packageConfig = getConfig(packageName);
+ final GamePackageConfiguration packageConfig = getConfig(packageName, userId);
if (packageConfig == null) {
return GamePackageConfiguration.GameModeConfiguration.DEFAULT_SCALING;
}
@@ -1186,22 +1204,46 @@ public final class GameManagerService extends IGameManagerService.Stub {
if (mGameServiceController != null) {
mGameServiceController.onBootComplete();
}
+ mContext.registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (Intent.ACTION_SHUTDOWN.equals(intent.getAction())) {
+ synchronized (mLock) {
+ // Note that the max wait time of broadcast is 10s (see
+ // {@ShutdownThread#MAX_BROADCAST_TIMEMAX_BROADCAST_TIME}) currently so
+ // this can be optional only if we have message delay plus processing
+ // time significant smaller to prevent data loss.
+ for (Map.Entry<Integer, GameManagerSettings> entry : mSettings.entrySet()) {
+ final int userId = entry.getKey();
+ sendUserMessage(userId, WRITE_SETTINGS,
+ Intent.ACTION_SHUTDOWN, 0 /*delayMillis*/);
+ sendUserMessage(userId,
+ WRITE_GAME_MODE_INTERVENTION_LIST_FILE, Intent.ACTION_SHUTDOWN,
+ 0 /*delayMillis*/);
+ }
+ }
+ }
+ }
+ }, new IntentFilter(Intent.ACTION_SHUTDOWN));
}
- void onUserStarting(@NonNull TargetUser user) {
- final int userId = user.getUserIdentifier();
+ private void sendUserMessage(int userId, int what, String eventForLog, int delayMillis) {
+ Message msg = mHandler.obtainMessage(what, userId);
+ if (!mHandler.sendMessageDelayed(msg, delayMillis)) {
+ Slog.e(TAG, "Failed to send user message " + what + " on " + eventForLog);
+ }
+ }
+ void onUserStarting(@NonNull TargetUser user, File settingDataDir) {
+ final int userId = user.getUserIdentifier();
synchronized (mLock) {
if (!mSettings.containsKey(userId)) {
- GameManagerSettings userSettings =
- new GameManagerSettings(Environment.getDataSystemDeDirectory(userId));
+ GameManagerSettings userSettings = new GameManagerSettings(settingDataDir);
mSettings.put(userId, userSettings);
userSettings.readPersistentDataLocked();
}
}
- final Message msg = mHandler.obtainMessage(POPULATE_GAME_MODE_SETTINGS);
- msg.obj = userId;
- mHandler.sendMessage(msg);
+ sendUserMessage(userId, POPULATE_GAME_MODE_SETTINGS, "ON_USER_STARTING", 0 /*delayMillis*/);
if (mGameServiceController != null) {
mGameServiceController.notifyUserStarted(user);
@@ -1221,9 +1263,7 @@ public final class GameManagerService extends IGameManagerService.Stub {
if (!mSettings.containsKey(userId)) {
return;
}
- final Message msg = mHandler.obtainMessage(REMOVE_SETTINGS);
- msg.obj = userId;
- mHandler.sendMessage(msg);
+ sendUserMessage(userId, REMOVE_SETTINGS, "ON_USER_STOPPING", 0 /*delayMillis*/);
}
if (mGameServiceController != null) {
@@ -1237,15 +1277,14 @@ public final class GameManagerService extends IGameManagerService.Stub {
synchronized (mLock) {
final int fromUserId = from.getUserIdentifier();
if (mSettings.containsKey(fromUserId)) {
- final Message msg = mHandler.obtainMessage(REMOVE_SETTINGS);
- msg.obj = fromUserId;
- mHandler.sendMessage(msg);
+ sendUserMessage(fromUserId, REMOVE_SETTINGS, "ON_USER_SWITCHING",
+ 0 /*delayMillis*/);
}
}
}
- final Message msg = mHandler.obtainMessage(POPULATE_GAME_MODE_SETTINGS);
- msg.obj = toUserId;
- mHandler.sendMessage(msg);
+
+ sendUserMessage(toUserId, POPULATE_GAME_MODE_SETTINGS, "ON_USER_SWITCHING",
+ 0 /*delayMillis*/);
if (mGameServiceController != null) {
mGameServiceController.notifyNewForegroundUser(to);
@@ -1265,7 +1304,7 @@ public final class GameManagerService extends IGameManagerService.Stub {
}
}
- private int modeToBitmask(@GameMode int gameMode) {
+ private static int modeToBitmask(@GameMode int gameMode) {
return (1 << gameMode);
}
@@ -1305,7 +1344,7 @@ public final class GameManagerService extends IGameManagerService.Stub {
resetFps(packageName, userId);
return;
}
- final GamePackageConfiguration packageConfig = getConfig(packageName);
+ final GamePackageConfiguration packageConfig = getConfig(packageName, userId);
if (packageConfig == null) {
Slog.v(TAG, "Package configuration not found for " + packageName);
return;
@@ -1318,7 +1357,7 @@ public final class GameManagerService extends IGameManagerService.Stub {
}
/**
- * Set the override Game Mode Configuration.
+ * Set the Game Mode Configuration override.
* Update the config if exists, create one if not.
*/
@VisibleForTesting
@@ -1326,95 +1365,86 @@ public final class GameManagerService extends IGameManagerService.Stub {
public void setGameModeConfigOverride(String packageName, @UserIdInt int userId,
@GameMode int gameMode, String fpsStr, String scaling) throws SecurityException {
checkPermission(Manifest.permission.MANAGE_GAME_MODE);
+ // Adding game mode config override of the given package name
+ GamePackageConfiguration configOverride;
synchronized (mLock) {
if (!mSettings.containsKey(userId)) {
return;
}
- }
- // Adding override game mode configuration of the given package name
- GamePackageConfiguration overrideConfig;
- synchronized (mOverrideConfigLock) {
- // look for the existing override GamePackageConfiguration
- overrideConfig = mOverrideConfigs.get(packageName);
- if (overrideConfig == null) {
- overrideConfig = new GamePackageConfiguration(packageName, userId);
- mOverrideConfigs.put(packageName, overrideConfig);
+ final GameManagerSettings settings = mSettings.get(userId);
+ // look for the existing GamePackageConfiguration override
+ configOverride = settings.getConfigOverride(packageName);
+ if (configOverride == null) {
+ configOverride = new GamePackageConfiguration(mPackageManager, packageName, userId);
+ settings.setConfigOverride(packageName, configOverride);
}
}
// modify GameModeConfiguration intervention settings
- GamePackageConfiguration.GameModeConfiguration overrideModeConfig =
- overrideConfig.getOrAddDefaultGameModeConfiguration(gameMode);
+ GamePackageConfiguration.GameModeConfiguration modeConfigOverride =
+ configOverride.getOrAddDefaultGameModeConfiguration(gameMode);
if (fpsStr != null) {
- overrideModeConfig.setFpsStr(fpsStr);
+ modeConfigOverride.setFpsStr(fpsStr);
} else {
- overrideModeConfig.setFpsStr(
+ modeConfigOverride.setFpsStr(
GamePackageConfiguration.GameModeConfiguration.DEFAULT_FPS);
}
if (scaling != null) {
- overrideModeConfig.setScaling(Float.parseFloat(scaling));
+ modeConfigOverride.setScaling(Float.parseFloat(scaling));
}
Slog.i(TAG, "Package Name: " + packageName
- + " FPS: " + String.valueOf(overrideModeConfig.getFps())
- + " Scaling: " + overrideModeConfig.getScaling());
+ + " FPS: " + String.valueOf(modeConfigOverride.getFps())
+ + " Scaling: " + modeConfigOverride.getScaling());
setGameMode(packageName, gameMode, userId);
}
/**
* Reset the overridden gameModeConfiguration of the given mode.
- * Remove the override config if game mode is not specified.
+ * Remove the config override if game mode is not specified.
*/
@VisibleForTesting
@RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
public void resetGameModeConfigOverride(String packageName, @UserIdInt int userId,
@GameMode int gameModeToReset) throws SecurityException {
checkPermission(Manifest.permission.MANAGE_GAME_MODE);
- synchronized (mLock) {
- if (!mSettings.containsKey(userId)) {
- return;
- }
+ final GamePackageConfiguration deviceConfig;
+ synchronized (mDeviceConfigLock) {
+ deviceConfig = mConfigs.get(packageName);
}
// resets GamePackageConfiguration of a given packageName.
// If a gameMode is specified, only reset the GameModeConfiguration of the gameMode.
- if (gameModeToReset != -1) {
- GamePackageConfiguration overrideConfig = null;
- synchronized (mOverrideConfigLock) {
- overrideConfig = mOverrideConfigs.get(packageName);
- }
-
- GamePackageConfiguration config = null;
- synchronized (mDeviceConfigLock) {
- config = mConfigs.get(packageName);
- }
-
- int[] modes = overrideConfig.getAvailableGameModes();
-
- // First check if the mode to reset exists
- boolean isGameModeExist = false;
- for (int mode : modes) {
- if (gameModeToReset == mode) {
- isGameModeExist = true;
- }
- }
- if (!isGameModeExist) {
+ synchronized (mLock) {
+ if (!mSettings.containsKey(userId)) {
return;
}
-
- // If the game mode to reset is the only mode other than standard mode,
- // the override config is removed.
- if (modes.length <= 2) {
- synchronized (mOverrideConfigLock) {
- mOverrideConfigs.remove(packageName);
+ final GameManagerSettings settings = mSettings.get(userId);
+ if (gameModeToReset != -1) {
+ final GamePackageConfiguration configOverride = settings.getConfigOverride(
+ packageName);
+ if (configOverride == null) {
+ return;
+ }
+ final int modesBitfield = configOverride.getAvailableGameModesBitfield();
+ if (!bitFieldContainsModeBitmask(modesBitfield, gameModeToReset)) {
+ return;
+ }
+ // if the game mode to reset is the only mode other than standard mode or there
+ // is device config, the config override is removed.
+ if (Integer.bitCount(modesBitfield) <= 2 || deviceConfig == null) {
+ settings.removeConfigOverride(packageName);
+ } else {
+ final GamePackageConfiguration.GameModeConfiguration defaultModeConfig =
+ deviceConfig.getGameModeConfiguration(gameModeToReset);
+ // otherwise we reset the mode by copying the original config.
+ if (defaultModeConfig == null) {
+ configOverride.removeModeConfig(gameModeToReset);
+ } else {
+ configOverride.addModeConfig(defaultModeConfig);
+ }
}
} else {
- // otherwise we reset the mode by copying the original config.
- overrideConfig.addModeConfig(config.getGameModeConfiguration(gameModeToReset));
- }
- } else {
- synchronized (mOverrideConfigLock) {
- // remove override config if there is one
- mOverrideConfigs.remove(packageName);
+ settings.removeConfigOverride(packageName);
}
}
@@ -1422,7 +1452,7 @@ public final class GameManagerService extends IGameManagerService.Stub {
// If not, set the game mode to standard
int gameMode = getGameMode(packageName, userId);
- final GamePackageConfiguration config = getConfig(packageName);
+ final GamePackageConfiguration config = getConfig(packageName, userId);
final int newGameMode = getNewGameMode(gameMode, config);
if (gameMode != newGameMode) {
setGameMode(packageName, GameManager.GAME_MODE_STANDARD, userId);
@@ -1460,8 +1490,8 @@ public final class GameManagerService extends IGameManagerService.Stub {
/**
* Returns the string listing all the interventions currently set to a game.
*/
- public String getInterventionList(String packageName) {
- final GamePackageConfiguration packageConfig = getConfig(packageName);
+ public String getInterventionList(String packageName, int userId) {
+ final GamePackageConfiguration packageConfig = getConfig(packageName, userId);
final StringBuilder listStrSb = new StringBuilder();
if (packageConfig == null) {
listStrSb.append("\n No intervention found for package ")
@@ -1487,7 +1517,7 @@ public final class GameManagerService extends IGameManagerService.Stub {
synchronized (mDeviceConfigLock) {
for (final String packageName : packageNames) {
final GamePackageConfiguration config =
- new GamePackageConfiguration(packageName, userId);
+ new GamePackageConfiguration(mPackageManager, packageName, userId);
if (config.isActive()) {
if (DEBUG) {
Slog.i(TAG, "Adding config: " + config.toString());
@@ -1526,15 +1556,11 @@ public final class GameManagerService extends IGameManagerService.Stub {
updateInterventions(packageName, gameMode, userId);
}
}
+ sendUserMessage(userId, WRITE_GAME_MODE_INTERVENTION_LIST_FILE,
+ "UPDATE_CONFIGS_FOR_USERS", 0 /*delayMillis*/);
} catch (Exception e) {
Slog.e(TAG, "Failed to update configs for user " + userId + ": " + e);
}
-
- final Message msg = mHandler.obtainMessage(WRITE_GAME_MODE_INTERVENTION_LIST_FILE);
- msg.obj = userId;
- if (!mHandler.hasEqualMessages(WRITE_GAME_MODE_INTERVENTION_LIST_FILE, userId)) {
- mHandler.sendMessage(msg);
- }
}
/*
@@ -1556,7 +1582,7 @@ public final class GameManagerService extends IGameManagerService.Stub {
final StringBuilder sb = new StringBuilder();
final List<String> installedGamesList = getInstalledGamePackageNamesByAllUsers(userId);
for (final String packageName : installedGamesList) {
- GamePackageConfiguration packageConfig = getConfig(packageName);
+ GamePackageConfiguration packageConfig = getConfig(packageName, userId);
if (packageConfig == null) {
continue;
}
@@ -1634,11 +1660,12 @@ public final class GameManagerService extends IGameManagerService.Stub {
/**
* @hide
*/
- @VisibleForTesting
- public GamePackageConfiguration getConfig(String packageName) {
+ public GamePackageConfiguration getConfig(String packageName, int userId) {
GamePackageConfiguration packageConfig = null;
- synchronized (mOverrideConfigLock) {
- packageConfig = mOverrideConfigs.get(packageName);
+ synchronized (mLock) {
+ if (mSettings.containsKey(userId)) {
+ packageConfig = mSettings.get(userId).getConfigOverride(packageName);
+ }
}
if (packageConfig == null) {
synchronized (mDeviceConfigLock) {
@@ -1679,9 +1706,6 @@ public final class GameManagerService extends IGameManagerService.Stub {
break;
case ACTION_PACKAGE_REMOVED:
if (!intent.getBooleanExtra(EXTRA_REPLACING, false)) {
- synchronized (mOverrideConfigLock) {
- mOverrideConfigs.remove(packageName);
- }
synchronized (mDeviceConfigLock) {
mConfigs.remove(packageName);
}
@@ -1689,6 +1713,11 @@ public final class GameManagerService extends IGameManagerService.Stub {
if (mSettings.containsKey(userId)) {
mSettings.get(userId).removeGame(packageName);
}
+ sendUserMessage(userId, WRITE_SETTINGS,
+ Intent.ACTION_PACKAGE_REMOVED, WRITE_DELAY_MILLIS);
+ sendUserMessage(userId,
+ WRITE_GAME_MODE_INTERVENTION_LIST_FILE,
+ Intent.ACTION_PACKAGE_REMOVED, WRITE_DELAY_MILLIS);
}
}
break;
diff --git a/services/core/java/com/android/server/app/GameManagerSettings.java b/services/core/java/com/android/server/app/GameManagerSettings.java
index 1455a6119c7e..116249895dcd 100644
--- a/services/core/java/com/android/server/app/GameManagerSettings.java
+++ b/services/core/java/com/android/server/app/GameManagerSettings.java
@@ -27,6 +27,8 @@ import android.util.Xml;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.XmlUtils;
+import com.android.server.app.GameManagerService.GamePackageConfiguration;
+import com.android.server.app.GameManagerService.GamePackageConfiguration.GameModeConfiguration;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -39,10 +41,11 @@ import java.util.Map;
/**
* Persists all GameService related settings.
+ *
* @hide
*/
public class GameManagerSettings {
-
+ public static final String TAG = "GameManagerService_GameManagerSettings";
// The XML file follows the below format:
// <?xml>
// <packages>
@@ -53,8 +56,14 @@ public class GameManagerSettings {
private static final String TAG_PACKAGE = "package";
private static final String TAG_PACKAGES = "packages";
+ private static final String TAG_GAME_MODE_CONFIG = "gameModeConfig";
+
private static final String ATTR_NAME = "name";
private static final String ATTR_GAME_MODE = "gameMode";
+ private static final String ATTR_SCALING = "scaling";
+ private static final String ATTR_FPS = "fps";
+ private static final String ATTR_USE_ANGLE = "useAngle";
+ private static final String ATTR_LOADING_BOOST_DURATION = "loadingBoost";
private final File mSystemDir;
@VisibleForTesting
@@ -62,6 +71,8 @@ public class GameManagerSettings {
// PackageName -> GameMode
private final ArrayMap<String, Integer> mGameModes = new ArrayMap<>();
+ // PackageName -> GamePackageConfiguration
+ private final ArrayMap<String, GamePackageConfiguration> mConfigOverrides = new ArrayMap<>();
GameManagerSettings(File dataDir) {
mSystemDir = new File(dataDir, "system");
@@ -74,7 +85,7 @@ public class GameManagerSettings {
}
/**
- * Return the game mode of a given package.
+ * Returns the game mode of a given package.
* This operation must be synced with an external lock.
*/
int getGameModeLocked(String packageName) {
@@ -85,7 +96,7 @@ public class GameManagerSettings {
}
/**
- * Set the game mode of a given package.
+ * Sets the game mode of a given package.
* This operation must be synced with an external lock.
*/
void setGameModeLocked(String packageName, int gameMode) {
@@ -93,15 +104,40 @@ public class GameManagerSettings {
}
/**
- * Remove the game mode of a given package.
+ * Removes all game settings of a given package.
* This operation must be synced with an external lock.
*/
void removeGame(String packageName) {
mGameModes.remove(packageName);
+ mConfigOverrides.remove(packageName);
+ }
+
+ /**
+ * Returns the game config override of a given package or null if absent.
+ * This operation must be synced with an external lock.
+ */
+ GamePackageConfiguration getConfigOverride(String packageName) {
+ return mConfigOverrides.get(packageName);
+ }
+
+ /**
+ * Sets the game config override of a given package.
+ * This operation must be synced with an external lock.
+ */
+ void setConfigOverride(String packageName, GamePackageConfiguration configOverride) {
+ mConfigOverrides.put(packageName, configOverride);
+ }
+
+ /**
+ * Removes the game mode config override of a given package.
+ * This operation must be synced with an external lock.
+ */
+ void removeConfigOverride(String packageName) {
+ mConfigOverrides.remove(packageName);
}
/**
- * Write all current game service settings into disk.
+ * Writes all current game service settings into disk.
* This operation must be synced with an external lock.
*/
void writePersistentDataLocked() {
@@ -115,9 +151,11 @@ public class GameManagerSettings {
serializer.startTag(null, TAG_PACKAGES);
for (Map.Entry<String, Integer> entry : mGameModes.entrySet()) {
+ String packageName = entry.getKey();
serializer.startTag(null, TAG_PACKAGE);
- serializer.attribute(null, ATTR_NAME, entry.getKey());
+ serializer.attribute(null, ATTR_NAME, packageName);
serializer.attributeInt(null, ATTR_GAME_MODE, entry.getValue());
+ writeGameModeConfigTags(serializer, mConfigOverrides.get(packageName));
serializer.endTag(null, TAG_PACKAGE);
}
serializer.endTag(null, TAG_PACKAGES);
@@ -133,20 +171,41 @@ public class GameManagerSettings {
return;
} catch (java.io.IOException e) {
mSettingsFile.failWrite(fstr);
- Slog.wtf(GameManagerService.TAG, "Unable to write game manager service settings, "
+ Slog.wtf(TAG, "Unable to write game manager service settings, "
+ "current changes will be lost at reboot", e);
}
}
+ private void writeGameModeConfigTags(TypedXmlSerializer serializer,
+ GamePackageConfiguration config) throws IOException {
+ if (config == null) {
+ return;
+ }
+ final int[] gameModes = config.getAvailableGameModes();
+ for (final int mode : gameModes) {
+ final GameModeConfiguration modeConfig = config.getGameModeConfiguration(mode);
+ if (modeConfig != null) {
+ serializer.startTag(null, TAG_GAME_MODE_CONFIG);
+ serializer.attributeInt(null, ATTR_GAME_MODE, mode);
+ serializer.attributeBoolean(null, ATTR_USE_ANGLE, modeConfig.getUseAngle());
+ serializer.attribute(null, ATTR_FPS, modeConfig.getFpsStr());
+ serializer.attributeFloat(null, ATTR_SCALING, modeConfig.getScaling());
+ serializer.attributeInt(null, ATTR_LOADING_BOOST_DURATION,
+ modeConfig.getLoadingBoostDuration());
+ serializer.endTag(null, TAG_GAME_MODE_CONFIG);
+ }
+ }
+ }
+
/**
- * Read game service settings from the disk.
+ * Reads game service settings from the disk.
* This operation must be synced with an external lock.
*/
boolean readPersistentDataLocked() {
mGameModes.clear();
if (!mSettingsFile.exists()) {
- Slog.v(GameManagerService.TAG, "Settings file doesn't exists, skip reading");
+ Slog.v(TAG, "Settings file doesn't exist, skip reading");
return false;
}
@@ -160,8 +219,7 @@ public class GameManagerSettings {
// Do nothing
}
if (type != XmlPullParser.START_TAG) {
- Slog.wtf(GameManagerService.TAG,
- "No start tag found in package manager settings");
+ Slog.wtf(TAG, "No start tag found in package manager settings");
return false;
}
@@ -173,35 +231,107 @@ public class GameManagerSettings {
}
String tagName = parser.getName();
- if (tagName.equals(TAG_PACKAGE)) {
+ if (type == XmlPullParser.START_TAG && TAG_PACKAGE.equals(tagName)) {
readPackage(parser);
} else {
- Slog.w(GameManagerService.TAG, "Unknown element: " + parser.getName());
XmlUtils.skipCurrentTag(parser);
+ Slog.w(TAG, "Unknown element under packages tag: " + tagName + " with type: "
+ + type);
}
}
} catch (XmlPullParserException | java.io.IOException e) {
- Slog.wtf(GameManagerService.TAG, "Error reading package manager settings", e);
+ Slog.wtf(TAG, "Error reading package manager settings", e);
return false;
}
-
return true;
}
+ // this must be called on tag of type START_TAG.
private void readPackage(TypedXmlPullParser parser) throws XmlPullParserException,
IOException {
- String name = null;
+ final String name = parser.getAttributeValue(null, ATTR_NAME);
+ if (name == null) {
+ Slog.wtf(TAG, "No package name found in package tag");
+ XmlUtils.skipCurrentTag(parser);
+ return;
+ }
int gameMode = GameManager.GAME_MODE_UNSUPPORTED;
try {
- name = parser.getAttributeValue(null, ATTR_NAME);
gameMode = parser.getAttributeInt(null, ATTR_GAME_MODE);
} catch (XmlPullParserException e) {
- Slog.wtf(GameManagerService.TAG, "Error reading game mode", e);
+ Slog.wtf(TAG, "Invalid game mode in package tag: "
+ + parser.getAttributeValue(null, ATTR_GAME_MODE), e);
+ return;
}
- if (name != null) {
- mGameModes.put(name, gameMode);
- } else {
- XmlUtils.skipCurrentTag(parser);
+ mGameModes.put(name, gameMode);
+ final int packageTagDepth = parser.getDepth();
+ int type;
+ final GamePackageConfiguration config = new GamePackageConfiguration(name);
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG
+ || parser.getDepth() > packageTagDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+ final String tagName = parser.getName();
+ if (type == XmlPullParser.START_TAG && TAG_GAME_MODE_CONFIG.equals(tagName)) {
+ readGameModeConfig(parser, config);
+ } else {
+ XmlUtils.skipCurrentTag(parser);
+ Slog.w(TAG, "Unknown element under package tag: " + tagName + " with type: "
+ + type);
+ }
+ }
+ if (config.getAvailableGameModes().length > 1) {
+ mConfigOverrides.put(name, config);
+ }
+ }
+
+ // this must be called on tag of type START_TAG.
+ private void readGameModeConfig(TypedXmlPullParser parser, GamePackageConfiguration config) {
+ final int gameMode;
+ try {
+ gameMode = parser.getAttributeInt(null, ATTR_GAME_MODE);
+ } catch (XmlPullParserException e) {
+ Slog.wtf(TAG, "Invalid game mode value in config tag: " + parser.getAttributeValue(null,
+ ATTR_GAME_MODE), e);
+ return;
+ }
+
+ final GameModeConfiguration modeConfig = config.getOrAddDefaultGameModeConfiguration(
+ gameMode);
+ try {
+ final float scaling = parser.getAttributeFloat(null, ATTR_SCALING);
+ modeConfig.setScaling(scaling);
+ } catch (XmlPullParserException e) {
+ final String rawScaling = parser.getAttributeValue(null, ATTR_SCALING);
+ if (rawScaling != null) {
+ Slog.wtf(TAG, "Invalid scaling value in config tag: " + rawScaling, e);
+ }
+ }
+
+ final String fps = parser.getAttributeValue(null, ATTR_FPS);
+ modeConfig.setFpsStr(fps != null ? fps : GameModeConfiguration.DEFAULT_FPS);
+
+ try {
+ final boolean useAngle = parser.getAttributeBoolean(null, ATTR_USE_ANGLE);
+ modeConfig.setUseAngle(useAngle);
+ } catch (XmlPullParserException e) {
+ final String rawUseAngle = parser.getAttributeValue(null, ATTR_USE_ANGLE);
+ if (rawUseAngle != null) {
+ Slog.wtf(TAG, "Invalid useAngle value in config tag: " + rawUseAngle, e);
+ }
+ }
+ try {
+ final int loadingBoostDuration = parser.getAttributeInt(null,
+ ATTR_LOADING_BOOST_DURATION);
+ modeConfig.setLoadingBoostDuration(loadingBoostDuration);
+ } catch (XmlPullParserException e) {
+ final String rawLoadingBoost = parser.getAttributeValue(null,
+ ATTR_LOADING_BOOST_DURATION);
+ if (rawLoadingBoost != null) {
+ Slog.wtf(TAG, "Invalid loading boost in config tag: " + rawLoadingBoost, e);
+ }
}
}
}
diff --git a/services/core/java/com/android/server/app/GameManagerShellCommand.java b/services/core/java/com/android/server/app/GameManagerShellCommand.java
index 6e289b10258e..cdbffbeae80b 100644
--- a/services/core/java/com/android/server/app/GameManagerShellCommand.java
+++ b/services/core/java/com/android/server/app/GameManagerShellCommand.java
@@ -81,7 +81,8 @@ public class GameManagerShellCommand extends ShellCommand {
final GameManagerService gameManagerService = (GameManagerService)
ServiceManager.getService(Context.GAME_SERVICE);
- final String listStr = gameManagerService.getInterventionList(packageName);
+ final String listStr = gameManagerService.getInterventionList(packageName,
+ ActivityManager.getCurrentUser());
if (listStr == null) {
pw.println("No interventions found for " + packageName);
diff --git a/services/core/java/com/android/server/attention/TEST_MAPPING b/services/core/java/com/android/server/attention/TEST_MAPPING
new file mode 100644
index 000000000000..35b8165b10c4
--- /dev/null
+++ b/services/core/java/com/android/server/attention/TEST_MAPPING
@@ -0,0 +1,24 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsVoiceInteractionTestCases",
+ "options": [
+ {
+ "include-filter": "android.voiceinteraction.cts.AlwaysOnHotwordDetectorTest"
+ },
+ {
+ "include-filter": "android.voiceinteraction.cts.HotwordDetectedResultTest"
+ },
+ {
+ "include-filter": "android.voiceinteraction.cts.HotwordDetectionServiceBasicTest"
+ },
+ {
+ "include-filter": "android.voiceinteraction.cts.HotwordDetectionServiceProximityTest"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ }
+ ]
+}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index b3aff65afb1f..eba9c7a5f14e 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -1915,7 +1915,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
return null;
}
- UUID getDeviceSensorUuid(AudioDeviceAttributes device) {
+ @Nullable UUID getDeviceSensorUuid(AudioDeviceAttributes device) {
synchronized (mDeviceStateLock) {
return mDeviceInventory.getDeviceSensorUuid(device);
}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 1312d082a832..c1f496905dba 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -16,6 +16,7 @@
package com.android.server.audio;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
@@ -375,7 +376,8 @@ public class AudioDeviceInventory {
makeLeAudioDeviceUnavailable(address, btInfo.mAudioSystemDevice);
} else if (switchToAvailable) {
makeLeAudioDeviceAvailable(address, BtHelper.getName(btInfo.mDevice),
- streamType, btInfo.mAudioSystemDevice, "onSetBtActiveDevice");
+ streamType, btInfo.mVolume, btInfo.mAudioSystemDevice,
+ "onSetBtActiveDevice");
}
break;
default: throw new IllegalArgumentException("Invalid profile "
@@ -1175,8 +1177,8 @@ public class AudioDeviceInventory {
}
@GuardedBy("mDevicesLock")
- private void makeLeAudioDeviceAvailable(String address, String name, int streamType, int device,
- String eventSource) {
+ private void makeLeAudioDeviceAvailable(String address, String name, int streamType,
+ int volumeIndex, int device, String eventSource) {
if (device != AudioSystem.DEVICE_NONE) {
/* Audio Policy sees Le Audio similar to A2DP. Let's make sure
* AUDIO_POLICY_FORCE_NO_BT_A2DP is not set
@@ -1197,7 +1199,9 @@ public class AudioDeviceInventory {
return;
}
- final int leAudioVolIndex = mDeviceBroker.getVssVolumeForDevice(streamType, device);
+ final int leAudioVolIndex = (volumeIndex == -1)
+ ? mDeviceBroker.getVssVolumeForDevice(streamType, device)
+ : volumeIndex;
final int maxIndex = mDeviceBroker.getMaxVssVolumeForStream(streamType);
mDeviceBroker.postSetLeAudioVolumeIndex(leAudioVolIndex, maxIndex, streamType);
mDeviceBroker.postApplyVolumeOnDevice(streamType, device, "makeLeAudioDeviceAvailable");
@@ -1511,7 +1515,7 @@ public class AudioDeviceInventory {
mDevRoleCapturePresetDispatchers.finishBroadcast();
}
- UUID getDeviceSensorUuid(AudioDeviceAttributes device) {
+ @Nullable UUID getDeviceSensorUuid(AudioDeviceAttributes device) {
final String key = DeviceInfo.makeDeviceListKey(device.getInternalType(),
device.getAddress());
synchronized (mDevicesLock) {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 5a20db309f19..785040ea8202 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -63,6 +63,7 @@ import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
+import android.content.res.Resources;
import android.database.ContentObserver;
import android.hardware.SensorPrivacyManager;
import android.hardware.SensorPrivacyManagerInternal;
@@ -412,10 +413,10 @@ public class AudioService extends IAudioService.Stub
protected static int[] MAX_STREAM_VOLUME = new int[] {
5, // STREAM_VOICE_CALL
7, // STREAM_SYSTEM
- 7, // STREAM_RING
+ 7, // STREAM_RING // configured by config_audio_ring_vol_steps
15, // STREAM_MUSIC
7, // STREAM_ALARM
- 7, // STREAM_NOTIFICATION
+ 7, // STREAM_NOTIFICATION // configured by config_audio_notif_vol_steps
15, // STREAM_BLUETOOTH_SCO
7, // STREAM_SYSTEM_ENFORCED
15, // STREAM_DTMF
@@ -1116,6 +1117,48 @@ public class AudioService extends IAudioService.Stub
MAX_STREAM_VOLUME[AudioSystem.STREAM_SYSTEM];
}
+ // Read following properties to configure max volume (number of steps) and default volume
+ // for STREAM_NOTIFICATION and STREAM_RING:
+ // config_audio_notif_vol_default
+ // config_audio_notif_vol_steps
+ // config_audio_ring_vol_default
+ // config_audio_ring_vol_steps
+ int[] streams = { AudioSystem.STREAM_NOTIFICATION, AudioSystem.STREAM_RING };
+ int[] stepsResId = { com.android.internal.R.integer.config_audio_notif_vol_steps,
+ com.android.internal.R.integer.config_audio_ring_vol_steps };
+ int[] defaultResId = { com.android.internal.R.integer.config_audio_notif_vol_default,
+ com.android.internal.R.integer.config_audio_ring_vol_default };
+ for (int s = 0; s < streams.length; s++) {
+ try {
+ final int maxVol = mContext.getResources().getInteger(stepsResId[s]);
+ if (maxVol <= 0) {
+ throw new IllegalArgumentException("Invalid negative max volume for stream "
+ + streams[s]);
+ }
+ Log.i(TAG, "Stream " + streams[s] + ": using max vol of " + maxVol);
+ MAX_STREAM_VOLUME[streams[s]] = maxVol;
+ } catch (Resources.NotFoundException e) {
+ Log.e(TAG, "Error querying max vol for stream type " + streams[s], e);
+ }
+ try {
+ final int defaultVol = mContext.getResources().getInteger(defaultResId[s]);
+ if (defaultVol > MAX_STREAM_VOLUME[streams[s]]) {
+ throw new IllegalArgumentException("Invalid default volume (" + defaultVol
+ + ") for stream " + streams[s] + ", greater than max volume of "
+ + MAX_STREAM_VOLUME[streams[s]]);
+ }
+ if (defaultVol < MIN_STREAM_VOLUME[streams[s]]) {
+ throw new IllegalArgumentException("Invalid default volume (" + defaultVol
+ + ") for stream " + streams[s] + ", lower than min volume of "
+ + MIN_STREAM_VOLUME[streams[s]]);
+ }
+ Log.i(TAG, "Stream " + streams[s] + ": using default vol of " + defaultVol);
+ AudioSystem.DEFAULT_STREAM_VOLUME[streams[s]] = defaultVol;
+ } catch (Resources.NotFoundException e) {
+ Log.e(TAG, "Error querying default vol for stream type " + streams[s], e);
+ }
+ }
+
if (looper == null) {
createAudioSystemThread();
} else {
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index e27fb1141850..aedbe4eb945a 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -353,6 +353,14 @@ public class SpatializerHelper {
mASA.getDevicesForAttributes(
DEFAULT_ATTRIBUTES, false /* forVolume */).toArray(ROUTING_DEVICES);
+ // check validity of routing information
+ if (ROUTING_DEVICES[0] == null) {
+ logloge("onRoutingUpdated: device is null, no Spatial Audio");
+ setDispatchAvailableState(false);
+ // not changing the spatializer level as this is likely a transient state
+ return;
+ }
+
// is media routed to a new device?
if (isWireless(ROUTING_DEVICES[0].getType())) {
addWirelessDeviceIfNew(ROUTING_DEVICES[0]);
@@ -563,7 +571,9 @@ public class SpatializerHelper {
// There may be different devices with the same device type (aliasing).
// We always send the full device state info on each change.
private void logDeviceState(SADeviceState deviceState, String event) {
- final String deviceName = AudioSystem.getDeviceName(deviceState.mDeviceType);
+ final int deviceType = AudioDeviceInfo.convertDeviceTypeToInternalDevice(
+ deviceState.mDeviceType);
+ final String deviceName = AudioSystem.getDeviceName(deviceType);
new MediaMetrics.Item(METRICS_DEVICE_PREFIX + deviceName)
.set(MediaMetrics.Property.ADDRESS, deviceState.mDeviceAddress)
.set(MediaMetrics.Property.ENABLED, deviceState.mEnabled ? "true" : "false")
@@ -1098,7 +1108,7 @@ public class SpatializerHelper {
logDeviceState(deviceState, "setHeadTrackerEnabled");
// check current routing to see if it affects the headtracking mode
- if (ROUTING_DEVICES[0].getType() == ada.getType()
+ if (ROUTING_DEVICES[0] != null && ROUTING_DEVICES[0].getType() == ada.getType()
&& ROUTING_DEVICES[0].getAddress().equals(ada.getAddress())) {
setDesiredHeadTrackingMode(enabled ? mDesiredHeadTrackingModeWhenEnabled
: Spatializer.HEAD_TRACKING_MODE_DISABLED);
@@ -1633,7 +1643,11 @@ public class SpatializerHelper {
private int getHeadSensorHandleUpdateTracker() {
int headHandle = -1;
- UUID routingDeviceUuid = mAudioService.getDeviceSensorUuid(ROUTING_DEVICES[0]);
+ final AudioDeviceAttributes currentDevice = ROUTING_DEVICES[0];
+ if (currentDevice == null) {
+ return headHandle;
+ }
+ UUID routingDeviceUuid = mAudioService.getDeviceSensorUuid(currentDevice);
// We limit only to Sensor.TYPE_HEAD_TRACKER here to avoid confusion
// with gaming sensors. (Note that Sensor.TYPE_ROTATION_VECTOR
// and Sensor.TYPE_GAME_ROTATION_VECTOR are supported internally by
@@ -1644,7 +1658,7 @@ public class SpatializerHelper {
final UUID uuid = sensor.getUuid();
if (uuid.equals(routingDeviceUuid)) {
headHandle = sensor.getHandle();
- if (!setHasHeadTracker(ROUTING_DEVICES[0])) {
+ if (!setHasHeadTracker(currentDevice)) {
headHandle = -1;
}
break;
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 16a060af66ad..931c692b5f01 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -752,7 +752,7 @@ public class Vpn {
return true;
}
- private boolean sendEventToVpnManagerApp(@NonNull String category, int errorClass,
+ private Intent buildVpnManagerEventIntent(@NonNull String category, int errorClass,
int errorCode, @NonNull final String packageName, @Nullable final String sessionKey,
@NonNull final VpnProfileState profileState, @Nullable final Network underlyingNetwork,
@Nullable final NetworkCapabilities nc, @Nullable final LinkProperties lp) {
@@ -771,6 +771,20 @@ public class Vpn {
intent.putExtra(VpnManager.EXTRA_ERROR_CODE, errorCode);
}
+ return intent;
+ }
+
+ private boolean sendEventToVpnManagerApp(@NonNull String category, int errorClass,
+ int errorCode, @NonNull final String packageName, @Nullable final String sessionKey,
+ @NonNull final VpnProfileState profileState, @Nullable final Network underlyingNetwork,
+ @Nullable final NetworkCapabilities nc, @Nullable final LinkProperties lp) {
+ final Intent intent = buildVpnManagerEventIntent(category, errorClass, errorCode,
+ packageName, sessionKey, profileState, underlyingNetwork, nc, lp);
+ return sendEventToVpnManagerApp(intent, packageName);
+ }
+
+ private boolean sendEventToVpnManagerApp(@NonNull final Intent intent,
+ @NonNull final String packageName) {
// Allow VpnManager app to temporarily run background services to handle this error.
// If an app requires anything beyond this grace period, they MUST either declare
// themselves as a foreground service, or schedule a job/workitem.
@@ -1182,12 +1196,25 @@ public class Vpn {
mContext.unbindService(mConnection);
cleanupVpnStateLocked();
} else if (mVpnRunner != null) {
- if (!VpnConfig.LEGACY_VPN.equals(mPackage)) {
- notifyVpnManagerVpnStopped(mPackage, mOwnerUID);
+ // Build intent first because the sessionKey will be reset after performing
+ // VpnRunner.exit(). Also, cache mOwnerUID even if ownerUID will not be changed in
+ // VpnRunner.exit() to prevent design being changed in the future.
+ // TODO(b/230548427): Remove SDK check once VPN related stuff are decoupled from
+ // ConnectivityServiceTest.
+ final int ownerUid = mOwnerUID;
+ Intent intent = null;
+ if (SdkLevel.isAtLeastT() && isVpnApp(mPackage)) {
+ intent = buildVpnManagerEventIntent(
+ VpnManager.CATEGORY_EVENT_DEACTIVATED_BY_USER,
+ -1 /* errorClass */, -1 /* errorCode*/, mPackage,
+ getSessionKeyLocked(), makeVpnProfileStateLocked(),
+ null /* underlyingNetwork */, null /* nc */, null /* lp */);
}
-
// cleanupVpnStateLocked() is called from mVpnRunner.exit()
mVpnRunner.exit();
+ if (intent != null && isVpnApp(mPackage)) {
+ notifyVpnManagerVpnStopped(mPackage, ownerUid, intent);
+ }
}
try {
@@ -2886,6 +2913,9 @@ public class Vpn {
final LinkProperties lp;
synchronized (Vpn.this) {
+ // Ignore stale runner.
+ if (mVpnRunner != this) return;
+
mInterface = interfaceName;
mConfig.mtu = maxMtu;
mConfig.interfaze = mInterface;
@@ -2987,6 +3017,9 @@ public class Vpn {
try {
synchronized (Vpn.this) {
+ // Ignore stale runner.
+ if (mVpnRunner != this) return;
+
mConfig.underlyingNetworks = new Network[] {network};
mNetworkCapabilities =
new NetworkCapabilities.Builder(mNetworkCapabilities)
@@ -3076,7 +3109,12 @@ public class Vpn {
// Clear mInterface to prevent Ikev2VpnRunner being cleared when
// interfaceRemoved() is called.
- mInterface = null;
+ synchronized (Vpn.this) {
+ // Ignore stale runner.
+ if (mVpnRunner != this) return;
+
+ mInterface = null;
+ }
// Without MOBIKE, we have no way to seamlessly migrate. Close on old
// (non-default) network, and start the new one.
resetIkeState();
@@ -3261,6 +3299,9 @@ public class Vpn {
/** Marks the state as FAILED, and disconnects. */
private void markFailedAndDisconnect(Exception exception) {
synchronized (Vpn.this) {
+ // Ignore stale runner.
+ if (mVpnRunner != this) return;
+
updateState(DetailedState.FAILED, exception.getMessage());
}
@@ -3345,6 +3386,9 @@ public class Vpn {
}
synchronized (Vpn.this) {
+ // Ignore stale runner.
+ if (mVpnRunner != this) return;
+
// TODO(b/230548427): Remove SDK check once VPN related stuff are
// decoupled from ConnectivityServiceTest.
if (SdkLevel.isAtLeastT() && category != null && isVpnApp(mPackage)) {
@@ -3371,6 +3415,9 @@ public class Vpn {
Log.d(TAG, "Resetting state for token: " + mCurrentToken);
synchronized (Vpn.this) {
+ // Ignore stale runner.
+ if (mVpnRunner != this) return;
+
// Since this method handles non-fatal errors only, set mInterface to null to
// prevent the NetworkManagementEventObserver from killing this VPN based on the
// interface going down (which we expect).
@@ -3993,6 +4040,7 @@ public class Vpn {
mConfig.proxyInfo = profile.proxy;
mConfig.requiresInternetValidation = profile.requiresInternetValidation;
mConfig.excludeLocalRoutes = profile.excludeLocalRoutes;
+ mConfig.allowBypass = profile.isBypassable;
switch (profile.type) {
case VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS:
@@ -4042,13 +4090,23 @@ public class Vpn {
// To stop the VPN profile, the caller must be the current prepared package and must be
// running an Ikev2VpnProfile.
if (isCurrentIkev2VpnLocked(packageName)) {
- notifyVpnManagerVpnStopped(packageName, mOwnerUID);
+ // Build intent first because the sessionKey will be reset after performing
+ // VpnRunner.exit(). Also, cache mOwnerUID even if ownerUID will not be changed in
+ // VpnRunner.exit() to prevent design being changed in the future.
+ final int ownerUid = mOwnerUID;
+ final Intent intent = buildVpnManagerEventIntent(
+ VpnManager.CATEGORY_EVENT_DEACTIVATED_BY_USER,
+ -1 /* errorClass */, -1 /* errorCode*/, packageName,
+ getSessionKeyLocked(), makeVpnProfileStateLocked(),
+ null /* underlyingNetwork */, null /* nc */, null /* lp */);
mVpnRunner.exit();
+ notifyVpnManagerVpnStopped(packageName, ownerUid, intent);
}
}
- private synchronized void notifyVpnManagerVpnStopped(String packageName, int ownerUID) {
+ private synchronized void notifyVpnManagerVpnStopped(String packageName, int ownerUID,
+ Intent intent) {
mAppOpsManager.finishOp(
AppOpsManager.OPSTR_ESTABLISH_VPN_MANAGER, ownerUID, packageName, null);
// The underlying network, NetworkCapabilities and LinkProperties are not
@@ -4057,10 +4115,7 @@ public class Vpn {
// TODO(b/230548427): Remove SDK check once VPN related stuff are decoupled from
// ConnectivityServiceTest.
if (SdkLevel.isAtLeastT()) {
- sendEventToVpnManagerApp(VpnManager.CATEGORY_EVENT_DEACTIVATED_BY_USER,
- -1 /* errorClass */, -1 /* errorCode*/, packageName,
- getSessionKeyLocked(), makeVpnProfileStateLocked(),
- null /* underlyingNetwork */, null /* nc */, null /* lp */);
+ sendEventToVpnManagerApp(intent, packageName);
}
}
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index a817cea1a674..6145a91cf7cd 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -148,6 +148,12 @@ class AutomaticBrightnessController {
// The currently accepted nominal ambient light level.
private float mAmbientLux;
+ // The last calculated ambient light level (long time window).
+ private float mSlowAmbientLux;
+
+ // The last calculated ambient light level (short time window).
+ private float mFastAmbientLux;
+
// The last ambient lux value prior to passing the darkening or brightening threshold.
private float mPreThresholdLux;
@@ -440,6 +446,14 @@ class AutomaticBrightnessController {
return mAmbientLux;
}
+ float getSlowAmbientLux() {
+ return mSlowAmbientLux;
+ }
+
+ float getFastAmbientLux() {
+ return mFastAmbientLux;
+ }
+
private boolean setDisplayPolicy(int policy) {
if (mDisplayPolicy == policy) {
return false;
@@ -812,20 +826,20 @@ class AutomaticBrightnessController {
// proposed ambient light value since the slow value might be sufficiently far enough away
// from the fast value to cause a recalculation while its actually just converging on
// the fast value still.
- float slowAmbientLux = calculateAmbientLux(time, mAmbientLightHorizonLong);
- float fastAmbientLux = calculateAmbientLux(time, mAmbientLightHorizonShort);
+ mSlowAmbientLux = calculateAmbientLux(time, mAmbientLightHorizonLong);
+ mFastAmbientLux = calculateAmbientLux(time, mAmbientLightHorizonShort);
- if ((slowAmbientLux >= mAmbientBrighteningThreshold
- && fastAmbientLux >= mAmbientBrighteningThreshold
+ if ((mSlowAmbientLux >= mAmbientBrighteningThreshold
+ && mFastAmbientLux >= mAmbientBrighteningThreshold
&& nextBrightenTransition <= time)
- || (slowAmbientLux <= mAmbientDarkeningThreshold
- && fastAmbientLux <= mAmbientDarkeningThreshold
+ || (mSlowAmbientLux <= mAmbientDarkeningThreshold
+ && mFastAmbientLux <= mAmbientDarkeningThreshold
&& nextDarkenTransition <= time)) {
mPreThresholdLux = mAmbientLux;
- setAmbientLux(fastAmbientLux);
+ setAmbientLux(mFastAmbientLux);
if (mLoggingEnabled) {
Slog.d(TAG, "updateAmbientLux: "
- + ((fastAmbientLux > mAmbientLux) ? "Brightened" : "Darkened") + ": "
+ + ((mFastAmbientLux > mAmbientLux) ? "Brightened" : "Darkened") + ": "
+ "mBrighteningLuxThreshold=" + mAmbientBrighteningThreshold + ", "
+ "mAmbientLightRingBuffer=" + mAmbientLightRingBuffer + ", "
+ "mAmbientLux=" + mAmbientLux);
@@ -994,8 +1008,9 @@ class AutomaticBrightnessController {
final String packageName = info.topActivity.getPackageName();
// If the app didn't change, there's nothing to do. Otherwise, we have to
// update the category and re-apply the brightness correction.
- if (mForegroundAppPackageName != null
- && mForegroundAppPackageName.equals(packageName)) {
+ String currentForegroundAppPackageName = mForegroundAppPackageName;
+ if (currentForegroundAppPackageName != null
+ && currentForegroundAppPackageName.equals(packageName)) {
return;
}
mPendingForegroundAppPackageName = packageName;
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 6f3a0c516a5b..2cbdad751b59 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -1640,11 +1640,16 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
// brightness cap, RBC state, etc.
mTempBrightnessEvent.setTime(System.currentTimeMillis());
mTempBrightnessEvent.setBrightness(brightnessState);
+ mTempBrightnessEvent.setPhysicalDisplayId(mUniqueDisplayId);
mTempBrightnessEvent.setReason(mBrightnessReason);
mTempBrightnessEvent.setHbmMax(mHbmController.getCurrentBrightnessMax());
mTempBrightnessEvent.setHbmMode(mHbmController.getHighBrightnessMode());
mTempBrightnessEvent.setFlags(mTempBrightnessEvent.getFlags()
- | (mIsRbcActive ? BrightnessEvent.FLAG_RBC : 0));
+ | (mIsRbcActive ? BrightnessEvent.FLAG_RBC : 0)
+ | (mPowerRequest.lowPowerMode ? BrightnessEvent.FLAG_LOW_POWER_MODE : 0));
+ mTempBrightnessEvent.setRbcStrength(mCdsi != null
+ ? mCdsi.getReduceBrightColorsStrength() : -1);
+ mTempBrightnessEvent.setPowerFactor(mPowerRequest.screenLowPowerBrightnessFactor);
// Temporary is what we use during slider interactions. We avoid logging those so that
// we don't spam logcat when the slider is being used.
boolean tempToTempTransition =
@@ -1653,9 +1658,17 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
== BrightnessReason.REASON_TEMPORARY;
if ((!mTempBrightnessEvent.equalsMainData(mLastBrightnessEvent) && !tempToTempTransition)
|| brightnessAdjustmentFlags != 0) {
+ float lastBrightness = mLastBrightnessEvent.getBrightness();
+ mTempBrightnessEvent.setInitialBrightness(lastBrightness);
+ mTempBrightnessEvent.setFastAmbientLux(
+ mAutomaticBrightnessController == null
+ ? -1f : mAutomaticBrightnessController.getFastAmbientLux());
+ mTempBrightnessEvent.setSlowAmbientLux(
+ mAutomaticBrightnessController == null
+ ? -1f : mAutomaticBrightnessController.getSlowAmbientLux());
+ mTempBrightnessEvent.setAutomaticBrightnessEnabled(mPowerRequest.useAutoBrightness);
mLastBrightnessEvent.copyFrom(mTempBrightnessEvent);
BrightnessEvent newEvent = new BrightnessEvent(mTempBrightnessEvent);
-
// Adjustment flags (and user-set flag) only get added after the equality checks since
// they are transient.
newEvent.setAdjustmentFlags(brightnessAdjustmentFlags);
@@ -1663,6 +1676,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
? BrightnessEvent.FLAG_USER_SET : 0));
Slog.i(mTag, newEvent.toString(/* includeTime= */ false));
+ if (userSetBrightnessChanged) {
+ logManualBrightnessEvent(newEvent);
+ }
if (mBrightnessEventRingBuffer != null) {
mBrightnessEventRingBuffer.append(newEvent);
}
@@ -2752,6 +2768,31 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
}
}
+ private void logManualBrightnessEvent(BrightnessEvent event) {
+ float appliedLowPowerMode = event.isLowPowerModeSet() ? event.getPowerFactor() : -1f;
+ int appliedRbcStrength = event.isRbcEnabled() ? event.getRbcStrength() : -1;
+ float appliedHbmMaxNits =
+ event.getHbmMode() == BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF
+ ? -1f : convertToNits(event.getHbmMax());
+ // thermalCapNits set to -1 if not currently capping max brightness
+ float appliedThermalCapNits =
+ event.getThermalMax() == PowerManager.BRIGHTNESS_MAX
+ ? -1f : convertToNits(event.getThermalMax());
+
+ FrameworkStatsLog.write(FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED,
+ convertToNits(event.getInitialBrightness()),
+ convertToNits(event.getBrightness()),
+ event.getSlowAmbientLux(),
+ event.getPhysicalDisplayId(),
+ event.isShortTermModelActive(),
+ appliedLowPowerMode,
+ appliedRbcStrength,
+ appliedHbmMaxNits,
+ appliedThermalCapNits,
+ event.isAutomaticBrightnessEnabled(),
+ FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__REASON__REASON_MANUAL);
+ }
+
private final class DisplayControllerHandler extends Handler {
DisplayControllerHandler(Looper looper) {
super(looper, null, true /*async*/);
diff --git a/services/core/java/com/android/server/display/brightness/BrightnessEvent.java b/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
index d831dbd4bb23..e3fa6220edf4 100644
--- a/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
+++ b/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
@@ -21,6 +21,8 @@ import android.os.PowerManager;
import android.os.SystemClock;
import android.util.TimeUtils;
+import com.android.internal.annotations.VisibleForTesting;
+
/**
* Represents a particular brightness change event.
*/
@@ -29,21 +31,29 @@ public final class BrightnessEvent {
public static final int FLAG_INVALID_LUX = 0x2;
public static final int FLAG_DOZE_SCALE = 0x4;
public static final int FLAG_USER_SET = 0x8;
- public static final int FLAG_IDLE_CURVE = 0x16;
+ public static final int FLAG_IDLE_CURVE = 0x10;
+ public static final int FLAG_LOW_POWER_MODE = 0x20;
private BrightnessReason mReason = new BrightnessReason();
private int mDisplayId;
+ private String mPhysicalDisplayId;
+ private long mTime;
private float mLux;
+ private float mFastAmbientLux;
+ private float mSlowAmbientLux;
private float mPreThresholdLux;
- private long mTime;
+ private float mInitialBrightness;
private float mBrightness;
private float mRecommendedBrightness;
private float mPreThresholdBrightness;
+ private int mHbmMode;
private float mHbmMax;
+ private int mRbcStrength;
private float mThermalMax;
- private int mHbmMode;
+ private float mPowerFactor;
private int mFlags;
private int mAdjustmentFlags;
+ private boolean mAutomaticBrightnessEnabled;
public BrightnessEvent(BrightnessEvent that) {
copyFrom(that);
@@ -60,37 +70,59 @@ public final class BrightnessEvent {
* @param that BrightnessEvent which is to be copied
*/
public void copyFrom(BrightnessEvent that) {
+ mReason.set(that.getReason());
mDisplayId = that.getDisplayId();
+ mPhysicalDisplayId = that.getPhysicalDisplayId();
mTime = that.getTime();
+ // Lux values
mLux = that.getLux();
+ mFastAmbientLux = that.getFastAmbientLux();
+ mSlowAmbientLux = that.getSlowAmbientLux();
mPreThresholdLux = that.getPreThresholdLux();
+ // Brightness values
+ mInitialBrightness = that.getInitialBrightness();
mBrightness = that.getBrightness();
mRecommendedBrightness = that.getRecommendedBrightness();
mPreThresholdBrightness = that.getPreThresholdBrightness();
+ // Different brightness modulations
+ mHbmMode = that.getHbmMode();
mHbmMax = that.getHbmMax();
+ mRbcStrength = that.getRbcStrength();
mThermalMax = that.getThermalMax();
+ mPowerFactor = that.getPowerFactor();
mFlags = that.getFlags();
- mHbmMode = that.getHbmMode();
- mReason.set(that.getReason());
mAdjustmentFlags = that.getAdjustmentFlags();
+ // Auto-brightness setting
+ mAutomaticBrightnessEnabled = that.isAutomaticBrightnessEnabled();
}
/**
* A utility to reset the BrightnessEvent to default values
*/
public void reset() {
+ mReason.set(null);
mTime = SystemClock.uptimeMillis();
- mBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
- mRecommendedBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+ mPhysicalDisplayId = "";
+ // Lux values
mLux = 0;
+ mFastAmbientLux = 0;
+ mSlowAmbientLux = 0;
mPreThresholdLux = 0;
+ // Brightness values
+ mInitialBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+ mBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+ mRecommendedBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
mPreThresholdBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+ // Different brightness modulations
+ mHbmMode = BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF;
mHbmMax = PowerManager.BRIGHTNESS_MAX;
+ mRbcStrength = 0;
mThermalMax = PowerManager.BRIGHTNESS_MAX;
+ mPowerFactor = 1f;
mFlags = 0;
- mHbmMode = BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF;
- mReason.set(null);
mAdjustmentFlags = 0;
+ // Auto-brightness setting
+ mAutomaticBrightnessEnabled = true;
}
/**
@@ -104,23 +136,34 @@ public final class BrightnessEvent {
public boolean equalsMainData(BrightnessEvent that) {
// This equals comparison purposefully ignores time since it is regularly changing and
// we don't want to log a brightness event just because the time changed.
- return mDisplayId == that.mDisplayId
+ return mReason.equals(that.mReason)
+ && mDisplayId == that.mDisplayId
+ && mPhysicalDisplayId.equals(that.mPhysicalDisplayId)
+ && Float.floatToRawIntBits(mLux) == Float.floatToRawIntBits(that.mLux)
+ && Float.floatToRawIntBits(mFastAmbientLux)
+ == Float.floatToRawIntBits(that.mFastAmbientLux)
+ && Float.floatToRawIntBits(mSlowAmbientLux)
+ == Float.floatToRawIntBits(that.mSlowAmbientLux)
+ && Float.floatToRawIntBits(mPreThresholdLux)
+ == Float.floatToRawIntBits(that.mPreThresholdLux)
+ && Float.floatToRawIntBits(mInitialBrightness)
+ == Float.floatToRawIntBits(that.mInitialBrightness)
&& Float.floatToRawIntBits(mBrightness)
== Float.floatToRawIntBits(that.mBrightness)
&& Float.floatToRawIntBits(mRecommendedBrightness)
== Float.floatToRawIntBits(that.mRecommendedBrightness)
&& Float.floatToRawIntBits(mPreThresholdBrightness)
== Float.floatToRawIntBits(that.mPreThresholdBrightness)
- && Float.floatToRawIntBits(mLux) == Float.floatToRawIntBits(that.mLux)
- && Float.floatToRawIntBits(mPreThresholdLux)
- == Float.floatToRawIntBits(that.mPreThresholdLux)
- && Float.floatToRawIntBits(mHbmMax) == Float.floatToRawIntBits(that.mHbmMax)
&& mHbmMode == that.mHbmMode
+ && Float.floatToRawIntBits(mHbmMax) == Float.floatToRawIntBits(that.mHbmMax)
+ && mRbcStrength == that.mRbcStrength
&& Float.floatToRawIntBits(mThermalMax)
== Float.floatToRawIntBits(that.mThermalMax)
+ && Float.floatToRawIntBits(mPowerFactor)
+ == Float.floatToRawIntBits(that.mPowerFactor)
&& mFlags == that.mFlags
&& mAdjustmentFlags == that.mAdjustmentFlags
- && mReason.equals(that.mReason);
+ && mAutomaticBrightnessEnabled == that.mAutomaticBrightnessEnabled;
}
/**
@@ -133,16 +176,23 @@ public final class BrightnessEvent {
return (includeTime ? TimeUtils.formatForLogging(mTime) + " - " : "")
+ "BrightnessEvent: "
+ "disp=" + mDisplayId
+ + ", physDisp=" + mPhysicalDisplayId
+ ", brt=" + mBrightness + ((mFlags & FLAG_USER_SET) != 0 ? "(user_set)" : "")
+ + ", initBrt=" + mInitialBrightness
+ ", rcmdBrt=" + mRecommendedBrightness
+ ", preBrt=" + mPreThresholdBrightness
+ ", lux=" + mLux
+ + ", fastLux=" + mFastAmbientLux
+ + ", slowLux=" + mSlowAmbientLux
+ ", preLux=" + mPreThresholdLux
+ ", hbmMax=" + mHbmMax
+ ", hbmMode=" + BrightnessInfo.hbmToString(mHbmMode)
+ + ", rbcStrength=" + mRbcStrength
+ ", thrmMax=" + mThermalMax
+ + ", powerFactor=" + mPowerFactor
+ ", flags=" + flagsToString()
- + ", reason=" + mReason.toString(mAdjustmentFlags);
+ + ", reason=" + mReason.toString(mAdjustmentFlags)
+ + ", autoBrightness=" + mAutomaticBrightnessEnabled;
}
@Override
@@ -150,12 +200,20 @@ public final class BrightnessEvent {
return toString(/* includeTime */ true);
}
+ public BrightnessReason getReason() {
+ return mReason;
+ }
+
public void setReason(BrightnessReason reason) {
this.mReason = reason;
}
- public BrightnessReason getReason() {
- return mReason;
+ public long getTime() {
+ return mTime;
+ }
+
+ public void setTime(long time) {
+ this.mTime = time;
}
public int getDisplayId() {
@@ -166,6 +224,14 @@ public final class BrightnessEvent {
this.mDisplayId = displayId;
}
+ public String getPhysicalDisplayId() {
+ return mPhysicalDisplayId;
+ }
+
+ public void setPhysicalDisplayId(String mPhysicalDisplayId) {
+ this.mPhysicalDisplayId = mPhysicalDisplayId;
+ }
+
public float getLux() {
return mLux;
}
@@ -174,6 +240,22 @@ public final class BrightnessEvent {
this.mLux = lux;
}
+ public float getFastAmbientLux() {
+ return mFastAmbientLux;
+ }
+
+ public void setFastAmbientLux(float mFastAmbientLux) {
+ this.mFastAmbientLux = mFastAmbientLux;
+ }
+
+ public float getSlowAmbientLux() {
+ return mSlowAmbientLux;
+ }
+
+ public void setSlowAmbientLux(float mSlowAmbientLux) {
+ this.mSlowAmbientLux = mSlowAmbientLux;
+ }
+
public float getPreThresholdLux() {
return mPreThresholdLux;
}
@@ -182,12 +264,12 @@ public final class BrightnessEvent {
this.mPreThresholdLux = preThresholdLux;
}
- public long getTime() {
- return mTime;
+ public float getInitialBrightness() {
+ return mInitialBrightness;
}
- public void setTime(long time) {
- this.mTime = time;
+ public void setInitialBrightness(float mInitialBrightness) {
+ this.mInitialBrightness = mInitialBrightness;
}
public float getBrightness() {
@@ -214,6 +296,14 @@ public final class BrightnessEvent {
this.mPreThresholdBrightness = preThresholdBrightness;
}
+ public int getHbmMode() {
+ return mHbmMode;
+ }
+
+ public void setHbmMode(int hbmMode) {
+ this.mHbmMode = hbmMode;
+ }
+
public float getHbmMax() {
return mHbmMax;
}
@@ -222,6 +312,18 @@ public final class BrightnessEvent {
this.mHbmMax = hbmMax;
}
+ public int getRbcStrength() {
+ return mRbcStrength;
+ }
+
+ public void setRbcStrength(int mRbcStrength) {
+ this.mRbcStrength = mRbcStrength;
+ }
+
+ public boolean isRbcEnabled() {
+ return (mFlags & FLAG_RBC) != 0;
+ }
+
public float getThermalMax() {
return mThermalMax;
}
@@ -230,12 +332,16 @@ public final class BrightnessEvent {
this.mThermalMax = thermalMax;
}
- public int getHbmMode() {
- return mHbmMode;
+ public float getPowerFactor() {
+ return mPowerFactor;
}
- public void setHbmMode(int hbmMode) {
- this.mHbmMode = hbmMode;
+ public void setPowerFactor(float mPowerFactor) {
+ this.mPowerFactor = mPowerFactor;
+ }
+
+ public boolean isLowPowerModeSet() {
+ return (mFlags & FLAG_LOW_POWER_MODE) != 0;
}
public int getFlags() {
@@ -246,6 +352,10 @@ public final class BrightnessEvent {
this.mFlags = flags;
}
+ public boolean isShortTermModelActive() {
+ return (mFlags & FLAG_USER_SET) != 0;
+ }
+
public int getAdjustmentFlags() {
return mAdjustmentFlags;
}
@@ -254,11 +364,25 @@ public final class BrightnessEvent {
this.mAdjustmentFlags = adjustmentFlags;
}
- private String flagsToString() {
+ public boolean isAutomaticBrightnessEnabled() {
+ return mAutomaticBrightnessEnabled;
+ }
+
+ public void setAutomaticBrightnessEnabled(boolean mAutomaticBrightnessEnabled) {
+ this.mAutomaticBrightnessEnabled = mAutomaticBrightnessEnabled;
+ }
+
+ /**
+ * A utility to stringify flags from a BrightnessEvent
+ * @return Stringified flags from BrightnessEvent
+ */
+ @VisibleForTesting
+ public String flagsToString() {
return ((mFlags & FLAG_USER_SET) != 0 ? "user_set " : "")
+ ((mFlags & FLAG_RBC) != 0 ? "rbc " : "")
+ ((mFlags & FLAG_INVALID_LUX) != 0 ? "invalid_lux " : "")
+ ((mFlags & FLAG_DOZE_SCALE) != 0 ? "doze_scale " : "")
- + ((mFlags & FLAG_IDLE_CURVE) != 0 ? "idle_curve " : "");
+ + ((mFlags & FLAG_IDLE_CURVE) != 0 ? "idle_curve " : "")
+ + ((mFlags & FLAG_LOW_POWER_MODE) != 0 ? "low_power_mode " : "");
}
}
diff --git a/services/core/java/com/android/server/display/color/ColorDisplayService.java b/services/core/java/com/android/server/display/color/ColorDisplayService.java
index 6e304163995b..366dfd1efd29 100644
--- a/services/core/java/com/android/server/display/color/ColorDisplayService.java
+++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java
@@ -1515,6 +1515,10 @@ public final class ColorDisplayService extends SystemService {
return mReduceBrightColorsTintController.isActivated();
}
+ public int getReduceBrightColorsStrength() {
+ return mReduceBrightColorsTintController.getStrength();
+ }
+
/**
* Gets the computed brightness, in nits, when the reduce bright colors feature is applied
* at the current strength.
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index 7b60345caf87..4e0489a5c4bc 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -218,6 +218,7 @@ public final class DreamManagerService extends SystemService {
}, pw, "", 200);
}
+ /** Whether a real dream is occurring. */
private boolean isDreamingInternal() {
synchronized (mLock) {
return mCurrentDreamToken != null && !mCurrentDreamIsPreview
@@ -225,6 +226,13 @@ public final class DreamManagerService extends SystemService {
}
}
+ /** Whether a real dream, or a dream preview is occurring. */
+ private boolean isDreamingOrInPreviewInternal() {
+ synchronized (mLock) {
+ return mCurrentDreamToken != null && !mCurrentDreamIsWaking;
+ }
+ }
+
protected void requestStartDreamFromShell() {
requestDreamInternal();
}
@@ -695,6 +703,19 @@ public final class DreamManagerService extends SystemService {
}
@Override // Binder call
+ public boolean isDreamingOrInPreview() {
+ checkPermission(android.Manifest.permission.READ_DREAM_STATE);
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ return isDreamingOrInPreviewInternal();
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+
+ @Override // Binder call
public void dream() {
checkPermission(android.Manifest.permission.WRITE_DREAM_STATE);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index d48acb18722a..23f437392d2b 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -910,11 +910,14 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
final String mImeControlTargetName;
@Nullable
final String mImeTargetNameFromWm;
+ @Nullable
+ final String mImeSurfaceParentName;
Entry(ClientState client, EditorInfo editorInfo, String focusedWindowName,
@SoftInputModeFlags int softInputMode, @SoftInputShowHideReason int reason,
boolean inFullscreenMode, String requestWindowName,
- @Nullable String imeControlTargetName, @Nullable String imeTargetName) {
+ @Nullable String imeControlTargetName, @Nullable String imeTargetName,
+ @Nullable String imeSurfaceParentName) {
mClientState = client;
mEditorInfo = editorInfo;
mFocusedWindowName = focusedWindowName;
@@ -926,6 +929,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
mRequestWindowName = requestWindowName;
mImeControlTargetName = imeControlTargetName;
mImeTargetNameFromWm = imeTargetName;
+ mImeSurfaceParentName = imeSurfaceParentName;
}
}
@@ -972,6 +976,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
pw.println(" imeTargetNameFromWm=" + entry.mImeTargetNameFromWm);
pw.print(prefix);
+ pw.println(" imeSurfaceParentName=" + entry.mImeSurfaceParentName);
+
+ pw.print(prefix);
pw.print(" editorInfo: ");
pw.print(" inputType=" + entry.mEditorInfo.inputType);
pw.print(" privateImeOptions=" + entry.mEditorInfo.privateImeOptions);
@@ -4676,7 +4683,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
mSoftInputShowHideHistory.addEntry(new SoftInputShowHideHistory.Entry(
mCurFocusedWindowClient, mCurEditorInfo, info.focusedWindowName,
mCurFocusedWindowSoftInputMode, reason, mInFullscreenMode,
- info.requestWindowName, info.imeControlTargetName, info.imeLayerTargetName));
+ info.requestWindowName, info.imeControlTargetName, info.imeLayerTargetName,
+ info.imeSurfaceParentName));
}
@BinderThread
diff --git a/services/core/java/com/android/server/location/LocationPermissions.java b/services/core/java/com/android/server/location/LocationPermissions.java
index ca2ff60203ca..f7da0d8639b8 100644
--- a/services/core/java/com/android/server/location/LocationPermissions.java
+++ b/services/core/java/com/android/server/location/LocationPermissions.java
@@ -26,8 +26,10 @@ import android.app.AppOpsManager;
import android.content.Context;
import android.os.Binder;
+import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
/** Utility class for dealing with location permissions. */
public final class LocationPermissions {
@@ -49,6 +51,7 @@ public final class LocationPermissions {
*/
public static final int PERMISSION_FINE = 2;
+ @Target(ElementType.TYPE_USE)
@IntDef({PERMISSION_NONE, PERMISSION_COARSE, PERMISSION_FINE})
@Retention(RetentionPolicy.SOURCE)
public @interface PermissionLevel {}
diff --git a/services/core/java/com/android/server/location/geofence/GeofenceKey.java b/services/core/java/com/android/server/location/geofence/GeofenceKey.java
deleted file mode 100644
index bbfa68f1e292..000000000000
--- a/services/core/java/com/android/server/location/geofence/GeofenceKey.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.location.geofence;
-
-import android.app.PendingIntent;
-import android.location.Geofence;
-
-import com.android.server.location.listeners.PendingIntentListenerRegistration;
-
-import java.util.Objects;
-
-// geofencing unfortunately allows multiple geofences under the same pending intent, even though
-// this makes no real sense. therefore we manufacture an artificial key to use (pendingintent +
-// geofence) instead of (pendingintent).
-final class GeofenceKey implements PendingIntentListenerRegistration.PendingIntentKey {
-
- private final PendingIntent mPendingIntent;
- private final Geofence mGeofence;
-
- GeofenceKey(PendingIntent pendingIntent, Geofence geofence) {
- mPendingIntent = Objects.requireNonNull(pendingIntent);
- mGeofence = Objects.requireNonNull(geofence);
- }
-
- @Override
- public PendingIntent getPendingIntent() {
- return mPendingIntent;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (!(o instanceof GeofenceKey)) {
- return false;
- }
- GeofenceKey that = (GeofenceKey) o;
- return mPendingIntent.equals(that.mPendingIntent) && mGeofence.equals(that.mGeofence);
- }
-
- @Override
- public int hashCode() {
- return mPendingIntent.hashCode();
- }
-}
diff --git a/services/core/java/com/android/server/location/geofence/GeofenceManager.java b/services/core/java/com/android/server/location/geofence/GeofenceManager.java
index b6342a472023..0f5e3d46fd22 100644
--- a/services/core/java/com/android/server/location/geofence/GeofenceManager.java
+++ b/services/core/java/com/android/server/location/geofence/GeofenceManager.java
@@ -59,8 +59,8 @@ import java.util.Objects;
* Manages all geofences.
*/
public class GeofenceManager extends
- ListenerMultiplexer<GeofenceKey, PendingIntent, GeofenceManager.GeofenceRegistration,
- LocationRequest> implements
+ ListenerMultiplexer<GeofenceManager.GeofenceKey, PendingIntent,
+ GeofenceManager.GeofenceRegistration, LocationRequest> implements
LocationListener {
private static final String TAG = "GeofenceManager";
@@ -73,13 +73,49 @@ public class GeofenceManager extends
private static final long MAX_LOCATION_AGE_MS = 5 * 60 * 1000L; // five minutes
private static final long MAX_LOCATION_INTERVAL_MS = 2 * 60 * 60 * 1000; // two hours
- protected final class GeofenceRegistration extends
- PendingIntentListenerRegistration<Geofence, PendingIntent> {
+ // geofencing unfortunately allows multiple geofences under the same pending intent, even though
+ // this makes no real sense. therefore we manufacture an artificial key to use (pendingintent +
+ // geofence) instead of (pendingintent).
+ static class GeofenceKey {
+
+ private final PendingIntent mPendingIntent;
+ private final Geofence mGeofence;
+
+ GeofenceKey(PendingIntent pendingIntent, Geofence geofence) {
+ mPendingIntent = Objects.requireNonNull(pendingIntent);
+ mGeofence = Objects.requireNonNull(geofence);
+ }
+
+ public PendingIntent getPendingIntent() {
+ return mPendingIntent;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof GeofenceKey) {
+ GeofenceKey that = (GeofenceKey) o;
+ return mPendingIntent.equals(that.mPendingIntent) && mGeofence.equals(
+ that.mGeofence);
+ }
+
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return mPendingIntent.hashCode();
+ }
+ }
+
+ protected class GeofenceRegistration extends
+ PendingIntentListenerRegistration<GeofenceKey, PendingIntent> {
private static final int STATE_UNKNOWN = 0;
private static final int STATE_INSIDE = 1;
private static final int STATE_OUTSIDE = 2;
+ private final Geofence mGeofence;
+ private final CallerIdentity mIdentity;
private final Location mCenter;
private final PowerManager.WakeLock mWakeLock;
@@ -89,13 +125,15 @@ public class GeofenceManager extends
// spam us, and because checking the values may be more expensive
private boolean mPermitted;
- private @Nullable Location mCachedLocation;
+ @Nullable private Location mCachedLocation;
private float mCachedLocationDistanceM;
- protected GeofenceRegistration(Geofence geofence, CallerIdentity identity,
+ GeofenceRegistration(Geofence geofence, CallerIdentity identity,
PendingIntent pendingIntent) {
- super(geofence, identity, pendingIntent);
+ super(pendingIntent);
+ mGeofence = geofence;
+ mIdentity = identity;
mCenter = new Location("");
mCenter.setLatitude(geofence.getLatitude());
mCenter.setLongitude(geofence.getLongitude());
@@ -107,16 +145,36 @@ public class GeofenceManager extends
mWakeLock.setWorkSource(identity.addToWorkSource(null));
}
+ public Geofence getGeofence() {
+ return mGeofence;
+ }
+
+ public CallerIdentity getIdentity() {
+ return mIdentity;
+ }
+
+ @Override
+ public String getTag() {
+ return TAG;
+ }
+
+ @Override
+ protected PendingIntent getPendingIntentFromKey(GeofenceKey geofenceKey) {
+ return geofenceKey.getPendingIntent();
+ }
+
@Override
protected GeofenceManager getOwner() {
return GeofenceManager.this;
}
@Override
- protected void onPendingIntentListenerRegister() {
+ protected void onRegister() {
+ super.onRegister();
+
mGeofenceState = STATE_UNKNOWN;
mPermitted = mLocationPermissionsHelper.hasLocationPermissions(PERMISSION_FINE,
- getIdentity());
+ mIdentity);
}
@Override
@@ -132,7 +190,7 @@ public class GeofenceManager extends
}
boolean onLocationPermissionsChanged(@Nullable String packageName) {
- if (packageName == null || getIdentity().getPackageName().equals(packageName)) {
+ if (packageName == null || mIdentity.getPackageName().equals(packageName)) {
return onLocationPermissionsChanged();
}
@@ -140,7 +198,7 @@ public class GeofenceManager extends
}
boolean onLocationPermissionsChanged(int uid) {
- if (getIdentity().getUid() == uid) {
+ if (mIdentity.getUid() == uid) {
return onLocationPermissionsChanged();
}
@@ -149,7 +207,7 @@ public class GeofenceManager extends
private boolean onLocationPermissionsChanged() {
boolean permitted = mLocationPermissionsHelper.hasLocationPermissions(PERMISSION_FINE,
- getIdentity());
+ mIdentity);
if (permitted != mPermitted) {
mPermitted = permitted;
return true;
@@ -164,12 +222,12 @@ public class GeofenceManager extends
mCachedLocationDistanceM = mCenter.distanceTo(mCachedLocation);
}
- return Math.abs(getRequest().getRadius() - mCachedLocationDistanceM);
+ return Math.abs(mGeofence.getRadius() - mCachedLocationDistanceM);
}
ListenerOperation<PendingIntent> onLocationChanged(Location location) {
// remove expired fences
- if (getRequest().isExpired()) {
+ if (mGeofence.isExpired()) {
remove();
return null;
}
@@ -178,7 +236,7 @@ public class GeofenceManager extends
mCachedLocationDistanceM = mCenter.distanceTo(mCachedLocation);
int oldState = mGeofenceState;
- float radius = Math.max(getRequest().getRadius(), location.getAccuracy());
+ float radius = Math.max(mGeofence.getRadius(), location.getAccuracy());
if (mCachedLocationDistanceM <= radius) {
mGeofenceState = STATE_INSIDE;
if (oldState != STATE_INSIDE) {
@@ -206,14 +264,14 @@ public class GeofenceManager extends
null, null, PendingIntentUtils.createDontSendToRestrictedAppsBundle(null));
} catch (PendingIntent.CanceledException e) {
mWakeLock.release();
- removeRegistration(new GeofenceKey(pendingIntent, getRequest()), this);
+ removeRegistration(new GeofenceKey(pendingIntent, mGeofence), this);
}
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
- builder.append(getIdentity());
+ builder.append(mIdentity);
ArraySet<String> flags = new ArraySet<>(1);
if (!mPermitted) {
@@ -223,7 +281,7 @@ public class GeofenceManager extends
builder.append(" ").append(flags);
}
- builder.append(" ").append(getRequest());
+ builder.append(" ").append(mGeofence);
return builder.toString();
}
}
@@ -258,10 +316,10 @@ public class GeofenceManager extends
protected final LocationUsageLogger mLocationUsageLogger;
@GuardedBy("mLock")
- private @Nullable LocationManager mLocationManager;
+ @Nullable private LocationManager mLocationManager;
@GuardedBy("mLock")
- private @Nullable Location mLastLocation;
+ @Nullable private Location mLastLocation;
public GeofenceManager(Context context, Injector injector) {
mContext = context.createAttributionContext(ATTRIBUTION_TAG);
@@ -271,11 +329,6 @@ public class GeofenceManager extends
mLocationUsageLogger = injector.getLocationUsageLogger();
}
- @Override
- public String getTag() {
- return TAG;
- }
-
private LocationManager getLocationManager() {
synchronized (mLock) {
if (mLocationManager == null) {
@@ -375,7 +428,7 @@ public class GeofenceManager extends
/* LocationRequest= */ null,
/* hasListener= */ false,
true,
- registration.getRequest(), true);
+ registration.getGeofence(), true);
}
@Override
@@ -389,7 +442,7 @@ public class GeofenceManager extends
/* LocationRequest= */ null,
/* hasListener= */ false,
true,
- registration.getRequest(), true);
+ registration.getGeofence(), true);
}
@Override
@@ -417,7 +470,7 @@ public class GeofenceManager extends
WorkSource workSource = null;
double minFenceDistanceM = Double.MAX_VALUE;
for (GeofenceRegistration registration : registrations) {
- if (registration.getRequest().isExpired(realtimeMs)) {
+ if (registration.getGeofence().isExpired(realtimeMs)) {
continue;
}
diff --git a/services/core/java/com/android/server/location/gnss/GnssAntennaInfoProvider.java b/services/core/java/com/android/server/location/gnss/GnssAntennaInfoProvider.java
index e3750074168c..62ab22a46ba8 100644
--- a/services/core/java/com/android/server/location/gnss/GnssAntennaInfoProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssAntennaInfoProvider.java
@@ -16,6 +16,7 @@
package com.android.server.location.gnss;
+import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
import static com.android.server.location.gnss.GnssManagerService.TAG;
import android.annotation.Nullable;
@@ -25,6 +26,7 @@ import android.location.util.identity.CallerIdentity;
import android.os.Binder;
import android.os.IBinder;
+import com.android.server.FgThread;
import com.android.server.location.gnss.hal.GnssNative;
import com.android.server.location.listeners.BinderListenerRegistration;
import com.android.server.location.listeners.ListenerMultiplexer;
@@ -45,17 +47,35 @@ public class GnssAntennaInfoProvider extends
* Registration object for GNSS listeners.
*/
protected class AntennaInfoListenerRegistration extends
- BinderListenerRegistration<Void, IGnssAntennaInfoListener> {
+ BinderListenerRegistration<IBinder, IGnssAntennaInfoListener> {
- protected AntennaInfoListenerRegistration(CallerIdentity callerIdentity,
+ private final CallerIdentity mIdentity;
+
+ protected AntennaInfoListenerRegistration(CallerIdentity identity,
IGnssAntennaInfoListener listener) {
- super(null, callerIdentity, listener);
+ super(identity.isMyProcess() ? FgThread.getExecutor() : DIRECT_EXECUTOR, listener);
+ mIdentity = identity;
+ }
+
+ @Override
+ protected String getTag() {
+ return TAG;
}
@Override
protected GnssAntennaInfoProvider getOwner() {
return GnssAntennaInfoProvider.this;
}
+
+ @Override
+ protected IBinder getBinderFromKey(IBinder key) {
+ return key;
+ }
+
+ @Override
+ public String toString() {
+ return mIdentity.toString();
+ }
}
private final GnssNative mGnssNative;
@@ -72,11 +92,6 @@ public class GnssAntennaInfoProvider extends
return mAntennaInfos;
}
- @Override
- public String getTag() {
- return TAG;
- }
-
public boolean isSupported() {
return mGnssNative.isAntennaInfoSupported();
}
diff --git a/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java b/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
index a54047665aba..82bcca2b8470 100644
--- a/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
+++ b/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
@@ -18,6 +18,7 @@ package com.android.server.location.gnss;
import static android.location.LocationManager.GPS_PROVIDER;
+import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
import static com.android.server.location.LocationPermissions.PERMISSION_FINE;
import static com.android.server.location.gnss.GnssManagerService.TAG;
@@ -33,6 +34,7 @@ import android.os.Process;
import android.util.ArraySet;
import com.android.internal.util.Preconditions;
+import com.android.server.FgThread;
import com.android.server.LocalServices;
import com.android.server.location.injector.AppForegroundHelper;
import com.android.server.location.injector.Injector;
@@ -67,16 +69,34 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter
* Registration object for GNSS listeners.
*/
protected class GnssListenerRegistration extends
- BinderListenerRegistration<TRequest, TListener> {
+ BinderListenerRegistration<IBinder, TListener> {
+
+ private final TRequest mRequest;
+ private final CallerIdentity mIdentity;
// we store these values because we don't trust the listeners not to give us dupes, not to
// spam us, and because checking the values may be more expensive
private boolean mForeground;
private boolean mPermitted;
- protected GnssListenerRegistration(@Nullable TRequest request,
- CallerIdentity callerIdentity, TListener listener) {
- super(request, callerIdentity, listener);
+ protected GnssListenerRegistration(TRequest request, CallerIdentity identity,
+ TListener listener) {
+ super(identity.isMyProcess() ? FgThread.getExecutor() : DIRECT_EXECUTOR, listener);
+ mRequest = request;
+ mIdentity = identity;
+ }
+
+ public final TRequest getRequest() {
+ return mRequest;
+ }
+
+ public final CallerIdentity getIdentity() {
+ return mIdentity;
+ }
+
+ @Override
+ public String getTag() {
+ return TAG;
}
@Override
@@ -84,6 +104,11 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter
return GnssListenerMultiplexer.this;
}
+ @Override
+ protected IBinder getBinderFromKey(IBinder key) {
+ return key;
+ }
+
/**
* Returns true if this registration is currently in the foreground.
*/
@@ -96,31 +121,16 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter
}
@Override
- protected final void onBinderListenerRegister() {
- mPermitted = mLocationPermissionsHelper.hasLocationPermissions(PERMISSION_FINE,
- getIdentity());
- mForeground = mAppForegroundHelper.isAppForeground(getIdentity().getUid());
+ protected void onRegister() {
+ super.onRegister();
- onGnssListenerRegister();
- }
-
- @Override
- protected final void onBinderListenerUnregister() {
- onGnssListenerUnregister();
+ mPermitted = mLocationPermissionsHelper.hasLocationPermissions(PERMISSION_FINE,
+ mIdentity);
+ mForeground = mAppForegroundHelper.isAppForeground(mIdentity.getUid());
}
- /**
- * May be overridden in place of {@link #onBinderListenerRegister()}.
- */
- protected void onGnssListenerRegister() {}
-
- /**
- * May be overridden in place of {@link #onBinderListenerUnregister()}.
- */
- protected void onGnssListenerUnregister() {}
-
boolean onLocationPermissionsChanged(@Nullable String packageName) {
- if (packageName == null || getIdentity().getPackageName().equals(packageName)) {
+ if (packageName == null || mIdentity.getPackageName().equals(packageName)) {
return onLocationPermissionsChanged();
}
@@ -128,7 +138,7 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter
}
boolean onLocationPermissionsChanged(int uid) {
- if (getIdentity().getUid() == uid) {
+ if (mIdentity.getUid() == uid) {
return onLocationPermissionsChanged();
}
@@ -137,7 +147,7 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter
private boolean onLocationPermissionsChanged() {
boolean permitted = mLocationPermissionsHelper.hasLocationPermissions(PERMISSION_FINE,
- getIdentity());
+ mIdentity);
if (permitted != mPermitted) {
mPermitted = permitted;
return true;
@@ -147,7 +157,7 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter
}
boolean onForegroundChanged(int uid, boolean foreground) {
- if (getIdentity().getUid() == uid && foreground != mForeground) {
+ if (mIdentity.getUid() == uid && foreground != mForeground) {
mForeground = foreground;
return true;
}
@@ -158,7 +168,7 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
- builder.append(getIdentity());
+ builder.append(mIdentity);
ArraySet<String> flags = new ArraySet<>(2);
if (!mForeground) {
@@ -171,8 +181,8 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter
builder.append(" ").append(flags);
}
- if (getRequest() != null) {
- builder.append(" ").append(getRequest());
+ if (mRequest != null) {
+ builder.append(" ").append(mRequest);
}
return builder.toString();
}
@@ -218,11 +228,6 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter
LocalServices.getService(LocationManagerInternal.class));
}
- @Override
- public String getTag() {
- return TAG;
- }
-
/**
* May be overridden by subclasses to return whether the service is supported or not. This value
* should never change for the lifetime of the multiplexer. If the service is unsupported, all
diff --git a/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java b/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
index e4e9d0153f7c..9f2a9cf86fb6 100644
--- a/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
@@ -40,10 +40,7 @@ import com.android.server.location.injector.SettingsHelper;
import java.util.Collection;
/**
- * An base implementation for GNSS measurements provider. It abstracts out the responsibility of
- * handling listeners, while still allowing technology specific implementations to be built.
- *
- * @hide
+ * GNSS measurements HAL module and listener multiplexer.
*/
public final class GnssMeasurementsProvider extends
GnssListenerMultiplexer<GnssMeasurementRequest, IGnssMeasurementsListener,
@@ -61,7 +58,9 @@ public final class GnssMeasurementsProvider extends
}
@Override
- protected void onGnssListenerRegister() {
+ protected void onRegister() {
+ super.onRegister();
+
executeOperation(listener -> listener.onStatusChanged(
GnssMeasurementsEvent.Callback.STATUS_READY));
}
diff --git a/services/core/java/com/android/server/location/gnss/GnssNavigationMessageProvider.java b/services/core/java/com/android/server/location/gnss/GnssNavigationMessageProvider.java
index e9fce0514a18..63134bb77ccb 100644
--- a/services/core/java/com/android/server/location/gnss/GnssNavigationMessageProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssNavigationMessageProvider.java
@@ -32,11 +32,7 @@ import com.android.server.location.injector.Injector;
import java.util.Collection;
/**
- * An base implementation for GPS navigation messages provider.
- * It abstracts out the responsibility of handling listeners, while still allowing technology
- * specific implementations to be built.
- *
- * @hide
+ * GNSS navigation message HAL module and listener multiplexer.
*/
public class GnssNavigationMessageProvider extends
GnssListenerMultiplexer<Void, IGnssNavigationMessageListener, Void> implements
@@ -51,7 +47,9 @@ public class GnssNavigationMessageProvider extends
}
@Override
- protected void onGnssListenerRegister() {
+ protected void onRegister() {
+ super.onRegister();
+
executeOperation(listener -> listener.onStatusChanged(
GnssNavigationMessage.Callback.STATUS_READY));
}
diff --git a/services/core/java/com/android/server/location/gnss/GnssNmeaProvider.java b/services/core/java/com/android/server/location/gnss/GnssNmeaProvider.java
index bfef97856838..d4e38b6a05db 100644
--- a/services/core/java/com/android/server/location/gnss/GnssNmeaProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssNmeaProvider.java
@@ -34,7 +34,7 @@ import java.util.Collection;
import java.util.function.Function;
/**
- * Implementation of a handler for {@link IGnssNmeaListener}.
+ * GNSS NMEA HAL module and listener multiplexer.
*/
class GnssNmeaProvider extends GnssListenerMultiplexer<Void, IGnssNmeaListener, Void> implements
GnssNative.BaseCallbacks, GnssNative.NmeaCallbacks {
@@ -97,7 +97,7 @@ class GnssNmeaProvider extends GnssListenerMultiplexer<Void, IGnssNmeaListener,
ListenerExecutor.ListenerOperation<IGnssNmeaListener>>() {
// only read in the nmea string if we need to
- private @Nullable String mNmea;
+ @Nullable private String mNmea;
@Override
public ListenerExecutor.ListenerOperation<IGnssNmeaListener> apply(
diff --git a/services/core/java/com/android/server/location/gnss/GnssStatusProvider.java b/services/core/java/com/android/server/location/gnss/GnssStatusProvider.java
index 0ce36d6a8276..41fa7a183288 100644
--- a/services/core/java/com/android/server/location/gnss/GnssStatusProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssStatusProvider.java
@@ -35,7 +35,7 @@ import com.android.server.location.injector.LocationUsageLogger;
import java.util.Collection;
/**
- * Implementation of a handler for {@link IGnssStatusListener}.
+ * GNSS status HAL module and listener multiplexer.
*/
public class GnssStatusProvider extends
GnssListenerMultiplexer<Void, IGnssStatusListener, Void> implements
diff --git a/services/core/java/com/android/server/location/listeners/BinderListenerRegistration.java b/services/core/java/com/android/server/location/listeners/BinderListenerRegistration.java
index 709e23615b14..5555aeb3e5e8 100644
--- a/services/core/java/com/android/server/location/listeners/BinderListenerRegistration.java
+++ b/services/core/java/com/android/server/location/listeners/BinderListenerRegistration.java
@@ -16,71 +16,59 @@
package com.android.server.location.listeners;
-import android.annotation.Nullable;
-import android.location.util.identity.CallerIdentity;
-import android.os.Binder;
import android.os.IBinder;
+import android.os.IBinder.DeathRecipient;
import android.os.RemoteException;
import android.util.Log;
+import java.util.NoSuchElementException;
+import java.util.concurrent.Executor;
+
/**
* A registration that works with IBinder keys, and registers a DeathListener to automatically
- * remove the registration if the binder dies. The key for this registration must either be an
- * {@link IBinder} or a {@link BinderKey}.
+ * remove the registration if the binder dies.
*
- * @param <TRequest> request type
+ * @param <TKey> key type
* @param <TListener> listener type
*/
-public abstract class BinderListenerRegistration<TRequest, TListener> extends
- RemoteListenerRegistration<TRequest, TListener> implements Binder.DeathRecipient {
+public abstract class BinderListenerRegistration<TKey, TListener> extends
+ RemovableListenerRegistration<TKey, TListener> implements DeathRecipient {
- /**
- * Interface to allow binder retrieval when keys are not themselves IBinders.
- */
- public interface BinderKey {
- /**
- * Returns the binder associated with this key.
- */
- IBinder getBinder();
+ protected BinderListenerRegistration(Executor executor, TListener listener) {
+ super(executor, listener);
}
- protected BinderListenerRegistration(@Nullable TRequest request, CallerIdentity callerIdentity,
- TListener listener) {
- super(request, callerIdentity, listener);
- }
+ protected abstract IBinder getBinderFromKey(TKey key);
@Override
- protected final void onRemovableListenerRegister() {
- IBinder binder = getBinderFromKey(getKey());
+ protected void onRegister() {
+ super.onRegister();
+
try {
- binder.linkToDeath(this, 0);
+ getBinderFromKey(getKey()).linkToDeath(this, 0);
} catch (RemoteException e) {
remove();
}
-
- onBinderListenerRegister();
}
@Override
- protected final void onRemovableListenerUnregister() {
- onBinderListenerUnregister();
- getBinderFromKey(getKey()).unlinkToDeath(this, 0);
- }
-
- /**
- * May be overridden in place of {@link #onRemovableListenerRegister()}.
- */
- protected void onBinderListenerRegister() {}
+ protected void onUnregister() {
+ try {
+ getBinderFromKey(getKey()).unlinkToDeath(this, 0);
+ } catch (NoSuchElementException e) {
+ // the only way this exception can occur should be if another exception has been thrown
+ // prior to registration completing, and that exception is currently unwinding the call
+ // stack and causing this cleanup. since that exception should crash us anyways, drop
+ // this exception so we're not hiding the original exception.
+ Log.w(getTag(), "failed to unregister binder death listener", e);
+ }
- /**
- * May be overridden in place of {@link #onRemovableListenerUnregister()}.
- */
- protected void onBinderListenerUnregister() {}
+ super.onUnregister();
+ }
- @Override
public void onOperationFailure(ListenerOperation<TListener> operation, Exception e) {
if (e instanceof RemoteException) {
- Log.w(getOwner().getTag(), "registration " + this + " removed", e);
+ Log.w(getTag(), "registration " + this + " removed", e);
remove();
} else {
super.onOperationFailure(operation, e);
@@ -90,9 +78,10 @@ public abstract class BinderListenerRegistration<TRequest, TListener> extends
@Override
public void binderDied() {
try {
- if (Log.isLoggable(getOwner().getTag(), Log.DEBUG)) {
- Log.d(getOwner().getTag(), "binder registration " + getIdentity() + " died");
+ if (Log.isLoggable(getTag(), Log.DEBUG)) {
+ Log.d(getTag(), "binder registration " + this + " died");
}
+
remove();
} catch (RuntimeException e) {
// the caller may swallow runtime exceptions, so we rethrow as assertion errors to
@@ -100,14 +89,4 @@ public abstract class BinderListenerRegistration<TRequest, TListener> extends
throw new AssertionError(e);
}
}
-
- private static IBinder getBinderFromKey(Object key) {
- if (key instanceof IBinder) {
- return (IBinder) key;
- } else if (key instanceof BinderKey) {
- return ((BinderKey) key).getBinder();
- } else {
- throw new IllegalArgumentException("key must be IBinder or BinderKey");
- }
- }
}
diff --git a/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java b/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java
index 33b08d41c07d..67ae26591d5b 100644
--- a/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java
+++ b/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java
@@ -18,7 +18,6 @@ package com.android.server.location.listeners;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.os.Build;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -37,40 +36,48 @@ import java.util.function.Function;
import java.util.function.Predicate;
/**
- * A base class to multiplex client listener registrations within system server. Every listener is
+ * A base class to multiplex some event source to multiple listener registrations. Every listener is
* represented by a registration object which stores all required state for a listener. Keys are
* used to uniquely identify every registration. Listener operations may be executed on
* registrations in order to invoke the represented listener.
*
- * Registrations are divided into two categories, active registrations and inactive registrations,
- * as defined by {@link #isActive(ListenerRegistration)}. If a registration's active state changes,
- * {@link #updateRegistrations(Predicate)} must be invoked and return true for any registration
- * whose active state may have changed. Listeners will only be invoked for active registrations.
+ * <p>Registrations are divided into two categories, active registrations and inactive
+ * registrations, as defined by {@link #isActive(ListenerRegistration)}. The set of active
+ * registrations is combined into a single merged registration, which is submitted to the backing
+ * event source when necessary in order to register with the event source. The merged registration
+ * is updated whenever the set of active registration changes. Listeners will only be invoked for
+ * active registrations.
*
- * The set of active registrations is combined into a single merged registration, which is submitted
- * to the backing service when necessary in order to register the service. The merged registration
- * is updated whenever the set of active registration changes.
+ * <p>In order to inform the multiplexer of state changes, if a registration's active state changes,
+ * or if the merged registration changes, {@link #updateRegistrations(Predicate)} or {@link
+ * #updateRegistration(Object, Predicate)} must be invoked and return true for any registration
+ * whose state may have changed in such a way that the active state or merged registration state has
+ * changed. It is acceptable to return true from a predicate even if nothing has changed, though
+ * this may result in extra pointless work.
*
- * Callbacks invoked for various changes will always be ordered according to this lifecycle list:
+ * <p>Callbacks invoked for various changes will always be ordered according to this lifecycle list:
*
* <ul>
- * <li>{@link #onRegister()}</li>
- * <li>{@link ListenerRegistration#onRegister(Object)}</li>
- * <li>{@link #onRegistrationAdded(Object, ListenerRegistration)}</li>
- * <li>{@link #onRegistrationReplaced(Object, ListenerRegistration, ListenerRegistration)} (only
- * invoked if this registration is replacing a prior registration)</li>
- * <li>{@link #onActive()}</li>
- * <li>{@link ListenerRegistration#onActive()}</li>
- * <li>{@link ListenerRegistration#onInactive()}</li>
- * <li>{@link #onInactive()}</li>
- * <li>{@link #onRegistrationRemoved(Object, ListenerRegistration)}</li>
- * <li>{@link ListenerRegistration#onUnregister()}</li>
- * <li>{@link #onUnregister()}</li>
+ * <li>{@link #onRegister()}
+ * <li>{@link ListenerRegistration#onRegister(Object)}
+ * <li>{@link #onRegistrationAdded(Object, ListenerRegistration)}
+ * <li>{@link #onActive()}
+ * <li>{@link ListenerRegistration#onActive()}
+ * <li>{@link ListenerRegistration#onInactive()}
+ * <li>{@link #onInactive()}
+ * <li>{@link #onRegistrationRemoved(Object, ListenerRegistration)}
+ * <li>{@link ListenerRegistration#onUnregister()}
+ * <li>{@link #onUnregister()}
* </ul>
*
- * Adding registrations is not allowed to be called re-entrantly (ie, while in the middle of some
- * other operation or callback. Removal is allowed re-entrantly, however only via
- * {@link #removeRegistration(Object, ListenerRegistration)}, not via any other removal method. This
+ * <p>If one registration replaces another, then {@link #onRegistrationReplaced(Object,
+ * ListenerRegistration, Object, ListenerRegistration)} is invoked instead of {@link
+ * #onRegistrationRemoved(Object, ListenerRegistration)} and {@link #onRegistrationAdded(Object,
+ * ListenerRegistration)}.
+ *
+ * <p>Adding registrations is not allowed to be called re-entrantly (ie, while in the middle of some
+ * other operation or callback). Removal is allowed re-entrantly, however only via {@link
+ * #removeRegistration(Object, ListenerRegistration)}, not via any other removal method. This
* ensures re-entrant removal does not accidentally remove the incorrect registration.
*
* @param <TKey> key type
@@ -81,30 +88,31 @@ import java.util.function.Predicate;
public abstract class ListenerMultiplexer<TKey, TListener,
TRegistration extends ListenerRegistration<TListener>, TMergedRegistration> {
- @GuardedBy("mRegistrations")
+ /**
+ * The lock object used by the multiplexer. Acquiring this lock allows for multiple operations
+ * on the multiplexer to be completed atomically. Otherwise, it is not required to hold this
+ * lock. This lock is held while invoking all lifecycle callbacks on both the multiplexer and
+ * any registrations.
+ */
+ protected final Object mMultiplexerLock = new Object();
+
+ @GuardedBy("mMultiplexerLock")
private final ArrayMap<TKey, TRegistration> mRegistrations = new ArrayMap<>();
- @GuardedBy("mRegistrations")
private final UpdateServiceBuffer mUpdateServiceBuffer = new UpdateServiceBuffer();
- @GuardedBy("mRegistrations")
private final ReentrancyGuard mReentrancyGuard = new ReentrancyGuard();
- @GuardedBy("mRegistrations")
+ @GuardedBy("mMultiplexerLock")
private int mActiveRegistrationsCount = 0;
- @GuardedBy("mRegistrations")
+ @GuardedBy("mMultiplexerLock")
private boolean mServiceRegistered = false;
- @GuardedBy("mRegistrations")
+ @GuardedBy("mMultiplexerLock")
@Nullable private TMergedRegistration mMerged;
/**
- * Should be implemented to return a unique identifying tag that may be used for logging, etc...
- */
- public abstract @NonNull String getTag();
-
- /**
* Should be implemented to register with the backing service with the given merged
* registration, and should return true if a matching call to {@link #unregisterWithService()}
* is required to unregister (ie, if registration succeeds). The set of registrations passed in
@@ -120,6 +128,7 @@ public abstract class ListenerMultiplexer<TKey, TListener,
* @see #mergeRegistrations(Collection)
* @see #reregisterWithService(Object, Object, Collection)
*/
+ @GuardedBy("mMultiplexerLock")
protected abstract boolean registerWithService(TMergedRegistration merged,
@NonNull Collection<TRegistration> registrations);
@@ -130,6 +139,7 @@ public abstract class ListenerMultiplexer<TKey, TListener,
*
* @see #registerWithService(Object, Collection)
*/
+ @GuardedBy("mMultiplexerLock")
protected boolean reregisterWithService(TMergedRegistration oldMerged,
TMergedRegistration newMerged, @NonNull Collection<TRegistration> registrations) {
return registerWithService(newMerged, registrations);
@@ -138,6 +148,7 @@ public abstract class ListenerMultiplexer<TKey, TListener,
/**
* Should be implemented to unregister from the backing service.
*/
+ @GuardedBy("mMultiplexerLock")
protected abstract void unregisterWithService();
/**
@@ -147,6 +158,7 @@ public abstract class ListenerMultiplexer<TKey, TListener,
* {@link #updateRegistrations(Predicate)} must be invoked with a function that returns true for
* any registrations that may have changed their active state.
*/
+ @GuardedBy("mMultiplexerLock")
protected abstract boolean isActive(@NonNull TRegistration registration);
/**
@@ -157,7 +169,8 @@ public abstract class ListenerMultiplexer<TKey, TListener,
* {@link #reregisterWithService(Object, Object, Collection)} will be invoked with the new
* merged registration so that the backing service can be updated.
*/
- protected abstract @Nullable TMergedRegistration mergeRegistrations(
+ @GuardedBy("mMultiplexerLock")
+ protected abstract TMergedRegistration mergeRegistrations(
@NonNull Collection<TRegistration> registrations);
/**
@@ -166,6 +179,7 @@ public abstract class ListenerMultiplexer<TKey, TListener,
* present while there are any registrations. Invoked while holding the multiplexer's internal
* lock.
*/
+ @GuardedBy("mMultiplexerLock")
protected void onRegister() {}
/**
@@ -174,28 +188,38 @@ public abstract class ListenerMultiplexer<TKey, TListener,
* present while there are any registrations. Invoked while holding the multiplexer's internal
* lock.
*/
+ @GuardedBy("mMultiplexerLock")
protected void onUnregister() {}
/**
* Invoked when a registration is added. Invoked while holding the multiplexer's internal lock.
*/
+ @GuardedBy("mMultiplexerLock")
protected void onRegistrationAdded(@NonNull TKey key, @NonNull TRegistration registration) {}
/**
- * Invoked instead of {@link #onRegistrationAdded(Object, ListenerRegistration)} if a
- * registration is replacing an old registration. The old registration will have already been
- * unregistered. Invoked while holding the multiplexer's internal lock. The default behavior is
- * simply to call into {@link #onRegistrationAdded(Object, ListenerRegistration)}.
+ * Invoked when one registration replaces another (through {@link #replaceRegistration(Object,
+ * Object, ListenerRegistration)}). The old registration has already been unregistered at this
+ * point. Invoked while holding the multiplexer's internal lock.
+ *
+ * <p>The default behavior is simply to call first {@link #onRegistrationRemoved(Object,
+ * ListenerRegistration)} and then {@link #onRegistrationAdded(Object, ListenerRegistration)}.
*/
- protected void onRegistrationReplaced(@NonNull TKey key, @NonNull TRegistration oldRegistration,
+ @GuardedBy("mMultiplexerLock")
+ protected void onRegistrationReplaced(
+ @NonNull TKey oldKey,
+ @NonNull TRegistration oldRegistration,
+ @NonNull TKey newKey,
@NonNull TRegistration newRegistration) {
- onRegistrationAdded(key, newRegistration);
+ onRegistrationRemoved(oldKey, oldRegistration);
+ onRegistrationAdded(newKey, newRegistration);
}
/**
* Invoked when a registration is removed. Invoked while holding the multiplexer's internal
* lock.
*/
+ @GuardedBy("mMultiplexerLock")
protected void onRegistrationRemoved(@NonNull TKey key, @NonNull TRegistration registration) {}
/**
@@ -204,6 +228,7 @@ public abstract class ListenerMultiplexer<TKey, TListener,
* need to be present while there are active registrations. Invoked while holding the
* multiplexer's internal lock.
*/
+ @GuardedBy("mMultiplexerLock")
protected void onActive() {}
/**
@@ -212,6 +237,7 @@ public abstract class ListenerMultiplexer<TKey, TListener,
* need to be present while there are active registrations. Invoked while holding the
* multiplexer's internal lock.
*/
+ @GuardedBy("mMultiplexerLock")
protected void onInactive() {}
/**
@@ -224,13 +250,12 @@ public abstract class ListenerMultiplexer<TKey, TListener,
/**
* Atomically removes the registration with the old key and adds a new registration with the
- * given key. If there was a registration for the old key,
- * {@link #onRegistrationReplaced(Object, ListenerRegistration, ListenerRegistration)} will be
- * invoked for the new registration and key instead of
- * {@link #onRegistrationAdded(Object, ListenerRegistration)}, even though they may not share
- * the same key. The old key may be the same value as the new key, in which case this function
- * is equivalent to {@link #putRegistration(Object, ListenerRegistration)}. This method cannot
- * be called to add a registration re-entrantly.
+ * given key. If there was a registration for the old key, {@link
+ * #onRegistrationReplaced(Object, ListenerRegistration, Object, ListenerRegistration)} will be
+ * invoked instead of {@link #onRegistrationAdded(Object, ListenerRegistration)}, even if they
+ * share the same key. The old key may be the same value as the new key, in which case this
+ * function is equivalent to {@link #putRegistration(Object, ListenerRegistration)}. This method
+ * cannot be called to add a registration re-entrantly.
*/
protected final void replaceRegistration(@NonNull TKey oldKey, @NonNull TKey key,
@NonNull TRegistration registration) {
@@ -238,7 +263,7 @@ public abstract class ListenerMultiplexer<TKey, TListener,
Objects.requireNonNull(key);
Objects.requireNonNull(registration);
- synchronized (mRegistrations) {
+ synchronized (mMultiplexerLock) {
// adding listeners reentrantly is not supported
Preconditions.checkState(!mReentrancyGuard.isReentrant());
@@ -257,12 +282,18 @@ public abstract class ListenerMultiplexer<TKey, TListener,
boolean wasEmpty = mRegistrations.isEmpty();
TRegistration oldRegistration = null;
- int index = mRegistrations.indexOfKey(oldKey);
- if (index >= 0) {
- oldRegistration = removeRegistration(index, oldKey != key);
+ int oldIndex = mRegistrations.indexOfKey(oldKey);
+ if (oldIndex >= 0) {
+ // remove ourselves instead of using remove(), to balance registration callbacks
+ oldRegistration = mRegistrations.valueAt(oldIndex);
+ unregister(oldRegistration);
+ oldRegistration.onUnregister();
+ if (oldKey != key) {
+ mRegistrations.removeAt(oldIndex);
+ }
}
- if (oldKey == key && index >= 0) {
- mRegistrations.setValueAt(index, registration);
+ if (oldKey == key && oldIndex >= 0) {
+ mRegistrations.setValueAt(oldIndex, registration);
} else {
mRegistrations.put(key, registration);
}
@@ -274,7 +305,7 @@ public abstract class ListenerMultiplexer<TKey, TListener,
if (oldRegistration == null) {
onRegistrationAdded(key, registration);
} else {
- onRegistrationReplaced(key, oldRegistration, registration);
+ onRegistrationReplaced(oldKey, oldRegistration, key, registration);
}
onRegistrationActiveChanged(registration);
}
@@ -282,29 +313,11 @@ public abstract class ListenerMultiplexer<TKey, TListener,
}
/**
- * Removes the registration with the given key. This method cannot be called to remove a
- * registration re-entrantly.
- */
- protected final void removeRegistration(@NonNull Object key) {
- synchronized (mRegistrations) {
- // this method does not support removing listeners reentrantly
- Preconditions.checkState(!mReentrancyGuard.isReentrant());
-
- int index = mRegistrations.indexOfKey(key);
- if (index < 0) {
- return;
- }
-
- removeRegistration(index, true);
- }
- }
-
- /**
* Removes all registrations with keys that satisfy the given predicate. This method cannot be
* called to remove a registration re-entrantly.
*/
protected final void removeRegistrationIf(@NonNull Predicate<TKey> predicate) {
- synchronized (mRegistrations) {
+ synchronized (mMultiplexerLock) {
// this method does not support removing listeners reentrantly
Preconditions.checkState(!mReentrancyGuard.isReentrant());
@@ -329,13 +342,31 @@ public abstract class ListenerMultiplexer<TKey, TListener,
}
/**
+ * Removes the registration with the given key. This method cannot be called to remove a
+ * registration re-entrantly.
+ */
+ protected final void removeRegistration(TKey key) {
+ synchronized (mMultiplexerLock) {
+ // this method does not support removing listeners reentrantly
+ Preconditions.checkState(!mReentrancyGuard.isReentrant());
+
+ int index = mRegistrations.indexOfKey(key);
+ if (index < 0) {
+ return;
+ }
+
+ removeRegistration(index);
+ }
+ }
+
+ /**
* Removes the given registration with the given key. If the given key has a different
* registration at the time this method is called, nothing happens. This method allows for
* re-entrancy, and may be called to remove a registration re-entrantly.
*/
- protected final void removeRegistration(@NonNull Object key,
+ protected final void removeRegistration(@NonNull TKey key,
@NonNull ListenerRegistration<?> registration) {
- synchronized (mRegistrations) {
+ synchronized (mMultiplexerLock) {
int index = mRegistrations.indexOfKey(key);
if (index < 0) {
return;
@@ -350,17 +381,13 @@ public abstract class ListenerMultiplexer<TKey, TListener,
unregister(typedRegistration);
mReentrancyGuard.markForRemoval(key, typedRegistration);
} else {
- removeRegistration(index, true);
+ removeRegistration(index);
}
}
}
- @GuardedBy("mRegistrations")
- private TRegistration removeRegistration(int index, boolean removeEntry) {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mRegistrations));
- }
-
+ @GuardedBy("mMultiplexerLock")
+ private void removeRegistration(int index) {
TKey key = mRegistrations.keyAt(index);
TRegistration registration = mRegistrations.valueAt(index);
@@ -376,15 +403,11 @@ public abstract class ListenerMultiplexer<TKey, TListener,
unregister(registration);
onRegistrationRemoved(key, registration);
registration.onUnregister();
- if (removeEntry) {
- mRegistrations.removeAt(index);
- if (mRegistrations.isEmpty()) {
- onUnregister();
- }
+ mRegistrations.removeAt(index);
+ if (mRegistrations.isEmpty()) {
+ onUnregister();
}
}
-
- return registration;
}
/**
@@ -392,14 +415,14 @@ public abstract class ListenerMultiplexer<TKey, TListener,
* registration accordingly.
*/
protected final void updateService() {
- synchronized (mRegistrations) {
+ synchronized (mMultiplexerLock) {
if (mUpdateServiceBuffer.isBuffered()) {
mUpdateServiceBuffer.markUpdateServiceRequired();
return;
}
- ArrayList<TRegistration> actives = new ArrayList<>(mRegistrations.size());
final int size = mRegistrations.size();
+ ArrayList<TRegistration> actives = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
TRegistration registration = mRegistrations.valueAt(i);
if (registration.isActive()) {
@@ -413,17 +436,17 @@ public abstract class ListenerMultiplexer<TKey, TListener,
mServiceRegistered = false;
unregisterWithService();
}
- return;
- }
-
- TMergedRegistration merged = mergeRegistrations(actives);
- if (!mServiceRegistered || !Objects.equals(merged, mMerged)) {
+ } else {
+ TMergedRegistration merged = mergeRegistrations(actives);
if (mServiceRegistered) {
- mServiceRegistered = reregisterWithService(mMerged, merged, actives);
+ if (!Objects.equals(merged, mMerged)) {
+ mServiceRegistered = reregisterWithService(mMerged, merged, actives);
+ mMerged = mServiceRegistered ? merged : null;
+ }
} else {
mServiceRegistered = registerWithService(merged, actives);
+ mMerged = mServiceRegistered ? merged : null;
}
- mMerged = mServiceRegistered ? merged : null;
}
}
}
@@ -437,7 +460,7 @@ public abstract class ListenerMultiplexer<TKey, TListener,
* reinitialized.
*/
protected final void resetService() {
- synchronized (mRegistrations) {
+ synchronized (mMultiplexerLock) {
if (mServiceRegistered) {
mMerged = null;
mServiceRegistered = false;
@@ -453,7 +476,31 @@ public abstract class ListenerMultiplexer<TKey, TListener,
* buffering {@code updateService()} until after multiple adds/removes/updates occur.
*/
public UpdateServiceLock newUpdateServiceLock() {
- return new UpdateServiceLock(mUpdateServiceBuffer.acquire());
+ return new UpdateServiceLock(mUpdateServiceBuffer);
+ }
+
+ /**
+ * Evaluates the predicate on all registrations until the predicate returns true, at which point
+ * evaluation will cease. Returns true if the predicate ever returned true, and returns false
+ * otherwise.
+ */
+ protected final boolean findRegistration(Predicate<TRegistration> predicate) {
+ synchronized (mMultiplexerLock) {
+ // we only acquire a reentrancy guard in case of removal while iterating. this method
+ // does not directly affect active state or merged state, so there is no advantage to
+ // acquiring an update source buffer.
+ try (ReentrancyGuard ignored = mReentrancyGuard.acquire()) {
+ final int size = mRegistrations.size();
+ for (int i = 0; i < size; i++) {
+ TRegistration registration = mRegistrations.valueAt(i);
+ if (predicate.test(registration)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
}
/**
@@ -463,7 +510,7 @@ public abstract class ListenerMultiplexer<TKey, TListener,
* the resulting changes.
*/
protected final void updateRegistrations(@NonNull Predicate<TRegistration> predicate) {
- synchronized (mRegistrations) {
+ synchronized (mMultiplexerLock) {
// since updating a registration can invoke a variety of callbacks, we need to ensure
// those callbacks themselves do not re-enter, as this could lead to out-of-order
// callbacks. note that try-with-resources ordering is meaningful here as well. we want
@@ -492,7 +539,7 @@ public abstract class ListenerMultiplexer<TKey, TListener,
*/
protected final boolean updateRegistration(@NonNull Object key,
@NonNull Predicate<TRegistration> predicate) {
- synchronized (mRegistrations) {
+ synchronized (mMultiplexerLock) {
// since updating a registration can invoke a variety of callbacks, we need to ensure
// those callbacks themselves do not re-enter, as this could lead to out-of-order
// callbacks. note that try-with-resources ordering is meaningful here as well. we want
@@ -515,12 +562,8 @@ public abstract class ListenerMultiplexer<TKey, TListener,
}
}
- @GuardedBy("mRegistrations")
+ @GuardedBy("mMultiplexerLock")
private void onRegistrationActiveChanged(TRegistration registration) {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mRegistrations));
- }
-
boolean active = registration.isRegistered() && isActive(registration);
boolean changed = registration.setActive(active);
if (changed) {
@@ -547,7 +590,7 @@ public abstract class ListenerMultiplexer<TKey, TListener,
*/
protected final void deliverToListeners(
@NonNull Function<TRegistration, ListenerOperation<TListener>> function) {
- synchronized (mRegistrations) {
+ synchronized (mMultiplexerLock) {
try (ReentrancyGuard ignored = mReentrancyGuard.acquire()) {
final int size = mRegistrations.size();
for (int i = 0; i < size; i++) {
@@ -571,7 +614,7 @@ public abstract class ListenerMultiplexer<TKey, TListener,
* </pre>
*/
protected final void deliverToListeners(@NonNull ListenerOperation<TListener> operation) {
- synchronized (mRegistrations) {
+ synchronized (mMultiplexerLock) {
try (ReentrancyGuard ignored = mReentrancyGuard.acquire()) {
final int size = mRegistrations.size();
for (int i = 0; i < size; i++) {
@@ -584,6 +627,7 @@ public abstract class ListenerMultiplexer<TKey, TListener,
}
}
+ @GuardedBy("mMultiplexerLock")
private void unregister(TRegistration registration) {
registration.unregisterInternal();
onRegistrationActiveChanged(registration);
@@ -593,7 +637,7 @@ public abstract class ListenerMultiplexer<TKey, TListener,
* Dumps debug information.
*/
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- synchronized (mRegistrations) {
+ synchronized (mMultiplexerLock) {
pw.print("service: ");
pw.print(getServiceState());
pw.println();
@@ -620,6 +664,7 @@ public abstract class ListenerMultiplexer<TKey, TListener,
* May be overridden to provide additional details on service state when dumping the manager
* state. Invoked while holding the multiplexer's internal lock.
*/
+ @GuardedBy("mMultiplexerLock")
protected String getServiceState() {
if (mServiceRegistered) {
if (mMerged != null) {
@@ -643,61 +688,63 @@ public abstract class ListenerMultiplexer<TKey, TListener,
*/
private final class ReentrancyGuard implements AutoCloseable {
- @GuardedBy("mRegistrations")
+ @GuardedBy("mMultiplexerLock")
private int mGuardCount;
- @GuardedBy("mRegistrations")
- private @Nullable ArraySet<Entry<Object, ListenerRegistration<?>>> mScheduledRemovals;
+
+ @GuardedBy("mMultiplexerLock")
+ @Nullable private ArraySet<Entry<TKey, ListenerRegistration<?>>> mScheduledRemovals;
ReentrancyGuard() {
mGuardCount = 0;
mScheduledRemovals = null;
}
- @GuardedBy("mRegistrations")
boolean isReentrant() {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mRegistrations));
+ synchronized (mMultiplexerLock) {
+ return mGuardCount != 0;
}
- return mGuardCount != 0;
}
- @GuardedBy("mRegistrations")
- void markForRemoval(Object key, ListenerRegistration<?> registration) {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mRegistrations));
- }
- Preconditions.checkState(isReentrant());
+ void markForRemoval(TKey key, ListenerRegistration<?> registration) {
+ synchronized (mMultiplexerLock) {
+ Preconditions.checkState(isReentrant());
- if (mScheduledRemovals == null) {
- mScheduledRemovals = new ArraySet<>(mRegistrations.size());
+ if (mScheduledRemovals == null) {
+ mScheduledRemovals = new ArraySet<>(mRegistrations.size());
+ }
+ mScheduledRemovals.add(new AbstractMap.SimpleImmutableEntry<>(key, registration));
}
- mScheduledRemovals.add(new AbstractMap.SimpleImmutableEntry<>(key, registration));
}
ReentrancyGuard acquire() {
- ++mGuardCount;
- return this;
+ synchronized (mMultiplexerLock) {
+ ++mGuardCount;
+ return this;
+ }
}
@Override
public void close() {
- ArraySet<Entry<Object, ListenerRegistration<?>>> scheduledRemovals = null;
+ synchronized (mMultiplexerLock) {
+ Preconditions.checkState(mGuardCount > 0);
- Preconditions.checkState(mGuardCount > 0);
- if (--mGuardCount == 0) {
- scheduledRemovals = mScheduledRemovals;
- mScheduledRemovals = null;
- }
+ ArraySet<Entry<TKey, ListenerRegistration<?>>> scheduledRemovals = null;
- if (scheduledRemovals == null) {
- return;
- }
+ if (--mGuardCount == 0) {
+ scheduledRemovals = mScheduledRemovals;
+ mScheduledRemovals = null;
+ }
- try (UpdateServiceBuffer ignored = mUpdateServiceBuffer.acquire()) {
- final int size = scheduledRemovals.size();
- for (int i = 0; i < size; i++) {
- Entry<Object, ListenerRegistration<?>> entry = scheduledRemovals.valueAt(i);
- removeRegistration(entry.getKey(), entry.getValue());
+ if (scheduledRemovals == null) {
+ return;
+ }
+
+ try (UpdateServiceBuffer ignored = mUpdateServiceBuffer.acquire()) {
+ final int size = scheduledRemovals.size();
+ for (int i = 0; i < size; i++) {
+ Entry<TKey, ListenerRegistration<?>> entry = scheduledRemovals.valueAt(i);
+ removeRegistration(entry.getKey(), entry.getValue());
+ }
}
}
}
@@ -721,6 +768,7 @@ public abstract class ListenerMultiplexer<TKey, TListener,
@GuardedBy("this")
private int mBufferCount;
+
@GuardedBy("this")
private boolean mUpdateServiceRequired;
@@ -765,18 +813,18 @@ public abstract class ListenerMultiplexer<TKey, TListener,
* {@link #close()}ed. This can be used to save work by acquiring the lock before multiple calls
* to updateService() are expected, and closing the lock after.
*/
- public final class UpdateServiceLock implements AutoCloseable {
+ public static final class UpdateServiceLock implements AutoCloseable {
- private @Nullable UpdateServiceBuffer mUpdateServiceBuffer;
+ @Nullable private ListenerMultiplexer<?, ?, ?, ?>.UpdateServiceBuffer mUpdateServiceBuffer;
- UpdateServiceLock(UpdateServiceBuffer updateServiceBuffer) {
- mUpdateServiceBuffer = updateServiceBuffer;
+ UpdateServiceLock(ListenerMultiplexer<?, ?, ?, ?>.UpdateServiceBuffer updateServiceBuffer) {
+ mUpdateServiceBuffer = updateServiceBuffer.acquire();
}
@Override
public void close() {
if (mUpdateServiceBuffer != null) {
- UpdateServiceBuffer buffer = mUpdateServiceBuffer;
+ ListenerMultiplexer<?, ?, ?, ?>.UpdateServiceBuffer buffer = mUpdateServiceBuffer;
mUpdateServiceBuffer = null;
buffer.close();
}
diff --git a/services/core/java/com/android/server/location/listeners/ListenerRegistration.java b/services/core/java/com/android/server/location/listeners/ListenerRegistration.java
index 711dde89ef13..fcb2a9b70336 100644
--- a/services/core/java/com/android/server/location/listeners/ListenerRegistration.java
+++ b/services/core/java/com/android/server/location/listeners/ListenerRegistration.java
@@ -35,7 +35,7 @@ public class ListenerRegistration<TListener> implements ListenerExecutor {
private boolean mActive;
- private volatile @Nullable TListener mListener;
+ @Nullable private volatile TListener mListener;
protected ListenerRegistration(Executor executor, TListener listener) {
mExecutor = Objects.requireNonNull(executor);
@@ -43,6 +43,13 @@ public class ListenerRegistration<TListener> implements ListenerExecutor {
mListener = Objects.requireNonNull(listener);
}
+ /**
+ * Returns a tag to use for logging. Should be overridden by subclasses.
+ */
+ protected String getTag() {
+ return "ListenerRegistration";
+ }
+
protected final Executor getExecutor() {
return mExecutor;
}
@@ -50,26 +57,36 @@ public class ListenerRegistration<TListener> implements ListenerExecutor {
/**
* May be overridden by subclasses. Invoked when registration occurs. Invoked while holding the
* owning multiplexer's internal lock.
+ *
+ * <p>If overridden you must ensure the superclass method is invoked (usually as the first thing
+ * in the overridden method).
*/
protected void onRegister(Object key) {}
/**
* May be overridden by subclasses. Invoked when unregistration occurs. Invoked while holding
* the owning multiplexer's internal lock.
+ *
+ * <p>If overridden you must ensure the superclass method is invoked (usually as the last thing
+ * in the overridden method).
*/
protected void onUnregister() {}
/**
- * May be overridden by subclasses. Invoked when this registration becomes active. If this
- * returns a non-null operation, that operation will be invoked for the listener. Invoked
- * while holding the owning multiplexer's internal lock.
+ * May be overridden by subclasses. Invoked when this registration becomes active. Invoked while
+ * holding the owning multiplexer's internal lock.
+ *
+ * <p>If overridden you must ensure the superclass method is invoked (usually as the first thing
+ * in the overridden method).
*/
protected void onActive() {}
/**
- * May be overridden by subclasses. Invoked when registration becomes inactive. If this returns
- * a non-null operation, that operation will be invoked for the listener. Invoked while holding
- * the owning multiplexer's internal lock.
+ * May be overridden by subclasses. Invoked when registration becomes inactive. Invoked while
+ * holding the owning multiplexer's internal lock.
+ *
+ * <p>If overridden you must ensure the superclass method is invoked (usually as the last thing
+ * in the overridden method).
*/
protected void onInactive() {}
diff --git a/services/core/java/com/android/server/location/listeners/PendingIntentListenerRegistration.java b/services/core/java/com/android/server/location/listeners/PendingIntentListenerRegistration.java
index 240ac0144293..c976601e6170 100644
--- a/services/core/java/com/android/server/location/listeners/PendingIntentListenerRegistration.java
+++ b/services/core/java/com/android/server/location/listeners/PendingIntentListenerRegistration.java
@@ -16,63 +16,47 @@
package com.android.server.location.listeners;
-import android.annotation.Nullable;
+import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
+
import android.app.PendingIntent;
-import android.location.util.identity.CallerIdentity;
import android.util.Log;
/**
* A registration that works with PendingIntent keys, and registers a CancelListener to
- * automatically remove the registration if the PendingIntent is canceled. The key for this
- * registration must either be a {@link PendingIntent} or a {@link PendingIntentKey}.
+ * automatically remove the registration if the PendingIntent is canceled.
*
- * @param <TRequest> request type
+ * @param <TKey> key type
* @param <TListener> listener type
*/
-public abstract class PendingIntentListenerRegistration<TRequest, TListener> extends
- RemoteListenerRegistration<TRequest, TListener> implements PendingIntent.CancelListener {
+public abstract class PendingIntentListenerRegistration<TKey, TListener> extends
+ RemovableListenerRegistration<TKey, TListener> implements PendingIntent.CancelListener {
- /**
- * Interface to allowed pending intent retrieval when keys are not themselves PendingIntents.
- */
- public interface PendingIntentKey {
- /**
- * Returns the pending intent associated with this key.
- */
- PendingIntent getPendingIntent();
+ protected PendingIntentListenerRegistration(TListener listener) {
+ super(DIRECT_EXECUTOR, listener);
}
- protected PendingIntentListenerRegistration(@Nullable TRequest request,
- CallerIdentity callerIdentity, TListener listener) {
- super(request, callerIdentity, listener);
- }
+ protected abstract PendingIntent getPendingIntentFromKey(TKey key);
@Override
- protected final void onRemovableListenerRegister() {
- getPendingIntentFromKey(getKey()).registerCancelListener(this);
- onPendingIntentListenerRegister();
- }
+ protected void onRegister() {
+ super.onRegister();
- @Override
- protected final void onRemovableListenerUnregister() {
- onPendingIntentListenerUnregister();
- getPendingIntentFromKey(getKey()).unregisterCancelListener(this);
+ if (!getPendingIntentFromKey(getKey()).addCancelListener(DIRECT_EXECUTOR, this)) {
+ remove();
+ }
}
- /**
- * May be overridden in place of {@link #onRemovableListenerRegister()}.
- */
- protected void onPendingIntentListenerRegister() {}
+ @Override
+ protected void onUnregister() {
+ getPendingIntentFromKey(getKey()).removeCancelListener(this);
- /**
- * May be overridden in place of {@link #onRemovableListenerUnregister()}.
- */
- protected void onPendingIntentListenerUnregister() {}
+ super.onUnregister();
+ }
@Override
protected void onOperationFailure(ListenerOperation<TListener> operation, Exception e) {
if (e instanceof PendingIntent.CanceledException) {
- Log.w(getOwner().getTag(), "registration " + this + " removed", e);
+ Log.w(getTag(), "registration " + this + " removed", e);
remove();
} else {
super.onOperationFailure(operation, e);
@@ -81,21 +65,10 @@ public abstract class PendingIntentListenerRegistration<TRequest, TListener> ext
@Override
public void onCanceled(PendingIntent intent) {
- if (Log.isLoggable(getOwner().getTag(), Log.DEBUG)) {
- Log.d(getOwner().getTag(),
- "pending intent registration " + getIdentity() + " canceled");
+ if (Log.isLoggable(getTag(), Log.DEBUG)) {
+ Log.d(getTag(), "pending intent registration " + this + " canceled");
}
remove();
}
-
- private PendingIntent getPendingIntentFromKey(Object key) {
- if (key instanceof PendingIntent) {
- return (PendingIntent) key;
- } else if (key instanceof PendingIntentKey) {
- return ((PendingIntentKey) key).getPendingIntent();
- } else {
- throw new IllegalArgumentException("key must be PendingIntent or PendingIntentKey");
- }
- }
-}
+} \ No newline at end of file
diff --git a/services/core/java/com/android/server/location/listeners/RemoteListenerRegistration.java b/services/core/java/com/android/server/location/listeners/RemoteListenerRegistration.java
deleted file mode 100644
index 4eca577dcf4f..000000000000
--- a/services/core/java/com/android/server/location/listeners/RemoteListenerRegistration.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.location.listeners;
-
-
-import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
-
-import android.annotation.Nullable;
-import android.location.util.identity.CallerIdentity;
-import android.os.Process;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.FgThread;
-
-import java.util.Objects;
-import java.util.concurrent.Executor;
-
-/**
- * A listener registration representing a remote (possibly from a different process) listener.
- * Listeners from a different process will be run on a direct executor, since the x-process listener
- * invocation should already be asynchronous. Listeners from the same process will be run on a
- * normal executor, since in-process listener invocation may be synchronous.
- *
- * @param <TRequest> request type
- * @param <TListener> listener type
- */
-public abstract class RemoteListenerRegistration<TRequest, TListener> extends
- RemovableListenerRegistration<TRequest, TListener> {
-
- @VisibleForTesting
- public static final Executor IN_PROCESS_EXECUTOR = FgThread.getExecutor();
-
- private static Executor chooseExecutor(CallerIdentity identity) {
- // if a client is in the same process as us, binder calls will execute synchronously and
- // we shouldn't run callbacks directly since they might be run under lock and deadlock
- if (identity.getPid() == Process.myPid()) {
- // there's a slight loophole here for pending intents - pending intent callbacks can
- // always be run on the direct executor since they're always asynchronous, but honestly
- // you shouldn't be using pending intent callbacks within the same process anyways
- return IN_PROCESS_EXECUTOR;
- } else {
- return DIRECT_EXECUTOR;
- }
- }
-
- private final CallerIdentity mIdentity;
-
- protected RemoteListenerRegistration(@Nullable TRequest request, CallerIdentity identity,
- TListener listener) {
- super(chooseExecutor(identity), request, listener);
- mIdentity = Objects.requireNonNull(identity);
- }
-
- /**
- * Returns the listener identity.
- */
- public final CallerIdentity getIdentity() {
- return mIdentity;
- }
-}
-
diff --git a/services/core/java/com/android/server/location/listeners/RemovableListenerRegistration.java b/services/core/java/com/android/server/location/listeners/RemovableListenerRegistration.java
index 618ff24b873b..3c302fbfaa63 100644
--- a/services/core/java/com/android/server/location/listeners/RemovableListenerRegistration.java
+++ b/services/core/java/com/android/server/location/listeners/RemovableListenerRegistration.java
@@ -20,22 +20,23 @@ import android.annotation.Nullable;
import java.util.Objects;
import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
/**
* A listener registration that stores its own key, and thus can remove itself. By default it will
* remove itself if any checked exception occurs on listener execution.
*
- * @param <TRequest> request type
+ * @param <TKey> key type
* @param <TListener> listener type
*/
-public abstract class RemovableListenerRegistration<TRequest, TListener> extends
- RequestListenerRegistration<TRequest, TListener> {
+public abstract class RemovableListenerRegistration<TKey, TListener> extends
+ ListenerRegistration<TListener> {
- private volatile @Nullable Object mKey;
+ @Nullable private volatile TKey mKey;
+ private final AtomicBoolean mRemoved = new AtomicBoolean(false);
- protected RemovableListenerRegistration(Executor executor, @Nullable TRequest request,
- TListener listener) {
- super(executor, request, listener);
+ protected RemovableListenerRegistration(Executor executor, TListener listener) {
+ super(executor, listener);
}
/**
@@ -43,46 +44,76 @@ public abstract class RemovableListenerRegistration<TRequest, TListener> extends
* with. Often this is easiest to accomplish by defining registration subclasses as non-static
* inner classes of the multiplexer they are to be used with.
*/
- protected abstract ListenerMultiplexer<?, ? super TListener, ?, ?> getOwner();
+ protected abstract ListenerMultiplexer<TKey, ? super TListener, ?, ?> getOwner();
/**
* Returns the key associated with this registration. May not be invoked before
* {@link #onRegister(Object)} or after {@link #onUnregister()}.
*/
- protected final Object getKey() {
+ protected final TKey getKey() {
return Objects.requireNonNull(mKey);
}
/**
- * Removes this registration. Does nothing if invoked before {@link #onRegister(Object)} or
- * after {@link #onUnregister()}. It is safe to invoke this from within either function.
+ * Convenience method equivalent to invoking {@link #remove(boolean)} with the
+ * {@code immediately} parameter set to true.
*/
public final void remove() {
- Object key = mKey;
- if (key != null) {
- getOwner().removeRegistration(key, this);
- }
+ remove(true);
}
- @Override
- protected final void onRegister(Object key) {
- mKey = Objects.requireNonNull(key);
- onRemovableListenerRegister();
+ /**
+ * Removes this registration. If the {@code immediately} parameter is true, all pending listener
+ * invocations will fail. If the {@code immediately} parameter is false, listener invocations
+ * that were scheduled before remove was invoked (including invocations scheduled within {@link
+ * #onRemove(boolean)}) will continue, but any listener invocations scheduled after remove was
+ * invoked will fail.
+ *
+ * <p>Only the first call to this method will ever go through (and so {@link #onRemove(boolean)}
+ * will only ever be invoked once).
+ *
+ * <p>Does nothing if invoked before {@link #onRegister()} or after {@link #onUnregister()}.
+ */
+ public final void remove(boolean immediately) {
+ TKey key = mKey;
+ if (key != null && !mRemoved.getAndSet(true)) {
+ onRemove(immediately);
+ if (immediately) {
+ getOwner().removeRegistration(key, this);
+ } else {
+ executeOperation(listener -> getOwner().removeRegistration(key, this));
+ }
+ }
}
+ /**
+ * Invoked just before this registration is removed due to {@link #remove(boolean)}, on the same
+ * thread as the responsible {@link #remove(boolean)} call.
+ *
+ * <p>This method will only ever be invoked once, no matter how many calls to {@link
+ * #remove(boolean)} are made, as any registration can only be removed once.
+ */
+ protected void onRemove(boolean immediately) {}
+
@Override
- protected final void onUnregister() {
- onRemovableListenerUnregister();
- mKey = null;
+ protected final void onRegister(Object key) {
+ super.onRegister(key);
+ mKey = (TKey) Objects.requireNonNull(key);
+ onRegister();
}
/**
- * May be overridden in place of {@link #onRegister(Object)}.
+ * May be overridden by subclasses. Invoked when registration occurs. Invoked while holding the
+ * owning multiplexer's internal lock.
+ *
+ * <p>If overridden you must ensure the superclass method is invoked (usually as the first thing
+ * in the overridden method).
*/
- protected void onRemovableListenerRegister() {}
+ protected void onRegister() {}
- /**
- * May be overridden in place of {@link #onUnregister()}.
- */
- protected void onRemovableListenerUnregister() {}
+ @Override
+ protected void onUnregister() {
+ mKey = null;
+ super.onUnregister();
+ }
}
diff --git a/services/core/java/com/android/server/location/listeners/RequestListenerRegistration.java b/services/core/java/com/android/server/location/listeners/RequestListenerRegistration.java
deleted file mode 100644
index 0c2fc9142d92..000000000000
--- a/services/core/java/com/android/server/location/listeners/RequestListenerRegistration.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.location.listeners;
-
-import java.util.concurrent.Executor;
-
-/**
- * A listener registration object which includes an associated request.
- *
- * @param <TRequest> request type
- * @param <TListener> listener type
- */
-public class RequestListenerRegistration<TRequest, TListener> extends
- ListenerRegistration<TListener> {
-
- private final TRequest mRequest;
-
- protected RequestListenerRegistration(Executor executor, TRequest request,
- TListener listener) {
- super(executor, listener);
- mRequest = request;
- }
-
- /**
- * Returns the request associated with this listener, or null if one wasn't supplied.
- */
- public TRequest getRequest() {
- return mRequest;
- }
-
- @Override
- public String toString() {
- if (mRequest == null) {
- return "[]";
- } else {
- return mRequest.toString();
- }
- }
-}
-
diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
index 549fd4918023..a69a079b679d 100644
--- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
@@ -67,7 +67,6 @@ import android.location.provider.ProviderProperties;
import android.location.provider.ProviderRequest;
import android.location.util.identity.CallerIdentity;
import android.os.Binder;
-import android.os.Build;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.IBinder;
@@ -115,7 +114,7 @@ import com.android.server.location.injector.SettingsHelper.UserSettingChangedLis
import com.android.server.location.injector.UserInfoHelper;
import com.android.server.location.injector.UserInfoHelper.UserListener;
import com.android.server.location.listeners.ListenerMultiplexer;
-import com.android.server.location.listeners.RemoteListenerRegistration;
+import com.android.server.location.listeners.RemovableListenerRegistration;
import com.android.server.location.settings.LocationSettings;
import com.android.server.location.settings.LocationUserSettings;
@@ -124,8 +123,10 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Executor;
import java.util.function.Predicate;
/**
@@ -354,44 +355,60 @@ public class LocationProviderManager extends
public void deliverOnFlushComplete(int requestCode) {}
}
- protected abstract class Registration extends RemoteListenerRegistration<LocationRequest,
- LocationTransport> {
+ protected abstract class Registration extends RemovableListenerRegistration<Object,
+ LocationTransport> {
+ private final LocationRequest mBaseRequest;
+ private final CallerIdentity mIdentity;
private final @PermissionLevel int mPermissionLevel;
// we cache these values because checking/calculating on the fly is more expensive
+ @GuardedBy("mMultiplexerLock")
private boolean mPermitted;
+ @GuardedBy("mMultiplexerLock")
private boolean mForeground;
+ @GuardedBy("mMultiplexerLock")
private LocationRequest mProviderLocationRequest;
+ @GuardedBy("mMultiplexerLock")
private boolean mIsUsingHighPower;
- private @Nullable Location mLastLocation = null;
+ @Nullable private Location mLastLocation = null;
- protected Registration(LocationRequest request, CallerIdentity identity,
+ protected Registration(LocationRequest request, CallerIdentity identity, Executor executor,
LocationTransport transport, @PermissionLevel int permissionLevel) {
- super(Objects.requireNonNull(request), identity, transport);
+ super(executor, transport);
Preconditions.checkArgument(identity.getListenerId() != null);
Preconditions.checkArgument(permissionLevel > PERMISSION_NONE);
Preconditions.checkArgument(!request.getWorkSource().isEmpty());
+ mBaseRequest = Objects.requireNonNull(request);
+ mIdentity = Objects.requireNonNull(identity);
mPermissionLevel = permissionLevel;
mProviderLocationRequest = request;
}
- @GuardedBy("mLock")
- @Override
- protected final void onRemovableListenerRegister() {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
+ public final CallerIdentity getIdentity() {
+ return mIdentity;
+ }
+
+ public final LocationRequest getRequest() {
+ synchronized (mMultiplexerLock) {
+ return mProviderLocationRequest;
}
+ }
+
+ @GuardedBy("mMultiplexerLock")
+ @Override
+ protected void onRegister() {
+ super.onRegister();
if (D) {
Log.d(TAG, mName + " provider added registration from " + getIdentity() + " -> "
+ getRequest());
}
- EVENT_LOG.logProviderClientRegistered(mName, getIdentity(), super.getRequest());
+ EVENT_LOG.logProviderClientRegistered(mName, getIdentity(), mBaseRequest);
// initialization order is important as there are ordering dependencies
mPermitted = mLocationPermissionsHelper.hasLocationPermissions(mPermissionLevel,
@@ -400,110 +417,72 @@ public class LocationProviderManager extends
mProviderLocationRequest = calculateProviderLocationRequest();
mIsUsingHighPower = isUsingHighPower();
- onProviderListenerRegister();
-
if (mForeground) {
EVENT_LOG.logProviderClientForeground(mName, getIdentity());
}
}
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
@Override
- protected final void onRemovableListenerUnregister() {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
-
- onProviderListenerUnregister();
-
+ protected void onUnregister() {
EVENT_LOG.logProviderClientUnregistered(mName, getIdentity());
if (D) {
Log.d(TAG, mName + " provider removed registration from " + getIdentity());
}
- }
- /**
- * Subclasses may override this instead of {@link #onRemovableListenerRegister()}.
- */
- @GuardedBy("mLock")
- protected void onProviderListenerRegister() {}
-
- /**
- * Subclasses may override this instead of {@link #onRemovableListenerUnregister()}.
- */
- @GuardedBy("mLock")
- protected void onProviderListenerUnregister() {}
+ super.onUnregister();
+ }
+ @GuardedBy("mMultiplexerLock")
@Override
- protected final void onActive() {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
-
+ protected void onActive() {
EVENT_LOG.logProviderClientActive(mName, getIdentity());
if (!getRequest().isHiddenFromAppOps()) {
mAppOpsHelper.startOpNoThrow(OP_MONITOR_LOCATION, getIdentity());
}
onHighPowerUsageChanged();
-
- onProviderListenerActive();
}
+ @GuardedBy("mMultiplexerLock")
@Override
- protected final void onInactive() {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
-
+ protected void onInactive() {
onHighPowerUsageChanged();
if (!getRequest().isHiddenFromAppOps()) {
mAppOpsHelper.finishOp(OP_MONITOR_LOCATION, getIdentity());
}
- onProviderListenerInactive();
-
EVENT_LOG.logProviderClientInactive(mName, getIdentity());
}
- /**
- * Subclasses may override this instead of {@link #onActive()}.
- */
- @GuardedBy("mLock")
- protected void onProviderListenerActive() {}
-
- /**
- * Subclasses may override this instead of {@link #onInactive()} ()}.
- */
- @GuardedBy("mLock")
- protected void onProviderListenerInactive() {}
-
- @Override
- public final LocationRequest getRequest() {
- return mProviderLocationRequest;
- }
-
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
final void setLastDeliveredLocation(@Nullable Location location) {
mLastLocation = location;
}
- @GuardedBy("mLock")
public final Location getLastDeliveredLocation() {
- return mLastLocation;
+ synchronized (mMultiplexerLock) {
+ return mLastLocation;
+ }
}
public @PermissionLevel int getPermissionLevel() {
- return mPermissionLevel;
+ synchronized (mMultiplexerLock) {
+ return mPermissionLevel;
+ }
}
public final boolean isForeground() {
- return mForeground;
+ synchronized (mMultiplexerLock) {
+ return mForeground;
+ }
}
public final boolean isPermitted() {
- return mPermitted;
+ synchronized (mMultiplexerLock) {
+ return mPermitted;
+ }
}
public final void flush(int requestCode) {
@@ -519,13 +498,14 @@ public class LocationProviderManager extends
return LocationProviderManager.this;
}
- @GuardedBy("mLock")
final boolean onProviderPropertiesChanged() {
- onHighPowerUsageChanged();
- return false;
+ synchronized (mMultiplexerLock) {
+ onHighPowerUsageChanged();
+ return false;
+ }
}
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
private void onHighPowerUsageChanged() {
boolean isUsingHighPower = isUsingHighPower();
if (isUsingHighPower != mIsUsingHighPower) {
@@ -541,12 +521,7 @@ public class LocationProviderManager extends
}
}
- @GuardedBy("mLock")
private boolean isUsingHighPower() {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
-
ProviderProperties properties = getProperties();
if (properties == null) {
return false;
@@ -557,30 +532,28 @@ public class LocationProviderManager extends
&& properties.getPowerUsage() == ProviderProperties.POWER_USAGE_HIGH;
}
- @GuardedBy("mLock")
final boolean onLocationPermissionsChanged(@Nullable String packageName) {
- if (packageName == null || getIdentity().getPackageName().equals(packageName)) {
- return onLocationPermissionsChanged();
- }
+ synchronized (mMultiplexerLock) {
+ if (packageName == null || getIdentity().getPackageName().equals(packageName)) {
+ return onLocationPermissionsChanged();
+ }
- return false;
+ return false;
+ }
}
- @GuardedBy("mLock")
final boolean onLocationPermissionsChanged(int uid) {
- if (getIdentity().getUid() == uid) {
- return onLocationPermissionsChanged();
- }
+ synchronized (mMultiplexerLock) {
+ if (getIdentity().getUid() == uid) {
+ return onLocationPermissionsChanged();
+ }
- return false;
+ return false;
+ }
}
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
private boolean onLocationPermissionsChanged() {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
-
boolean permitted = mLocationPermissionsHelper.hasLocationPermissions(mPermissionLevel,
getIdentity());
if (permitted != mPermitted) {
@@ -603,82 +576,73 @@ public class LocationProviderManager extends
return false;
}
- @GuardedBy("mLock")
final boolean onAdasGnssLocationEnabledChanged(int userId) {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
+ synchronized (mMultiplexerLock) {
+ if (getIdentity().getUserId() == userId) {
+ return onProviderLocationRequestChanged();
+ }
- if (getIdentity().getUserId() == userId) {
- return onProviderLocationRequestChanged();
+ return false;
}
-
- return false;
}
- @GuardedBy("mLock")
final boolean onForegroundChanged(int uid, boolean foreground) {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
+ synchronized (mMultiplexerLock) {
+ if (getIdentity().getUid() == uid && foreground != mForeground) {
+ if (D) {
+ Log.v(TAG, mName + " provider uid " + uid + " foreground = " + foreground);
+ }
- if (getIdentity().getUid() == uid && foreground != mForeground) {
- if (D) {
- Log.v(TAG, mName + " provider uid " + uid + " foreground = " + foreground);
- }
+ mForeground = foreground;
- mForeground = foreground;
+ if (mForeground) {
+ EVENT_LOG.logProviderClientForeground(mName, getIdentity());
+ } else {
+ EVENT_LOG.logProviderClientBackground(mName, getIdentity());
+ }
- if (mForeground) {
- EVENT_LOG.logProviderClientForeground(mName, getIdentity());
- } else {
- EVENT_LOG.logProviderClientBackground(mName, getIdentity());
+ // note that onProviderLocationRequestChanged() is always called
+ return onProviderLocationRequestChanged()
+ || mLocationPowerSaveModeHelper.getLocationPowerSaveMode()
+ == LOCATION_MODE_FOREGROUND_ONLY;
}
- // note that onProviderLocationRequestChanged() is always called
- return onProviderLocationRequestChanged()
- || mLocationPowerSaveModeHelper.getLocationPowerSaveMode()
- == LOCATION_MODE_FOREGROUND_ONLY;
+ return false;
}
-
- return false;
}
- @GuardedBy("mLock")
final boolean onProviderLocationRequestChanged() {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
-
- LocationRequest newRequest = calculateProviderLocationRequest();
- if (mProviderLocationRequest.equals(newRequest)) {
- return false;
- }
+ synchronized (mMultiplexerLock) {
+ LocationRequest newRequest = calculateProviderLocationRequest();
+ if (mProviderLocationRequest.equals(newRequest)) {
+ return false;
+ }
- LocationRequest oldRequest = mProviderLocationRequest;
- mProviderLocationRequest = newRequest;
- onHighPowerUsageChanged();
- updateService();
+ LocationRequest oldRequest = mProviderLocationRequest;
+ mProviderLocationRequest = newRequest;
+ onHighPowerUsageChanged();
+ updateService();
- // if bypass state has changed then the active state may have changed
- return oldRequest.isBypass() != newRequest.isBypass();
+ // if bypass state has changed then the active state may have changed
+ return oldRequest.isBypass() != newRequest.isBypass();
+ }
}
+ @GuardedBy("mMultiplexerLock")
private LocationRequest calculateProviderLocationRequest() {
- LocationRequest baseRequest = super.getRequest();
- LocationRequest.Builder builder = new LocationRequest.Builder(baseRequest);
+ LocationRequest.Builder builder = new LocationRequest.Builder(mBaseRequest);
if (mPermissionLevel < PERMISSION_FINE) {
builder.setQuality(LocationRequest.QUALITY_LOW_POWER);
- if (baseRequest.getIntervalMillis() < MIN_COARSE_INTERVAL_MS) {
+ if (mBaseRequest.getIntervalMillis() < MIN_COARSE_INTERVAL_MS) {
builder.setIntervalMillis(MIN_COARSE_INTERVAL_MS);
}
- if (baseRequest.getMinUpdateIntervalMillis() < MIN_COARSE_INTERVAL_MS) {
+ if (mBaseRequest.getMinUpdateIntervalMillis() < MIN_COARSE_INTERVAL_MS) {
builder.setMinUpdateIntervalMillis(MIN_COARSE_INTERVAL_MS);
}
}
- boolean locationSettingsIgnored = baseRequest.isLocationSettingsIgnored();
+ boolean locationSettingsIgnored = mBaseRequest.isLocationSettingsIgnored();
if (locationSettingsIgnored) {
// if we are not currently allowed use location settings ignored, disable it
if (!mSettingsHelper.getIgnoreSettingsAllowlist().contains(
@@ -690,7 +654,7 @@ public class LocationProviderManager extends
builder.setLocationSettingsIgnored(locationSettingsIgnored);
}
- boolean adasGnssBypass = baseRequest.isAdasGnssBypass();
+ boolean adasGnssBypass = mBaseRequest.isAdasGnssBypass();
if (adasGnssBypass) {
// if we are not currently allowed use adas gnss bypass, disable it
if (!GPS_PROVIDER.equals(mName)) {
@@ -710,7 +674,7 @@ public class LocationProviderManager extends
if (!locationSettingsIgnored && !isThrottlingExempt()) {
// throttle in the background
if (!mForeground) {
- builder.setIntervalMillis(max(baseRequest.getIntervalMillis(),
+ builder.setIntervalMillis(max(mBaseRequest.getIntervalMillis(),
mSettingsHelper.getBackgroundThrottleIntervalMs()));
}
}
@@ -727,8 +691,7 @@ public class LocationProviderManager extends
return mLocationManagerInternal.isProvider(null, getIdentity());
}
- @GuardedBy("mLock")
- abstract @Nullable ListenerOperation<LocationTransport> acceptLocationChange(
+ @Nullable abstract ListenerOperation<LocationTransport> acceptLocationChange(
LocationResult fineLocationResult);
@Override
@@ -769,13 +732,19 @@ public class LocationProviderManager extends
final ExternalWakeLockReleaser mWakeLockReleaser;
private volatile ProviderTransport mProviderTransport;
+
+ @GuardedBy("mMultiplexerLock")
private int mNumLocationsDelivered = 0;
+ @GuardedBy("mMultiplexerLock")
private long mExpirationRealtimeMs = Long.MAX_VALUE;
protected <TTransport extends LocationTransport & ProviderTransport> LocationRegistration(
- LocationRequest request, CallerIdentity identity, TTransport transport,
+ LocationRequest request,
+ CallerIdentity identity,
+ Executor executor,
+ TTransport transport,
@PermissionLevel int permissionLevel) {
- super(request, identity, transport, permissionLevel);
+ super(request, identity, executor, transport, permissionLevel);
mProviderTransport = transport;
mWakeLock = Objects.requireNonNull(mContext.getSystemService(PowerManager.class))
.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG);
@@ -789,9 +758,13 @@ public class LocationProviderManager extends
mProviderTransport = null;
}
- @GuardedBy("mLock")
+ // TODO: remove suppression when GuardedBy analysis can recognize lock from super class
+ @SuppressWarnings("GuardedBy")
+ @GuardedBy("mMultiplexerLock")
@Override
- protected final void onProviderListenerRegister() {
+ protected void onRegister() {
+ super.onRegister();
+
long registerTimeMs = SystemClock.elapsedRealtime();
mExpirationRealtimeMs = getRequest().getExpirationRealtimeMs(registerTimeMs);
@@ -810,8 +783,6 @@ public class LocationProviderManager extends
// start listening for provider enabled/disabled events
addEnabledListener(this);
- onLocationListenerRegister();
-
// if the provider is currently disabled, let the client know immediately
int userId = getIdentity().getUserId();
if (!isEnabled(userId)) {
@@ -819,9 +790,11 @@ public class LocationProviderManager extends
}
}
- @GuardedBy("mLock")
+ // TODO: remove suppression when GuardedBy analysis can recognize lock from super class
+ @SuppressWarnings("GuardedBy")
+ @GuardedBy("mMultiplexerLock")
@Override
- protected final void onProviderListenerUnregister() {
+ protected void onUnregister() {
// stop listening for provider enabled/disabled events
removeEnabledListener(this);
@@ -830,24 +803,16 @@ public class LocationProviderManager extends
mAlarmHelper.cancel(this);
}
- onLocationListenerUnregister();
+ super.onUnregister();
}
- /**
- * Subclasses may override this instead of {@link #onRemovableListenerRegister()}.
- */
- @GuardedBy("mLock")
- protected void onLocationListenerRegister() {}
-
- /**
- * Subclasses may override this instead of {@link #onRemovableListenerUnregister()}.
- */
- @GuardedBy("mLock")
- protected void onLocationListenerUnregister() {}
-
- @GuardedBy("mLock")
+ // TODO: remove suppression when GuardedBy analysis can recognize lock from super class
+ @SuppressWarnings("GuardedBy")
+ @GuardedBy("mMultiplexerLock")
@Override
- protected final void onProviderListenerActive() {
+ protected void onActive() {
+ super.onActive();
+
// a new registration may not get a location immediately, the provider request may be
// delayed. therefore we deliver a historical location if available. since delivering an
// older location could be considered a breaking change for some applications, we only
@@ -883,21 +848,17 @@ public class LocationProviderManager extends
+ " expired at " + TimeUtils.formatRealtime(mExpirationRealtimeMs));
}
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
// no need to remove alarm after it's fired
mExpirationRealtimeMs = Long.MAX_VALUE;
remove();
}
}
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
@Override
@Nullable ListenerOperation<LocationTransport> acceptLocationChange(
LocationResult fineLocationResult) {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
-
// check expiration time - alarm is not guaranteed to go off at the right time,
// especially for short intervals
if (SystemClock.elapsedRealtime() >= mExpirationRealtimeMs) {
@@ -1017,9 +978,7 @@ public class LocationProviderManager extends
+ " finished after " + mNumLocationsDelivered + " updates");
}
- synchronized (mLock) {
- remove();
- }
+ remove();
}
}
}
@@ -1049,12 +1008,18 @@ public class LocationProviderManager extends
LocationListenerRegistration(LocationRequest request, CallerIdentity identity,
LocationListenerTransport transport, @PermissionLevel int permissionLevel) {
- super(request, identity, transport, permissionLevel);
+ super(request, identity,
+ identity.isMyProcess() ? FgThread.getExecutor() : DIRECT_EXECUTOR, transport,
+ permissionLevel);
}
- @GuardedBy("mLock")
+ // TODO: remove suppression when GuardedBy analysis can recognize lock from super class
+ @SuppressWarnings("GuardedBy")
+ @GuardedBy("mMultiplexerLock")
@Override
- protected void onLocationListenerRegister() {
+ protected void onRegister() {
+ super.onRegister();
+
try {
((IBinder) getKey()).linkToDeath(this, 0);
} catch (RemoteException e) {
@@ -1062,10 +1027,22 @@ public class LocationProviderManager extends
}
}
- @GuardedBy("mLock")
+ // TODO: remove suppression when GuardedBy analysis can recognize lock from super class
+ @SuppressWarnings("GuardedBy")
+ @GuardedBy("mMultiplexerLock")
@Override
- protected void onLocationListenerUnregister() {
- ((IBinder) getKey()).unlinkToDeath(this, 0);
+ protected void onUnregister() {
+ try {
+ ((IBinder) getKey()).unlinkToDeath(this, 0);
+ } catch (NoSuchElementException e) {
+ // the only way this exception can occur should be if another exception has been
+ // thrown prior to registration completing, and that exception is currently
+ // unwinding the call stack and causing this cleanup. since that exception should
+ // crash us anyways, drop this exception so we're not hiding the original exception.
+ Log.w(getTag(), "failed to unregister binder death listener", e);
+ }
+
+ super.onUnregister();
}
@Override
@@ -1083,9 +1060,7 @@ public class LocationProviderManager extends
private void onTransportFailure(Exception e) {
if (e instanceof RemoteException) {
Log.w(TAG, mName + " provider registration " + getIdentity() + " removed", e);
- synchronized (mLock) {
- remove();
- }
+ remove();
} else {
throw new AssertionError(e);
}
@@ -1098,9 +1073,7 @@ public class LocationProviderManager extends
Log.d(TAG, mName + " provider registration " + getIdentity() + " died");
}
- synchronized (mLock) {
- remove();
- }
+ remove();
} catch (RuntimeException e) {
// the caller may swallow runtime exceptions, so we rethrow as assertion errors to
// ensure the crash is seen
@@ -1115,21 +1088,27 @@ public class LocationProviderManager extends
LocationPendingIntentRegistration(LocationRequest request,
CallerIdentity identity, LocationPendingIntentTransport transport,
@PermissionLevel int permissionLevel) {
- super(request, identity, transport, permissionLevel);
+ super(request, identity, DIRECT_EXECUTOR, transport, permissionLevel);
}
- @GuardedBy("mLock")
+ // TODO: remove suppression when GuardedBy analysis can recognize lock from super class
+ @SuppressWarnings("GuardedBy")
+ @GuardedBy("mMultiplexerLock")
@Override
- protected void onLocationListenerRegister() {
+ protected void onRegister() {
+ super.onRegister();
if (!((PendingIntent) getKey()).addCancelListener(DIRECT_EXECUTOR, this)) {
remove();
}
}
- @GuardedBy("mLock")
+ // TODO: remove suppression when GuardedBy analysis can recognize lock from super class
+ @SuppressWarnings("GuardedBy")
+ @GuardedBy("mMultiplexerLock")
@Override
- protected void onLocationListenerUnregister() {
+ protected void onUnregister() {
((PendingIntent) getKey()).removeCancelListener(this);
+ super.onUnregister();
}
@Override
@@ -1147,9 +1126,7 @@ public class LocationProviderManager extends
private void onTransportFailure(Exception e) {
if (e instanceof PendingIntent.CanceledException) {
Log.w(TAG, mName + " provider registration " + getIdentity() + " removed", e);
- synchronized (mLock) {
- remove();
- }
+ remove();
} else {
throw new AssertionError(e);
}
@@ -1161,25 +1138,32 @@ public class LocationProviderManager extends
Log.d(TAG, mName + " provider registration " + getIdentity() + " canceled");
}
- synchronized (mLock) {
- remove();
- }
+ remove();
}
}
protected final class GetCurrentLocationListenerRegistration extends Registration implements
IBinder.DeathRecipient, OnAlarmListener {
+ @GuardedBy("mMultiplexerLock")
private long mExpirationRealtimeMs = Long.MAX_VALUE;
GetCurrentLocationListenerRegistration(LocationRequest request,
CallerIdentity identity, LocationTransport transport, int permissionLevel) {
- super(request, identity, transport, permissionLevel);
+ super(request,
+ identity,
+ identity.isMyProcess() ? FgThread.getExecutor() : DIRECT_EXECUTOR,
+ transport,
+ permissionLevel);
}
- @GuardedBy("mLock")
+ // TODO: remove suppression when GuardedBy analysis can recognize lock from super class
+ @SuppressWarnings("GuardedBy")
+ @GuardedBy("mMultiplexerLock")
@Override
- protected void onProviderListenerRegister() {
+ protected void onRegister() {
+ super.onRegister();
+
try {
((IBinder) getKey()).linkToDeath(this, 0);
} catch (RemoteException e) {
@@ -1202,20 +1186,36 @@ public class LocationProviderManager extends
}
}
- @GuardedBy("mLock")
+ // TODO: remove suppression when GuardedBy analysis can recognize lock from super class
+ @SuppressWarnings("GuardedBy")
+ @GuardedBy("mMultiplexerLock")
@Override
- protected void onProviderListenerUnregister() {
+ protected void onUnregister() {
// remove alarm for expiration
if (mExpirationRealtimeMs < Long.MAX_VALUE) {
mAlarmHelper.cancel(this);
}
- ((IBinder) getKey()).unlinkToDeath(this, 0);
+ try {
+ ((IBinder) getKey()).unlinkToDeath(this, 0);
+ } catch (NoSuchElementException e) {
+ // the only way this exception can occur should be if another exception has been
+ // thrown prior to registration completing, and that exception is currently
+ // unwinding the call stack and causing this cleanup. since that exception should
+ // crash us anyways, drop this exception so we're not hiding the original exception.
+ Log.w(getTag(), "failed to unregister binder death listener", e);
+ }
+
+ super.onUnregister();
}
- @GuardedBy("mLock")
+ // TODO: remove suppression when GuardedBy analysis can recognize lock from super class
+ @SuppressWarnings("GuardedBy")
+ @GuardedBy("mMultiplexerLock")
@Override
- protected void onProviderListenerActive() {
+ protected void onActive() {
+ super.onActive();
+
Location lastLocation = getLastLocationUnsafe(
getIdentity().getUserId(),
getPermissionLevel(),
@@ -1226,17 +1226,19 @@ public class LocationProviderManager extends
}
}
- @GuardedBy("mLock")
+ // TODO: remove suppression when GuardedBy analysis can recognize lock from super class
+ @SuppressWarnings("GuardedBy")
+ @GuardedBy("mMultiplexerLock")
@Override
- protected void onProviderListenerInactive() {
+ protected void onInactive() {
// if we go inactive for any reason, fail immediately
executeOperation(acceptLocationChange(null));
+ super.onInactive();
}
+ @GuardedBy("mMultiplexerLock")
void deliverNull() {
- synchronized (mLock) {
- executeOperation(acceptLocationChange(null));
- }
+ executeOperation(acceptLocationChange(null));
}
@Override
@@ -1246,21 +1248,17 @@ public class LocationProviderManager extends
+ " expired at " + TimeUtils.formatRealtime(mExpirationRealtimeMs));
}
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
// no need to remove alarm after it's fired
mExpirationRealtimeMs = Long.MAX_VALUE;
executeOperation(acceptLocationChange(null));
}
}
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
@Override
@Nullable ListenerOperation<LocationTransport> acceptLocationChange(
@Nullable LocationResult fineLocationResult) {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
-
// check expiration time - alarm is not guaranteed to go off at the right time,
// especially for short intervals
if (SystemClock.elapsedRealtime() >= mExpirationRealtimeMs) {
@@ -1311,9 +1309,7 @@ public class LocationProviderManager extends
// on failure we're automatically removed anyways, no need to attempt removal
// again
if (success) {
- synchronized (mLock) {
- remove();
- }
+ remove();
}
}
};
@@ -1324,9 +1320,7 @@ public class LocationProviderManager extends
Exception e) {
if (e instanceof RemoteException) {
Log.w(TAG, mName + " provider registration " + getIdentity() + " removed", e);
- synchronized (mLock) {
- remove();
- }
+ remove();
} else {
throw new AssertionError(e);
}
@@ -1339,9 +1333,7 @@ public class LocationProviderManager extends
Log.d(TAG, mName + " provider registration " + getIdentity() + " died");
}
- synchronized (mLock) {
- remove();
- }
+ remove();
} catch (RuntimeException e) {
// the caller may swallow runtime exceptions, so we rethrow as assertion errors to
// ensure the crash is seen
@@ -1350,23 +1342,21 @@ public class LocationProviderManager extends
}
}
- protected final Object mLock = new Object();
-
protected final String mName;
- private final @Nullable PassiveLocationProviderManager mPassiveManager;
+ @Nullable private final PassiveLocationProviderManager mPassiveManager;
protected final Context mContext;
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
private @State int mState;
// maps of user id to value
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
private final SparseBooleanArray mEnabled; // null or not present means unknown
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
private final SparseArray<LastLocation> mLastLocations;
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
private final ArrayList<ProviderEnabledListener> mEnabledListeners;
private final CopyOnWriteArrayList<IProviderRequestListener> mProviderRequestListeners;
@@ -1418,14 +1408,14 @@ public class LocationProviderManager extends
private final ScreenInteractiveChangedListener mScreenInteractiveChangedListener =
this::onScreenInteractiveChanged;
- // acquiring mLock makes operations on mProvider atomic, but is otherwise unnecessary
+ // acquiring mMultiplexerLock makes operations on mProvider atomic, but is otherwise unnecessary
protected final MockableLocationProvider mProvider;
- @GuardedBy("mLock")
- private @Nullable OnAlarmListener mDelayedRegister;
+ @GuardedBy("mMultiplexerLock")
+ @Nullable private OnAlarmListener mDelayedRegister;
- @GuardedBy("mLock")
- private @Nullable StateChangedListener mStateChangedListener;
+ @GuardedBy("mMultiplexerLock")
+ @Nullable private StateChangedListener mStateChangedListener;
public LocationProviderManager(Context context, Injector injector,
String name, @Nullable PassiveLocationProviderManager passiveManager) {
@@ -1453,19 +1443,14 @@ public class LocationProviderManager extends
mLocationUsageLogger = injector.getLocationUsageLogger();
mLocationFudger = new LocationFudger(mSettingsHelper.getCoarseLocationAccuracyM());
- mProvider = new MockableLocationProvider(mLock);
+ mProvider = new MockableLocationProvider(mMultiplexerLock);
// set listener last, since this lets our reference escape
mProvider.getController().setListener(this);
}
- @Override
- public String getTag() {
- return TAG;
- }
-
public void startManager(@Nullable StateChangedListener listener) {
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
Preconditions.checkState(mState == STATE_STOPPED);
mState = STATE_STARTED;
mStateChangedListener = listener;
@@ -1485,7 +1470,7 @@ public class LocationProviderManager extends
}
public void stopManager() {
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
Preconditions.checkState(mState == STATE_STARTED);
mState = STATE_STOPPING;
@@ -1522,11 +1507,11 @@ public class LocationProviderManager extends
return mProvider.getState();
}
- public @Nullable CallerIdentity getProviderIdentity() {
+ @Nullable public CallerIdentity getProviderIdentity() {
return mProvider.getState().identity;
}
- public @Nullable ProviderProperties getProperties() {
+ @Nullable public ProviderProperties getProperties() {
return mProvider.getState().properties;
}
@@ -1543,7 +1528,7 @@ public class LocationProviderManager extends
Preconditions.checkArgument(userId >= 0);
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
int index = mEnabled.indexOfKey(userId);
if (index < 0) {
// this generally shouldn't occur, but might be possible due to race conditions
@@ -1558,14 +1543,14 @@ public class LocationProviderManager extends
}
public void addEnabledListener(ProviderEnabledListener listener) {
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
Preconditions.checkState(mState != STATE_STOPPED);
mEnabledListeners.add(listener);
}
}
public void removeEnabledListener(ProviderEnabledListener listener) {
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
Preconditions.checkState(mState != STATE_STOPPED);
mEnabledListeners.remove(listener);
}
@@ -1582,7 +1567,7 @@ public class LocationProviderManager extends
}
public void setRealProvider(@Nullable AbstractLocationProvider provider) {
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
Preconditions.checkState(mState != STATE_STOPPED);
final long identity = Binder.clearCallingIdentity();
@@ -1595,7 +1580,7 @@ public class LocationProviderManager extends
}
public void setMockProvider(@Nullable MockLocationProvider provider) {
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
Preconditions.checkState(mState != STATE_STOPPED);
EVENT_LOG.logProviderMocked(mName, provider != null);
@@ -1622,7 +1607,7 @@ public class LocationProviderManager extends
}
public void setMockProviderAllowed(boolean enabled) {
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
if (!mProvider.isMock()) {
throw new IllegalArgumentException(mName + " provider is not a test provider");
}
@@ -1637,7 +1622,7 @@ public class LocationProviderManager extends
}
public void setMockProviderLocation(Location location) {
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
if (!mProvider.isMock()) {
throw new IllegalArgumentException(mName + " provider is not a test provider");
}
@@ -1659,7 +1644,7 @@ public class LocationProviderManager extends
}
}
- public @Nullable Location getLastLocation(LastLocationRequest request,
+ @Nullable public Location getLastLocation(LastLocationRequest request,
CallerIdentity identity, @PermissionLevel int permissionLevel) {
request = calculateLastLocationRequest(request, identity);
@@ -1732,7 +1717,7 @@ public class LocationProviderManager extends
* location, even if the permissionLevel is coarse. You are responsible for coarsening the
* location if necessary.
*/
- public @Nullable Location getLastLocationUnsafe(int userId,
+ @Nullable public Location getLastLocationUnsafe(int userId,
@PermissionLevel int permissionLevel, boolean isBypass,
long maximumAgeMs) {
if (userId == UserHandle.USER_ALL) {
@@ -1756,7 +1741,7 @@ public class LocationProviderManager extends
Preconditions.checkArgument(userId >= 0);
Location location;
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
Preconditions.checkState(mState != STATE_STOPPED);
LastLocation lastLocation = mLastLocations.get(userId);
if (lastLocation == null) {
@@ -1778,7 +1763,7 @@ public class LocationProviderManager extends
}
public void injectLastLocation(Location location, int userId) {
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
Preconditions.checkState(mState != STATE_STOPPED);
if (getLastLocationUnsafe(userId, PERMISSION_FINE, false, Long.MAX_VALUE) == null) {
setLastLocation(location, userId);
@@ -1800,7 +1785,7 @@ public class LocationProviderManager extends
Preconditions.checkArgument(userId >= 0);
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
LastLocation lastLocation = mLastLocations.get(userId);
if (lastLocation == null) {
lastLocation = new LastLocation();
@@ -1814,7 +1799,7 @@ public class LocationProviderManager extends
}
}
- public @Nullable ICancellationSignal getCurrentLocation(LocationRequest request,
+ @Nullable public ICancellationSignal getCurrentLocation(LocationRequest request,
CallerIdentity identity, int permissionLevel, ILocationCallback callback) {
if (request.getDurationMillis() > MAX_GET_CURRENT_LOCATION_TIMEOUT_MS) {
request = new LocationRequest.Builder(request)
@@ -1829,7 +1814,7 @@ public class LocationProviderManager extends
new GetCurrentLocationTransport(callback),
permissionLevel);
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
Preconditions.checkState(mState != STATE_STOPPED);
final long ident = Binder.clearCallingIdentity();
try {
@@ -1849,9 +1834,7 @@ public class LocationProviderManager extends
() -> {
final long ident = Binder.clearCallingIdentity();
try {
- synchronized (mLock) {
- removeRegistration(callback.asBinder(), registration);
- }
+ removeRegistration(callback.asBinder(), registration);
} catch (RuntimeException e) {
// since this is within a oneway binder transaction there is nowhere
// for exceptions to go - move onto another thread to crash system
@@ -1885,7 +1868,7 @@ public class LocationProviderManager extends
new LocationListenerTransport(listener),
permissionLevel);
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
Preconditions.checkState(mState != STATE_STOPPED);
final long ident = Binder.clearCallingIdentity();
try {
@@ -1904,7 +1887,7 @@ public class LocationProviderManager extends
new LocationPendingIntentTransport(mContext, pendingIntent),
permissionLevel);
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
Preconditions.checkState(mState != STATE_STOPPED);
final long identity = Binder.clearCallingIdentity();
try {
@@ -1916,42 +1899,38 @@ public class LocationProviderManager extends
}
public void flush(ILocationListener listener, int requestCode) {
- synchronized (mLock) {
- final long identity = Binder.clearCallingIdentity();
- try {
- boolean flushed = updateRegistration(listener.asBinder(), registration -> {
- registration.flush(requestCode);
- return false;
- });
- if (!flushed) {
- throw new IllegalArgumentException("unregistered listener cannot be flushed");
- }
- } finally {
- Binder.restoreCallingIdentity(identity);
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ boolean flushed = updateRegistration(listener.asBinder(), registration -> {
+ registration.flush(requestCode);
+ return false;
+ });
+ if (!flushed) {
+ throw new IllegalArgumentException("unregistered listener cannot be flushed");
}
+ } finally {
+ Binder.restoreCallingIdentity(identity);
}
}
public void flush(PendingIntent pendingIntent, int requestCode) {
- synchronized (mLock) {
- final long identity = Binder.clearCallingIdentity();
- try {
- boolean flushed = updateRegistration(pendingIntent, registration -> {
- registration.flush(requestCode);
- return false;
- });
- if (!flushed) {
- throw new IllegalArgumentException(
- "unregistered pending intent cannot be flushed");
- }
- } finally {
- Binder.restoreCallingIdentity(identity);
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ boolean flushed = updateRegistration(pendingIntent, registration -> {
+ registration.flush(requestCode);
+ return false;
+ });
+ if (!flushed) {
+ throw new IllegalArgumentException(
+ "unregistered pending intent cannot be flushed");
}
+ } finally {
+ Binder.restoreCallingIdentity(identity);
}
}
public void unregisterLocationRequest(ILocationListener listener) {
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
Preconditions.checkState(mState != STATE_STOPPED);
final long identity = Binder.clearCallingIdentity();
try {
@@ -1963,7 +1942,7 @@ public class LocationProviderManager extends
}
public void unregisterLocationRequest(PendingIntent pendingIntent) {
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
Preconditions.checkState(mState != STATE_STOPPED);
final long identity = Binder.clearCallingIdentity();
try {
@@ -1974,13 +1953,9 @@ public class LocationProviderManager extends
}
}
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
@Override
protected void onRegister() {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
-
mSettingsHelper.addOnBackgroundThrottleIntervalChangedListener(
mBackgroundThrottleIntervalChangedListener);
mSettingsHelper.addOnBackgroundThrottlePackageWhitelistChangedListener(
@@ -1997,13 +1972,9 @@ public class LocationProviderManager extends
mScreenInteractiveHelper.addListener(mScreenInteractiveChangedListener);
}
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
@Override
protected void onUnregister() {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
-
mSettingsHelper.removeOnBackgroundThrottleIntervalChangedListener(
mBackgroundThrottleIntervalChangedListener);
mSettingsHelper.removeOnBackgroundThrottlePackageWhitelistChangedListener(
@@ -2019,13 +1990,9 @@ public class LocationProviderManager extends
mScreenInteractiveHelper.removeListener(mScreenInteractiveChangedListener);
}
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
@Override
protected void onRegistrationAdded(Object key, Registration registration) {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
-
mLocationUsageLogger.logLocationApiUsage(
LocationStatsEnums.USAGE_STARTED,
LocationStatsEnums.API_REQUEST_LOCATION_UPDATES,
@@ -2038,23 +2005,21 @@ public class LocationProviderManager extends
null, registration.isForeground());
}
- @GuardedBy("mLock")
+ // TODO: remove suppression when GuardedBy analysis can recognize lock from super class
+ @SuppressWarnings("GuardedBy")
+ @GuardedBy("mMultiplexerLock")
@Override
- protected void onRegistrationReplaced(Object key, Registration oldRegistration,
- Registration newRegistration) {
+ protected void onRegistrationReplaced(Object oldKey, Registration oldRegistration,
+ Object newKey, Registration newRegistration) {
// by saving the last delivered location state we are able to potentially delay the
// resulting provider request longer and save additional power
newRegistration.setLastDeliveredLocation(oldRegistration.getLastDeliveredLocation());
- super.onRegistrationReplaced(key, oldRegistration, newRegistration);
+ super.onRegistrationReplaced(oldKey, oldRegistration, newKey, newRegistration);
}
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
@Override
protected void onRegistrationRemoved(Object key, Registration registration) {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
-
mLocationUsageLogger.logLocationApiUsage(
LocationStatsEnums.USAGE_ENDED,
LocationStatsEnums.API_REQUEST_LOCATION_UPDATES,
@@ -2067,21 +2032,17 @@ public class LocationProviderManager extends
null, registration.isForeground());
}
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
@Override
protected boolean registerWithService(ProviderRequest request,
Collection<Registration> registrations) {
return reregisterWithService(ProviderRequest.EMPTY_REQUEST, request, registrations);
}
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
@Override
protected boolean reregisterWithService(ProviderRequest oldRequest,
ProviderRequest newRequest, Collection<Registration> registrations) {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
-
// calculate how long the new request should be delayed before sending it off to the
// provider, under the assumption that once we send the request off, the provider will
// immediately attempt to deliver a new location satisfying that request.
@@ -2117,7 +2078,7 @@ public class LocationProviderManager extends
mDelayedRegister = new OnAlarmListener() {
@Override
public void onAlarm() {
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
if (mDelayedRegister == this) {
mDelayedRegister = null;
setProviderRequest(newRequest);
@@ -2135,17 +2096,13 @@ public class LocationProviderManager extends
return true;
}
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
@Override
protected void unregisterWithService() {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
-
setProviderRequest(ProviderRequest.EMPTY_REQUEST);
}
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
void setProviderRequest(ProviderRequest request) {
if (mDelayedRegister != null) {
mAlarmHelper.cancel(mDelayedRegister);
@@ -2166,13 +2123,9 @@ public class LocationProviderManager extends
});
}
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
@Override
protected boolean isActive(Registration registration) {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
-
if (!registration.isPermitted()) {
return false;
}
@@ -2236,13 +2189,9 @@ public class LocationProviderManager extends
return true;
}
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
@Override
protected ProviderRequest mergeRegistrations(Collection<Registration> registrations) {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
-
long intervalMs = ProviderRequest.INTERVAL_DISABLED;
int quality = LocationRequest.QUALITY_LOW_POWER;
long maxUpdateDelayMs = Long.MAX_VALUE;
@@ -2307,7 +2256,7 @@ public class LocationProviderManager extends
.build();
}
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
protected long calculateRequestDelayMillis(long newIntervalMs,
Collection<Registration> registrations) {
// calculate the minimum delay across all registrations, ensuring that it is not more than
@@ -2349,7 +2298,7 @@ public class LocationProviderManager extends
}
private void onUserChanged(int userId, int change) {
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
if (mState == STATE_STOPPED) {
return;
}
@@ -2372,15 +2321,13 @@ public class LocationProviderManager extends
private void onLocationUserSettingsChanged(int userId, LocationUserSettings oldSettings,
LocationUserSettings newSettings) {
if (oldSettings.isAdasGnssLocationEnabled() != newSettings.isAdasGnssLocationEnabled()) {
- synchronized (mLock) {
- updateRegistrations(
- registration -> registration.onAdasGnssLocationEnabledChanged(userId));
- }
+ updateRegistrations(
+ registration -> registration.onAdasGnssLocationEnabledChanged(userId));
}
}
private void onLocationEnabledChanged(int userId) {
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
if (mState == STATE_STOPPED) {
return;
}
@@ -2390,88 +2337,64 @@ public class LocationProviderManager extends
}
private void onScreenInteractiveChanged(boolean screenInteractive) {
- synchronized (mLock) {
- switch (mLocationPowerSaveModeHelper.getLocationPowerSaveMode()) {
- case LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF:
- if (!GPS_PROVIDER.equals(mName)) {
- break;
- }
- // fall through
- case LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF:
- // fall through
- case LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF:
- updateRegistrations(registration -> true);
- break;
- default:
+ switch (mLocationPowerSaveModeHelper.getLocationPowerSaveMode()) {
+ case LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF:
+ if (!GPS_PROVIDER.equals(mName)) {
break;
- }
+ }
+ // fall through
+ case LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF:
+ // fall through
+ case LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF:
+ updateRegistrations(registration -> true);
+ break;
+ default:
+ break;
}
}
private void onBackgroundThrottlePackageWhitelistChanged() {
- synchronized (mLock) {
- updateRegistrations(Registration::onProviderLocationRequestChanged);
- }
+ updateRegistrations(Registration::onProviderLocationRequestChanged);
}
private void onBackgroundThrottleIntervalChanged() {
- synchronized (mLock) {
- updateRegistrations(Registration::onProviderLocationRequestChanged);
- }
+ updateRegistrations(Registration::onProviderLocationRequestChanged);
}
private void onLocationPowerSaveModeChanged(@LocationPowerSaveMode int locationPowerSaveMode) {
- synchronized (mLock) {
- // this is rare, just assume everything has changed to keep it simple
- updateRegistrations(registration -> true);
- }
+ // this is rare, just assume everything has changed to keep it simple
+ updateRegistrations(registration -> true);
}
private void onAppForegroundChanged(int uid, boolean foreground) {
- synchronized (mLock) {
- updateRegistrations(registration -> registration.onForegroundChanged(uid, foreground));
- }
+ updateRegistrations(registration -> registration.onForegroundChanged(uid, foreground));
}
private void onAdasAllowlistChanged() {
- synchronized (mLock) {
- updateRegistrations(Registration::onProviderLocationRequestChanged);
- }
+ updateRegistrations(Registration::onProviderLocationRequestChanged);
}
private void onIgnoreSettingsWhitelistChanged() {
- synchronized (mLock) {
- updateRegistrations(Registration::onProviderLocationRequestChanged);
- }
+ updateRegistrations(Registration::onProviderLocationRequestChanged);
}
private void onLocationPackageBlacklistChanged(int userId) {
- synchronized (mLock) {
- updateRegistrations(registration -> registration.getIdentity().getUserId() == userId);
- }
+ updateRegistrations(registration -> registration.getIdentity().getUserId() == userId);
}
private void onLocationPermissionsChanged(@Nullable String packageName) {
- synchronized (mLock) {
- updateRegistrations(
- registration -> registration.onLocationPermissionsChanged(packageName));
- }
+ updateRegistrations(
+ registration -> registration.onLocationPermissionsChanged(packageName));
}
private void onLocationPermissionsChanged(int uid) {
- synchronized (mLock) {
- updateRegistrations(registration -> registration.onLocationPermissionsChanged(uid));
- }
+ updateRegistrations(registration -> registration.onLocationPermissionsChanged(uid));
}
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
@Override
public void onStateChanged(
AbstractLocationProvider.State oldState, AbstractLocationProvider.State newState) {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
-
if (oldState.allowed != newState.allowed) {
onEnabledChanged(UserHandle.USER_ALL);
}
@@ -2487,13 +2410,9 @@ public class LocationProviderManager extends
}
}
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
@Override
public void onReportLocation(LocationResult locationResult) {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
-
LocationResult filtered;
if (mPassiveManager != null) {
filtered = locationResult.filter(location -> {
@@ -2549,12 +2468,8 @@ public class LocationProviderManager extends
}
}
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
private void onUserStarted(int userId) {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
-
if (userId == UserHandle.USER_NULL) {
return;
}
@@ -2572,12 +2487,8 @@ public class LocationProviderManager extends
}
}
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
private void onUserStopped(int userId) {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
-
if (userId == UserHandle.USER_NULL) {
return;
}
@@ -2592,12 +2503,8 @@ public class LocationProviderManager extends
}
}
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
private void onEnabledChanged(int userId) {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
-
if (userId == UserHandle.USER_NULL) {
// used during initialization - ignore since many lower level operations (checking
// settings for instance) do not support the null user
@@ -2697,7 +2604,7 @@ public class LocationProviderManager extends
}
public void dump(FileDescriptor fd, IndentingPrintWriter ipw, String[] args) {
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
ipw.print(mName);
ipw.print(" provider");
if (mProvider.isMock()) {
@@ -2738,10 +2645,10 @@ public class LocationProviderManager extends
private static class LastLocation {
- private @Nullable Location mFineLocation;
- private @Nullable Location mCoarseLocation;
- private @Nullable Location mFineBypassLocation;
- private @Nullable Location mCoarseBypassLocation;
+ @Nullable private Location mFineLocation;
+ @Nullable private Location mCoarseLocation;
+ @Nullable private Location mFineBypassLocation;
+ @Nullable private Location mCoarseBypassLocation;
LastLocation() {}
@@ -2765,7 +2672,7 @@ public class LocationProviderManager extends
mCoarseLocation = null;
}
- public @Nullable Location get(@PermissionLevel int permissionLevel,
+ @Nullable public Location get(@PermissionLevel int permissionLevel,
boolean isBypass) {
switch (permissionLevel) {
case PERMISSION_FINE:
@@ -2862,7 +2769,7 @@ public class LocationProviderManager extends
private static class GatedCallback implements Runnable {
@GuardedBy("this")
- private @Nullable Runnable mCallback;
+ @Nullable private Runnable mCallback;
@GuardedBy("this")
private boolean mGate;
diff --git a/services/core/java/com/android/server/location/provider/PassiveLocationProviderManager.java b/services/core/java/com/android/server/location/provider/PassiveLocationProviderManager.java
index b35af4f6475c..0cb4f9e0a0ac 100644
--- a/services/core/java/com/android/server/location/provider/PassiveLocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/PassiveLocationProviderManager.java
@@ -54,7 +54,7 @@ public class PassiveLocationProviderManager extends LocationProviderManager {
* Reports a new location to passive location provider clients.
*/
public void updateLocation(LocationResult locationResult) {
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
PassiveLocationProvider passive = (PassiveLocationProvider) mProvider.getProvider();
Preconditions.checkState(passive != null);
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index c5f73625379f..8ab3a949dda7 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -1587,7 +1587,7 @@ public class LockSettingsService extends ILockSettings.Stub {
if (!savedCredential.isNone()) {
throw new IllegalStateException("Saved credential given, but user has no SP");
}
- initializeSyntheticPasswordLocked(savedCredential, userId);
+ initializeSyntheticPasswordLocked(userId);
} else if (savedCredential.isNone() && isProfileWithUnifiedLock(userId)) {
// get credential from keystore when profile has unified lock
try {
@@ -2513,35 +2513,21 @@ public class LockSettingsService extends ILockSettings.Stub {
}
/**
- * Creates the synthetic password (SP) for the given user and protects it with the user's LSKF.
+ * Creates the synthetic password (SP) for the given user and protects it with an empty LSKF.
* This is called just once in the lifetime of the user: the first time a nonempty LSKF is set,
* or when an escrow token is activated on a device with an empty LSKF.
- *
- * Maintains the SP invariants described in {@link SyntheticPasswordManager}.
*/
@GuardedBy("mSpManager")
@VisibleForTesting
- SyntheticPassword initializeSyntheticPasswordLocked(LockscreenCredential credential,
- int userId) {
+ SyntheticPassword initializeSyntheticPasswordLocked(int userId) {
Slog.i(TAG, "Initialize SyntheticPassword for user: " + userId);
Preconditions.checkState(getCurrentLskfBasedProtectorId(userId) ==
SyntheticPasswordManager.NULL_PROTECTOR_ID,
"Cannot reinitialize SP");
final SyntheticPassword sp = mSpManager.newSyntheticPassword(userId);
- long protectorId = mSpManager.createLskfBasedProtector(getGateKeeperService(), credential,
- sp, userId);
- if (!credential.isNone()) {
- mSpManager.newSidForUser(getGateKeeperService(), sp, userId);
- mSpManager.verifyChallenge(getGateKeeperService(), sp, 0L, userId);
- setUserKeyProtection(userId, sp.deriveFileBasedEncryptionKey());
- setKeystorePassword(sp.deriveKeyStorePassword(), userId);
- } else {
- clearUserKeyProtection(userId, null);
- setKeystorePassword(null, userId);
- gateKeeperClearSecureUserId(userId);
- }
- fixateNewestUserKeyAuth(userId);
+ final long protectorId = mSpManager.createLskfBasedProtector(getGateKeeperService(),
+ LockscreenCredential.createNone(), sp, userId);
setCurrentLskfBasedProtectorId(protectorId, userId);
onSyntheticPasswordKnown(userId, sp);
return sp;
@@ -2818,8 +2804,7 @@ public class LockSettingsService extends ILockSettings.Stub {
if (!isUserSecure(userId)) {
long protectorId = getCurrentLskfBasedProtectorId(userId);
if (protectorId == SyntheticPasswordManager.NULL_PROTECTOR_ID) {
- sp = initializeSyntheticPasswordLocked(LockscreenCredential.createNone(),
- userId);
+ sp = initializeSyntheticPasswordLocked(userId);
} else {
sp = mSpManager.unlockLskfBasedProtector(getGateKeeperService(), protectorId,
LockscreenCredential.createNone(), userId, null).syntheticPassword;
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 2070c2b31236..b97aa5f83347 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -1445,6 +1445,11 @@ public class NotificationManagerService extends SystemService {
}
if (flags != data.getFlags()) {
+ int changedFlags = data.getFlags() ^ flags;
+ if ((changedFlags & FLAG_SUPPRESS_NOTIFICATION) != 0) {
+ // Suppress notification flag changed, clear any effects
+ clearEffectsLocked(key);
+ }
data.setFlags(flags);
// Shouldn't alert again just because of a flag change.
r.getNotification().flags |= FLAG_ONLY_ALERT_ONCE;
@@ -1596,6 +1601,20 @@ public class NotificationManagerService extends SystemService {
updateLightsLocked();
}
+ @GuardedBy("mNotificationLock")
+ private void clearEffectsLocked(String key) {
+ if (key.equals(mSoundNotificationKey)) {
+ clearSoundLocked();
+ }
+ if (key.equals(mVibrateNotificationKey)) {
+ clearVibrateLocked();
+ }
+ boolean removed = mLights.remove(key);
+ if (removed) {
+ updateLightsLocked();
+ }
+ }
+
protected final BroadcastReceiver mLocaleChangeReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -5170,7 +5189,8 @@ public class NotificationManagerService extends SystemService {
extras,
mRankingHelper.findExtractor(ValidateNotificationPeople.class),
MATCHES_CALL_FILTER_CONTACTS_TIMEOUT_MS,
- MATCHES_CALL_FILTER_TIMEOUT_AFFINITY);
+ MATCHES_CALL_FILTER_TIMEOUT_AFFINITY,
+ Binder.getCallingUid());
}
@Override
@@ -7813,7 +7833,8 @@ public class NotificationManagerService extends SystemService {
&& (record.getSuppressedVisualEffects() & SUPPRESSED_EFFECT_STATUS_BAR) != 0;
if (!record.isUpdate
&& record.getImportance() > IMPORTANCE_MIN
- && !suppressedByDnd) {
+ && !suppressedByDnd
+ && isNotificationForCurrentUser(record)) {
sendAccessibilityEvent(record);
sentAccessibilityEvent = true;
}
diff --git a/services/core/java/com/android/server/notification/ReviewNotificationPermissionsJobService.java b/services/core/java/com/android/server/notification/ReviewNotificationPermissionsJobService.java
index fde45f71a844..acb36a0660cb 100644
--- a/services/core/java/com/android/server/notification/ReviewNotificationPermissionsJobService.java
+++ b/services/core/java/com/android/server/notification/ReviewNotificationPermissionsJobService.java
@@ -42,10 +42,6 @@ public class ReviewNotificationPermissionsJobService extends JobService {
*/
public static void scheduleJob(Context context, long rescheduleTimeMillis) {
JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
- // if the job already exists for some reason, cancel & reschedule
- if (jobScheduler.getPendingJob(JOB_ID) != null) {
- jobScheduler.cancel(JOB_ID);
- }
ComponentName component = new ComponentName(
context, ReviewNotificationPermissionsJobService.class);
JobInfo newJob = new JobInfo.Builder(JOB_ID, component)
diff --git a/services/core/java/com/android/server/notification/ZenLog.java b/services/core/java/com/android/server/notification/ZenLog.java
index 7d7f3a96595c..c0bc474cd8fa 100644
--- a/services/core/java/com/android/server/notification/ZenLog.java
+++ b/services/core/java/com/android/server/notification/ZenLog.java
@@ -66,6 +66,8 @@ public class ZenLog {
private static final int TYPE_SET_NOTIFICATION_POLICY = 16;
private static final int TYPE_SET_CONSOLIDATED_ZEN_POLICY = 17;
private static final int TYPE_MATCHES_CALL_FILTER = 18;
+ private static final int TYPE_RECORD_CALLER = 19;
+ private static final int TYPE_CHECK_REPEAT_CALLER = 20;
private static int sNext;
private static int sSize;
@@ -167,11 +169,28 @@ public class ZenLog {
+ hintsToString(newHints) + ",listeners=" + listenerCount);
}
- /*
+ /**
* Trace calls to matchesCallFilter with the result of the call and the reason for the result.
*/
- public static void traceMatchesCallFilter(boolean result, String reason) {
- append(TYPE_MATCHES_CALL_FILTER, "result=" + result + ", reason=" + reason);
+ public static void traceMatchesCallFilter(boolean result, String reason, int callingUid) {
+ append(TYPE_MATCHES_CALL_FILTER, "result=" + result + ", reason=" + reason
+ + ", calling uid=" + callingUid);
+ }
+
+ /**
+ * Trace what information is available about an incoming call when it's recorded
+ */
+ public static void traceRecordCaller(boolean hasPhone, boolean hasUri) {
+ append(TYPE_RECORD_CALLER, "has phone number=" + hasPhone + ", has uri=" + hasUri);
+ }
+
+ /**
+ * Trace what information was provided about a caller when checking whether it is from a repeat
+ * caller
+ */
+ public static void traceCheckRepeatCaller(boolean found, boolean hasPhone, boolean hasUri) {
+ append(TYPE_CHECK_REPEAT_CALLER, "res=" + found + ", given phone number=" + hasPhone
+ + ", given uri=" + hasUri);
}
private static String subscribeResult(IConditionProvider provider, RemoteException e) {
@@ -198,6 +217,8 @@ public class ZenLog {
case TYPE_SET_NOTIFICATION_POLICY: return "set_notification_policy";
case TYPE_SET_CONSOLIDATED_ZEN_POLICY: return "set_consolidated_policy";
case TYPE_MATCHES_CALL_FILTER: return "matches_call_filter";
+ case TYPE_RECORD_CALLER: return "record_caller";
+ case TYPE_CHECK_REPEAT_CALLER: return "check_repeat_caller";
default: return "unknown";
}
}
diff --git a/services/core/java/com/android/server/notification/ZenModeFiltering.java b/services/core/java/com/android/server/notification/ZenModeFiltering.java
index b0d40efed690..7e36aed81d4a 100644
--- a/services/core/java/com/android/server/notification/ZenModeFiltering.java
+++ b/services/core/java/com/android/server/notification/ZenModeFiltering.java
@@ -101,23 +101,24 @@ public class ZenModeFiltering {
*/
public static boolean matchesCallFilter(Context context, int zen, NotificationManager.Policy
consolidatedPolicy, UserHandle userHandle, Bundle extras,
- ValidateNotificationPeople validator, int contactsTimeoutMs, float timeoutAffinity) {
+ ValidateNotificationPeople validator, int contactsTimeoutMs, float timeoutAffinity,
+ int callingUid) {
if (zen == Global.ZEN_MODE_NO_INTERRUPTIONS) {
- ZenLog.traceMatchesCallFilter(false, "no interruptions");
+ ZenLog.traceMatchesCallFilter(false, "no interruptions", callingUid);
return false; // nothing gets through
}
if (zen == Global.ZEN_MODE_ALARMS) {
- ZenLog.traceMatchesCallFilter(false, "alarms only");
+ ZenLog.traceMatchesCallFilter(false, "alarms only", callingUid);
return false; // not an alarm
}
if (zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) {
if (consolidatedPolicy.allowRepeatCallers()
&& REPEAT_CALLERS.isRepeat(context, extras, null)) {
- ZenLog.traceMatchesCallFilter(true, "repeat caller");
+ ZenLog.traceMatchesCallFilter(true, "repeat caller", callingUid);
return true;
}
if (!consolidatedPolicy.allowCalls()) {
- ZenLog.traceMatchesCallFilter(false, "calls not allowed");
+ ZenLog.traceMatchesCallFilter(false, "calls not allowed", callingUid);
return false; // no other calls get through
}
if (validator != null) {
@@ -125,11 +126,12 @@ public class ZenModeFiltering {
contactsTimeoutMs, timeoutAffinity);
boolean match =
audienceMatches(consolidatedPolicy.allowCallsFrom(), contactAffinity);
- ZenLog.traceMatchesCallFilter(match, "contact affinity " + contactAffinity);
+ ZenLog.traceMatchesCallFilter(match, "contact affinity " + contactAffinity,
+ callingUid);
return match;
}
}
- ZenLog.traceMatchesCallFilter(true, "no restrictions");
+ ZenLog.traceMatchesCallFilter(true, "no restrictions", callingUid);
return true;
}
@@ -419,6 +421,7 @@ public class ZenModeFiltering {
private synchronized void recordCallers(String[] people, ArraySet<String> phoneNumbers,
long now) {
+ boolean recorded = false, hasTel = false, hasOther = false;
for (int i = 0; i < people.length; i++) {
String person = people[i];
if (person == null) continue;
@@ -427,10 +430,16 @@ public class ZenModeFiltering {
// while ideally we should not need to decode this, sometimes we have seen tel
// numbers given in an encoded format
String tel = Uri.decode(uri.getSchemeSpecificPart());
- if (tel != null) mTelCalls.put(tel, now);
+ if (tel != null) {
+ mTelCalls.put(tel, now);
+ recorded = true;
+ hasTel = true;
+ }
} else {
// for non-tel calls, store the entire string, uri-component and all
mOtherCalls.put(person, now);
+ recorded = true;
+ hasOther = true;
}
}
@@ -438,9 +447,16 @@ public class ZenModeFiltering {
// provided; these are in the format of just a phone number string
if (phoneNumbers != null) {
for (String num : phoneNumbers) {
- if (num != null) mTelCalls.put(num, now);
+ if (num != null) {
+ mTelCalls.put(num, now);
+ recorded = true;
+ hasTel = true;
+ }
}
}
+ if (recorded) {
+ ZenLog.traceRecordCaller(hasTel, hasOther);
+ }
}
// helper function to check mTelCalls array for a number, and also check its decoded
@@ -468,6 +484,8 @@ public class ZenModeFiltering {
// previously recorded phone call.
private synchronized boolean checkCallers(Context context, String[] people,
ArraySet<String> phoneNumbers) {
+ boolean found = false, checkedTel = false, checkedOther = false;
+
// get the default country code for checking telephone numbers
final String defaultCountryCode =
context.getSystemService(TelephonyManager.class).getNetworkCountryIso();
@@ -477,12 +495,14 @@ public class ZenModeFiltering {
final Uri uri = Uri.parse(person);
if ("tel".equals(uri.getScheme())) {
String number = uri.getSchemeSpecificPart();
+ checkedTel = true;
if (checkForNumber(number, defaultCountryCode)) {
- return true;
+ found = true;
}
} else {
+ checkedOther = true;
if (mOtherCalls.containsKey(person)) {
- return true;
+ found = true;
}
}
}
@@ -490,14 +510,16 @@ public class ZenModeFiltering {
// also check any passed-in phone numbers
if (phoneNumbers != null) {
for (String num : phoneNumbers) {
+ checkedTel = true;
if (checkForNumber(num, defaultCountryCode)) {
- return true;
+ found = true;
}
}
}
// no matches
- return false;
+ ZenLog.traceCheckRepeatCaller(found, checkedTel, checkedOther);
+ return found;
}
}
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 6135fe8acbed..d42667951608 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -177,10 +177,12 @@ public class ZenModeHelper {
}
public boolean matchesCallFilter(UserHandle userHandle, Bundle extras,
- ValidateNotificationPeople validator, int contactsTimeoutMs, float timeoutAffinity) {
+ ValidateNotificationPeople validator, int contactsTimeoutMs, float timeoutAffinity,
+ int callingUid) {
synchronized (mConfig) {
return ZenModeFiltering.matchesCallFilter(mContext, mZenMode, mConsolidatedPolicy,
- userHandle, extras, validator, contactsTimeoutMs, timeoutAffinity);
+ userHandle, extras, validator, contactsTimeoutMs, timeoutAffinity,
+ callingUid);
}
}
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index 715967369ffb..51b36dd9c579 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -39,6 +39,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
import android.app.IActivityManager;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -1416,16 +1417,18 @@ public final class OverlayManagerService extends SystemService {
private static void broadcastActionOverlayChanged(@NonNull final Set<String> targetPackages,
final int userId) {
+ final PackageManagerInternal pmInternal =
+ LocalServices.getService(PackageManagerInternal.class);
+ final ActivityManagerInternal amInternal =
+ LocalServices.getService(ActivityManagerInternal.class);
CollectionUtils.forEach(targetPackages, target -> {
final Intent intent = new Intent(ACTION_OVERLAY_CHANGED,
Uri.fromParts("package", target, null));
intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
- try {
- ActivityManager.getService().broadcastIntent(null, intent, null, null, 0, null,
- null, null, android.app.AppOpsManager.OP_NONE, null, false, false, userId);
- } catch (RemoteException e) {
- Slog.e(TAG, "broadcastActionOverlayChanged remote exception", e);
- }
+ final int[] allowList = pmInternal.getVisibilityAllowList(target, userId);
+ amInternal.broadcastIntent(intent, null /* resultTo */, null /* requiredPermissions */,
+ false /* serialized */, userId, allowList, null /* filterExtrasForReceiver */,
+ null /* bOptions */);
});
}
diff --git a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
index b05e44f9e625..da76738fa025 100644
--- a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
+++ b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
@@ -306,7 +306,7 @@ final class OverlayManagerShellCommand extends ShellCommand {
} else {
final String resourceName = getNextArgRequired();
final String typeStr = getNextArgRequired();
- final String strData = getNextArgRequired();
+ final String strData = String.join(" ", peekRemainingArgs());
addOverlayValue(overlayBuilder, resourceName, typeStr, strData);
}
diff --git a/services/core/java/com/android/server/pm/AppsFilterBase.java b/services/core/java/com/android/server/pm/AppsFilterBase.java
index 8501c5e5d236..5b3eff9c41f3 100644
--- a/services/core/java/com/android/server/pm/AppsFilterBase.java
+++ b/services/core/java/com/android/server/pm/AppsFilterBase.java
@@ -28,6 +28,7 @@ import android.content.pm.SigningDetails;
import android.os.Binder;
import android.os.Handler;
import android.os.Process;
+import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
import android.text.TextUtils;
@@ -340,7 +341,9 @@ public abstract class AppsFilterBase implements AppsFilterSnapshot {
return !isForceQueryable(targetPkgSetting.getAppId())
&& !isImplicitlyQueryable(callingAppId, targetPkgSetting.getAppId());
}
- if (mCacheReady) { // use cache
+ // use cache
+ if (mCacheReady && SystemProperties.getBoolean("debug.pm.use_app_filter_cache",
+ true)) {
if (!shouldFilterApplicationUsingCache(callingUid,
targetPkgSetting.getAppId(),
userId)) {
diff --git a/services/core/java/com/android/server/pm/BroadcastHelper.java b/services/core/java/com/android/server/pm/BroadcastHelper.java
index 2aec18778615..4e9c472c479a 100644
--- a/services/core/java/com/android/server/pm/BroadcastHelper.java
+++ b/services/core/java/com/android/server/pm/BroadcastHelper.java
@@ -28,7 +28,6 @@ import android.Manifest;
import android.annotation.AppIdInt;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.BroadcastOptions;
@@ -41,19 +40,20 @@ import android.content.pm.PackageInstaller;
import android.net.Uri;
import android.os.Bundle;
import android.os.PowerExemptionManager;
-import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
-import android.text.TextUtils;
+import android.util.IntArray;
import android.util.Log;
+import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.util.ArrayUtils;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
+import java.util.function.BiFunction;
+import java.util.function.Supplier;
/**
* Helper class to send broadcasts for various situations.
@@ -80,6 +80,7 @@ public final class BroadcastHelper {
final int flags, final String targetPkg, final IIntentReceiver finishedReceiver,
final int[] userIds, int[] instantUserIds,
@Nullable SparseArray<int[]> broadcastAllowList,
+ @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
@Nullable Bundle bOptions) {
try {
final IActivityManager am = ActivityManager.getService();
@@ -93,11 +94,13 @@ public final class BroadcastHelper {
if (ArrayUtils.isEmpty(instantUserIds)) {
doSendBroadcast(action, pkg, extras, flags, targetPkg, finishedReceiver,
- resolvedUserIds, false /* isInstantApp */, broadcastAllowList, bOptions);
+ resolvedUserIds, false /* isInstantApp */, broadcastAllowList,
+ filterExtrasForReceiver, bOptions);
} else {
// send restricted broadcasts for instant apps
doSendBroadcast(action, pkg, extras, flags, targetPkg, finishedReceiver,
- instantUserIds, true /* isInstantApp */, null, bOptions);
+ instantUserIds, true /* isInstantApp */, null,
+ null /* filterExtrasForReceiver */, bOptions);
}
} catch (RemoteException ex) {
}
@@ -113,6 +116,7 @@ public final class BroadcastHelper {
public void doSendBroadcast(String action, String pkg, Bundle extras,
int flags, String targetPkg, IIntentReceiver finishedReceiver,
int[] userIds, boolean isInstantApp, @Nullable SparseArray<int[]> broadcastAllowList,
+ @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
@Nullable Bundle bOptions) {
for (int userId : userIds) {
final Intent intent = new Intent(action,
@@ -148,45 +152,30 @@ public final class BroadcastHelper {
intent, finishedReceiver, requiredPermissions,
finishedReceiver != null, userId,
broadcastAllowList == null ? null : broadcastAllowList.get(userId),
- bOptions);
+ filterExtrasForReceiver, bOptions);
}
}
- public void sendResourcesChangedBroadcast(@NonNull Computer snapshot, boolean mediaStatus,
- boolean replacing, @NonNull String[] pkgNames, @NonNull int[] uids) {
+ public void sendResourcesChangedBroadcast(@NonNull Supplier<Computer> snapshotComputer,
+ boolean mediaStatus, boolean replacing, @NonNull String[] pkgNames,
+ @NonNull int[] uids) {
if (ArrayUtils.isEmpty(pkgNames) || ArrayUtils.isEmpty(uids)) {
return;
}
-
- try {
- final IActivityManager am = ActivityManager.getService();
- if (am == null) {
- return;
- }
-
- final int[] resolvedUserIds = am.getRunningUserIds();
- for (int userId : resolvedUserIds) {
- final var lists = getBroadcastParams(snapshot, pkgNames, uids, userId);
- for (int i = 0; i < lists.size(); i++) {
- // Send broadcasts here
- final Bundle extras = new Bundle(3);
- final BroadcastParams list = lists.get(i);
- extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST,
- list.getPackageNames());
- extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, list.getUids());
- extras.putBoolean(Intent.EXTRA_REPLACING, replacing);
- final SparseArray<int[]> allowList = list.getAllowList().size() == 0
- ? null : list.getAllowList();
- final String action =
- mediaStatus ? Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE
- : Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE;
- sendPackageBroadcast(action, null /* pkg */, extras, 0 /* flags */,
- null /* targetPkg */, null /* finishedReceiver */, new int[]{userId},
- null /* instantUserIds */, allowList, null /* bOptions */);
- }
- }
- } catch (RemoteException ex) {
+ Bundle extras = new Bundle();
+ extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgNames);
+ extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uids);
+ if (replacing) {
+ extras.putBoolean(Intent.EXTRA_REPLACING, replacing);
}
+ String action = mediaStatus ? Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE
+ : Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE;
+ sendPackageBroadcast(action, null /* pkg */, extras, 0 /* flags */,
+ null /* targetPkg */, null /* finishedReceiver */, null /* userIds */,
+ null /* instantUserIds */, null /* broadcastAllowList */,
+ (callingUid, intentExtras) -> filterExtrasChangedPackageList(
+ snapshotComputer.get(), callingUid, intentExtras),
+ null /* bOptions */);
}
/**
@@ -266,7 +255,8 @@ public final class BroadcastHelper {
final int flags = !componentNames.contains(packageName)
? Intent.FLAG_RECEIVER_REGISTERED_ONLY : 0;
sendPackageBroadcast(Intent.ACTION_PACKAGE_CHANGED, packageName, extras, flags, null, null,
- userIds, instantUserIds, broadcastAllowList, null);
+ userIds, instantUserIds, broadcastAllowList, null /* filterExtrasForReceiver */,
+ null /* bOptions */);
}
public static void sendDeviceCustomizationReadyBroadcast() {
@@ -334,53 +324,72 @@ public final class BroadcastHelper {
sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED,
packageName, extras, 0, null, null, userIds, instantUserIds,
- broadcastAllowlist, null);
+ broadcastAllowlist, null /* filterExtrasForReceiver */, null);
}
public void sendFirstLaunchBroadcast(String pkgName, String installerPkg,
int[] userIds, int[] instantUserIds) {
sendPackageBroadcast(Intent.ACTION_PACKAGE_FIRST_LAUNCH, pkgName, null, 0,
- installerPkg, null, userIds, instantUserIds, null /* broadcastAllowList */, null);
+ installerPkg, null, userIds, instantUserIds, null /* broadcastAllowList */,
+ null /* filterExtrasForReceiver */, null);
}
/**
- * Get broadcast params list based on the given package and uid list. The broadcast params are
- * used to send broadcast separately if the given packages have different visibility allow list.
+ * Filter package names for the intent extras {@link Intent#EXTRA_CHANGED_PACKAGE_LIST} and
+ * {@link Intent#EXTRA_CHANGED_UID_LIST} by using the rules of the package visibility.
*
- * @param pkgList The names of packages which have changes.
- * @param uidList The uids of packages which have changes.
- * @param userId The user where packages reside.
- * @return The list of {@link BroadcastParams} object.
+ * @param callingUid The uid that is going to access the intent extras.
+ * @param extras The intent extras to filter
+ * @return An extras that have been filtered, or {@code null} if the given uid is unable to
+ * access all the packages in the extras.
*/
- public List<BroadcastParams> getBroadcastParams(@NonNull Computer snapshot,
- @NonNull String[] pkgList, @NonNull int[] uidList, @UserIdInt int userId) {
- final List<BroadcastParams> lists = new ArrayList<>(pkgList.length);
- // Get allow lists for the pkg in the pkgList. Merge into the existed pkgs and uids if
- // allow lists are the same.
- for (int i = 0; i < pkgList.length; i++) {
- final String pkgName = pkgList[i];
- final int uid = uidList[i];
- if (TextUtils.isEmpty(pkgName) || Process.INVALID_UID == uid) {
+ @Nullable
+ public static Bundle filterExtrasChangedPackageList(@NonNull Computer snapshot, int callingUid,
+ @NonNull Bundle extras) {
+ if (UserHandle.isCore(callingUid)) {
+ // see all
+ return extras;
+ }
+ final String[] pkgs = extras.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+ if (ArrayUtils.isEmpty(pkgs)) {
+ return extras;
+ }
+ final int userId = extras.getInt(
+ Intent.EXTRA_USER_HANDLE, UserHandle.getUserId(callingUid));
+ final int[] uids = extras.getIntArray(Intent.EXTRA_CHANGED_UID_LIST);
+ final Pair<String[], int[]> filteredPkgs =
+ filterPackages(snapshot, pkgs, uids, callingUid, userId);
+ if (ArrayUtils.isEmpty(filteredPkgs.first)) {
+ // caller is unable to access this intent
+ return null;
+ }
+ final Bundle filteredExtras = new Bundle(extras);
+ filteredExtras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, filteredPkgs.first);
+ filteredExtras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, filteredPkgs.second);
+ return filteredExtras;
+ }
+
+ @NonNull
+ private static Pair<String[], int[]> filterPackages(@NonNull Computer snapshot,
+ @NonNull String[] pkgs, @Nullable int[] uids, int callingUid, int userId) {
+ final int pkgSize = pkgs.length;
+ final int uidSize = !ArrayUtils.isEmpty(uids) ? uids.length : 0;
+
+ final ArrayList<String> pkgList = new ArrayList<>(pkgSize);
+ final IntArray uidList = uidSize > 0 ? new IntArray(uidSize) : null;
+ for (int i = 0; i < pkgSize; i++) {
+ final String packageName = pkgs[i];
+ if (snapshot.shouldFilterApplication(
+ snapshot.getPackageStateInternal(packageName), callingUid, userId)) {
continue;
}
- int[] allowList = snapshot.getVisibilityAllowList(pkgName, userId);
- if (allowList == null) {
- allowList = new int[0];
- }
- boolean merged = false;
- for (int j = 0; j < lists.size(); j++) {
- final BroadcastParams list = lists.get(j);
- if (Arrays.equals(list.getAllowList().get(userId), allowList)) {
- list.addPackage(pkgName, uid);
- merged = true;
- break;
- }
- }
- if (!merged) {
- lists.add(new BroadcastParams(pkgName, uid, allowList, userId));
+ pkgList.add(packageName);
+ if (uidList != null && i < uidSize) {
+ uidList.add(uids[i]);
}
}
-
- return lists;
+ return new Pair<>(
+ pkgList.size() > 0 ? pkgList.toArray(new String[pkgList.size()]) : null,
+ uidList != null && uidList.size() > 0 ? uidList.toArray() : null);
}
}
diff --git a/services/core/java/com/android/server/pm/BroadcastParams.java b/services/core/java/com/android/server/pm/BroadcastParams.java
deleted file mode 100644
index 279aab040f7b..000000000000
--- a/services/core/java/com/android/server/pm/BroadcastParams.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.pm;
-
-import android.annotation.IntRange;
-import android.annotation.NonNull;
-import android.annotation.UserIdInt;
-import android.util.IntArray;
-import android.util.SparseArray;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * A helper class that contains information about package names and uids that share the same allow
- * list for sending broadcasts. Used by various package helpers.
- */
-final class BroadcastParams {
- private final @NonNull List<String> mPackageNames;
- private final @NonNull IntArray mUids;
- private final @NonNull SparseArray<int[]> mAllowList;
-
- BroadcastParams(@NonNull String packageName, @IntRange(from = 0) int uid,
- @NonNull int[] allowList, @UserIdInt int userId) {
- mPackageNames = new ArrayList<>(Arrays.asList(packageName));
- mUids = IntArray.wrap(new int[]{uid});
- mAllowList = new SparseArray<>(1);
- mAllowList.put(userId, allowList);
- }
-
- public void addPackage(@NonNull String packageName, @IntRange(from = 0) int uid) {
- mPackageNames.add(packageName);
- mUids.add(uid);
- }
-
- public @NonNull String[] getPackageNames() {
- return mPackageNames.toArray(new String[0]);
- }
-
- public @NonNull int[] getUids() {
- return mUids.toArray();
- }
-
- public @NonNull SparseArray<int[]> getAllowList() {
- return mAllowList;
- }
-}
diff --git a/services/core/java/com/android/server/pm/DistractingPackageHelper.java b/services/core/java/com/android/server/pm/DistractingPackageHelper.java
index 53e97540de23..dbf0d29f15e0 100644
--- a/services/core/java/com/android/server/pm/DistractingPackageHelper.java
+++ b/services/core/java/com/android/server/pm/DistractingPackageHelper.java
@@ -27,7 +27,6 @@ import android.os.UserHandle;
import android.util.ArraySet;
import android.util.IntArray;
import android.util.Slog;
-import android.util.SparseArray;
import com.android.internal.util.ArrayUtils;
import com.android.server.pm.pkg.PackageStateInternal;
@@ -127,7 +126,7 @@ public final class DistractingPackageHelper {
if (!changedPackagesList.isEmpty()) {
final String[] changedPackages = changedPackagesList.toArray(
new String[changedPackagesList.size()]);
- sendDistractingPackagesChanged(snapshot, changedPackages, changedUids.toArray(), userId,
+ sendDistractingPackagesChanged(changedPackages, changedUids.toArray(), userId,
restrictionFlags);
mPm.scheduleWritePackageRestrictions(userId);
}
@@ -169,7 +168,7 @@ public final class DistractingPackageHelper {
if (!changedPackages.isEmpty()) {
final String[] packageArray = changedPackages.toArray(
new String[changedPackages.size()]);
- sendDistractingPackagesChanged(snapshot, packageArray, changedUids.toArray(), userId,
+ sendDistractingPackagesChanged(packageArray, changedUids.toArray(), userId,
RESTRICTION_NONE);
mPm.scheduleWritePackageRestrictions(userId);
}
@@ -182,24 +181,21 @@ public final class DistractingPackageHelper {
* @param uidList The uids of packages which have suspension changes.
* @param userId The user where packages reside.
*/
- void sendDistractingPackagesChanged(@NonNull Computer snapshot, @NonNull String[] pkgList,
- int[] uidList, int userId, int distractionFlags) {
- final List<BroadcastParams> lists = mBroadcastHelper.getBroadcastParams(
- snapshot, pkgList, uidList, userId);
+ void sendDistractingPackagesChanged(@NonNull String[] pkgList, int[] uidList, int userId,
+ int distractionFlags) {
+ final Bundle extras = new Bundle();
+ extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgList);
+ extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidList);
+ extras.putInt(Intent.EXTRA_DISTRACTION_RESTRICTIONS, distractionFlags);
+
final Handler handler = mInjector.getHandler();
- for (int i = 0; i < lists.size(); i++) {
- final Bundle extras = new Bundle(3);
- final BroadcastParams list = lists.get(i);
- extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, list.getPackageNames());
- extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, list.getUids());
- extras.putInt(Intent.EXTRA_DISTRACTION_RESTRICTIONS, distractionFlags);
- final SparseArray<int[]> allowList = list.getAllowList().size() == 0
- ? null : list.getAllowList();
- handler.post(() -> mBroadcastHelper.sendPackageBroadcast(
- Intent.ACTION_DISTRACTING_PACKAGES_CHANGED, null /* pkg */,
- extras, Intent.FLAG_RECEIVER_REGISTERED_ONLY, null /* targetPkg */,
- null /* finishedReceiver */, new int[]{userId}, null /* instantUserIds */,
- allowList, null /* bOptions */));
- }
+ handler.post(() -> mBroadcastHelper.sendPackageBroadcast(
+ Intent.ACTION_DISTRACTING_PACKAGES_CHANGED, null /* pkg */,
+ extras, Intent.FLAG_RECEIVER_REGISTERED_ONLY, null /* targetPkg */,
+ null /* finishedReceiver */, new int[]{userId}, null /* instantUserIds */,
+ null /* broadcastAllowList */,
+ (callingUid, intentExtras) -> BroadcastHelper.filterExtrasChangedPackageList(
+ mPm.snapshotComputer(), callingUid, intentExtras),
+ null /* bOptions */));
}
}
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 728d2d154078..9db9837ffc45 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -2626,7 +2626,7 @@ final class InstallPackageHelper {
}
final String[] pkgNames = new String[]{res.mRemovedInfo.mRemovedPackage};
final int[] uids = new int[]{res.mRemovedInfo.mUid};
- mBroadcastHelper.sendResourcesChangedBroadcast(mPm.snapshotComputer(),
+ mBroadcastHelper.sendResourcesChangedBroadcast(mPm::snapshotComputer,
false /* mediaStatus */, true /* replacing */, pkgNames, uids);
}
res.mRemovedInfo.sendPackageRemovedBroadcasts(killApp, false /*removedBySystem*/);
@@ -2804,7 +2804,7 @@ final class InstallPackageHelper {
}
final String[] pkgNames = new String[]{packageName};
final int[] uids = new int[]{res.mPkg.getUid()};
- mBroadcastHelper.sendResourcesChangedBroadcast(mPm.snapshotComputer(),
+ mBroadcastHelper.sendResourcesChangedBroadcast(mPm::snapshotComputer,
true /* mediaStatus */, true /* replacing */, pkgNames, uids);
}
} else if (!ArrayUtils.isEmpty(res.mLibraryConsumers)) { // if static shared lib
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 72f3850891ad..5ad39f226879 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -2947,7 +2947,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService
@Nullable Bundle bOptions) {
mHandler.post(() -> mBroadcastHelper.sendPackageBroadcast(action, pkg, extras, flags,
targetPkg, finishedReceiver, userIds, instantUserIds, broadcastAllowList,
- bOptions));
+ null /* filterExtrasForReceiver */, bOptions));
}
@Override
@@ -6309,21 +6309,6 @@ public class PackageManagerService implements PackageSender, TestUtilityService
}
}
- /**
- * Ask the package manager to compile layouts in the given package.
- */
- @Override
- public boolean compileLayouts(String packageName) {
- AndroidPackage pkg;
- synchronized (mLock) {
- pkg = mPackages.get(packageName);
- if (pkg == null) {
- return false;
- }
- }
- return mArtManagerService.compileLayouts(pkg);
- }
-
@Nullable
@Override
public String removeLegacyDefaultBrowserPackageName(int userId) {
@@ -7077,7 +7062,8 @@ public class PackageManagerService implements PackageSender, TestUtilityService
mResolveActivity.processName = pkg.getProcessName();
mResolveActivity.launchMode = ActivityInfo.LAUNCH_MULTIPLE;
mResolveActivity.flags = ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS
- | ActivityInfo.FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS;
+ | ActivityInfo.FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS
+ | ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES;
mResolveActivity.theme = 0;
mResolveActivity.exported = true;
mResolveActivity.enabled = true;
@@ -7110,7 +7096,8 @@ public class PackageManagerService implements PackageSender, TestUtilityService
mResolveActivity.launchMode = ActivityInfo.LAUNCH_MULTIPLE;
mResolveActivity.documentLaunchMode = ActivityInfo.DOCUMENT_LAUNCH_NEVER;
mResolveActivity.flags = ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS
- | ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY;
+ | ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY
+ | ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES;
mResolveActivity.theme = R.style.Theme_Material_Dialog_Alert;
mResolveActivity.exported = true;
mResolveActivity.enabled = true;
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index dba7dcd21721..da89b9e7a599 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -1792,7 +1792,6 @@ class PackageManagerShellCommand extends ShellCommand {
String checkProfilesRaw = null;
boolean secondaryDex = false;
String split = null;
- boolean compileLayouts = false;
String opt;
while ((opt = getNextOption()) != null) {
@@ -1812,9 +1811,6 @@ class PackageManagerShellCommand extends ShellCommand {
case "-r":
compilationReason = getNextArgRequired();
break;
- case "--compile-layouts":
- compileLayouts = true;
- break;
case "--check-prof":
checkProfilesRaw = getNextArgRequired();
break;
@@ -1848,14 +1844,15 @@ class PackageManagerShellCommand extends ShellCommand {
final boolean compilerFilterGiven = compilerFilter != null;
final boolean compilationReasonGiven = compilationReason != null;
- // Make sure exactly one of -m, -r, or --compile-layouts is given.
- if ((!compilerFilterGiven && !compilationReasonGiven && !compileLayouts)
- || (!compilerFilterGiven && compilationReasonGiven && compileLayouts)
- || (compilerFilterGiven && !compilationReasonGiven && compileLayouts)
- || (compilerFilterGiven && compilationReasonGiven && !compileLayouts)
- || (compilerFilterGiven && compilationReasonGiven && compileLayouts)) {
- pw.println("Must specify exactly one of compilation filter (\"-m\"), compilation " +
- "reason (\"-r\"), or compile layouts (\"--compile-layouts\")");
+ // Make sure exactly one of -m, or -r is given.
+ if (compilerFilterGiven && compilationReasonGiven) {
+ pw.println("Cannot use compilation filter (\"-m\") and compilation reason (\"-r\") "
+ + "at the same time");
+ return 1;
+ }
+ if (!compilerFilterGiven && !compilationReasonGiven) {
+ pw.println("Cannot run without any of compilation filter (\"-m\") and compilation "
+ + "reason (\"-r\")");
return 1;
}
@@ -1920,19 +1917,12 @@ class PackageManagerShellCommand extends ShellCommand {
pw.flush();
}
- boolean result = true;
- if (compileLayouts) {
- PackageManagerInternal internal = LocalServices.getService(
- PackageManagerInternal.class);
- result = internal.compileLayouts(packageName);
- } else {
- result = secondaryDex
+ final boolean result = secondaryDex
? mInterface.performDexOptSecondary(packageName,
targetCompilerFilter, forceCompilation)
: mInterface.performDexOptMode(packageName,
checkProfiles, targetCompilerFilter, forceCompilation,
true /* bootComplete */, split);
- }
if (!result) {
failedPackages.add(packageName);
}
diff --git a/services/core/java/com/android/server/pm/StorageEventHelper.java b/services/core/java/com/android/server/pm/StorageEventHelper.java
index f621d8b2533a..41c6c0fc4c7f 100644
--- a/services/core/java/com/android/server/pm/StorageEventHelper.java
+++ b/services/core/java/com/android/server/pm/StorageEventHelper.java
@@ -298,7 +298,7 @@ public final class StorageEventHelper extends StorageEventListener {
packageNames[i] = pkg.getPackageName();
packageUids[i] = pkg.getUid();
}
- mBroadcastHelper.sendResourcesChangedBroadcast(mPm.snapshotComputer(), mediaStatus,
+ mBroadcastHelper.sendResourcesChangedBroadcast(mPm::snapshotComputer, mediaStatus,
replacing, packageNames, packageUids);
}
diff --git a/services/core/java/com/android/server/pm/SuspendPackageHelper.java b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
index 01aecd95afb9..6847b7083967 100644
--- a/services/core/java/com/android/server/pm/SuspendPackageHelper.java
+++ b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
@@ -38,7 +38,6 @@ import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.IntArray;
import android.util.Slog;
-import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
@@ -184,11 +183,9 @@ public final class SuspendPackageHelper {
}
});
- final Computer newSnapshot = mPm.snapshotComputer();
-
if (!changedPackagesList.isEmpty()) {
final String[] changedPackages = changedPackagesList.toArray(new String[0]);
- sendPackagesSuspendedForUser(newSnapshot,
+ sendPackagesSuspendedForUser(
suspended ? Intent.ACTION_PACKAGES_SUSPENDED
: Intent.ACTION_PACKAGES_UNSUSPENDED,
changedPackages, changedUids.toArray(), userId);
@@ -197,7 +194,7 @@ public final class SuspendPackageHelper {
}
// Send the suspension changed broadcast to ensure suspension state is not stale.
if (!modifiedPackages.isEmpty()) {
- sendPackagesSuspendedForUser(newSnapshot, Intent.ACTION_PACKAGES_SUSPENSION_CHANGED,
+ sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_SUSPENSION_CHANGED,
modifiedPackages.toArray(new String[0]), modifiedUids.toArray(), userId);
}
return unmodifiablePackages.toArray(new String[0]);
@@ -326,14 +323,12 @@ public final class SuspendPackageHelper {
}
});
- final Computer newSnapshot = mPm.snapshotComputer();
-
mPm.scheduleWritePackageRestrictions(userId);
if (!unsuspendedPackages.isEmpty()) {
final String[] packageArray = unsuspendedPackages.toArray(
new String[unsuspendedPackages.size()]);
sendMyPackageSuspendedOrUnsuspended(packageArray, false, userId);
- sendPackagesSuspendedForUser(newSnapshot, Intent.ACTION_PACKAGES_UNSUSPENDED,
+ sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_UNSUSPENDED,
packageArray, unsuspendedUids.toArray(), userId);
}
}
@@ -585,23 +580,19 @@ public final class SuspendPackageHelper {
* @param userId The user where packages reside.
*/
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
- void sendPackagesSuspendedForUser(@NonNull Computer snapshot, @NonNull String intent,
- @NonNull String[] pkgList, @NonNull int[] uidList, int userId) {
- final List<BroadcastParams> lists = mBroadcastHelper.getBroadcastParams(
- snapshot, pkgList, uidList, userId);
+ void sendPackagesSuspendedForUser(@NonNull String intent, @NonNull String[] pkgList,
+ @NonNull int[] uidList, int userId) {
final Handler handler = mInjector.getHandler();
- for (int i = 0; i < lists.size(); i++) {
- final Bundle extras = new Bundle(3);
- final BroadcastParams list = lists.get(i);
- extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, list.getPackageNames());
- extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, list.getUids());
- final SparseArray<int[]> allowList = list.getAllowList().size() == 0
- ? null : list.getAllowList();
- handler.post(() -> mBroadcastHelper.sendPackageBroadcast(intent, null /* pkg */,
- extras, Intent.FLAG_RECEIVER_REGISTERED_ONLY, null /* targetPkg */,
- null /* finishedReceiver */, new int[]{userId}, null /* instantUserIds */,
- allowList, null /* bOptions */));
- }
+ final Bundle extras = new Bundle(3);
+ extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgList);
+ extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidList);
+ handler.post(() -> mBroadcastHelper.sendPackageBroadcast(intent, null /* pkg */,
+ extras, Intent.FLAG_RECEIVER_REGISTERED_ONLY, null /* targetPkg */,
+ null /* finishedReceiver */, new int[]{userId}, null /* instantUserIds */,
+ null /* broadcastAllowList */,
+ (callingUid, intentExtras) -> BroadcastHelper.filterExtrasChangedPackageList(
+ mPm.snapshotComputer(), callingUid, intentExtras),
+ null /* bOptions */));
}
private String getKnownPackageName(@NonNull Computer snapshot,
@@ -652,7 +643,7 @@ public final class SuspendPackageHelper {
}
mBroadcastHelper.doSendBroadcast(action, null, intentExtras,
Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND, packageName, null,
- targetUserIds, false, null, null);
+ targetUserIds, false, null, null, null);
}
});
}
diff --git a/services/core/java/com/android/server/pm/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java
index 8dc94287688d..5731af68df95 100644
--- a/services/core/java/com/android/server/pm/UserManagerInternal.java
+++ b/services/core/java/com/android/server/pm/UserManagerInternal.java
@@ -21,6 +21,7 @@ import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.Context;
import android.content.pm.UserInfo;
+import android.content.pm.UserProperties;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.os.UserManager;
@@ -305,4 +306,13 @@ public abstract class UserManagerInternal {
* for users that already existed on-disk from an older version of Android.
*/
public abstract boolean shouldIgnorePrepareStorageErrors(int userId);
+
+ /**
+ * Returns the {@link UserProperties} of the given user, or {@code null} if it is not found.
+ * NB: The actual object is returned. So do NOT modify it!
+ */
+ public abstract @Nullable UserProperties getUserProperties(@UserIdInt int userId);
+
+ /** TODO(b/239982558): add javadoc / mention invalid_id is used to unassing */
+ public abstract void assignUserToDisplay(@UserIdInt int userId, int displayId);
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index a0335e89ceec..025e97318ba9 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -52,6 +52,7 @@ import android.content.pm.PackagePartitions;
import android.content.pm.ShortcutServiceInternal;
import android.content.pm.UserInfo;
import android.content.pm.UserInfo.UserInfoFlag;
+import android.content.pm.UserProperties;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Bitmap;
@@ -107,6 +108,7 @@ import android.util.TypedValue;
import android.util.TypedXmlPullParser;
import android.util.TypedXmlSerializer;
import android.util.Xml;
+import android.view.Display;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -180,7 +182,7 @@ public class UserManagerService extends IUserManager.Stub {
private static final String TAG_NAME = "name";
private static final String TAG_ACCOUNT = "account";
private static final String ATTR_FLAGS = "flags";
- private static final String ATTR_TYPE = "type";
+ private static final String ATTR_TYPE = "type"; // userType
private static final String ATTR_ICON_PATH = "icon";
private static final String ATTR_ID = "id";
private static final String ATTR_CREATION_TIME = "created";
@@ -215,6 +217,7 @@ public class UserManagerService extends IUserManager.Stub {
private static final String TAG_ENTRY = "entry";
private static final String TAG_VALUE = "value";
private static final String TAG_SEED_ACCOUNT_OPTIONS = "seedAccountOptions";
+ private static final String TAG_USER_PROPERTIES = "userProperties";
private static final String TAG_LAST_REQUEST_QUIET_MODE_ENABLED_CALL =
"lastRequestQuietModeEnabledCall";
private static final String TAG_IGNORE_PREPARE_STORAGE_ERRORS =
@@ -259,7 +262,7 @@ public class UserManagerService extends IUserManager.Stub {
@VisibleForTesting
static final int MAX_RECENTLY_REMOVED_IDS_SIZE = 100;
- private static final int USER_VERSION = 9;
+ private static final int USER_VERSION = 10;
private static final long EPOCH_PLUS_30_YEARS = 30L * 365 * 24 * 60 * 60 * 1000L; // ms
@@ -325,6 +328,9 @@ public class UserManagerService extends IUserManager.Stub {
// Whether to perist the seed account information to be available after a boot
boolean persistSeedData;
+ /** Properties of the user whose default values originate from its user type. */
+ UserProperties userProperties;
+
/** Elapsed realtime since boot when the user started. */
long startRealtime;
@@ -620,6 +626,16 @@ public class UserManagerService extends IUserManager.Stub {
@GuardedBy("mUserStates")
private final WatchedUserStates mUserStates = new WatchedUserStates();
+ /**
+ * Used on devices that support background users (key) running on secondary displays (value).
+ *
+ * <p>Is {@code null} by default and instantiated on demand when the users are started on
+ * secondary displays.
+ */
+ @Nullable
+ @GuardedBy("mUsersLock")
+ private SparseIntArray mUsersOnSecondaryDisplays;
+
private static UserManagerService sInstance;
public static UserManagerService getInstance() {
@@ -1487,6 +1503,36 @@ public class UserManagerService extends IUserManager.Stub {
return userTypeDetails != null && userTypeDetails.isSystem();
}
+ /**
+ * Returns a *copy* of the given user's UserProperties, stripping out any information for which
+ * the caller lacks permission.
+ */
+ @Override
+ public @NonNull UserProperties getUserPropertiesCopy(@UserIdInt int userId) {
+ checkQueryOrInteractPermissionIfCallerInOtherProfileGroup(userId, "getUserProperties");
+ final UserProperties origProperties = getUserPropertiesInternal(userId);
+ if (origProperties != null) {
+ int callingUid = Binder.getCallingUid();
+ boolean exposeAllFields = callingUid == Process.SYSTEM_UID;
+ boolean hasManage = hasPermissionGranted(Manifest.permission.MANAGE_USERS, callingUid);
+ boolean hasQuery = hasPermissionGranted(Manifest.permission.QUERY_USERS, callingUid);
+ return new UserProperties(origProperties, exposeAllFields, hasManage, hasQuery);
+ }
+ // A non-existent or partial user will reach here.
+ throw new IllegalArgumentException("Cannot access properties for user " + userId);
+ }
+
+ /** Returns the user's actual, canonical UserProperties object. Do not edit it externally. */
+ private @Nullable UserProperties getUserPropertiesInternal(@UserIdInt int userId) {
+ synchronized (mUsersLock) {
+ final UserData userData = getUserDataLU(userId);
+ if (userData != null) {
+ return userData.userProperties;
+ }
+ }
+ return null;
+ }
+
@Override
public boolean hasBadge(@UserIdInt int userId) {
checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId, "hasBadge");
@@ -1576,6 +1622,10 @@ public class UserManagerService extends IUserManager.Stub {
public boolean isProfile(@UserIdInt int userId) {
checkQueryOrInteractPermissionIfCallerInOtherProfileGroup(userId, "isProfile");
+ return isProfileUnchecked(userId);
+ }
+
+ private boolean isProfileUnchecked(@UserIdInt int userId) {
synchronized (mUsersLock) {
UserInfo userInfo = getUserInfoLU(userId);
return userInfo != null && userInfo.isProfile();
@@ -1663,15 +1713,33 @@ public class UserManagerService extends IUserManager.Stub {
+ ") is visible");
}
- // First check current foreground user (on main display)
+ return isUserVisibleUnchecked(userId);
+ }
+
+ private boolean isUserVisibleUnchecked(@UserIdInt int userId) {
+ // First check current foreground user and their profiles (on main display)
+ if (isCurrentUserOrRunningProfileOfCurrentUser(userId)) {
+ return true;
+ }
+
+ // TODO(b/239824814): STOPSHIP - add CTS tests (requires change on testing infra)
+ synchronized (mUsersLock) {
+ if (mUsersOnSecondaryDisplays != null) {
+ // TODO(b/239824814): make sure it handles profile as well
+ return (mUsersOnSecondaryDisplays.indexOfKey(userId) >= 0);
+ }
+ }
+
+ return false;
+ }
+
+ private boolean isCurrentUserOrRunningProfileOfCurrentUser(@UserIdInt int userId) {
int currentUserId = Binder.withCleanCallingIdentity(() -> ActivityManager.getCurrentUser());
if (currentUserId == userId) {
return true;
}
- // Then profiles of current user
- // TODO(b/239824814): consider using AMInternal.isCurrentProfile() instead
if (isProfile(userId)) {
int parentId = Binder.withCleanCallingIdentity(() -> getProfileParentId(userId));
if (parentId == currentUserId) {
@@ -1679,10 +1747,24 @@ public class UserManagerService extends IUserManager.Stub {
}
}
- // TODO(b/239824814): support background users on secondary display (and their profiles)
return false;
}
+ // TODO(b/239982558): currently used just by shell command, might need to move to
+ // UserManagerInternal if needed by other components (like WindowManagerService)
+ // TODO(b/239982558): add unit test
+ // TODO(b/239982558): try to merge with isUserVisibleUnchecked()
+ private boolean isUserVisibleOnDisplay(@UserIdInt int userId, int displayId) {
+ if (displayId == Display.DEFAULT_DISPLAY) {
+ return isCurrentUserOrRunningProfileOfCurrentUser(userId);
+ }
+ synchronized (mUsersLock) {
+ // TODO(b/239824814): make sure it handles profile as well
+ return mUsersOnSecondaryDisplays != null && mUsersOnSecondaryDisplays.get(userId,
+ Display.INVALID_DISPLAY) == displayId;
+ }
+ }
+
@Override
public @NonNull String getUserName() {
final int callingUid = Binder.getCallingUid();
@@ -3268,6 +3350,7 @@ public class UserManagerService extends IUserManager.Stub {
@VisibleForTesting
void upgradeIfNecessaryLP(Bundle oldGlobalUserRestrictions, int userVersion,
int userTypeVersion) {
+ Slog.i(LOG_TAG, "Upgrading users from userVersion " + userVersion + " to " + USER_VERSION);
Set<Integer> userIdsToWrite = new ArraySet<>();
final int originalVersion = mUserVersion;
final int originalUserTypeVersion = mUserTypeVersion;
@@ -3403,6 +3486,27 @@ public class UserManagerService extends IUserManager.Stub {
userVersion = 9;
}
+ if (userVersion < 10) {
+ // Add UserProperties.
+ synchronized (mUsersLock) {
+ for (int i = 0; i < mUsers.size(); i++) {
+ final UserData userData = mUsers.valueAt(i);
+ final UserTypeDetails userTypeDetails = mUserTypes.get(userData.info.userType);
+ if (userTypeDetails == null) {
+ throw new IllegalStateException(
+ "Cannot upgrade user because " + userData.info.userType
+ + " isn't defined on this device!");
+ }
+ userData.userProperties = new UserProperties(
+ userTypeDetails.getDefaultUserPropertiesReference());
+ userIdsToWrite.add(userData.info.id);
+ }
+ }
+ userVersion = 10;
+ }
+
+ // Reminder: If you add another upgrade, make sure to increment USER_VERSION too.
+
// Done with userVersion changes, moving on to deal with userTypeVersion upgrades
// Upgrade from previous user type to a new user type
final int newUserTypeVersion = UserTypeFactory.getUserTypeVersion();
@@ -3417,6 +3521,11 @@ public class UserManagerService extends IUserManager.Stub {
Slog.w(LOG_TAG, "User version " + mUserVersion + " didn't upgrade as expected to "
+ USER_VERSION);
} else {
+ if (userVersion > USER_VERSION) {
+ Slog.wtf(LOG_TAG, "Upgraded user version " + mUserVersion + " is higher the SDK's "
+ + "one of " + USER_VERSION + ". Someone forgot to update USER_VERSION?");
+ }
+
mUserVersion = userVersion;
mUserTypeVersion = newUserTypeVersion;
@@ -3536,6 +3645,8 @@ public class UserManagerService extends IUserManager.Stub {
flags |= mUserTypes.get(systemUserType).getDefaultUserInfoFlags();
UserInfo system = new UserInfo(UserHandle.USER_SYSTEM, null, null, flags, systemUserType);
UserData userData = putUserInfo(system);
+ userData.userProperties = new UserProperties(
+ mUserTypes.get(userData.info.userType).getDefaultUserPropertiesReference());
mNextSerialNumber = MIN_USER_ID;
mUserVersion = USER_VERSION;
mUserTypeVersion = UserTypeFactory.getUserTypeVersion();
@@ -3710,6 +3821,12 @@ public class UserManagerService extends IUserManager.Stub {
serializer.endTag(null, TAG_SEED_ACCOUNT_OPTIONS);
}
+ if (userData.userProperties != null) {
+ serializer.startTag(null, TAG_USER_PROPERTIES);
+ userData.userProperties.writeToXml(serializer);
+ serializer.endTag(null, TAG_USER_PROPERTIES);
+ }
+
if (userData.getLastRequestQuietModeEnabledMillis() != 0L) {
serializer.startTag(/* namespace */ null, TAG_LAST_REQUEST_QUIET_MODE_ENABLED_CALL);
serializer.text(String.valueOf(userData.getLastRequestQuietModeEnabledMillis()));
@@ -3829,6 +3946,7 @@ public class UserManagerService extends IUserManager.Stub {
String seedAccountName = null;
String seedAccountType = null;
PersistableBundle seedAccountOptions = null;
+ UserProperties userProperties = null;
Bundle baseRestrictions = null;
Bundle legacyLocalRestrictions = null;
RestrictionsSet localRestrictions = null;
@@ -3907,6 +4025,17 @@ public class UserManagerService extends IUserManager.Stub {
} else if (TAG_SEED_ACCOUNT_OPTIONS.equals(tag)) {
seedAccountOptions = PersistableBundle.restoreFromXml(parser);
persistSeedData = true;
+ } else if (TAG_USER_PROPERTIES.equals(tag)) {
+ // We already got the userType above (if it exists), so we can use it.
+ // And it must exist, since ATTR_TYPE historically predates PROPERTIES.
+ final UserTypeDetails userTypeDetails = mUserTypes.get(userType);
+ if (userTypeDetails == null) {
+ Slog.e(LOG_TAG, "User has properties but no user type!");
+ return null;
+ }
+ final UserProperties defaultProps
+ = userTypeDetails.getDefaultUserPropertiesReference();
+ userProperties = new UserProperties(parser, defaultProps);
} else if (TAG_LAST_REQUEST_QUIET_MODE_ENABLED_CALL.equals(tag)) {
type = parser.next();
if (type == XmlPullParser.TEXT) {
@@ -3943,6 +4072,7 @@ public class UserManagerService extends IUserManager.Stub {
userData.seedAccountType = seedAccountType;
userData.persistSeedData = persistSeedData;
userData.seedAccountOptions = seedAccountOptions;
+ userData.userProperties = userProperties;
userData.setLastRequestQuietModeEnabledMillis(lastRequestQuietModeEnabledTimestamp);
if (ignorePrepareStorageErrors) {
userData.setIgnorePrepareStorageErrors();
@@ -4209,9 +4339,9 @@ public class UserManagerService extends IUserManager.Stub {
UserManager.USER_OPERATION_ERROR_MAX_USERS);
}
// Keep logic in sync with getRemainingCreatableUserCount()
- if (!isGuest && !isManagedProfile && !isDemo && isUserLimitReached()) {
+ if (!isGuest && !isProfile && !isDemo && isUserLimitReached()) {
// If the user limit has been reached, we cannot add a user (except guest/demo).
- // Note that managed profiles can bypass it in certain circumstances (taken
+ // Note that profiles can bypass it in certain circumstances (taken
// into account in the profile check below).
throwCheckedUserOperationException(
"Cannot add user. Maximum user limit is reached.",
@@ -4278,6 +4408,8 @@ public class UserManagerService extends IUserManager.Stub {
}
userData = new UserData();
userData.info = userInfo;
+ userData.userProperties = new UserProperties(
+ userTypeDetails.getDefaultUserPropertiesReference());
mUsers.put(userId, userData);
}
writeUserLP(userData);
@@ -5604,6 +5736,7 @@ public class UserManagerService extends IUserManager.Stub {
}
// If we got here, we probably recycled user ids, so invalidate any caches.
UserManager.invalidateStaticUserProperties();
+ UserManager.invalidateUserPropertiesCache();
if (nextId < 0) {
throw new IllegalStateException("No user id available!");
}
@@ -5776,6 +5909,11 @@ public class UserManagerService extends IUserManager.Stub {
pw.println(" --reboot (which does a full reboot) or");
pw.println(" --no-restart (which requires a manual restart)");
pw.println();
+ pw.println(" is-user-visible [--display DISPLAY_ID] <USER_ID>");
+ pw.println(" Checks if the given user is visible in the given display.");
+ pw.println(" If the display option is not set, it uses the user's context to check");
+ pw.println(" (so it emulates what apps would get from UserManager.isUserVisible())");
+ pw.println();
}
@Override
@@ -5792,6 +5930,8 @@ public class UserManagerService extends IUserManager.Stub {
return runReportPackageAllowlistProblems();
case "set-system-user-mode-emulation":
return runSetSystemUserModeEmulation();
+ case "is-user-visible":
+ return runIsUserVisible();
default:
return handleDefaultCommands(cmd);
}
@@ -5841,9 +5981,6 @@ public class UserManagerService extends IUserManager.Stub {
for (int i = 0; i < size; i++) {
final UserInfo user = users.get(i);
final boolean running = am.isUserRunning(user.id, 0);
- final boolean current = user.id == currentUser;
- final boolean hasParent = user.profileGroupId != user.id
- && user.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID;
if (verbose) {
final DevicePolicyManagerInternal dpm = getDevicePolicyManagerInternal();
String deviceOwner = "";
@@ -5862,7 +5999,11 @@ public class UserManagerService extends IUserManager.Stub {
Binder.restoreCallingIdentity(ident);
}
}
- pw.printf("%d: id=%d, name=%s, type=%s, flags=%s%s%s%s%s%s%s%s%s\n",
+ final boolean current = user.id == currentUser;
+ final boolean hasParent = user.profileGroupId != user.id
+ && user.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID;
+ final boolean visible = isUserVisibleUnchecked(user.id);
+ pw.printf("%d: id=%d, name=%s, type=%s, flags=%s%s%s%s%s%s%s%s%s%s\n",
i,
user.id,
user.name,
@@ -5874,7 +6015,9 @@ public class UserManagerService extends IUserManager.Stub {
user.preCreated ? " (pre-created)" : "",
user.convertedFromPreCreated ? " (converted)" : "",
deviceOwner, profileOwner,
- current ? " (current)" : "");
+ current ? " (current)" : "",
+ visible ? " (visible)" : ""
+ );
} else {
// NOTE: the standard "list users" command is used by integration tests and
// hence should not be changed. If you need to add more info, use the
@@ -6031,6 +6174,56 @@ public class UserManagerService extends IUserManager.Stub {
return 0;
}
+ private int runIsUserVisible() {
+ PrintWriter pw = getOutPrintWriter();
+ int displayId = Display.INVALID_DISPLAY;
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ boolean invalidOption = false;
+ switch (opt) {
+ case "--display":
+ displayId = Integer.parseInt(getNextArgRequired());
+ invalidOption = displayId == Display.INVALID_DISPLAY;
+ break;
+ default:
+ invalidOption = true;
+ }
+ if (invalidOption) {
+ pw.println("Invalid option: " + opt);
+ return -1;
+ }
+ }
+ int userId = UserHandle.parseUserArg(getNextArgRequired());
+ switch (userId) {
+ case UserHandle.USER_ALL:
+ case UserHandle.USER_CURRENT_OR_SELF:
+ case UserHandle.USER_NULL:
+ pw.printf("invalid value (%d) for --user option\n", userId);
+ return -1;
+ case UserHandle.USER_CURRENT:
+ userId = ActivityManager.getCurrentUser();
+ break;
+ }
+
+ boolean isVisible;
+ if (displayId != Display.INVALID_DISPLAY) {
+ isVisible = isUserVisibleOnDisplay(userId, displayId);
+ } else {
+ isVisible = getUserManagerForUser(userId).isUserVisible();
+ }
+ pw.println(isVisible);
+ return 0;
+ }
+
+ /**
+ * Gets the {@link UserManager} associated with the context of the given user.
+ */
+ private UserManager getUserManagerForUser(int userId) {
+ UserHandle user = UserHandle.of(userId);
+ Context context = mContext.createContextAsUser(user, /* flags= */ 0);
+ return context.getSystemService(UserManager.class);
+ }
+
/**
* Confirms if the build is debuggable
*
@@ -6130,12 +6323,17 @@ public class UserManagerService extends IUserManager.Stub {
pw.print(" Cached user IDs (including pre-created): ");
pw.println(Arrays.toString(mUserIdsIncludingPreCreated));
}
-
} // synchronized (mPackagesLock)
// Multiple Users on Multiple Display info
pw.println(" Supports users on secondary displays: "
+ UserManager.isUsersOnSecondaryDisplaysEnabled());
+ synchronized (mUsersLock) {
+ if (mUsersOnSecondaryDisplays != null) {
+ pw.print(" Users on secondary displays: ");
+ pw.println(mUsersOnSecondaryDisplays);
+ }
+ }
// Dump some capabilities
pw.println();
@@ -6300,6 +6498,10 @@ public class UserManagerService extends IUserManager.Stub {
}
}
+ if (userData.userProperties != null) {
+ userData.userProperties.println(pw, " ");
+ }
+
pw.println(" Ignore errors preparing storage: "
+ userData.getIgnorePrepareStorageErrors());
}
@@ -6686,6 +6888,92 @@ public class UserManagerService extends IUserManager.Stub {
return userData != null && userData.getIgnorePrepareStorageErrors();
}
}
+
+ @Override
+ public @Nullable UserProperties getUserProperties(@UserIdInt int userId) {
+ final UserProperties props = getUserPropertiesInternal(userId);
+ if (props == null) {
+ Slog.w(LOG_TAG, "A null UserProperties was returned for user " + userId);
+ }
+ return props;
+ }
+
+ @Override
+ public void assignUserToDisplay(int userId, int displayId) {
+ if (DBG) {
+ Slogf.d(LOG_TAG, "assignUserToDisplay(%d, %d): mUsersOnSecondaryDisplays=%s",
+ userId, displayId, mUsersOnSecondaryDisplays);
+ }
+ // TODO(b/240613396) throw exception if feature not supported
+
+ if (displayId == Display.INVALID_DISPLAY) {
+ synchronized (mUsersLock) {
+ if (mUsersOnSecondaryDisplays == null) {
+ if (false) {
+ // TODO(b/240613396): remove this if once we check for support above
+ Slogf.wtf(LOG_TAG, "assignUserToDisplay(%d, %d): no "
+ + "mUsersOnSecondaryDisplays", userId, displayId);
+ }
+ return;
+ }
+ if (DBG) {
+ Slogf.d(LOG_TAG, "Removing %d from mUsersOnSecondaryDisplays", userId);
+ }
+ mUsersOnSecondaryDisplays.delete(userId);
+ if (mUsersOnSecondaryDisplays.size() == 0) {
+ if (DBG) {
+ Slogf.d(LOG_TAG, "Removing mUsersOnSecondaryDisplays");
+ }
+ mUsersOnSecondaryDisplays = null;
+ }
+ }
+ return;
+ }
+
+ synchronized (mUsersLock) {
+ if (mUsersOnSecondaryDisplays == null) {
+ if (DBG) {
+ Slogf.d(LOG_TAG, "Creating mUsersOnSecondaryDisplays");
+ }
+ mUsersOnSecondaryDisplays = new SparseIntArray();
+ }
+ if (DBG) {
+ Slogf.d(LOG_TAG, "Adding %d->%d to mUsersOnSecondaryDisplays",
+ userId, displayId);
+ }
+
+ if (isProfileUnchecked(userId)) {
+ // Profile can only start in the same display as parent
+ int parentUserId = getProfileParentId(userId);
+ int parentDisplayId = mUsersOnSecondaryDisplays.get(parentUserId);
+ if (displayId != parentDisplayId) {
+ throw new IllegalStateException("Cannot assign profile " + userId + " to "
+ + "display " + displayId + " as its parent (user " + parentUserId
+ + ") is assigned to display " + parentDisplayId);
+ }
+ } else {
+ // Check if display is available
+ for (int i = 0; i < mUsersOnSecondaryDisplays.size(); i++) {
+ // Make sure display is not used by other users...
+ // TODO(b/240736142); currently, if a user was started in a display, it
+ // would need to be stopped first, so "switching" a user on secondary
+ // diplay requires 2 non-atomic operations (stop and start). Once this logic
+ // is refactored, it should be atomic.
+ if (mUsersOnSecondaryDisplays.valueAt(i) == displayId) {
+ throw new IllegalStateException("Cannot assign " + userId + " to "
+ + "display " + displayId + " as it's already assigned to "
+ + "user " + mUsersOnSecondaryDisplays.keyAt(i));
+ }
+ // TODO(b/239982558) also check that user is not already assigned to other
+ // display (including 0). That would be harder to tested under CTS though
+ // (for example, would need to add a new AM method to start user in bg on
+ // main display), so it's better to test on unit tests
+ }
+ }
+
+ mUsersOnSecondaryDisplays.put(userId, displayId);
+ }
+ }
}
/**
diff --git a/services/core/java/com/android/server/pm/UserTypeDetails.java b/services/core/java/com/android/server/pm/UserTypeDetails.java
index 2f3ca664c126..ebb9f98320cf 100644
--- a/services/core/java/com/android/server/pm/UserTypeDetails.java
+++ b/services/core/java/com/android/server/pm/UserTypeDetails.java
@@ -23,6 +23,7 @@ import android.annotation.Nullable;
import android.annotation.StringRes;
import android.content.pm.UserInfo;
import android.content.pm.UserInfo.UserInfoFlag;
+import android.content.pm.UserProperties;
import android.content.res.Resources;
import android.os.Bundle;
import android.os.UserManager;
@@ -171,6 +172,12 @@ public final class UserTypeDetails {
private final @CrossProfileIntentFilter.AccessControlLevel int
mCrossProfileIntentFilterAccessControl;
+ /**
+ * The default {@link UserProperties} for the user type.
+ * <p> The uninitialized value of each property is implied by {@link UserProperties.Builder}.
+ */
+ private final @NonNull UserProperties mDefaultUserProperties;
+
private UserTypeDetails(@NonNull String name, boolean enabled, int maxAllowed,
@UserInfoFlag int baseType, @UserInfoFlag int defaultUserInfoPropertyFlags, int label,
int maxAllowedPerParent,
@@ -183,7 +190,8 @@ public final class UserTypeDetails {
@Nullable List<DefaultCrossProfileIntentFilter> defaultCrossProfileIntentFilters,
boolean isMediaSharedWithParent,
boolean isCredentialSharableWithParent,
- @CrossProfileIntentFilter.AccessControlLevel int accessControlLevel) {
+ @CrossProfileIntentFilter.AccessControlLevel int accessControlLevel,
+ @NonNull UserProperties defaultUserProperties) {
this.mName = name;
this.mEnabled = enabled;
this.mMaxAllowed = maxAllowed;
@@ -205,6 +213,7 @@ public final class UserTypeDetails {
this.mIsMediaSharedWithParent = isMediaSharedWithParent;
this.mIsCredentialSharableWithParent = isCredentialSharableWithParent;
this.mCrossProfileIntentFilterAccessControl = accessControlLevel;
+ this.mDefaultUserProperties = defaultUserProperties;
}
/**
@@ -310,18 +319,6 @@ public final class UserTypeDetails {
return mDarkThemeBadgeColors[Math.min(badgeIndex, mDarkThemeBadgeColors.length - 1)];
}
- public boolean isProfile() {
- return (mBaseType & UserInfo.FLAG_PROFILE) != 0;
- }
-
- public boolean isFull() {
- return (mBaseType & UserInfo.FLAG_FULL) != 0;
- }
-
- public boolean isSystem() {
- return (mBaseType & UserInfo.FLAG_SYSTEM) != 0;
- }
-
/**
* Returns true if the user has shared media with parent user or false otherwise.
*/
@@ -347,6 +344,26 @@ public final class UserTypeDetails {
return mCrossProfileIntentFilterAccessControl;
}
+ /**
+ * Returns the reference to the default {@link UserProperties} for this type of user.
+ * This is not a copy. Do NOT modify this object.
+ */
+ public @NonNull UserProperties getDefaultUserPropertiesReference() {
+ return mDefaultUserProperties;
+ }
+
+ public boolean isProfile() {
+ return (mBaseType & UserInfo.FLAG_PROFILE) != 0;
+ }
+
+ public boolean isFull() {
+ return (mBaseType & UserInfo.FLAG_FULL) != 0;
+ }
+
+ public boolean isSystem() {
+ return (mBaseType & UserInfo.FLAG_SYSTEM) != 0;
+ }
+
/** Returns a {@link Bundle} representing the default user restrictions. */
@NonNull Bundle getDefaultRestrictions() {
return BundleUtils.clone(mDefaultRestrictions);
@@ -384,6 +401,7 @@ public final class UserTypeDetails {
pw.print(prefix); pw.print("mDefaultUserInfoFlags: ");
pw.println(UserInfo.flagsToString(mDefaultUserInfoPropertyFlags));
pw.print(prefix); pw.print("mLabel: "); pw.println(mLabel);
+ mDefaultUserProperties.println(pw, prefix);
final String restrictionsPrefix = prefix + " ";
if (isSystem()) {
@@ -442,6 +460,9 @@ public final class UserTypeDetails {
private boolean mIsCredentialSharableWithParent = false;
private @CrossProfileIntentFilter.AccessControlLevel int
mCrossProfileIntentFilterAccessControl = CrossProfileIntentFilter.ACCESS_LEVEL_ALL;
+ // Default UserProperties cannot be null but for efficiency we don't initialize it now.
+ // If it isn't set explicitly, {@link UserProperties.Builder#build()} will be used.
+ private @Nullable UserProperties mDefaultUserProperties = null;
public Builder setName(String name) {
mName = name;
@@ -560,6 +581,23 @@ public final class UserTypeDetails {
return this;
}
+ /**
+ * Sets (replacing if necessary) the default UserProperties object for this user type.
+ * Takes a builder, rather than a built object, to efficiently ensure that a fresh copy of
+ * properties is stored (since it later might be modified by UserProperties#updateFromXml).
+ */
+ public Builder setDefaultUserProperties(UserProperties.Builder userPropertiesBuilder) {
+ mDefaultUserProperties = userPropertiesBuilder.build();
+ return this;
+ }
+
+ public @NonNull UserProperties getDefaultUserProperties() {
+ if (mDefaultUserProperties == null) {
+ mDefaultUserProperties = new UserProperties.Builder().build();
+ }
+ return mDefaultUserProperties;
+ }
+
@UserInfoFlag int getBaseType() {
return mBaseType;
}
@@ -604,7 +642,8 @@ public final class UserTypeDetails {
mDefaultCrossProfileIntentFilters,
mIsMediaSharedWithParent,
mIsCredentialSharableWithParent,
- mCrossProfileIntentFilterAccessControl);
+ mCrossProfileIntentFilterAccessControl,
+ getDefaultUserProperties());
}
private boolean hasBadge() {
diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java
index 857a9757b03a..b98d20eaa38e 100644
--- a/services/core/java/com/android/server/pm/UserTypeFactory.java
+++ b/services/core/java/com/android/server/pm/UserTypeFactory.java
@@ -37,6 +37,7 @@ import static android.os.UserManager.USER_TYPE_SYSTEM_HEADLESS;
import static com.android.server.pm.UserTypeDetails.UNLIMITED_NUMBER_OF_USERS;
import android.content.pm.UserInfo;
+import android.content.pm.UserProperties;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.os.Build;
@@ -124,7 +125,10 @@ public final class UserTypeFactory {
.setIsMediaSharedWithParent(true)
.setCrossProfileIntentFilterAccessControl(
CrossProfileIntentFilter.ACCESS_LEVEL_SYSTEM)
- .setIsCredentialSharableWithParent(true);
+ .setIsCredentialSharableWithParent(true)
+ .setDefaultUserProperties(new UserProperties.Builder()
+ .setStartWithParent(true)
+ .setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_WITH_PARENT));
}
/**
@@ -156,7 +160,10 @@ public final class UserTypeFactory {
.setDefaultRestrictions(getDefaultManagedProfileRestrictions())
.setDefaultSecureSettings(getDefaultManagedProfileSecureSettings())
.setDefaultCrossProfileIntentFilters(getDefaultManagedCrossProfileIntentFilter())
- .setIsCredentialSharableWithParent(true);
+ .setIsCredentialSharableWithParent(true)
+ .setDefaultUserProperties(new UserProperties.Builder()
+ .setStartWithParent(true)
+ .setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_SEPARATE));
}
/**
@@ -396,6 +403,9 @@ public final class UserTypeFactory {
setResAttributeArray(parser, builder::setBadgeColors);
} else if (isProfile && "badge-colors-dark".equals(childName)) {
setResAttributeArray(parser, builder::setDarkThemeBadgeColors);
+ } else if ("user-properties".equals(childName)) {
+ builder.getDefaultUserProperties()
+ .updateFromXml(XmlUtils.makeTyped(parser));
} else {
Slog.w(LOG_TAG, "Unrecognized tag " + childName + " in "
+ parser.getPositionDescription());
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
index 5b8f473bad2e..c129f37b6d16 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
@@ -1256,6 +1256,18 @@ public class ParsingPackageUtils {
return input.success(pkg.addPermission(result.getResult()));
}
+ private int parseMinOrMaxSdkVersion(TypedArray sa, int attr, int defaultValue) {
+ int val = defaultValue;
+ TypedValue peekVal = sa.peekValue(attr);
+ if (peekVal != null) {
+ if (peekVal.type >= TypedValue.TYPE_FIRST_INT
+ && peekVal.type <= TypedValue.TYPE_LAST_INT) {
+ val = peekVal.data;
+ }
+ }
+ return val;
+ }
+
private ParseResult<ParsingPackage> parseUsesPermission(ParseInput input,
ParsingPackage pkg, Resources res, XmlResourceParser parser)
throws IOException, XmlPullParserException {
@@ -1266,14 +1278,13 @@ public class ParsingPackageUtils {
String name = sa.getNonResourceString(
R.styleable.AndroidManifestUsesPermission_name);
- int maxSdkVersion = 0;
- TypedValue val = sa.peekValue(
- R.styleable.AndroidManifestUsesPermission_maxSdkVersion);
- if (val != null) {
- if (val.type >= TypedValue.TYPE_FIRST_INT && val.type <= TypedValue.TYPE_LAST_INT) {
- maxSdkVersion = val.data;
- }
- }
+ int minSdkVersion = parseMinOrMaxSdkVersion(sa,
+ R.styleable.AndroidManifestUsesPermission_minSdkVersion,
+ Integer.MIN_VALUE);
+
+ int maxSdkVersion = parseMinOrMaxSdkVersion(sa,
+ R.styleable.AndroidManifestUsesPermission_maxSdkVersion,
+ Integer.MAX_VALUE);
final ArraySet<String> requiredFeatures = new ArraySet<>();
String feature = sa.getNonConfigurationString(
@@ -1338,7 +1349,8 @@ public class ParsingPackageUtils {
return success;
}
- if ((maxSdkVersion != 0) && (maxSdkVersion < Build.VERSION.RESOURCES_SDK_INT)) {
+ if (Build.VERSION.RESOURCES_SDK_INT < minSdkVersion
+ || Build.VERSION.RESOURCES_SDK_INT > maxSdkVersion) {
return success;
}
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java
index 4b0a8e2778c0..466c4c9a31d6 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationShell.java
@@ -103,7 +103,6 @@ public class DomainVerificationShell {
pw.println(" <DOMAINS>: space separated list of domains to change, or \"all\" to");
pw.println(" change every domain.");
pw.println(" set-app-links-allowed --user <USER_ID> [--package <PACKAGE>] <ALLOWED>");
- pw.println(" <ENABLED> <DOMAINS>...");
pw.println(" Toggle the auto verified link handling setting for a package.");
pw.println(" --user <USER_ID>: the user to change selections for");
pw.println(" --package <PACKAGE>: the package to set, or \"all\" to set all packages");
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 5816e984704e..51bf55759962 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -2082,22 +2082,21 @@ public class PhoneWindowManager implements WindowManagerPolicy {
mWindowManagerInternal.registerAppTransitionListener(new AppTransitionListener() {
@Override
- public int onAppTransitionStartingLocked(boolean keyguardGoingAway,
- boolean keyguardOccluding, long duration, long statusBarAnimationStartTime,
+ public int onAppTransitionStartingLocked(long statusBarAnimationStartTime,
long statusBarAnimationDuration) {
- // When remote animation is enabled for KEYGUARD_GOING_AWAY transition, SysUI
- // receives IRemoteAnimationRunner#onAnimationStart to start animation, so we don't
- // need to call IKeyguardService#keyguardGoingAway here.
- return handleStartTransitionForKeyguardLw(keyguardGoingAway
- && !WindowManagerService.sEnableRemoteKeyguardGoingAwayAnimation,
- keyguardOccluding, duration);
+ return handleTransitionForKeyguardLw(false /* startKeyguardExitAnimation */,
+ false /* notifyOccluded */);
}
@Override
- public void onAppTransitionCancelledLocked(boolean keyguardGoingAway) {
- handleStartTransitionForKeyguardLw(
- keyguardGoingAway, false /* keyguardOccludingStarted */,
- 0 /* duration */);
+ public void onAppTransitionCancelledLocked(boolean keyguardGoingAwayCancelled,
+ boolean keyguardOccludedCancelled) {
+ // When app KEYGUARD_GOING_AWAY or (UN)OCCLUDE app transition is canceled, we need
+ // to trigger relevant IKeyguardService calls to sync keyguard status in
+ // WindowManagerService and SysUI.
+ handleTransitionForKeyguardLw(
+ keyguardGoingAwayCancelled /* startKeyguardExitAnimation */,
+ keyguardOccludedCancelled /* notifyOccluded */);
}
});
@@ -3263,31 +3262,39 @@ public class PhoneWindowManager implements WindowManagerPolicy {
mPendingKeyguardOccluded = occluded;
mKeyguardOccludedChanged = true;
} else {
- setKeyguardOccludedLw(occluded, false /* force */,
- false /* transitionStarted */);
+ setKeyguardOccludedLw(occluded, true /* notify */);
}
}
@Override
- public int applyKeyguardOcclusionChange(boolean transitionStarted) {
+ public int applyKeyguardOcclusionChange(boolean notify) {
if (mKeyguardOccludedChanged) {
if (DEBUG_KEYGUARD) Slog.d(TAG, "transition/occluded changed occluded="
+ mPendingKeyguardOccluded);
- if (setKeyguardOccludedLw(mPendingKeyguardOccluded, false /* force */,
- transitionStarted)) {
+ if (setKeyguardOccludedLw(mPendingKeyguardOccluded, notify)) {
return FINISH_LAYOUT_REDO_LAYOUT | FINISH_LAYOUT_REDO_WALLPAPER;
}
}
return 0;
}
- private int handleStartTransitionForKeyguardLw(boolean keyguardGoingAway,
- boolean keyguardOccluding, long duration) {
- final int redoLayout = applyKeyguardOcclusionChange(keyguardOccluding);
+ /**
+ * Called when keyguard related app transition starts, or cancelled.
+ *
+ * @param startKeyguardExitAnimation Trigger IKeyguardService#startKeyguardExitAnimation to
+ * start keyguard exit animation.
+ * @param notifyOccluded Trigger IKeyguardService#setOccluded binder call to notify whether
+ * the top activity can occlude the keyguard or not.
+ *
+ * @return Whether the flags have changed and we have to redo the layout.
+ */
+ private int handleTransitionForKeyguardLw(boolean startKeyguardExitAnimation,
+ boolean notifyOccluded) {
+ final int redoLayout = applyKeyguardOcclusionChange(notifyOccluded);
if (redoLayout != 0) return redoLayout;
- if (keyguardGoingAway) {
+ if (startKeyguardExitAnimation) {
if (DEBUG_KEYGUARD) Slog.d(TAG, "Starting keyguard exit animation");
- startKeyguardExitAnimation(SystemClock.uptimeMillis(), duration);
+ startKeyguardExitAnimation(SystemClock.uptimeMillis());
}
return 0;
}
@@ -3519,28 +3526,18 @@ public class PhoneWindowManager implements WindowManagerPolicy {
* Updates the occluded state of the Keyguard.
*
* @param isOccluded Whether the Keyguard is occluded by another window.
- * @param force notify the occluded status to KeyguardService and update flags even though
- * occlude status doesn't change.
- * @param transitionStarted {@code true} if keyguard (un)occluded transition started.
+ * @param notify Notify keyguard occlude status change immediately via
+ * {@link com.android.internal.policy.IKeyguardService}.
* @return Whether the flags have changed and we have to redo the layout.
*/
- private boolean setKeyguardOccludedLw(boolean isOccluded, boolean force,
- boolean transitionStarted) {
+ private boolean setKeyguardOccludedLw(boolean isOccluded, boolean notify) {
if (DEBUG_KEYGUARD) Slog.d(TAG, "setKeyguardOccluded occluded=" + isOccluded);
mKeyguardOccludedChanged = false;
- if (isKeyguardOccluded() == isOccluded && !force) {
+ if (isKeyguardOccluded() == isOccluded) {
return false;
}
-
- final boolean showing = mKeyguardDelegate.isShowing();
- final boolean animate = showing && !isOccluded;
- // When remote animation is enabled for keyguard (un)occlude transition, KeyguardService
- // uses remote animation start as a signal to update its occlusion status ,so we don't need
- // to notify here.
- final boolean notify = !WindowManagerService.sEnableRemoteKeyguardOccludeAnimation
- || !transitionStarted;
- mKeyguardDelegate.setOccluded(isOccluded, animate, notify);
- return showing;
+ mKeyguardDelegate.setOccluded(isOccluded, notify);
+ return mKeyguardDelegate.isShowing();
}
/** {@inheritDoc} */
@@ -4936,10 +4933,10 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
@Override
- public void startKeyguardExitAnimation(long startTime, long fadeoutDuration) {
+ public void startKeyguardExitAnimation(long startTime) {
if (mKeyguardDelegate != null) {
if (DEBUG_KEYGUARD) Slog.d(TAG, "PWM.startKeyguardExitAnimation");
- mKeyguardDelegate.startKeyguardExitAnimation(startTime, fadeoutDuration);
+ mKeyguardDelegate.startKeyguardExitAnimation(startTime);
}
}
@@ -5121,18 +5118,10 @@ public class PhoneWindowManager implements WindowManagerPolicy {
/** {@inheritDoc} */
@Override
- public void userActivity() {
- // ***************************************
- // NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE
- // ***************************************
- // THIS IS CALLED FROM DEEP IN THE POWER MANAGER
- // WITH ITS LOCKS HELD.
- //
- // This code must be VERY careful about the locks
- // it acquires.
- // In fact, the current code acquires way too many,
- // and probably has lurking deadlocks.
-
+ public void userActivity(int displayGroupId, int event) {
+ if (displayGroupId == DEFAULT_DISPLAY && event == PowerManager.USER_ACTIVITY_EVENT_TOUCH) {
+ mDefaultDisplayPolicy.onUserActivityEventTouch();
+ }
synchronized (mScreenLockTimeout) {
if (mLockScreenTimerActive) {
// reset the timer
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index e8a3dcd5635f..2b0405073323 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -171,10 +171,10 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants {
void onKeyguardOccludedChangedLw(boolean occluded);
/**
- * Applies a keyguard occlusion change if one happened.
- * @param transitionStarted Whether keyguard (un)occlude transition is starting or not.
+ * @param notify {@code true} if the status change should be immediately notified via
+ * {@link com.android.internal.policy.IKeyguardService}
*/
- int applyKeyguardOcclusionChange(boolean transitionStarted);
+ int applyKeyguardOcclusionChange(boolean notify);
/**
* Interface to the Window Manager state associated with a particular
@@ -1006,7 +1006,7 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants {
* Called when userActivity is signalled in the power manager.
* This is safe to call from any thread, with any window manager locks held or not.
*/
- public void userActivity();
+ void userActivity(int displayGroupId, int event);
/**
* Called when we have finished booting and can now display the home
@@ -1129,11 +1129,10 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants {
/**
* Notifies the keyguard to start fading out.
+ * @param startTime the start time of the animation in uptime milliseconds
*
- * @param startTime the start time of the animation in uptime milliseconds
- * @param fadeoutDuration the duration of the exit animation, in milliseconds
*/
- void startKeyguardExitAnimation(long startTime, long fadeoutDuration);
+ void startKeyguardExitAnimation(long startTime);
/**
* Called when System UI has been started.
diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
index b79ac6f68be2..7737421654ee 100644
--- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
+++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
@@ -249,10 +249,10 @@ public class KeyguardServiceDelegate {
}
}
- public void setOccluded(boolean isOccluded, boolean animate, boolean notify) {
+ public void setOccluded(boolean isOccluded, boolean notify) {
if (mKeyguardService != null && notify) {
- if (DEBUG) Log.v(TAG, "setOccluded(" + isOccluded + ") animate=" + animate);
- mKeyguardService.setOccluded(isOccluded, animate);
+ if (DEBUG) Log.v(TAG, "setOccluded(" + isOccluded + ")");
+ mKeyguardService.setOccluded(isOccluded, false /* animate */);
}
mKeyguardState.occluded = isOccluded;
}
@@ -394,9 +394,9 @@ public class KeyguardServiceDelegate {
}
}
- public void startKeyguardExitAnimation(long startTime, long fadeoutDuration) {
+ public void startKeyguardExitAnimation(long startTime) {
if (mKeyguardService != null) {
- mKeyguardService.startKeyguardExitAnimation(startTime, fadeoutDuration);
+ mKeyguardService.startKeyguardExitAnimation(startTime, 0);
}
}
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index 685b744c8062..dad9584c6722 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -571,7 +571,8 @@ public class Notifier {
/**
* Called when there has been user activity.
*/
- public void onUserActivity(int displayGroupId, int event, int uid) {
+ public void onUserActivity(int displayGroupId, @PowerManager.UserActivityEvent int event,
+ int uid) {
if (DEBUG) {
Slog.d(TAG, "onUserActivity: event=" + event + ", uid=" + uid);
}
@@ -712,7 +713,7 @@ public class Notifier {
}
TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
tm.notifyUserActivity();
- mPolicy.userActivity();
+ mPolicy.userActivity(displayGroupId, event);
mFaceDownDetector.userActivity(event);
mScreenUndimDetector.userActivity(displayGroupId);
}
diff --git a/services/core/java/com/android/server/power/PowerGroup.java b/services/core/java/com/android/server/power/PowerGroup.java
index fec61ac8f2cf..9fe53fbfaf25 100644
--- a/services/core/java/com/android/server/power/PowerGroup.java
+++ b/services/core/java/com/android/server/power/PowerGroup.java
@@ -74,6 +74,8 @@ public class PowerGroup {
private long mLastPowerOnTime;
private long mLastUserActivityTime;
private long mLastUserActivityTimeNoChangeLights;
+ @PowerManager.UserActivityEvent
+ private int mLastUserActivityEvent;
/** Timestamp (milliseconds since boot) of the last time the power group was awoken.*/
private long mLastWakeTime;
/** Timestamp (milliseconds since boot) of the last time the power group was put to sleep. */
@@ -244,7 +246,7 @@ public class PowerGroup {
return true;
}
- boolean dozeLocked(long eventTime, int uid, int reason) {
+ boolean dozeLocked(long eventTime, int uid, @PowerManager.GoToSleepReason int reason) {
if (eventTime < getLastWakeTimeLocked() || !isInteractive(mWakefulness)) {
return false;
}
@@ -253,9 +255,14 @@ public class PowerGroup {
try {
reason = Math.min(PowerManager.GO_TO_SLEEP_REASON_MAX,
Math.max(reason, PowerManager.GO_TO_SLEEP_REASON_MIN));
+ long millisSinceLastUserActivity = eventTime - Math.max(
+ mLastUserActivityTimeNoChangeLights, mLastUserActivityTime);
Slog.i(TAG, "Powering off display group due to "
- + PowerManager.sleepReasonToString(reason) + " (groupId= " + getGroupId()
- + ", uid= " + uid + ")...");
+ + PowerManager.sleepReasonToString(reason)
+ + " (groupId= " + getGroupId() + ", uid= " + uid
+ + ", millisSinceLastUserActivity=" + millisSinceLastUserActivity
+ + ", lastUserActivityEvent=" + PowerManager.userActivityEventToString(
+ mLastUserActivityEvent) + ")...");
setSandmanSummonedLocked(/* isSandmanSummoned= */ true);
setWakefulnessLocked(WAKEFULNESS_DOZING, eventTime, uid, reason, /* opUid= */ 0,
@@ -266,14 +273,16 @@ public class PowerGroup {
return true;
}
- boolean sleepLocked(long eventTime, int uid, int reason) {
+ boolean sleepLocked(long eventTime, int uid, @PowerManager.GoToSleepReason int reason) {
if (eventTime < mLastWakeTime || getWakefulnessLocked() == WAKEFULNESS_ASLEEP) {
return false;
}
Trace.traceBegin(Trace.TRACE_TAG_POWER, "sleepPowerGroup");
try {
- Slog.i(TAG, "Sleeping power group (groupId=" + getGroupId() + ", uid=" + uid + ")...");
+ Slog.i(TAG,
+ "Sleeping power group (groupId=" + getGroupId() + ", uid=" + uid + ", reason="
+ + PowerManager.sleepReasonToString(reason) + ")...");
setSandmanSummonedLocked(/* isSandmanSummoned= */ true);
setWakefulnessLocked(WAKEFULNESS_ASLEEP, eventTime, uid, reason, /* opUid= */0,
/* opPackageName= */ null, /* details= */ null);
@@ -287,16 +296,20 @@ public class PowerGroup {
return mLastUserActivityTime;
}
- void setLastUserActivityTimeLocked(long lastUserActivityTime) {
+ void setLastUserActivityTimeLocked(long lastUserActivityTime,
+ @PowerManager.UserActivityEvent int event) {
mLastUserActivityTime = lastUserActivityTime;
+ mLastUserActivityEvent = event;
}
public long getLastUserActivityTimeNoChangeLightsLocked() {
return mLastUserActivityTimeNoChangeLights;
}
- public void setLastUserActivityTimeNoChangeLightsLocked(long time) {
+ public void setLastUserActivityTimeNoChangeLightsLocked(long time,
+ @PowerManager.UserActivityEvent int event) {
mLastUserActivityTimeNoChangeLights = time;
+ mLastUserActivityEvent = event;
}
public int getUserActivitySummaryLocked() {
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 2a1748c441dc..ca3599ca7fa0 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -1216,6 +1216,7 @@ public final class PowerManagerService extends SystemService
return;
}
+ Slog.i(TAG, "onFlip(): Face " + (isFaceDown ? "down." : "up."));
mIsFaceDown = isFaceDown;
if (isFaceDown) {
final long currentTime = mClock.uptimeMillis();
@@ -1937,12 +1938,13 @@ public final class PowerManagerService extends SystemService
// Called from native code.
@SuppressWarnings("unused")
- private void userActivityFromNative(long eventTime, int event, int displayId, int flags) {
+ private void userActivityFromNative(long eventTime, @PowerManager.UserActivityEvent int event,
+ int displayId, int flags) {
userActivityInternal(displayId, eventTime, event, flags, Process.SYSTEM_UID);
}
- private void userActivityInternal(int displayId, long eventTime, int event, int flags,
- int uid) {
+ private void userActivityInternal(int displayId, long eventTime,
+ @PowerManager.UserActivityEvent int event, int flags, int uid) {
synchronized (mLock) {
if (displayId == Display.INVALID_DISPLAY) {
if (userActivityNoUpdateLocked(eventTime, event, flags, uid)) {
@@ -1993,11 +1995,12 @@ public final class PowerManagerService extends SystemService
@GuardedBy("mLock")
private boolean userActivityNoUpdateLocked(final PowerGroup powerGroup, long eventTime,
- int event, int flags, int uid) {
+ @PowerManager.UserActivityEvent int event, int flags, int uid) {
final int groupId = powerGroup.getGroupId();
if (DEBUG_SPEW) {
Slog.d(TAG, "userActivityNoUpdateLocked: groupId=" + groupId
- + ", eventTime=" + eventTime + ", event=" + event
+ + ", eventTime=" + eventTime
+ + ", event=" + PowerManager.userActivityEventToString(event)
+ ", flags=0x" + Integer.toHexString(flags) + ", uid=" + uid);
}
@@ -2032,7 +2035,7 @@ public final class PowerManagerService extends SystemService
if ((flags & PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS) != 0) {
if (eventTime > powerGroup.getLastUserActivityTimeNoChangeLightsLocked()
&& eventTime > powerGroup.getLastUserActivityTimeLocked()) {
- powerGroup.setLastUserActivityTimeNoChangeLightsLocked(eventTime);
+ powerGroup.setLastUserActivityTimeNoChangeLightsLocked(eventTime, event);
mDirty |= DIRTY_USER_ACTIVITY;
if (event == PowerManager.USER_ACTIVITY_EVENT_BUTTON) {
mDirty |= DIRTY_QUIESCENT;
@@ -2042,7 +2045,7 @@ public final class PowerManagerService extends SystemService
}
} else {
if (eventTime > powerGroup.getLastUserActivityTimeLocked()) {
- powerGroup.setLastUserActivityTimeLocked(eventTime);
+ powerGroup.setLastUserActivityTimeLocked(eventTime, event);
mDirty |= DIRTY_USER_ACTIVITY;
if (event == PowerManager.USER_ACTIVITY_EVENT_BUTTON) {
mDirty |= DIRTY_QUIESCENT;
@@ -2069,7 +2072,8 @@ public final class PowerManagerService extends SystemService
@WakeReason int reason, String details, int uid, String opPackageName, int opUid) {
if (DEBUG_SPEW) {
Slog.d(TAG, "wakePowerGroupLocked: eventTime=" + eventTime
- + ", groupId=" + powerGroup.getGroupId() + ", uid=" + uid);
+ + ", groupId=" + powerGroup.getGroupId()
+ + ", reason=" + PowerManager.wakeReasonToString(reason) + ", uid=" + uid);
}
if (mForceSuspendActive || !mSystemReady) {
return;
@@ -2092,11 +2096,11 @@ public final class PowerManagerService extends SystemService
@GuardedBy("mLock")
private boolean dozePowerGroupLocked(final PowerGroup powerGroup, long eventTime,
- int reason, int uid) {
+ @GoToSleepReason int reason, int uid) {
if (DEBUG_SPEW) {
Slog.d(TAG, "dozePowerGroup: eventTime=" + eventTime
- + ", groupId=" + powerGroup.getGroupId() + ", reason=" + reason
- + ", uid=" + uid);
+ + ", groupId=" + powerGroup.getGroupId()
+ + ", reason=" + PowerManager.sleepReasonToString(reason) + ", uid=" + uid);
}
if (!mSystemReady || !mBootCompleted) {
@@ -2107,10 +2111,12 @@ public final class PowerManagerService extends SystemService
}
@GuardedBy("mLock")
- private boolean sleepPowerGroupLocked(final PowerGroup powerGroup, long eventTime, int reason,
- int uid) {
+ private boolean sleepPowerGroupLocked(final PowerGroup powerGroup, long eventTime,
+ @GoToSleepReason int reason, int uid) {
if (DEBUG_SPEW) {
- Slog.d(TAG, "sleepPowerGroup: eventTime=" + eventTime + ", uid=" + uid);
+ Slog.d(TAG, "sleepPowerGroup: eventTime=" + eventTime
+ + ", groupId=" + powerGroup.getGroupId()
+ + ", reason=" + PowerManager.sleepReasonToString(reason) + ", uid=" + uid);
}
if (!mBootCompleted || !mSystemReady) {
return false;
@@ -2172,7 +2178,11 @@ public final class PowerManagerService extends SystemService
case WAKEFULNESS_DOZING:
traceMethodName = "goToSleep";
Slog.i(TAG, "Going to sleep due to " + PowerManager.sleepReasonToString(reason)
- + " (uid " + uid + ")...");
+ + " (uid " + uid + ", screenOffTimeout=" + mScreenOffTimeoutSetting
+ + ", activityTimeoutWM=" + mUserActivityTimeoutOverrideFromWindowManager
+ + ", maxDimRatio=" + mMaximumScreenDimRatioConfig
+ + ", maxDimDur=" + mMaximumScreenDimDurationConfig + ")...");
+
mLastGlobalSleepTime = eventTime;
mLastGlobalSleepReason = reason;
mLastGlobalSleepTimeRealtime = mClock.elapsedRealtime();
@@ -4257,7 +4267,7 @@ public final class PowerManagerService extends SystemService
void onUserActivity() {
synchronized (mLock) {
mPowerGroups.get(Display.DEFAULT_DISPLAY_GROUP).setLastUserActivityTimeLocked(
- mClock.uptimeMillis());
+ mClock.uptimeMillis(), PowerManager.USER_ACTIVITY_EVENT_OTHER);
}
}
@@ -5645,7 +5655,8 @@ public final class PowerManagerService extends SystemService
}
@Override // Binder call
- public void userActivity(int displayId, long eventTime, int event, int flags) {
+ public void userActivity(int displayId, long eventTime,
+ @PowerManager.UserActivityEvent int event, int flags) {
final long now = mClock.uptimeMillis();
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER)
!= PackageManager.PERMISSION_GRANTED
diff --git a/services/core/java/com/android/server/power/ThermalManagerService.java b/services/core/java/com/android/server/power/ThermalManagerService.java
index 96823c8ae9ab..f3785885e4ec 100644
--- a/services/core/java/com/android/server/power/ThermalManagerService.java
+++ b/services/core/java/com/android/server/power/ThermalManagerService.java
@@ -72,6 +72,12 @@ import java.util.concurrent.atomic.AtomicBoolean;
public class ThermalManagerService extends SystemService {
private static final String TAG = ThermalManagerService.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
+ /** Input range limits for getThermalHeadroom API */
+ public static final int MIN_FORECAST_SEC = 0;
+ public static final int MAX_FORECAST_SEC = 60;
+
/** Lock to protect listen list. */
private final Object mLock = new Object();
@@ -478,6 +484,13 @@ public class ThermalManagerService extends SystemService {
return Float.NaN;
}
+ if (forecastSeconds < MIN_FORECAST_SEC || forecastSeconds > MAX_FORECAST_SEC) {
+ if (DEBUG) {
+ Slog.d(TAG, "Invalid forecastSeconds: " + forecastSeconds);
+ }
+ return Float.NaN;
+ }
+
return mTemperatureWatcher.getForecast(forecastSeconds);
}
diff --git a/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java
index fe4aa534df6f..df902c2916ba 100644
--- a/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java
+++ b/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java
@@ -657,7 +657,7 @@ public class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStat
// Now that we have finally received all the data, we can tell mStats about it.
synchronized (mStats) {
- mStats.addHistoryEventLocked(
+ mStats.recordHistoryEventLocked(
elapsedRealtime,
uptime,
BatteryStats.HistoryItem.EVENT_COLLECT_EXTERNAL_STATS,
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 0c9ada8fa6db..37643c3c6f6f 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -108,6 +108,7 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.BackgroundThread;
import com.android.internal.os.BatteryStatsHistory;
+import com.android.internal.os.BatteryStatsHistory.HistoryStepDetailsCalculator;
import com.android.internal.os.BatteryStatsHistoryIterator;
import com.android.internal.os.BinderCallsStats;
import com.android.internal.os.BinderTransactionNameResolver;
@@ -173,7 +174,6 @@ public class BatteryStatsImpl extends BatteryStats {
private static final boolean DEBUG_ENERGY_CPU = DEBUG_ENERGY;
private static final boolean DEBUG_BINDER_STATS = false;
private static final boolean DEBUG_MEMORY = false;
- private static final boolean DEBUG_HISTORY = false;
// TODO: remove "tcp" from network methods, since we measure total stats.
@@ -322,6 +322,11 @@ public class BatteryStatsImpl extends BatteryStats {
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
protected Queue<UidToRemove> mPendingRemovedUids = new LinkedList<>();
+ @NonNull
+ BatteryStatsHistory copyHistory() {
+ return mHistory.copy();
+ }
+
@VisibleForTesting
public final class UidToRemove {
private final int mStartUid;
@@ -413,7 +418,7 @@ public class BatteryStatsImpl extends BatteryStats {
if (changed) {
final long uptimeMs = mClock.uptimeMillis();
final long elapsedRealtimeMs = mClock.elapsedRealtime();
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.writeHistoryItem(elapsedRealtimeMs, uptimeMs);
}
}
}
@@ -668,16 +673,16 @@ public class BatteryStatsImpl extends BatteryStats {
/**
* Mapping isolated uids to the actual owning app uid.
*/
- final SparseIntArray mIsolatedUids = new SparseIntArray();
+ private final SparseIntArray mIsolatedUids = new SparseIntArray();
/**
* Internal reference count of isolated uids.
*/
- final SparseIntArray mIsolatedUidRefCounts = new SparseIntArray();
+ private final SparseIntArray mIsolatedUidRefCounts = new SparseIntArray();
/**
* The statistics we have collected organized by uids.
*/
- final SparseArray<BatteryStatsImpl.Uid> mUidStats = new SparseArray<>();
+ private final SparseArray<BatteryStatsImpl.Uid> mUidStats = new SparseArray<>();
// A set of pools of currently active timers. When a timer is queried, we will divide the
// elapsed time by the number of active timers to arrive at that timer's share of the time.
@@ -685,20 +690,21 @@ public class BatteryStatsImpl extends BatteryStats {
// changes.
@VisibleForTesting
protected ArrayList<StopwatchTimer> mPartialTimers = new ArrayList<>();
- final ArrayList<StopwatchTimer> mFullTimers = new ArrayList<>();
- final ArrayList<StopwatchTimer> mWindowTimers = new ArrayList<>();
- final ArrayList<StopwatchTimer> mDrawTimers = new ArrayList<>();
- final SparseArray<ArrayList<StopwatchTimer>> mSensorTimers = new SparseArray<>();
- final ArrayList<StopwatchTimer> mWifiRunningTimers = new ArrayList<>();
- final ArrayList<StopwatchTimer> mFullWifiLockTimers = new ArrayList<>();
- final ArrayList<StopwatchTimer> mWifiMulticastTimers = new ArrayList<>();
- final ArrayList<StopwatchTimer> mWifiScanTimers = new ArrayList<>();
- final SparseArray<ArrayList<StopwatchTimer>> mWifiBatchedScanTimers = new SparseArray<>();
- final ArrayList<StopwatchTimer> mAudioTurnedOnTimers = new ArrayList<>();
- final ArrayList<StopwatchTimer> mVideoTurnedOnTimers = new ArrayList<>();
- final ArrayList<StopwatchTimer> mFlashlightTurnedOnTimers = new ArrayList<>();
- final ArrayList<StopwatchTimer> mCameraTurnedOnTimers = new ArrayList<>();
- final ArrayList<StopwatchTimer> mBluetoothScanOnTimers = new ArrayList<>();
+ private final ArrayList<StopwatchTimer> mFullTimers = new ArrayList<>();
+ private final ArrayList<StopwatchTimer> mWindowTimers = new ArrayList<>();
+ private final ArrayList<StopwatchTimer> mDrawTimers = new ArrayList<>();
+ private final SparseArray<ArrayList<StopwatchTimer>> mSensorTimers = new SparseArray<>();
+ private final ArrayList<StopwatchTimer> mWifiRunningTimers = new ArrayList<>();
+ private final ArrayList<StopwatchTimer> mFullWifiLockTimers = new ArrayList<>();
+ private final ArrayList<StopwatchTimer> mWifiMulticastTimers = new ArrayList<>();
+ private final ArrayList<StopwatchTimer> mWifiScanTimers = new ArrayList<>();
+ private final SparseArray<ArrayList<StopwatchTimer>> mWifiBatchedScanTimers =
+ new SparseArray<>();
+ private final ArrayList<StopwatchTimer> mAudioTurnedOnTimers = new ArrayList<>();
+ private final ArrayList<StopwatchTimer> mVideoTurnedOnTimers = new ArrayList<>();
+ private final ArrayList<StopwatchTimer> mFlashlightTurnedOnTimers = new ArrayList<>();
+ private final ArrayList<StopwatchTimer> mCameraTurnedOnTimers = new ArrayList<>();
+ private final ArrayList<StopwatchTimer> mBluetoothScanOnTimers = new ArrayList<>();
// Last partial timers we use for distributing CPU usage.
@VisibleForTesting
@@ -713,69 +719,24 @@ public class BatteryStatsImpl extends BatteryStats {
protected final TimeBase mOnBatteryScreenOffTimeBase = new TimeBase(true);
private boolean mSystemReady;
- boolean mShuttingDown;
-
- final HistoryEventTracker mActiveEvents = new HistoryEventTracker();
-
- long mHistoryBaseTimeMs;
- protected boolean mHaveBatteryLevel = false;
- protected boolean mRecordingHistory = false;
- int mNumHistoryItems;
-
- private static final int HISTORY_TAG_INDEX_LIMIT = 0x7ffe;
- private static final int MAX_HISTORY_TAG_STRING_LENGTH = 1024;
-
- final HashMap<HistoryTag, Integer> mHistoryTagPool = new HashMap<>();
- private SparseArray<HistoryTag> mHistoryTags;
- final Parcel mHistoryBuffer = Parcel.obtain();
- final HistoryItem mHistoryLastWritten = new HistoryItem();
- final HistoryItem mHistoryLastLastWritten = new HistoryItem();
- final HistoryItem mHistoryAddTmp = new HistoryItem();
- int mNextHistoryTagIdx = 0;
- int mNumHistoryTagChars = 0;
- int mHistoryBufferLastPos = -1;
- int mActiveHistoryStates = 0xffffffff;
- int mActiveHistoryStates2 = 0xffffffff;
- long mLastHistoryElapsedRealtimeMs = 0;
- long mTrackRunningHistoryElapsedRealtimeMs = 0;
- long mTrackRunningHistoryUptimeMs = 0;
+ private boolean mShuttingDown;
+
+ private final HistoryEventTracker mActiveEvents = new HistoryEventTracker();
+ private final HistoryStepDetailsCalculatorImpl mStepDetailsCalculator =
+ new HistoryStepDetailsCalculatorImpl();
+
+ private boolean mHaveBatteryLevel = false;
+ private boolean mBatteryPluggedIn;
+ private int mBatteryStatus;
+ private int mBatteryLevel;
+ private int mBatteryPlugType;
+ private int mBatteryChargeUah;
+ private int mBatteryHealth;
+ private int mBatteryTemperature;
+ private int mBatteryVoltageMv = -1;
@NonNull
- final BatteryStatsHistory mBatteryStatsHistory;
-
- final HistoryItem mHistoryCur = new HistoryItem();
-
- // Used by computeHistoryStepDetails
- HistoryStepDetails mLastHistoryStepDetails = null;
- byte mLastHistoryStepLevel = 0;
- final HistoryStepDetails mCurHistoryStepDetails = new HistoryStepDetails();
- final HistoryStepDetails mTmpHistoryStepDetails = new HistoryStepDetails();
-
- /**
- * Total time (in milliseconds) spent executing in user code.
- */
- long mLastStepCpuUserTimeMs;
- long mCurStepCpuUserTimeMs;
- /**
- * Total time (in milliseconds) spent executing in kernel code.
- */
- long mLastStepCpuSystemTimeMs;
- long mCurStepCpuSystemTimeMs;
- /**
- * Times from /proc/stat (but measured in milliseconds).
- */
- long mLastStepStatUserTimeMs;
- long mLastStepStatSystemTimeMs;
- long mLastStepStatIOWaitTimeMs;
- long mLastStepStatIrqTimeMs;
- long mLastStepStatSoftIrqTimeMs;
- long mLastStepStatIdleTimeMs;
- long mCurStepStatUserTimeMs;
- long mCurStepStatSystemTimeMs;
- long mCurStepStatIOWaitTimeMs;
- long mCurStepStatIrqTimeMs;
- long mCurStepStatSoftIrqTimeMs;
- long mCurStepStatIdleTimeMs;
+ private final BatteryStatsHistory mHistory;
private BatteryStatsHistoryIterator mBatteryStatsHistoryIterator;
@@ -1391,7 +1352,6 @@ public class BatteryStatsImpl extends BatteryStats {
int mDischargeUnplugLevel;
int mDischargePlugLevel;
int mDischargeCurrentLevel;
- int mCurrentBatteryLevel;
int mLowDischargeAmountSinceCharge;
int mHighDischargeAmountSinceCharge;
int mDischargeScreenOnUnplugLevel;
@@ -1443,7 +1403,6 @@ public class BatteryStatsImpl extends BatteryStats {
private int mNumConnectivityChange;
- private int mBatteryVoltageMv = -1;
private int mEstimatedBatteryCapacityMah = -1;
private int mLastLearnedBatteryCapacityUah = -1;
@@ -1627,28 +1586,27 @@ public class BatteryStatsImpl extends BatteryStats {
}
public BatteryStatsImpl(Clock clock) {
- this(clock, (File) null);
+ this(clock, null);
}
public BatteryStatsImpl(Clock clock, File historyDirectory) {
init(clock);
+ mHandler = null;
+ mConstants = new Constants(mHandler);
mStartClockTimeMs = clock.currentTimeMillis();
mCheckinFile = null;
mDailyFile = null;
if (historyDirectory == null) {
mStatsFile = null;
- mBatteryStatsHistory = new BatteryStatsHistory(mHistoryBuffer);
+ mHistory = new BatteryStatsHistory(mStepDetailsCalculator, mClock);
} else {
mStatsFile = new AtomicFile(new File(historyDirectory, "batterystats.bin"));
- mBatteryStatsHistory = new BatteryStatsHistory(mHistoryBuffer, historyDirectory,
- this::getMaxHistoryFiles);
+ mHistory = new BatteryStatsHistory(historyDirectory, mConstants.MAX_HISTORY_FILES,
+ mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock);
}
- mHandler = null;
mPlatformIdleStateCallback = null;
mMeasuredEnergyRetriever = null;
mUserInfoProvider = null;
- mConstants = new Constants(mHandler);
- clearHistoryLocked();
}
private void init(Clock clock) {
@@ -3911,406 +3869,188 @@ public class BatteryStatsImpl extends BatteryStats {
return kmt;
}
- /**
- * Returns the index for the specified tag. If this is the first time the tag is encountered
- * while writing the current history buffer, the method returns
- * <code>(index | TAG_FIRST_OCCURRENCE_FLAG)</code>
- */
- private int writeHistoryTag(HistoryTag tag) {
- if (tag.string == null) {
- Slog.wtfStack(TAG, "writeHistoryTag called with null name");
- }
-
- final int stringLength = tag.string.length();
- if (stringLength > MAX_HISTORY_TAG_STRING_LENGTH) {
- Slog.e(TAG, "Long battery history tag: " + tag.string);
- tag.string = tag.string.substring(0, MAX_HISTORY_TAG_STRING_LENGTH);
- }
-
- Integer idxObj = mHistoryTagPool.get(tag);
- int idx;
- if (idxObj != null) {
- idx = idxObj;
- if ((idx & BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG) != 0) {
- mHistoryTagPool.put(tag, idx & ~BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG);
- }
- return idx;
- } else if (mNextHistoryTagIdx < HISTORY_TAG_INDEX_LIMIT) {
- idx = mNextHistoryTagIdx;
- HistoryTag key = new HistoryTag();
- key.setTo(tag);
- tag.poolIdx = idx;
- mHistoryTagPool.put(key, idx);
- mNextHistoryTagIdx++;
-
- mNumHistoryTagChars += stringLength + 1;
- if (mHistoryTags != null) {
- mHistoryTags.put(idx, key);
- }
- return idx | BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG;
- } else {
- // Tag pool overflow: include the tag itself in the parcel
- return HISTORY_TAG_INDEX_LIMIT | BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG;
- }
- }
-
- /*
- The history delta format uses flags to denote further data in subsequent ints in the parcel.
-
- There is always the first token, which may contain the delta time, or an indicator of
- the length of the time (int or long) following this token.
-
- First token: always present,
- 31 23 15 7 0
- █M|L|K|J|I|H|G|F█E|D|C|B|A|T|T|T█T|T|T|T|T|T|T|T█T|T|T|T|T|T|T|T█
-
- T: the delta time if it is <= 0x7fffd. Otherwise 0x7fffe indicates an int immediately
- follows containing the time, and 0x7ffff indicates a long immediately follows with the
- delta time.
- A: battery level changed and an int follows with battery data.
- B: state changed and an int follows with state change data.
- C: state2 has changed and an int follows with state2 change data.
- D: wakelock/wakereason has changed and an wakelock/wakereason struct follows.
- E: event data has changed and an event struct follows.
- F: battery charge in coulombs has changed and an int with the charge follows.
- G: state flag denoting that the mobile radio was active.
- H: state flag denoting that the wifi radio was active.
- I: state flag denoting that a wifi scan occurred.
- J: state flag denoting that a wifi full lock was held.
- K: state flag denoting that the gps was on.
- L: state flag denoting that a wakelock was held.
- M: state flag denoting that the cpu was running.
-
- Time int/long: if T in the first token is 0x7ffff or 0x7fffe, then an int or long follows
- with the time delta.
-
- Battery level int: if A in the first token is set,
- 31 23 15 7 0
- █L|L|L|L|L|L|L|T█T|T|T|T|T|T|T|T█T|V|V|V|V|V|V|V█V|V|V|V|V|V|V|D█
-
- D: indicates that extra history details follow.
- V: the battery voltage.
- T: the battery temperature.
- L: the battery level (out of 100).
-
- State change int: if B in the first token is set,
- 31 23 15 7 0
- █S|S|S|H|H|H|P|P█F|E|D|C|B| | |A█ | | | | | | | █ | | | | | | | █
-
- A: wifi multicast was on.
- B: battery was plugged in.
- C: screen was on.
- D: phone was scanning for signal.
- E: audio was on.
- F: a sensor was active.
-
- State2 change int: if C in the first token is set,
- 31 23 15 7 0
- █M|L|K|J|I|H|H|G█F|E|D|C| | | | █ | | | | | | | █ |B|B|B|A|A|A|A█
-
- A: 4 bits indicating the wifi supplicant state: {@link BatteryStats#WIFI_SUPPL_STATE_NAMES}.
- B: 3 bits indicating the wifi signal strength: 0, 1, 2, 3, 4.
- C: a bluetooth scan was active.
- D: the camera was active.
- E: bluetooth was on.
- F: a phone call was active.
- G: the device was charging.
- H: 2 bits indicating the device-idle (doze) state: off, light, full
- I: the flashlight was on.
- J: wifi was on.
- K: wifi was running.
- L: video was playing.
- M: power save mode was on.
-
- Wakelock/wakereason struct: if D in the first token is set,
- TODO(adamlesinski): describe wakelock/wakereason struct.
-
- Event struct: if E in the first token is set,
- TODO(adamlesinski): describe the event struct.
-
- History step details struct: if D in the battery level int is set,
- TODO(adamlesinski): describe the history step details struct.
-
- Battery charge int: if F in the first token is set, an int representing the battery charge
- in coulombs follows.
- */
+ private class HistoryStepDetailsCalculatorImpl implements HistoryStepDetailsCalculator {
+ private final HistoryStepDetails mDetails = new HistoryStepDetails();
- @GuardedBy("this")
- public void writeHistoryDelta(Parcel dest, HistoryItem cur, HistoryItem last) {
- if (last == null || cur.cmd != HistoryItem.CMD_UPDATE) {
- dest.writeInt(BatteryStatsHistory.DELTA_TIME_ABS);
- cur.writeToParcel(dest, 0);
- return;
- }
+ private boolean mHasHistoryStepDetails;
- final long deltaTime = cur.time - last.time;
- final int lastBatteryLevelInt = buildBatteryLevelInt(last);
- final int lastStateInt = buildStateInt(last);
+ private int mLastHistoryStepLevel;
- int deltaTimeToken;
- if (deltaTime < 0 || deltaTime > Integer.MAX_VALUE) {
- deltaTimeToken = BatteryStatsHistory.DELTA_TIME_LONG;
- } else if (deltaTime >= BatteryStatsHistory.DELTA_TIME_ABS) {
- deltaTimeToken = BatteryStatsHistory.DELTA_TIME_INT;
- } else {
- deltaTimeToken = (int)deltaTime;
- }
- int firstToken = deltaTimeToken | (cur.states & BatteryStatsHistory.DELTA_STATE_MASK);
- final int includeStepDetails = mLastHistoryStepLevel > cur.batteryLevel
- ? BatteryStatsHistory.BATTERY_DELTA_LEVEL_FLAG : 0;
- final boolean computeStepDetails = includeStepDetails != 0
- || mLastHistoryStepDetails == null;
- final int batteryLevelInt = buildBatteryLevelInt(cur) | includeStepDetails;
- final boolean batteryLevelIntChanged = batteryLevelInt != lastBatteryLevelInt;
- if (batteryLevelIntChanged) {
- firstToken |= BatteryStatsHistory.DELTA_BATTERY_LEVEL_FLAG;
- }
- final int stateInt = buildStateInt(cur);
- final boolean stateIntChanged = stateInt != lastStateInt;
- if (stateIntChanged) {
- firstToken |= BatteryStatsHistory.DELTA_STATE_FLAG;
- }
- final boolean state2IntChanged = cur.states2 != last.states2;
- if (state2IntChanged) {
- firstToken |= BatteryStatsHistory.DELTA_STATE2_FLAG;
- }
- if (cur.wakelockTag != null || cur.wakeReasonTag != null) {
- firstToken |= BatteryStatsHistory.DELTA_WAKELOCK_FLAG;
- }
- if (cur.eventCode != HistoryItem.EVENT_NONE) {
- firstToken |= BatteryStatsHistory.DELTA_EVENT_FLAG;
- }
-
- final boolean batteryChargeChanged = cur.batteryChargeUah != last.batteryChargeUah;
- if (batteryChargeChanged) {
- firstToken |= BatteryStatsHistory.DELTA_BATTERY_CHARGE_FLAG;
- }
- dest.writeInt(firstToken);
- if (DEBUG) Slog.i(TAG, "WRITE DELTA: firstToken=0x" + Integer.toHexString(firstToken)
- + " deltaTime=" + deltaTime);
-
- if (deltaTimeToken >= BatteryStatsHistory.DELTA_TIME_INT) {
- if (deltaTimeToken == BatteryStatsHistory.DELTA_TIME_INT) {
- if (DEBUG) Slog.i(TAG, "WRITE DELTA: int deltaTime=" + (int)deltaTime);
- dest.writeInt((int)deltaTime);
- } else {
- if (DEBUG) Slog.i(TAG, "WRITE DELTA: long deltaTime=" + deltaTime);
- dest.writeLong(deltaTime);
- }
- }
- if (batteryLevelIntChanged) {
- dest.writeInt(batteryLevelInt);
- if (DEBUG) Slog.i(TAG, "WRITE DELTA: batteryToken=0x"
- + Integer.toHexString(batteryLevelInt)
- + " batteryLevel=" + cur.batteryLevel
- + " batteryTemp=" + cur.batteryTemperature
- + " batteryVolt=" + (int)cur.batteryVoltage);
- }
- if (stateIntChanged) {
- dest.writeInt(stateInt);
- if (DEBUG) Slog.i(TAG, "WRITE DELTA: stateToken=0x"
- + Integer.toHexString(stateInt)
- + " batteryStatus=" + cur.batteryStatus
- + " batteryHealth=" + cur.batteryHealth
- + " batteryPlugType=" + cur.batteryPlugType
- + " states=0x" + Integer.toHexString(cur.states));
- }
- if (state2IntChanged) {
- dest.writeInt(cur.states2);
- if (DEBUG) Slog.i(TAG, "WRITE DELTA: states2=0x"
- + Integer.toHexString(cur.states2));
- }
- if (cur.wakelockTag != null || cur.wakeReasonTag != null) {
- int wakeLockIndex;
- int wakeReasonIndex;
- if (cur.wakelockTag != null) {
- wakeLockIndex = writeHistoryTag(cur.wakelockTag);
- if (DEBUG) Slog.i(TAG, "WRITE DELTA: wakelockTag=#" + cur.wakelockTag.poolIdx
- + " " + cur.wakelockTag.uid + ":" + cur.wakelockTag.string);
- } else {
- wakeLockIndex = 0xffff;
- }
- if (cur.wakeReasonTag != null) {
- wakeReasonIndex = writeHistoryTag(cur.wakeReasonTag);
- if (DEBUG) Slog.i(TAG, "WRITE DELTA: wakeReasonTag=#" + cur.wakeReasonTag.poolIdx
- + " " + cur.wakeReasonTag.uid + ":" + cur.wakeReasonTag.string);
- } else {
- wakeReasonIndex = 0xffff;
- }
- dest.writeInt((wakeReasonIndex<<16) | wakeLockIndex);
- if (cur.wakelockTag != null
- && (wakeLockIndex & BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG) != 0) {
- cur.wakelockTag.writeToParcel(dest, 0);
- cur.tagsFirstOccurrence = true;
- }
- if (cur.wakeReasonTag != null
- && (wakeReasonIndex & BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG) != 0) {
- cur.wakeReasonTag.writeToParcel(dest, 0);
- cur.tagsFirstOccurrence = true;
- }
- }
- if (cur.eventCode != HistoryItem.EVENT_NONE) {
- final int index = writeHistoryTag(cur.eventTag);
- final int codeAndIndex = (cur.eventCode & 0xffff) | (index << 16);
- dest.writeInt(codeAndIndex);
- if ((index & BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG) != 0) {
- cur.eventTag.writeToParcel(dest, 0);
- cur.tagsFirstOccurrence = true;
+ /**
+ * Total time (in milliseconds) spent executing in user code.
+ */
+ private long mLastStepCpuUserTimeMs;
+ private long mCurStepCpuUserTimeMs;
+ /**
+ * Total time (in milliseconds) spent executing in kernel code.
+ */
+ private long mLastStepCpuSystemTimeMs;
+ private long mCurStepCpuSystemTimeMs;
+ /**
+ * Times from /proc/stat (but measured in milliseconds).
+ */
+ private long mLastStepStatUserTimeMs;
+ private long mLastStepStatSystemTimeMs;
+ private long mLastStepStatIOWaitTimeMs;
+ private long mLastStepStatIrqTimeMs;
+ private long mLastStepStatSoftIrqTimeMs;
+ private long mLastStepStatIdleTimeMs;
+ private long mCurStepStatUserTimeMs;
+ private long mCurStepStatSystemTimeMs;
+ private long mCurStepStatIOWaitTimeMs;
+ private long mCurStepStatIrqTimeMs;
+ private long mCurStepStatSoftIrqTimeMs;
+ private long mCurStepStatIdleTimeMs;
+
+ @Override
+ public HistoryStepDetails getHistoryStepDetails() {
+ if (mBatteryLevel >= mLastHistoryStepLevel && mHasHistoryStepDetails) {
+ mLastHistoryStepLevel = mBatteryLevel;
+ return null;
}
- if (DEBUG) Slog.i(TAG, "WRITE DELTA: event=" + cur.eventCode + " tag=#"
- + cur.eventTag.poolIdx + " " + cur.eventTag.uid + ":"
- + cur.eventTag.string);
- }
- if (computeStepDetails) {
+
+ // Perform a CPU update right after we do this collection, so we have started
+ // collecting good data for the next step.
+ requestImmediateCpuUpdate();
+
if (mPlatformIdleStateCallback != null) {
- mCurHistoryStepDetails.statSubsystemPowerState =
+ mDetails.statSubsystemPowerState =
mPlatformIdleStateCallback.getSubsystemLowPowerStats();
if (DEBUG) Slog.i(TAG, "WRITE SubsystemPowerState:" +
- mCurHistoryStepDetails.statSubsystemPowerState);
-
- }
- computeHistoryStepDetails(mCurHistoryStepDetails, mLastHistoryStepDetails);
- if (includeStepDetails != 0) {
- mCurHistoryStepDetails.writeToParcel(dest);
- }
- cur.stepDetails = mCurHistoryStepDetails;
- mLastHistoryStepDetails = mCurHistoryStepDetails;
- } else {
- cur.stepDetails = null;
- }
- if (mLastHistoryStepLevel < cur.batteryLevel) {
- mLastHistoryStepDetails = null;
- }
- mLastHistoryStepLevel = cur.batteryLevel;
-
- if (batteryChargeChanged) {
- if (DEBUG) Slog.i(TAG, "WRITE DELTA: batteryChargeUah=" + cur.batteryChargeUah);
- dest.writeInt(cur.batteryChargeUah);
- }
- dest.writeDouble(cur.modemRailChargeMah);
- dest.writeDouble(cur.wifiRailChargeMah);
- }
-
- private int buildBatteryLevelInt(HistoryItem h) {
- return ((((int)h.batteryLevel)<<25)&0xfe000000)
- | ((((int)h.batteryTemperature)<<15)&0x01ff8000)
- | ((((int)h.batteryVoltage)<<1)&0x00007ffe);
- }
-
- private int buildStateInt(HistoryItem h) {
- int plugType = 0;
- if ((h.batteryPlugType&BatteryManager.BATTERY_PLUGGED_AC) != 0) {
- plugType = 1;
- } else if ((h.batteryPlugType&BatteryManager.BATTERY_PLUGGED_USB) != 0) {
- plugType = 2;
- } else if ((h.batteryPlugType&BatteryManager.BATTERY_PLUGGED_WIRELESS) != 0) {
- plugType = 3;
- }
- return ((h.batteryStatus & BatteryStatsHistory.STATE_BATTERY_STATUS_MASK)
- << BatteryStatsHistory.STATE_BATTERY_STATUS_SHIFT)
- | ((h.batteryHealth & BatteryStatsHistory.STATE_BATTERY_HEALTH_MASK)
- << BatteryStatsHistory.STATE_BATTERY_HEALTH_SHIFT)
- | ((plugType & BatteryStatsHistory.STATE_BATTERY_PLUG_MASK)
- << BatteryStatsHistory.STATE_BATTERY_PLUG_SHIFT)
- | (h.states & (~BatteryStatsHistory.STATE_BATTERY_MASK));
- }
-
- private void computeHistoryStepDetails(final HistoryStepDetails out,
- final HistoryStepDetails last) {
- final HistoryStepDetails tmp = last != null ? mTmpHistoryStepDetails : out;
-
- // Perform a CPU update right after we do this collection, so we have started
- // collecting good data for the next step.
- requestImmediateCpuUpdate();
-
- if (last == null) {
- // We are not generating a delta, so all we need to do is reset the stats
- // we will later be doing a delta from.
- final int NU = mUidStats.size();
- for (int i=0; i<NU; i++) {
- final BatteryStatsImpl.Uid uid = mUidStats.valueAt(i);
- uid.mLastStepUserTimeMs = uid.mCurStepUserTimeMs;
- uid.mLastStepSystemTimeMs = uid.mCurStepSystemTimeMs;
- }
- mLastStepCpuUserTimeMs = mCurStepCpuUserTimeMs;
- mLastStepCpuSystemTimeMs = mCurStepCpuSystemTimeMs;
- mLastStepStatUserTimeMs = mCurStepStatUserTimeMs;
- mLastStepStatSystemTimeMs = mCurStepStatSystemTimeMs;
- mLastStepStatIOWaitTimeMs = mCurStepStatIOWaitTimeMs;
- mLastStepStatIrqTimeMs = mCurStepStatIrqTimeMs;
- mLastStepStatSoftIrqTimeMs = mCurStepStatSoftIrqTimeMs;
- mLastStepStatIdleTimeMs = mCurStepStatIdleTimeMs;
- tmp.clear();
- return;
- }
- if (DEBUG) {
- Slog.d(TAG, "Step stats last: user=" + mLastStepCpuUserTimeMs + " sys="
- + mLastStepStatSystemTimeMs + " io=" + mLastStepStatIOWaitTimeMs
- + " irq=" + mLastStepStatIrqTimeMs + " sirq="
- + mLastStepStatSoftIrqTimeMs + " idle=" + mLastStepStatIdleTimeMs);
- Slog.d(TAG, "Step stats cur: user=" + mCurStepCpuUserTimeMs + " sys="
- + mCurStepStatSystemTimeMs + " io=" + mCurStepStatIOWaitTimeMs
- + " irq=" + mCurStepStatIrqTimeMs + " sirq="
- + mCurStepStatSoftIrqTimeMs + " idle=" + mCurStepStatIdleTimeMs);
- }
- out.userTime = (int) (mCurStepCpuUserTimeMs - mLastStepCpuUserTimeMs);
- out.systemTime = (int) (mCurStepCpuSystemTimeMs - mLastStepCpuSystemTimeMs);
- out.statUserTime = (int) (mCurStepStatUserTimeMs - mLastStepStatUserTimeMs);
- out.statSystemTime = (int) (mCurStepStatSystemTimeMs - mLastStepStatSystemTimeMs);
- out.statIOWaitTime = (int) (mCurStepStatIOWaitTimeMs - mLastStepStatIOWaitTimeMs);
- out.statIrqTime = (int) (mCurStepStatIrqTimeMs - mLastStepStatIrqTimeMs);
- out.statSoftIrqTime = (int) (mCurStepStatSoftIrqTimeMs - mLastStepStatSoftIrqTimeMs);
- out.statIdlTime = (int) (mCurStepStatIdleTimeMs - mLastStepStatIdleTimeMs);
- out.appCpuUid1 = out.appCpuUid2 = out.appCpuUid3 = -1;
- out.appCpuUTime1 = out.appCpuUTime2 = out.appCpuUTime3 = 0;
- out.appCpuSTime1 = out.appCpuSTime2 = out.appCpuSTime3 = 0;
- final int NU = mUidStats.size();
- for (int i=0; i<NU; i++) {
- final BatteryStatsImpl.Uid uid = mUidStats.valueAt(i);
- final int totalUTimeMs = (int) (uid.mCurStepUserTimeMs - uid.mLastStepUserTimeMs);
- final int totalSTimeMs = (int) (uid.mCurStepSystemTimeMs - uid.mLastStepSystemTimeMs);
- final int totalTimeMs = totalUTimeMs + totalSTimeMs;
- uid.mLastStepUserTimeMs = uid.mCurStepUserTimeMs;
- uid.mLastStepSystemTimeMs = uid.mCurStepSystemTimeMs;
- if (totalTimeMs <= (out.appCpuUTime3 + out.appCpuSTime3)) {
- continue;
- }
- if (totalTimeMs <= (out.appCpuUTime2 + out.appCpuSTime2)) {
- out.appCpuUid3 = uid.mUid;
- out.appCpuUTime3 = totalUTimeMs;
- out.appCpuSTime3 = totalSTimeMs;
+ mDetails.statSubsystemPowerState);
+ }
+
+ if (!mHasHistoryStepDetails) {
+ // We are not generating a delta, so all we need to do is reset the stats
+ // we will later be doing a delta from.
+ final int uidCount = mUidStats.size();
+ for (int i = 0; i < uidCount; i++) {
+ final BatteryStatsImpl.Uid uid = mUidStats.valueAt(i);
+ uid.mLastStepUserTimeMs = uid.mCurStepUserTimeMs;
+ uid.mLastStepSystemTimeMs = uid.mCurStepSystemTimeMs;
+ }
+ mLastStepCpuUserTimeMs = mCurStepCpuUserTimeMs;
+ mLastStepCpuSystemTimeMs = mCurStepCpuSystemTimeMs;
+ mLastStepStatUserTimeMs = mCurStepStatUserTimeMs;
+ mLastStepStatSystemTimeMs = mCurStepStatSystemTimeMs;
+ mLastStepStatIOWaitTimeMs = mCurStepStatIOWaitTimeMs;
+ mLastStepStatIrqTimeMs = mCurStepStatIrqTimeMs;
+ mLastStepStatSoftIrqTimeMs = mCurStepStatSoftIrqTimeMs;
+ mLastStepStatIdleTimeMs = mCurStepStatIdleTimeMs;
+ mDetails.clear();
} else {
- out.appCpuUid3 = out.appCpuUid2;
- out.appCpuUTime3 = out.appCpuUTime2;
- out.appCpuSTime3 = out.appCpuSTime2;
- if (totalTimeMs <= (out.appCpuUTime1 + out.appCpuSTime1)) {
- out.appCpuUid2 = uid.mUid;
- out.appCpuUTime2 = totalUTimeMs;
- out.appCpuSTime2 = totalSTimeMs;
- } else {
- out.appCpuUid2 = out.appCpuUid1;
- out.appCpuUTime2 = out.appCpuUTime1;
- out.appCpuSTime2 = out.appCpuSTime1;
- out.appCpuUid1 = uid.mUid;
- out.appCpuUTime1 = totalUTimeMs;
- out.appCpuSTime1 = totalSTimeMs;
+ if (DEBUG) {
+ Slog.d(TAG, "Step stats last: user=" + mLastStepCpuUserTimeMs + " sys="
+ + mLastStepStatSystemTimeMs + " io=" + mLastStepStatIOWaitTimeMs
+ + " irq=" + mLastStepStatIrqTimeMs + " sirq="
+ + mLastStepStatSoftIrqTimeMs + " idle=" + mLastStepStatIdleTimeMs);
+ Slog.d(TAG, "Step stats cur: user=" + mCurStepCpuUserTimeMs + " sys="
+ + mCurStepStatSystemTimeMs + " io=" + mCurStepStatIOWaitTimeMs
+ + " irq=" + mCurStepStatIrqTimeMs + " sirq="
+ + mCurStepStatSoftIrqTimeMs + " idle=" + mCurStepStatIdleTimeMs);
+ }
+ mDetails.userTime = (int) (mCurStepCpuUserTimeMs - mLastStepCpuUserTimeMs);
+ mDetails.systemTime = (int) (mCurStepCpuSystemTimeMs - mLastStepCpuSystemTimeMs);
+ mDetails.statUserTime = (int) (mCurStepStatUserTimeMs - mLastStepStatUserTimeMs);
+ mDetails.statSystemTime =
+ (int) (mCurStepStatSystemTimeMs - mLastStepStatSystemTimeMs);
+ mDetails.statIOWaitTime =
+ (int) (mCurStepStatIOWaitTimeMs - mLastStepStatIOWaitTimeMs);
+ mDetails.statIrqTime = (int) (mCurStepStatIrqTimeMs - mLastStepStatIrqTimeMs);
+ mDetails.statSoftIrqTime =
+ (int) (mCurStepStatSoftIrqTimeMs - mLastStepStatSoftIrqTimeMs);
+ mDetails.statIdlTime = (int) (mCurStepStatIdleTimeMs - mLastStepStatIdleTimeMs);
+ mDetails.appCpuUid1 = mDetails.appCpuUid2 = mDetails.appCpuUid3 = -1;
+ mDetails.appCpuUTime1 = mDetails.appCpuUTime2 = mDetails.appCpuUTime3 = 0;
+ mDetails.appCpuSTime1 = mDetails.appCpuSTime2 = mDetails.appCpuSTime3 = 0;
+ final int uidCount = mUidStats.size();
+ for (int i = 0; i < uidCount; i++) {
+ final BatteryStatsImpl.Uid uid = mUidStats.valueAt(i);
+ final int totalUTimeMs =
+ (int) (uid.mCurStepUserTimeMs - uid.mLastStepUserTimeMs);
+ final int totalSTimeMs =
+ (int) (uid.mCurStepSystemTimeMs - uid.mLastStepSystemTimeMs);
+ final int totalTimeMs = totalUTimeMs + totalSTimeMs;
+ uid.mLastStepUserTimeMs = uid.mCurStepUserTimeMs;
+ uid.mLastStepSystemTimeMs = uid.mCurStepSystemTimeMs;
+ if (totalTimeMs <= (mDetails.appCpuUTime3 + mDetails.appCpuSTime3)) {
+ continue;
+ }
+ if (totalTimeMs <= (mDetails.appCpuUTime2 + mDetails.appCpuSTime2)) {
+ mDetails.appCpuUid3 = uid.mUid;
+ mDetails.appCpuUTime3 = totalUTimeMs;
+ mDetails.appCpuSTime3 = totalSTimeMs;
+ } else {
+ mDetails.appCpuUid3 = mDetails.appCpuUid2;
+ mDetails.appCpuUTime3 = mDetails.appCpuUTime2;
+ mDetails.appCpuSTime3 = mDetails.appCpuSTime2;
+ if (totalTimeMs <= (mDetails.appCpuUTime1 + mDetails.appCpuSTime1)) {
+ mDetails.appCpuUid2 = uid.mUid;
+ mDetails.appCpuUTime2 = totalUTimeMs;
+ mDetails.appCpuSTime2 = totalSTimeMs;
+ } else {
+ mDetails.appCpuUid2 = mDetails.appCpuUid1;
+ mDetails.appCpuUTime2 = mDetails.appCpuUTime1;
+ mDetails.appCpuSTime2 = mDetails.appCpuSTime1;
+ mDetails.appCpuUid1 = uid.mUid;
+ mDetails.appCpuUTime1 = totalUTimeMs;
+ mDetails.appCpuSTime1 = totalSTimeMs;
+ }
+ }
}
+ mLastStepCpuUserTimeMs = mCurStepCpuUserTimeMs;
+ mLastStepCpuSystemTimeMs = mCurStepCpuSystemTimeMs;
+ mLastStepStatUserTimeMs = mCurStepStatUserTimeMs;
+ mLastStepStatSystemTimeMs = mCurStepStatSystemTimeMs;
+ mLastStepStatIOWaitTimeMs = mCurStepStatIOWaitTimeMs;
+ mLastStepStatIrqTimeMs = mCurStepStatIrqTimeMs;
+ mLastStepStatSoftIrqTimeMs = mCurStepStatSoftIrqTimeMs;
+ mLastStepStatIdleTimeMs = mCurStepStatIdleTimeMs;
+ }
+
+ mHasHistoryStepDetails = mBatteryLevel <= mLastHistoryStepLevel;
+ mLastHistoryStepLevel = mBatteryLevel;
+
+ return mDetails;
+ }
+
+ public void addCpuStats(int totalUTimeMs, int totalSTimeMs, int statUserTimeMs,
+ int statSystemTimeMs, int statIOWaitTimeMs, int statIrqTimeMs,
+ int statSoftIrqTimeMs, int statIdleTimeMs) {
+ if (DEBUG) {
+ Slog.d(TAG, "Adding cpu: tuser=" + totalUTimeMs + " tsys=" + totalSTimeMs
+ + " user=" + statUserTimeMs + " sys=" + statSystemTimeMs
+ + " io=" + statIOWaitTimeMs + " irq=" + statIrqTimeMs
+ + " sirq=" + statSoftIrqTimeMs + " idle=" + statIdleTimeMs);
}
+ mCurStepCpuUserTimeMs += totalUTimeMs;
+ mCurStepCpuSystemTimeMs += totalSTimeMs;
+ mCurStepStatUserTimeMs += statUserTimeMs;
+ mCurStepStatSystemTimeMs += statSystemTimeMs;
+ mCurStepStatIOWaitTimeMs += statIOWaitTimeMs;
+ mCurStepStatIrqTimeMs += statIrqTimeMs;
+ mCurStepStatSoftIrqTimeMs += statSoftIrqTimeMs;
+ mCurStepStatIdleTimeMs += statIdleTimeMs;
+ }
+
+ @Override
+ public void clear() {
+ mHasHistoryStepDetails = false;
+ mLastStepCpuUserTimeMs = mCurStepCpuUserTimeMs = 0;
+ mLastStepCpuSystemTimeMs = mCurStepCpuSystemTimeMs = 0;
+ mLastStepStatUserTimeMs = mCurStepStatUserTimeMs = 0;
+ mLastStepStatSystemTimeMs = mCurStepStatSystemTimeMs = 0;
+ mLastStepStatIOWaitTimeMs = mCurStepStatIOWaitTimeMs = 0;
+ mLastStepStatIrqTimeMs = mCurStepStatIrqTimeMs = 0;
+ mLastStepStatSoftIrqTimeMs = mCurStepStatSoftIrqTimeMs = 0;
+ mLastStepStatIdleTimeMs = mCurStepStatIdleTimeMs = 0;
}
- mLastStepCpuUserTimeMs = mCurStepCpuUserTimeMs;
- mLastStepCpuSystemTimeMs = mCurStepCpuSystemTimeMs;
- mLastStepStatUserTimeMs = mCurStepStatUserTimeMs;
- mLastStepStatSystemTimeMs = mCurStepStatSystemTimeMs;
- mLastStepStatIOWaitTimeMs = mCurStepStatIOWaitTimeMs;
- mLastStepStatIrqTimeMs = mCurStepStatIrqTimeMs;
- mLastStepStatSoftIrqTimeMs = mCurStepStatSoftIrqTimeMs;
- mLastStepStatIdleTimeMs = mCurStepStatIdleTimeMs;
}
@GuardedBy("this")
@Override
public void commitCurrentHistoryBatchLocked() {
- mHistoryLastWritten.cmd = HistoryItem.CMD_NULL;
+ mHistory.commitCurrentHistoryBatchLocked();
}
@GuardedBy("this")
@@ -4326,191 +4066,9 @@ public class BatteryStatsImpl extends BatteryStats {
}
@GuardedBy("this")
- void addHistoryBufferLocked(long elapsedRealtimeMs, long uptimeMs, HistoryItem cur) {
- if (!mHaveBatteryLevel || !mRecordingHistory) {
- return;
- }
-
- final long timeDiffMs = (mHistoryBaseTimeMs + elapsedRealtimeMs) - mHistoryLastWritten.time;
- final int diffStates = mHistoryLastWritten.states^(cur.states&mActiveHistoryStates);
- final int diffStates2 = mHistoryLastWritten.states2^(cur.states2&mActiveHistoryStates2);
- final int lastDiffStates = mHistoryLastWritten.states^mHistoryLastLastWritten.states;
- final int lastDiffStates2 = mHistoryLastWritten.states2^mHistoryLastLastWritten.states2;
- if (DEBUG) {
- Slog.i(TAG, "ADD: tdelta=" + timeDiffMs + " diff="
- + Integer.toHexString(diffStates) + " lastDiff="
- + Integer.toHexString(lastDiffStates) + " diff2="
- + Integer.toHexString(diffStates2) + " lastDiff2="
- + Integer.toHexString(lastDiffStates2));
- }
- if (mHistoryBufferLastPos >= 0 && mHistoryLastWritten.cmd == HistoryItem.CMD_UPDATE
- && timeDiffMs < 1000 && (diffStates & lastDiffStates) == 0
- && (diffStates2&lastDiffStates2) == 0
- && (!mHistoryLastWritten.tagsFirstOccurrence && !cur.tagsFirstOccurrence)
- && (mHistoryLastWritten.wakelockTag == null || cur.wakelockTag == null)
- && (mHistoryLastWritten.wakeReasonTag == null || cur.wakeReasonTag == null)
- && mHistoryLastWritten.stepDetails == null
- && (mHistoryLastWritten.eventCode == HistoryItem.EVENT_NONE
- || cur.eventCode == HistoryItem.EVENT_NONE)
- && mHistoryLastWritten.batteryLevel == cur.batteryLevel
- && mHistoryLastWritten.batteryStatus == cur.batteryStatus
- && mHistoryLastWritten.batteryHealth == cur.batteryHealth
- && mHistoryLastWritten.batteryPlugType == cur.batteryPlugType
- && mHistoryLastWritten.batteryTemperature == cur.batteryTemperature
- && mHistoryLastWritten.batteryVoltage == cur.batteryVoltage) {
- // We can merge this new change in with the last one. Merging is
- // allowed as long as only the states have changed, and within those states
- // as long as no bit has changed both between now and the last entry, as
- // well as the last entry and the one before it (so we capture any toggles).
- if (DEBUG) Slog.i(TAG, "ADD: rewinding back to " + mHistoryBufferLastPos);
- mHistoryBuffer.setDataSize(mHistoryBufferLastPos);
- mHistoryBuffer.setDataPosition(mHistoryBufferLastPos);
- mHistoryBufferLastPos = -1;
- elapsedRealtimeMs = mHistoryLastWritten.time - mHistoryBaseTimeMs;
- // If the last written history had a wakelock tag, we need to retain it.
- // Note that the condition above made sure that we aren't in a case where
- // both it and the current history item have a wakelock tag.
- if (mHistoryLastWritten.wakelockTag != null) {
- cur.wakelockTag = cur.localWakelockTag;
- cur.wakelockTag.setTo(mHistoryLastWritten.wakelockTag);
- }
- // If the last written history had a wake reason tag, we need to retain it.
- // Note that the condition above made sure that we aren't in a case where
- // both it and the current history item have a wakelock tag.
- if (mHistoryLastWritten.wakeReasonTag != null) {
- cur.wakeReasonTag = cur.localWakeReasonTag;
- cur.wakeReasonTag.setTo(mHistoryLastWritten.wakeReasonTag);
- }
- // If the last written history had an event, we need to retain it.
- // Note that the condition above made sure that we aren't in a case where
- // both it and the current history item have an event.
- if (mHistoryLastWritten.eventCode != HistoryItem.EVENT_NONE) {
- cur.eventCode = mHistoryLastWritten.eventCode;
- cur.eventTag = cur.localEventTag;
- cur.eventTag.setTo(mHistoryLastWritten.eventTag);
- }
- mHistoryLastWritten.setTo(mHistoryLastLastWritten);
- }
- final int dataSize = mHistoryBuffer.dataSize();
-
- if (dataSize >= mConstants.MAX_HISTORY_BUFFER) {
- //open a new history file.
- final long start = SystemClock.uptimeMillis();
- writeHistoryLocked();
- if (DEBUG) {
- Slog.d(TAG, "addHistoryBufferLocked writeHistoryLocked takes ms:"
- + (SystemClock.uptimeMillis() - start));
- }
- mBatteryStatsHistory.startNextFile();
- mHistoryBuffer.setDataSize(0);
- mHistoryBuffer.setDataPosition(0);
- mHistoryBuffer.setDataCapacity(mConstants.MAX_HISTORY_BUFFER / 2);
- mHistoryBufferLastPos = -1;
- mHistoryLastWritten.clear();
- mHistoryLastLastWritten.clear();
-
- // Mark every entry in the pool with a flag indicating that the tag
- // has not yet been encountered while writing the current history buffer.
- for (Map.Entry<HistoryTag, Integer> entry: mHistoryTagPool.entrySet()) {
- entry.setValue(entry.getValue() | BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG);
- }
- // Make a copy of mHistoryCur.
- HistoryItem copy = new HistoryItem();
- copy.setTo(cur);
- // startRecordingHistory will reset mHistoryCur.
- startRecordingHistory(elapsedRealtimeMs, uptimeMs, false);
- // Add the copy into history buffer.
- addHistoryBufferLocked(elapsedRealtimeMs, HistoryItem.CMD_UPDATE, copy);
- return;
- }
-
- if (dataSize == 0) {
- // The history is currently empty; we need it to start with a time stamp.
- cur.currentTime = mClock.currentTimeMillis();
- addHistoryBufferLocked(elapsedRealtimeMs, HistoryItem.CMD_RESET, cur);
- }
- addHistoryBufferLocked(elapsedRealtimeMs, HistoryItem.CMD_UPDATE, cur);
- }
-
- @GuardedBy("this")
- private void addHistoryBufferLocked(long elapsedRealtimeMs, byte cmd, HistoryItem cur) {
- if (mBatteryStatsHistoryIterator != null) {
- throw new IllegalStateException("Can't do this while iterating history!");
- }
- mHistoryBufferLastPos = mHistoryBuffer.dataPosition();
- mHistoryLastLastWritten.setTo(mHistoryLastWritten);
- final boolean hasTags = mHistoryLastWritten.tagsFirstOccurrence || cur.tagsFirstOccurrence;
- mHistoryLastWritten.setTo(mHistoryBaseTimeMs + elapsedRealtimeMs, cmd, cur);
- mHistoryLastWritten.tagsFirstOccurrence = hasTags;
- mHistoryLastWritten.states &= mActiveHistoryStates;
- mHistoryLastWritten.states2 &= mActiveHistoryStates2;
- writeHistoryDelta(mHistoryBuffer, mHistoryLastWritten, mHistoryLastLastWritten);
- mLastHistoryElapsedRealtimeMs = elapsedRealtimeMs;
- cur.wakelockTag = null;
- cur.wakeReasonTag = null;
- cur.eventCode = HistoryItem.EVENT_NONE;
- cur.eventTag = null;
- cur.tagsFirstOccurrence = false;
- if (DEBUG_HISTORY) Slog.i(TAG, "Writing history buffer: was " + mHistoryBufferLastPos
- + " now " + mHistoryBuffer.dataPosition()
- + " size is now " + mHistoryBuffer.dataSize());
- }
-
- @GuardedBy("this")
- void addHistoryRecordLocked(long elapsedRealtimeMs, long uptimeMs) {
- if (mTrackRunningHistoryElapsedRealtimeMs != 0) {
- final long diffElapsedMs = elapsedRealtimeMs - mTrackRunningHistoryElapsedRealtimeMs;
- final long diffUptimeMs = uptimeMs - mTrackRunningHistoryUptimeMs;
- if (diffUptimeMs < (diffElapsedMs - 20)) {
- final long wakeElapsedTimeMs = elapsedRealtimeMs - (diffElapsedMs - diffUptimeMs);
- mHistoryAddTmp.setTo(mHistoryLastWritten);
- mHistoryAddTmp.wakelockTag = null;
- mHistoryAddTmp.wakeReasonTag = null;
- mHistoryAddTmp.eventCode = HistoryItem.EVENT_NONE;
- mHistoryAddTmp.states &= ~HistoryItem.STATE_CPU_RUNNING_FLAG;
- addHistoryRecordInnerLocked(wakeElapsedTimeMs, uptimeMs, mHistoryAddTmp);
- }
- }
- mHistoryCur.states |= HistoryItem.STATE_CPU_RUNNING_FLAG;
- mTrackRunningHistoryElapsedRealtimeMs = elapsedRealtimeMs;
- mTrackRunningHistoryUptimeMs = uptimeMs;
- addHistoryRecordInnerLocked(elapsedRealtimeMs, uptimeMs, mHistoryCur);
- }
-
- @GuardedBy("this")
- void addHistoryRecordInnerLocked(long elapsedRealtimeMs, long uptimeMs, HistoryItem cur) {
- addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, cur);
- }
-
- @GuardedBy("this")
- public void addHistoryEventLocked(long elapsedRealtimeMs, long uptimeMs, int code,
+ public void recordHistoryEventLocked(long elapsedRealtimeMs, long uptimeMs, int code,
String name, int uid) {
- mHistoryCur.eventCode = code;
- mHistoryCur.eventTag = mHistoryCur.localEventTag;
- mHistoryCur.eventTag.string = name;
- mHistoryCur.eventTag.uid = uid;
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
- }
-
- @GuardedBy("this")
- void clearHistoryLocked() {
- if (DEBUG_HISTORY) Slog.i(TAG, "********** CLEARING HISTORY!");
- mHistoryBaseTimeMs = 0;
- mLastHistoryElapsedRealtimeMs = 0;
- mTrackRunningHistoryElapsedRealtimeMs = 0;
- mTrackRunningHistoryUptimeMs = 0;
-
- mHistoryBuffer.setDataSize(0);
- mHistoryBuffer.setDataPosition(0);
- mHistoryBuffer.setDataCapacity(mConstants.MAX_HISTORY_BUFFER / 2);
- mHistoryLastLastWritten.clear();
- mHistoryLastWritten.clear();
- mHistoryTagPool.clear();
- mNextHistoryTagIdx = 0;
- mNumHistoryTagChars = 0;
- mHistoryBufferLastPos = -1;
- mActiveHistoryStates = 0xffffffff;
- mActiveHistoryStates2 = 0xffffffff;
+ mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, code, name, uid);
}
@GuardedBy("this")
@@ -4663,13 +4221,13 @@ public class BatteryStatsImpl extends BatteryStats {
if (!mActiveEvents.updateState(code, name, uid, 0)) {
return;
}
- addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, code, name, uid);
+ mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, code, name, uid);
}
@GuardedBy("this")
public void noteCurrentTimeChangedLocked(long currentTimeMs,
long elapsedRealtimeMs, long uptimeMs) {
- recordCurrentTimeChangeLocked(currentTimeMs, elapsedRealtimeMs, uptimeMs);
+ mHistory.recordCurrentTimeChange(elapsedRealtimeMs, uptimeMs, currentTimeMs);
}
@GuardedBy("this")
@@ -4686,7 +4244,7 @@ public class BatteryStatsImpl extends BatteryStats {
if (!mRecordAllHistory) {
return;
}
- addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_PROC_START, name, uid);
+ mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_PROC_START, name, uid);
}
@GuardedBy("this")
@@ -4744,8 +4302,7 @@ public class BatteryStatsImpl extends BatteryStats {
if (!mRecordAllHistory) {
return;
}
- addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_PROC_FINISH,
- name, uid);
+ mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_PROC_FINISH, name, uid);
}
@GuardedBy("this")
@@ -4761,7 +4318,7 @@ public class BatteryStatsImpl extends BatteryStats {
if (!mActiveEvents.updateState(HistoryItem.EVENT_SYNC_START, name, uid, 0)) {
return;
}
- addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_SYNC_START, name, uid);
+ mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_SYNC_START, name, uid);
}
@GuardedBy("this")
@@ -4777,8 +4334,7 @@ public class BatteryStatsImpl extends BatteryStats {
if (!mActiveEvents.updateState(HistoryItem.EVENT_SYNC_FINISH, name, uid, 0)) {
return;
}
- addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_SYNC_FINISH,
- name, uid);
+ mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_SYNC_FINISH, name, uid);
}
@GuardedBy("this")
@@ -4794,7 +4350,7 @@ public class BatteryStatsImpl extends BatteryStats {
if (!mActiveEvents.updateState(HistoryItem.EVENT_JOB_START, name, uid, 0)) {
return;
}
- addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_JOB_START, name, uid);
+ mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_JOB_START, name, uid);
}
@GuardedBy("this")
@@ -4812,7 +4368,7 @@ public class BatteryStatsImpl extends BatteryStats {
if (!mActiveEvents.updateState(HistoryItem.EVENT_JOB_FINISH, name, uid, 0)) {
return;
}
- addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_JOB_FINISH, name, uid);
+ mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_JOB_FINISH, name, uid);
}
@GuardedBy("this")
@@ -4860,7 +4416,7 @@ public class BatteryStatsImpl extends BatteryStats {
for (int i = 0; i < workSource.size(); ++i) {
uid = mapUid(workSource.getUid(i));
if (mActiveEvents.updateState(historyItem, name, uid, 0)) {
- addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, historyItem, name, uid);
+ mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, historyItem, name, uid);
}
}
@@ -4869,7 +4425,7 @@ public class BatteryStatsImpl extends BatteryStats {
for (int i = 0; i < workChains.size(); ++i) {
uid = mapUid(workChains.get(i).getAttributionUid());
if (mActiveEvents.updateState(historyItem, name, uid, 0)) {
- addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, historyItem, name, uid);
+ mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, historyItem, name, uid);
}
}
}
@@ -4877,7 +4433,7 @@ public class BatteryStatsImpl extends BatteryStats {
uid = mapUid(uid);
if (mActiveEvents.updateState(historyItem, name, uid, 0)) {
- addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, historyItem, name, uid);
+ mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, historyItem, name, uid);
}
}
}
@@ -4952,7 +4508,7 @@ public class BatteryStatsImpl extends BatteryStats {
for (HashMap.Entry<String, SparseIntArray> ent : active.entrySet()) {
SparseIntArray uids = ent.getValue();
for (int j=0; j<uids.size(); j++) {
- addHistoryEventLocked(mSecRealtime, mSecUptime,
+ mHistory.recordEvent(mSecRealtime, mSecUptime,
HistoryItem.EVENT_PROC_FINISH, ent.getKey(), uids.keyAt(j));
}
}
@@ -4967,8 +4523,8 @@ public class BatteryStatsImpl extends BatteryStats {
for (HashMap.Entry<String, SparseIntArray> ent : active.entrySet()) {
SparseIntArray uids = ent.getValue();
for (int j=0; j<uids.size(); j++) {
- addHistoryEventLocked(mSecRealtime, mSecUptime,
- HistoryItem.EVENT_PROC_START, ent.getKey(), uids.keyAt(j));
+ mHistory.recordEvent(mSecRealtime, mSecUptime, HistoryItem.EVENT_PROC_START,
+ ent.getKey(), uids.keyAt(j));
}
}
}
@@ -5011,30 +4567,19 @@ public class BatteryStatsImpl extends BatteryStats {
if (mRecordAllHistory) {
if (mActiveEvents.updateState(HistoryItem.EVENT_WAKE_LOCK_START, historyName,
mappedUid, 0)) {
- addHistoryEventLocked(elapsedRealtimeMs, uptimeMs,
+ mHistory.recordEvent(elapsedRealtimeMs, uptimeMs,
HistoryItem.EVENT_WAKE_LOCK_START, historyName, mappedUid);
}
}
if (mWakeLockNesting == 0) {
- mHistoryCur.states |= HistoryItem.STATE_WAKE_LOCK_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "Start wake lock to: "
- + Integer.toHexString(mHistoryCur.states));
- mHistoryCur.wakelockTag = mHistoryCur.localWakelockTag;
- mHistoryCur.wakelockTag.string = historyName;
- mHistoryCur.wakelockTag.uid = mappedUid;
mWakeLockImportant = !unimportantForLogging;
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
- } else if (!mWakeLockImportant && !unimportantForLogging
- && mHistoryLastWritten.cmd == HistoryItem.CMD_UPDATE) {
- if (mHistoryLastWritten.wakelockTag != null) {
- // We'll try to update the last tag.
- mHistoryLastWritten.wakelockTag = null;
- mHistoryCur.wakelockTag = mHistoryCur.localWakelockTag;
- mHistoryCur.wakelockTag.string = historyName;
- mHistoryCur.wakelockTag.uid = mappedUid;
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
- }
- mWakeLockImportant = true;
+ mHistory.recordWakelockStartEvent(elapsedRealtimeMs, uptimeMs, historyName,
+ mappedUid);
+ } else if (!mWakeLockImportant && !unimportantForLogging) {
+ if (mHistory.maybeUpdateWakelockTag(elapsedRealtimeMs, uptimeMs, historyName,
+ mappedUid)) {
+ mWakeLockImportant = true;
+ }
}
mWakeLockNesting++;
}
@@ -5087,15 +4632,13 @@ public class BatteryStatsImpl extends BatteryStats {
}
if (mActiveEvents.updateState(HistoryItem.EVENT_WAKE_LOCK_FINISH, historyName,
mappedUid, 0)) {
- addHistoryEventLocked(elapsedRealtimeMs, uptimeMs,
+ mHistory.recordEvent(elapsedRealtimeMs, uptimeMs,
HistoryItem.EVENT_WAKE_LOCK_FINISH, historyName, mappedUid);
}
}
if (mWakeLockNesting == 0) {
- mHistoryCur.states &= ~HistoryItem.STATE_WAKE_LOCK_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "Stop wake lock to: "
- + Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordStateStopEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE_WAKE_LOCK_FLAG);
}
}
if (mappedUid >= 0) {
@@ -5286,7 +4829,7 @@ public class BatteryStatsImpl extends BatteryStats {
mappedUid, 0)) {
return;
}
- addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_LONG_WAKE_LOCK_START,
+ mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_LONG_WAKE_LOCK_START,
historyName, mappedUid);
if (mappedUid != uid) {
// Prevent the isolated uid mapping from being removed while the wakelock is
@@ -5339,7 +4882,7 @@ public class BatteryStatsImpl extends BatteryStats {
mappedUid, 0)) {
return;
}
- addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_LONG_WAKE_LOCK_FINISH,
+ mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_LONG_WAKE_LOCK_FINISH,
historyName, mappedUid);
if (mappedUid != uid) {
// Decrement the ref count for the isolated uid and delete the mapping if uneeded.
@@ -5361,15 +4904,10 @@ public class BatteryStatsImpl extends BatteryStats {
@GuardedBy("this")
public void noteWakeupReasonLocked(String reason, long elapsedRealtimeMs, long uptimeMs) {
- if (DEBUG_HISTORY) Slog.v(TAG, "Wakeup reason \"" + reason +"\": "
- + Integer.toHexString(mHistoryCur.states));
aggregateLastWakeupUptimeLocked(elapsedRealtimeMs, uptimeMs);
- mHistoryCur.wakeReasonTag = mHistoryCur.localWakeReasonTag;
- mHistoryCur.wakeReasonTag.string = reason;
- mHistoryCur.wakeReasonTag.uid = 0;
+ mHistory.recordWakeupEvent(elapsedRealtimeMs, uptimeMs, reason);
mLastWakeupReason = reason;
mLastWakeupUptimeMs = uptimeMs;
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
}
@GuardedBy("this")
@@ -5380,22 +4918,11 @@ public class BatteryStatsImpl extends BatteryStats {
@GuardedBy("this")
public void finishAddingCpuLocked(int totalUTimeMs, int totalSTimeMs, int statUserTimeMs,
- int statSystemTimeMs, int statIOWaitTimeMs, int statIrqTimeMs,
- int statSoftIrqTimeMs, int statIdleTimeMs) {
- if (DEBUG) {
- Slog.d(TAG, "Adding cpu: tuser=" + totalUTimeMs + " tsys=" + totalSTimeMs
- + " user=" + statUserTimeMs + " sys=" + statSystemTimeMs
- + " io=" + statIOWaitTimeMs + " irq=" + statIrqTimeMs
- + " sirq=" + statSoftIrqTimeMs + " idle=" + statIdleTimeMs);
- }
- mCurStepCpuUserTimeMs += totalUTimeMs;
- mCurStepCpuSystemTimeMs += totalSTimeMs;
- mCurStepStatUserTimeMs += statUserTimeMs;
- mCurStepStatSystemTimeMs += statSystemTimeMs;
- mCurStepStatIOWaitTimeMs += statIOWaitTimeMs;
- mCurStepStatIrqTimeMs += statIrqTimeMs;
- mCurStepStatSoftIrqTimeMs += statSoftIrqTimeMs;
- mCurStepStatIdleTimeMs += statIdleTimeMs;
+ int statSystemTimeMs, int statIOWaitTimeMs, int statIrqTimeMs,
+ int statSoftIrqTimeMs, int statIdleTimeMs) {
+ mStepDetailsCalculator.addCpuStats(totalUTimeMs, totalSTimeMs, statUserTimeMs,
+ statSystemTimeMs, statIOWaitTimeMs, statIrqTimeMs,
+ statSoftIrqTimeMs, statIdleTimeMs);
}
public void noteProcessDiedLocked(int uid, int pid) {
@@ -5425,10 +4952,8 @@ public class BatteryStatsImpl extends BatteryStats {
public void noteStartSensorLocked(int uid, int sensor, long elapsedRealtimeMs, long uptimeMs) {
uid = mapUid(uid);
if (mSensorNesting == 0) {
- mHistoryCur.states |= HistoryItem.STATE_SENSOR_ON_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "Start sensor to: "
- + Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordStateStartEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE_SENSOR_ON_FLAG);
}
mSensorNesting++;
getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
@@ -5445,10 +4970,8 @@ public class BatteryStatsImpl extends BatteryStats {
uid = mapUid(uid);
mSensorNesting--;
if (mSensorNesting == 0) {
- mHistoryCur.states &= ~HistoryItem.STATE_SENSOR_ON_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "Stop sensor to: "
- + Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordStateStopEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE_SENSOR_ON_FLAG);
}
getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
.noteStopSensor(sensor, elapsedRealtimeMs);
@@ -5498,10 +5021,8 @@ public class BatteryStatsImpl extends BatteryStats {
}
final int mappedUid = mapUid(uid);
if (mGpsNesting == 0) {
- mHistoryCur.states |= HistoryItem.STATE_GPS_ON_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "Start GPS to: "
- + Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordStateStartEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE_GPS_ON_FLAG);
}
mGpsNesting++;
@@ -5526,10 +5047,8 @@ public class BatteryStatsImpl extends BatteryStats {
final int mappedUid = mapUid(uid);
mGpsNesting--;
if (mGpsNesting == 0) {
- mHistoryCur.states &= ~HistoryItem.STATE_GPS_ON_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "Stop GPS to: "
- + Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordStateStopEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE_GPS_ON_FLAG);
stopAllGpsSignalQualityTimersLocked(-1, elapsedRealtimeMs);
mGpsSignalQualityBin = -1;
}
@@ -5562,12 +5081,9 @@ public class BatteryStatsImpl extends BatteryStats {
if(!mGpsSignalQualityTimer[signalLevel].isRunningLocked()) {
mGpsSignalQualityTimer[signalLevel].startRunningLocked(elapsedRealtimeMs);
}
- mHistoryCur.states2 = (mHistoryCur.states2&~HistoryItem.STATE2_GPS_SIGNAL_QUALITY_MASK)
- | (signalLevel << HistoryItem.STATE2_GPS_SIGNAL_QUALITY_SHIFT);
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordGpsSignalQualityEvent(elapsedRealtimeMs, uptimeMs, signalLevel);
mGpsSignalQualityBin = signalLevel;
}
- return;
}
@GuardedBy("this")
@@ -5740,41 +5256,33 @@ public class BatteryStatsImpl extends BatteryStats {
}
}
- boolean updateHistory = false;
+ int startStates = 0;
+ int stopStates = 0;
if (Display.isDozeState(state) && !Display.isDozeState(oldState)) {
- mHistoryCur.states |= HistoryItem.STATE_SCREEN_DOZE_FLAG;
+ startStates |= HistoryItem.STATE_SCREEN_DOZE_FLAG;
mScreenDozeTimer.startRunningLocked(elapsedRealtimeMs);
- updateHistory = true;
} else if (Display.isDozeState(oldState) && !Display.isDozeState(state)) {
- mHistoryCur.states &= ~HistoryItem.STATE_SCREEN_DOZE_FLAG;
+ stopStates |= HistoryItem.STATE_SCREEN_DOZE_FLAG;
mScreenDozeTimer.stopRunningLocked(elapsedRealtimeMs);
- updateHistory = true;
}
if (Display.isOnState(state)) {
- mHistoryCur.states |= HistoryItem.STATE_SCREEN_ON_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "Screen on to: "
- + Integer.toHexString(mHistoryCur.states));
+ startStates |= HistoryItem.STATE_SCREEN_ON_FLAG;
mScreenOnTimer.startRunningLocked(elapsedRealtimeMs);
if (mScreenBrightnessBin >= 0) {
mScreenBrightnessTimer[mScreenBrightnessBin]
.startRunningLocked(elapsedRealtimeMs);
}
- updateHistory = true;
} else if (Display.isOnState(oldState)) {
- mHistoryCur.states &= ~HistoryItem.STATE_SCREEN_ON_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "Screen off to: "
- + Integer.toHexString(mHistoryCur.states));
+ stopStates |= HistoryItem.STATE_SCREEN_ON_FLAG;
mScreenOnTimer.stopRunningLocked(elapsedRealtimeMs);
if (mScreenBrightnessBin >= 0) {
mScreenBrightnessTimer[mScreenBrightnessBin]
.stopRunningLocked(elapsedRealtimeMs);
}
- updateHistory = true;
}
- if (updateHistory) {
- if (DEBUG_HISTORY) Slog.v(TAG, "Screen state to: "
- + Display.stateToString(state));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ if (startStates != 0 || stopStates != 0) {
+ mHistory.recordStateChangeEvent(elapsedRealtimeMs, uptimeMs, startStates,
+ stopStates);
}
// Per screen state Cpu stats needed. Prepare to schedule an external sync.
@@ -5888,13 +5396,7 @@ public class BatteryStatsImpl extends BatteryStats {
long uptimeMs) {
if (mScreenBrightnessBin != overallBin) {
if (overallBin >= 0) {
- mHistoryCur.states = (mHistoryCur.states & ~HistoryItem.STATE_BRIGHTNESS_MASK)
- | (overallBin << HistoryItem.STATE_BRIGHTNESS_SHIFT);
- if (DEBUG_HISTORY) {
- Slog.v(TAG, "Screen brightness " + overallBin + " to: "
- + Integer.toHexString(mHistoryCur.states));
- }
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordScreenBrightnessEvent(elapsedRealtimeMs, uptimeMs, overallBin);
}
if (mScreenState == Display.STATE_ON) {
if (mScreenBrightnessBin >= 0) {
@@ -5911,7 +5413,8 @@ public class BatteryStatsImpl extends BatteryStats {
}
@GuardedBy("this")
- public void noteUserActivityLocked(int uid, int event, long elapsedRealtimeMs, long uptimeMs) {
+ public void noteUserActivityLocked(int uid, @PowerManager.UserActivityEvent int event,
+ long elapsedRealtimeMs, long uptimeMs) {
if (mOnBatteryInternal) {
uid = mapUid(uid);
getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs).noteUserActivityLocked(event);
@@ -5921,8 +5424,8 @@ public class BatteryStatsImpl extends BatteryStats {
@GuardedBy("this")
public void noteWakeUpLocked(String reason, int reasonUid,
long elapsedRealtimeMs, long uptimeMs) {
- addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_SCREEN_WAKE_UP,
- reason, reasonUid);
+ mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_SCREEN_WAKE_UP, reason,
+ reasonUid);
}
@GuardedBy("this")
@@ -5941,7 +5444,7 @@ public class BatteryStatsImpl extends BatteryStats {
@GuardedBy("this")
public void noteConnectivityChangedLocked(int type, String extra,
long elapsedRealtimeMs, long uptimeMs) {
- addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_CONNECTIVITY_CHANGED,
+ mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_CONNECTIVITY_CHANGED,
extra, type);
mNumConnectivityChange++;
}
@@ -5950,7 +5453,7 @@ public class BatteryStatsImpl extends BatteryStats {
private void noteMobileRadioApWakeupLocked(final long elapsedRealtimeMillis,
final long uptimeMillis, int uid) {
uid = mapUid(uid);
- addHistoryEventLocked(elapsedRealtimeMillis, uptimeMillis, HistoryItem.EVENT_WAKEUP_AP, "",
+ mHistory.recordEvent(elapsedRealtimeMillis, uptimeMillis, HistoryItem.EVENT_WAKEUP_AP, "",
uid);
getUidStatsLocked(uid, elapsedRealtimeMillis, uptimeMillis).noteMobileRadioApWakeupLocked();
}
@@ -5976,7 +5479,8 @@ public class BatteryStatsImpl extends BatteryStats {
}
mMobileRadioActiveStartTimeMs = realElapsedRealtimeMs = timestampNs / (1000 * 1000);
- mHistoryCur.states |= HistoryItem.STATE_MOBILE_RADIO_ACTIVE_FLAG;
+ mHistory.recordStateStartEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE_MOBILE_RADIO_ACTIVE_FLAG);
} else {
realElapsedRealtimeMs = timestampNs / (1000*1000);
long lastUpdateTimeMs = mMobileRadioActiveStartTimeMs;
@@ -5988,11 +5492,9 @@ public class BatteryStatsImpl extends BatteryStats {
mMobileRadioActiveAdjustedTime.addCountLocked(elapsedRealtimeMs
- realElapsedRealtimeMs);
}
- mHistoryCur.states &= ~HistoryItem.STATE_MOBILE_RADIO_ACTIVE_FLAG;
+ mHistory.recordStateStopEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE_MOBILE_RADIO_ACTIVE_FLAG);
}
- if (DEBUG_HISTORY) Slog.v(TAG, "Mobile network active " + active + " to: "
- + Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
mMobileRadioPowerState = powerState;
// Inform current RatBatteryStats that the modem active state might have changed.
@@ -6042,17 +5544,14 @@ public class BatteryStatsImpl extends BatteryStats {
mCurStepMode = (mCurStepMode&~STEP_LEVEL_MODE_POWER_SAVE) | stepState;
mPowerSaveModeEnabled = enabled;
if (enabled) {
- mHistoryCur.states2 |= HistoryItem.STATE2_POWER_SAVE_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "Power save mode enabled to: "
- + Integer.toHexString(mHistoryCur.states2));
+ mHistory.recordState2StartEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE2_POWER_SAVE_FLAG);
mPowerSaveModeEnabledTimer.startRunningLocked(elapsedRealtimeMs);
} else {
- mHistoryCur.states2 &= ~HistoryItem.STATE2_POWER_SAVE_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "Power save mode disabled to: "
- + Integer.toHexString(mHistoryCur.states2));
+ mHistory.recordState2StopEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE2_POWER_SAVE_FLAG);
mPowerSaveModeEnabledTimer.stopRunningLocked(elapsedRealtimeMs);
}
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
FrameworkStatsLog.write(FrameworkStatsLog.BATTERY_SAVER_MODE_STATE_CHANGED,
enabled
? FrameworkStatsLog.BATTERY_SAVER_MODE_STATE_CHANGED__STATE__ON
@@ -6076,7 +5575,7 @@ public class BatteryStatsImpl extends BatteryStats {
nowLightIdling = true;
}
if (activeReason != null && (mDeviceIdling || mDeviceLightIdling)) {
- addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_ACTIVE,
+ mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_ACTIVE,
activeReason, activeUid);
}
if (mDeviceIdling != nowIdling || mDeviceLightIdling != nowLightIdling) {
@@ -6106,11 +5605,7 @@ public class BatteryStatsImpl extends BatteryStats {
}
}
if (mDeviceIdleMode != mode) {
- mHistoryCur.states2 = (mHistoryCur.states2 & ~HistoryItem.STATE2_DEVICE_IDLE_MASK)
- | (mode << HistoryItem.STATE2_DEVICE_IDLE_SHIFT);
- if (DEBUG_HISTORY) Slog.v(TAG, "Device idle mode changed to: "
- + Integer.toHexString(mHistoryCur.states2));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordDeviceIdleEvent(elapsedRealtimeMs, uptimeMs, mode);
long lastDuration = elapsedRealtimeMs - mLastIdleTimeStartMs;
mLastIdleTimeStartMs = elapsedRealtimeMs;
if (mDeviceIdleMode == DEVICE_IDLE_MODE_LIGHT) {
@@ -6138,7 +5633,7 @@ public class BatteryStatsImpl extends BatteryStats {
public void notePackageInstalledLocked(String pkgName, long versionCode,
long elapsedRealtimeMs, long uptimeMs) {
// XXX need to figure out what to do with long version codes.
- addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_PACKAGE_INSTALLED,
+ mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_PACKAGE_INSTALLED,
pkgName, (int)versionCode);
PackageChange pc = new PackageChange();
pc.mPackageName = pkgName;
@@ -6150,8 +5645,8 @@ public class BatteryStatsImpl extends BatteryStats {
@GuardedBy("this")
public void notePackageUninstalledLocked(String pkgName,
long elapsedRealtimeMs, long uptimeMs) {
- addHistoryEventLocked(elapsedRealtimeMs, uptimeMs,
- HistoryItem.EVENT_PACKAGE_UNINSTALLED, pkgName, 0);
+ mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_PACKAGE_UNINSTALLED,
+ pkgName, 0);
PackageChange pc = new PackageChange();
pc.mPackageName = pkgName;
pc.mUpdate = true;
@@ -6180,10 +5675,8 @@ public class BatteryStatsImpl extends BatteryStats {
@GuardedBy("this")
public void notePhoneOnLocked(long elapsedRealtimeMs, long uptimeMs) {
if (!mPhoneOn) {
- mHistoryCur.states2 |= HistoryItem.STATE2_PHONE_IN_CALL_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "Phone on to: "
- + Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordState2StartEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE2_PHONE_IN_CALL_FLAG);
mPhoneOn = true;
mPhoneOnTimer.startRunningLocked(elapsedRealtimeMs);
}
@@ -6192,10 +5685,8 @@ public class BatteryStatsImpl extends BatteryStats {
@GuardedBy("this")
public void notePhoneOffLocked(long elapsedRealtimeMs, long uptimeMs) {
if (mPhoneOn) {
- mHistoryCur.states2 &= ~HistoryItem.STATE2_PHONE_IN_CALL_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "Phone off to: "
- + Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordState2StopEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE2_PHONE_IN_CALL_FLAG);
mPhoneOn = false;
mPhoneOnTimer.stopRunningLocked(elapsedRealtimeMs);
}
@@ -6233,11 +5724,12 @@ public class BatteryStatsImpl extends BatteryStats {
if (mUsbDataState != newState) {
mUsbDataState = newState;
if (connected) {
- mHistoryCur.states2 |= HistoryItem.STATE2_USB_DATA_LINK_FLAG;
+ mHistory.recordState2StartEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE2_USB_DATA_LINK_FLAG);
} else {
- mHistoryCur.states2 &= ~HistoryItem.STATE2_USB_DATA_LINK_FLAG;
+ mHistory.recordState2StopEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE2_USB_DATA_LINK_FLAG);
}
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
}
}
@@ -6258,6 +5750,10 @@ public class BatteryStatsImpl extends BatteryStats {
long elapsedRealtimeMs, long uptimeMs) {
boolean scanning = false;
boolean newHistory = false;
+ int addStateFlag = 0;
+ int removeStateFlag = 0;
+ int newState = -1;
+ int newSignalStrength = -1;
mPhoneServiceStateRaw = state;
mPhoneSimStateRaw = simState;
@@ -6286,10 +5782,8 @@ public class BatteryStatsImpl extends BatteryStats {
scanning = true;
strengthBin = CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
if (!mPhoneSignalScanningTimer.isRunningLocked()) {
- mHistoryCur.states |= HistoryItem.STATE_PHONE_SCANNING_FLAG;
+ addStateFlag = HistoryItem.STATE_PHONE_SCANNING_FLAG;
newHistory = true;
- if (DEBUG_HISTORY) Slog.v(TAG, "Phone started scanning to: "
- + Integer.toHexString(mHistoryCur.states));
mPhoneSignalScanningTimer.startRunningLocked(elapsedRealtimeMs);
FrameworkStatsLog.write(FrameworkStatsLog.PHONE_SERVICE_STATE_CHANGED, state,
simState, strengthBin);
@@ -6299,9 +5793,7 @@ public class BatteryStatsImpl extends BatteryStats {
if (!scanning) {
// If we are no longer scanning, then stop the scanning timer.
if (mPhoneSignalScanningTimer.isRunningLocked()) {
- mHistoryCur.states &= ~HistoryItem.STATE_PHONE_SCANNING_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "Phone stopped scanning to: "
- + Integer.toHexString(mHistoryCur.states));
+ removeStateFlag = HistoryItem.STATE_PHONE_SCANNING_FLAG;
newHistory = true;
mPhoneSignalScanningTimer.stopRunningLocked(elapsedRealtimeMs);
FrameworkStatsLog.write(FrameworkStatsLog.PHONE_SERVICE_STATE_CHANGED, state,
@@ -6310,10 +5802,7 @@ public class BatteryStatsImpl extends BatteryStats {
}
if (mPhoneServiceState != state) {
- mHistoryCur.states = (mHistoryCur.states&~HistoryItem.STATE_PHONE_STATE_MASK)
- | (state << HistoryItem.STATE_PHONE_STATE_SHIFT);
- if (DEBUG_HISTORY) Slog.v(TAG, "Phone state " + state + " to: "
- + Integer.toHexString(mHistoryCur.states));
+ newState = state;
newHistory = true;
mPhoneServiceState = state;
}
@@ -6327,11 +5816,7 @@ public class BatteryStatsImpl extends BatteryStats {
if (!mPhoneSignalStrengthsTimer[strengthBin].isRunningLocked()) {
mPhoneSignalStrengthsTimer[strengthBin].startRunningLocked(elapsedRealtimeMs);
}
- mHistoryCur.states =
- (mHistoryCur.states & ~HistoryItem.STATE_PHONE_SIGNAL_STRENGTH_MASK)
- | (strengthBin << HistoryItem.STATE_PHONE_SIGNAL_STRENGTH_SHIFT);
- if (DEBUG_HISTORY) Slog.v(TAG, "Signal strength " + strengthBin + " to: "
- + Integer.toHexString(mHistoryCur.states));
+ newSignalStrength = strengthBin;
newHistory = true;
FrameworkStatsLog.write(
FrameworkStatsLog.PHONE_SIGNAL_STRENGTH_CHANGED, strengthBin);
@@ -6342,7 +5827,8 @@ public class BatteryStatsImpl extends BatteryStats {
}
if (newHistory) {
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordPhoneStateChangeEvent(elapsedRealtimeMs, uptimeMs,
+ addStateFlag, removeStateFlag, newState, newSignalStrength);
}
}
@@ -6466,11 +5952,7 @@ public class BatteryStatsImpl extends BatteryStats {
if (DEBUG) Log.i(TAG, "Phone Data Connection -> " + dataType + " = " + hasData);
if (mPhoneDataConnectionType != bin) {
- mHistoryCur.states = (mHistoryCur.states&~HistoryItem.STATE_DATA_CONNECTION_MASK)
- | (bin << HistoryItem.STATE_DATA_CONNECTION_SHIFT);
- if (DEBUG_HISTORY) Slog.v(TAG, "Data connection " + bin + " to: "
- + Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordDataConnectionTypeChangeEvent(elapsedRealtimeMs, uptimeMs, bin);
if (mPhoneDataConnectionType >= 0) {
mPhoneDataConnectionsTimer[mPhoneDataConnectionType].stopRunningLocked(
elapsedRealtimeMs);
@@ -6543,10 +6025,8 @@ public class BatteryStatsImpl extends BatteryStats {
@GuardedBy("this")
public void noteWifiOnLocked(long elapsedRealtimeMs, long uptimeMs) {
if (!mWifiOn) {
- mHistoryCur.states2 |= HistoryItem.STATE2_WIFI_ON_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "WIFI on to: "
- + Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordState2StartEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE2_WIFI_ON_FLAG);
mWifiOn = true;
mWifiOnTimer.startRunningLocked(elapsedRealtimeMs);
scheduleSyncExternalStatsLocked("wifi-off", ExternalStatsSync.UPDATE_WIFI);
@@ -6556,10 +6036,8 @@ public class BatteryStatsImpl extends BatteryStats {
@GuardedBy("this")
public void noteWifiOffLocked(long elapsedRealtimeMs, long uptimeMs) {
if (mWifiOn) {
- mHistoryCur.states2 &= ~HistoryItem.STATE2_WIFI_ON_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "WIFI off to: "
- + Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordState2StopEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE2_WIFI_ON_FLAG);
mWifiOn = false;
mWifiOnTimer.stopRunningLocked(elapsedRealtimeMs);
scheduleSyncExternalStatsLocked("wifi-on", ExternalStatsSync.UPDATE_WIFI);
@@ -6570,10 +6048,8 @@ public class BatteryStatsImpl extends BatteryStats {
public void noteAudioOnLocked(int uid, long elapsedRealtimeMs, long uptimeMs) {
uid = mapUid(uid);
if (mAudioOnNesting == 0) {
- mHistoryCur.states |= HistoryItem.STATE_AUDIO_ON_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "Audio on to: "
- + Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordStateStartEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE_AUDIO_ON_FLAG);
mAudioOnTimer.startRunningLocked(elapsedRealtimeMs);
}
mAudioOnNesting++;
@@ -6588,10 +6064,8 @@ public class BatteryStatsImpl extends BatteryStats {
}
uid = mapUid(uid);
if (--mAudioOnNesting == 0) {
- mHistoryCur.states &= ~HistoryItem.STATE_AUDIO_ON_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "Audio off to: "
- + Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordStateStopEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE_AUDIO_ON_FLAG);
mAudioOnTimer.stopRunningLocked(elapsedRealtimeMs);
}
getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
@@ -6602,10 +6076,8 @@ public class BatteryStatsImpl extends BatteryStats {
public void noteVideoOnLocked(int uid, long elapsedRealtimeMs, long uptimeMs) {
uid = mapUid(uid);
if (mVideoOnNesting == 0) {
- mHistoryCur.states2 |= HistoryItem.STATE2_VIDEO_ON_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "Video on to: "
- + Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordState2StartEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE2_VIDEO_ON_FLAG);
mVideoOnTimer.startRunningLocked(elapsedRealtimeMs);
}
mVideoOnNesting++;
@@ -6620,10 +6092,8 @@ public class BatteryStatsImpl extends BatteryStats {
}
uid = mapUid(uid);
if (--mVideoOnNesting == 0) {
- mHistoryCur.states2 &= ~HistoryItem.STATE2_VIDEO_ON_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "Video off to: "
- + Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordState2StopEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE2_VIDEO_ON_FLAG);
mVideoOnTimer.stopRunningLocked(elapsedRealtimeMs);
}
getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
@@ -6634,10 +6104,8 @@ public class BatteryStatsImpl extends BatteryStats {
public void noteResetAudioLocked(long elapsedRealtimeMs, long uptimeMs) {
if (mAudioOnNesting > 0) {
mAudioOnNesting = 0;
- mHistoryCur.states &= ~HistoryItem.STATE_AUDIO_ON_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "Audio off to: "
- + Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordStateStopEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE_AUDIO_ON_FLAG);
mAudioOnTimer.stopAllRunningLocked(elapsedRealtimeMs);
for (int i=0; i<mUidStats.size(); i++) {
BatteryStatsImpl.Uid uid = mUidStats.valueAt(i);
@@ -6650,10 +6118,8 @@ public class BatteryStatsImpl extends BatteryStats {
public void noteResetVideoLocked(long elapsedRealtimeMs, long uptimeMs) {
if (mVideoOnNesting > 0) {
mVideoOnNesting = 0;
- mHistoryCur.states2 &= ~HistoryItem.STATE2_VIDEO_ON_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "Video off to: "
- + Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordState2StopEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE2_VIDEO_ON_FLAG);
mVideoOnTimer.stopAllRunningLocked(elapsedRealtimeMs);
for (int i=0; i<mUidStats.size(); i++) {
BatteryStatsImpl.Uid uid = mUidStats.valueAt(i);
@@ -6705,10 +6171,8 @@ public class BatteryStatsImpl extends BatteryStats {
public void noteFlashlightOnLocked(int uid, long elapsedRealtimeMs, long uptimeMs) {
uid = mapUid(uid);
if (mFlashlightOnNesting++ == 0) {
- mHistoryCur.states2 |= HistoryItem.STATE2_FLASHLIGHT_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "Flashlight on to: "
- + Integer.toHexString(mHistoryCur.states2));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordState2StartEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE2_FLASHLIGHT_FLAG);
mFlashlightOnTimer.startRunningLocked(elapsedRealtimeMs);
}
getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
@@ -6722,10 +6186,8 @@ public class BatteryStatsImpl extends BatteryStats {
}
uid = mapUid(uid);
if (--mFlashlightOnNesting == 0) {
- mHistoryCur.states2 &= ~HistoryItem.STATE2_FLASHLIGHT_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "Flashlight off to: "
- + Integer.toHexString(mHistoryCur.states2));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordState2StopEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE2_FLASHLIGHT_FLAG);
mFlashlightOnTimer.stopRunningLocked(elapsedRealtimeMs);
}
getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
@@ -6736,10 +6198,8 @@ public class BatteryStatsImpl extends BatteryStats {
public void noteCameraOnLocked(int uid, long elapsedRealtimeMs, long uptimeMs) {
uid = mapUid(uid);
if (mCameraOnNesting++ == 0) {
- mHistoryCur.states2 |= HistoryItem.STATE2_CAMERA_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "Camera on to: "
- + Integer.toHexString(mHistoryCur.states2));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordState2StartEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE2_CAMERA_FLAG);
mCameraOnTimer.startRunningLocked(elapsedRealtimeMs);
}
getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
@@ -6753,10 +6213,8 @@ public class BatteryStatsImpl extends BatteryStats {
}
uid = mapUid(uid);
if (--mCameraOnNesting == 0) {
- mHistoryCur.states2 &= ~HistoryItem.STATE2_CAMERA_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "Camera off to: "
- + Integer.toHexString(mHistoryCur.states2));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordState2StopEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE2_CAMERA_FLAG);
mCameraOnTimer.stopRunningLocked(elapsedRealtimeMs);
}
getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
@@ -6767,10 +6225,8 @@ public class BatteryStatsImpl extends BatteryStats {
public void noteResetCameraLocked(long elapsedRealtimeMs, long uptimeMs) {
if (mCameraOnNesting > 0) {
mCameraOnNesting = 0;
- mHistoryCur.states2 &= ~HistoryItem.STATE2_CAMERA_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "Camera off to: "
- + Integer.toHexString(mHistoryCur.states2));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordState2StopEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE2_CAMERA_FLAG);
mCameraOnTimer.stopAllRunningLocked(elapsedRealtimeMs);
for (int i=0; i<mUidStats.size(); i++) {
BatteryStatsImpl.Uid uid = mUidStats.valueAt(i);
@@ -6783,10 +6239,8 @@ public class BatteryStatsImpl extends BatteryStats {
public void noteResetFlashlightLocked(long elapsedRealtimeMs, long uptimeMs) {
if (mFlashlightOnNesting > 0) {
mFlashlightOnNesting = 0;
- mHistoryCur.states2 &= ~HistoryItem.STATE2_FLASHLIGHT_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "Flashlight off to: "
- + Integer.toHexString(mHistoryCur.states2));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordState2StopEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE2_FLASHLIGHT_FLAG);
mFlashlightOnTimer.stopAllRunningLocked(elapsedRealtimeMs);
for (int i=0; i<mUidStats.size(); i++) {
BatteryStatsImpl.Uid uid = mUidStats.valueAt(i);
@@ -6803,10 +6257,8 @@ public class BatteryStatsImpl extends BatteryStats {
}
uid = mapUid(uid);
if (mBluetoothScanNesting == 0) {
- mHistoryCur.states2 |= HistoryItem.STATE2_BLUETOOTH_SCAN_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "BLE scan started for: "
- + Integer.toHexString(mHistoryCur.states2));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordState2StartEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE2_BLUETOOTH_SCAN_FLAG);
mBluetoothScanTimer.startRunningLocked(elapsedRealtimeMs);
}
mBluetoothScanNesting++;
@@ -6847,10 +6299,8 @@ public class BatteryStatsImpl extends BatteryStats {
uid = mapUid(uid);
mBluetoothScanNesting--;
if (mBluetoothScanNesting == 0) {
- mHistoryCur.states2 &= ~HistoryItem.STATE2_BLUETOOTH_SCAN_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "BLE scan stopped for: "
- + Integer.toHexString(mHistoryCur.states2));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordState2StopEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE2_BLUETOOTH_SCAN_FLAG);
mBluetoothScanTimer.stopRunningLocked(elapsedRealtimeMs);
}
getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
@@ -6885,10 +6335,8 @@ public class BatteryStatsImpl extends BatteryStats {
public void noteResetBluetoothScanLocked(long elapsedRealtimeMs, long uptimeMs) {
if (mBluetoothScanNesting > 0) {
mBluetoothScanNesting = 0;
- mHistoryCur.states2 &= ~HistoryItem.STATE2_BLUETOOTH_SCAN_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "BLE can stopped for: "
- + Integer.toHexString(mHistoryCur.states2));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordState2StopEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE2_BLUETOOTH_SCAN_FLAG);
mBluetoothScanTimer.stopAllRunningLocked(elapsedRealtimeMs);
for (int i=0; i<mUidStats.size(); i++) {
BatteryStatsImpl.Uid uid = mUidStats.valueAt(i);
@@ -6928,7 +6376,7 @@ public class BatteryStatsImpl extends BatteryStats {
private void noteWifiRadioApWakeupLocked(final long elapsedRealtimeMillis,
final long uptimeMillis, int uid) {
uid = mapUid(uid);
- addHistoryEventLocked(elapsedRealtimeMillis, uptimeMillis, HistoryItem.EVENT_WAKEUP_AP, "",
+ mHistory.recordEvent(elapsedRealtimeMillis, uptimeMillis, HistoryItem.EVENT_WAKEUP_AP, "",
uid);
getUidStatsLocked(uid, elapsedRealtimeMillis, uptimeMillis).noteWifiRadioApWakeupLocked();
}
@@ -6944,15 +6392,14 @@ public class BatteryStatsImpl extends BatteryStats {
if (uid > 0) {
noteWifiRadioApWakeupLocked(elapsedRealtimeMs, uptimeMs, uid);
}
- mHistoryCur.states |= HistoryItem.STATE_WIFI_RADIO_ACTIVE_FLAG;
+ mHistory.recordStateStartEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE_WIFI_RADIO_ACTIVE_FLAG);
mWifiActiveTimer.startRunningLocked(elapsedRealtimeMs);
} else {
- mHistoryCur.states &= ~HistoryItem.STATE_WIFI_RADIO_ACTIVE_FLAG;
+ mHistory.recordStateStopEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE_WIFI_RADIO_ACTIVE_FLAG);
mWifiActiveTimer.stopRunningLocked(timestampNs / (1000 * 1000));
}
- if (DEBUG_HISTORY) Slog.v(TAG, "Wifi network active " + active + " to: "
- + Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
mWifiRadioPowerState = powerState;
}
}
@@ -6960,10 +6407,8 @@ public class BatteryStatsImpl extends BatteryStats {
@GuardedBy("this")
public void noteWifiRunningLocked(WorkSource ws, long elapsedRealtimeMs, long uptimeMs) {
if (!mGlobalWifiRunning) {
- mHistoryCur.states2 |= HistoryItem.STATE2_WIFI_RUNNING_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "WIFI running to: "
- + Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordState2StartEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE2_WIFI_RUNNING_FLAG);
mGlobalWifiRunning = true;
mGlobalWifiRunningTimer.startRunningLocked(elapsedRealtimeMs);
int N = ws.size();
@@ -7031,10 +6476,8 @@ public class BatteryStatsImpl extends BatteryStats {
@GuardedBy("this")
public void noteWifiStoppedLocked(WorkSource ws, long elapsedRealtimeMs, long uptimeMs) {
if (mGlobalWifiRunning) {
- mHistoryCur.states2 &= ~HistoryItem.STATE2_WIFI_RUNNING_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "WIFI stopped to: "
- + Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordState2StopEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE2_WIFI_RUNNING_FLAG);
mGlobalWifiRunning = false;
mGlobalWifiRunningTimer.stopRunningLocked(elapsedRealtimeMs);
int N = ws.size();
@@ -7082,12 +6525,7 @@ public class BatteryStatsImpl extends BatteryStats {
}
mWifiSupplState = supplState;
mWifiSupplStateTimer[supplState].startRunningLocked(elapsedRealtimeMs);
- mHistoryCur.states2 =
- (mHistoryCur.states2&~HistoryItem.STATE2_WIFI_SUPPL_STATE_MASK)
- | (supplState << HistoryItem.STATE2_WIFI_SUPPL_STATE_SHIFT);
- if (DEBUG_HISTORY) Slog.v(TAG, "Wifi suppl state " + supplState + " to: "
- + Integer.toHexString(mHistoryCur.states2));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordWifiSupplicantStateChangeEvent(elapsedRealtimeMs, uptimeMs, supplState);
}
}
@@ -7116,12 +6554,8 @@ public class BatteryStatsImpl extends BatteryStats {
if (!mWifiSignalStrengthsTimer[strengthBin].isRunningLocked()) {
mWifiSignalStrengthsTimer[strengthBin].startRunningLocked(elapsedRealtimeMs);
}
- mHistoryCur.states2 =
- (mHistoryCur.states2&~HistoryItem.STATE2_WIFI_SIGNAL_STRENGTH_MASK)
- | (strengthBin << HistoryItem.STATE2_WIFI_SIGNAL_STRENGTH_SHIFT);
- if (DEBUG_HISTORY) Slog.v(TAG, "Wifi signal strength " + strengthBin + " to: "
- + Integer.toHexString(mHistoryCur.states2));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordWifiSignalStrengthChangeEvent(elapsedRealtimeMs, uptimeMs,
+ strengthBin);
} else {
stopAllWifiSignalStrengthTimersLocked(-1, elapsedRealtimeMs);
}
@@ -7134,10 +6568,8 @@ public class BatteryStatsImpl extends BatteryStats {
@GuardedBy("this")
public void noteFullWifiLockAcquiredLocked(int uid, long elapsedRealtimeMs, long uptimeMs) {
if (mWifiFullLockNesting == 0) {
- mHistoryCur.states |= HistoryItem.STATE_WIFI_FULL_LOCK_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "WIFI full lock on to: "
- + Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordStateStartEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE_WIFI_FULL_LOCK_FLAG);
}
mWifiFullLockNesting++;
getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
@@ -7148,10 +6580,8 @@ public class BatteryStatsImpl extends BatteryStats {
public void noteFullWifiLockReleasedLocked(int uid, long elapsedRealtimeMs, long uptimeMs) {
mWifiFullLockNesting--;
if (mWifiFullLockNesting == 0) {
- mHistoryCur.states &= ~HistoryItem.STATE_WIFI_FULL_LOCK_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "WIFI full lock off to: "
- + Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordStateStopEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE_WIFI_FULL_LOCK_FLAG);
}
getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
.noteFullWifiLockReleasedLocked(elapsedRealtimeMs);
@@ -7167,10 +6597,8 @@ public class BatteryStatsImpl extends BatteryStats {
@GuardedBy("this")
public void noteWifiScanStartedLocked(int uid, long elapsedRealtimeMs, long uptimeMs) {
if (mWifiScanNesting == 0) {
- mHistoryCur.states |= HistoryItem.STATE_WIFI_SCAN_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "WIFI scan started for: "
- + Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordStateStartEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE_WIFI_SCAN_FLAG);
}
mWifiScanNesting++;
getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
@@ -7186,10 +6614,8 @@ public class BatteryStatsImpl extends BatteryStats {
public void noteWifiScanStoppedLocked(int uid, long elapsedRealtimeMs, long uptimeMs) {
mWifiScanNesting--;
if (mWifiScanNesting == 0) {
- mHistoryCur.states &= ~HistoryItem.STATE_WIFI_SCAN_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "WIFI scan stopped for: "
- + Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordStateStopEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE_WIFI_SCAN_FLAG);
}
getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
.noteWifiScanStoppedLocked(elapsedRealtimeMs);
@@ -7214,14 +6640,10 @@ public class BatteryStatsImpl extends BatteryStats {
public void noteWifiMulticastEnabledLocked(int uid, long elapsedRealtimeMs, long uptimeMs) {
uid = mapUid(uid);
if (mWifiMulticastNesting == 0) {
- mHistoryCur.states |= HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "WIFI multicast on to: "
- + Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
-
+ mHistory.recordStateStartEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG);
// Start Wifi Multicast overall timer
if (!mWifiMulticastWakelockTimer.isRunningLocked()) {
- if (DEBUG_HISTORY) Slog.v(TAG, "WiFi Multicast Overall Timer Started");
mWifiMulticastWakelockTimer.startRunningLocked(elapsedRealtimeMs);
}
}
@@ -7235,14 +6657,12 @@ public class BatteryStatsImpl extends BatteryStats {
uid = mapUid(uid);
mWifiMulticastNesting--;
if (mWifiMulticastNesting == 0) {
- mHistoryCur.states &= ~HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "WIFI multicast off to: "
- + Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordStateStopEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG);
// Stop Wifi Multicast overall timer
if (mWifiMulticastWakelockTimer.isRunningLocked()) {
- if (DEBUG_HISTORY) Slog.v(TAG, "Multicast Overall Timer Stopped");
+ if (DEBUG) Slog.v(TAG, "Multicast Overall Timer Stopped");
mWifiMulticastWakelockTimer.stopRunningLocked(elapsedRealtimeMs);
}
}
@@ -7994,8 +7414,9 @@ public class BatteryStatsImpl extends BatteryStats {
// If the start clock time has changed by more than a year, then presumably
// the previous time was completely bogus. So we are going to figure out a
// new time based on how much time has elapsed since we started counting.
- recordCurrentTimeChangeLocked(currentTimeMs, mClock.elapsedRealtime(),
- mClock.uptimeMillis());
+ mHistory.recordCurrentTimeChange(mClock.elapsedRealtime(), mClock.uptimeMillis(),
+ currentTimeMs
+ );
return currentTimeMs - (mClock.elapsedRealtime() - (mRealtimeStartUs / 1000));
}
return mStartClockTimeMs;
@@ -9416,14 +8837,14 @@ public class BatteryStatsImpl extends BatteryStats {
}
@Override
- public void noteUserActivityLocked(int type) {
+ public void noteUserActivityLocked(@PowerManager.UserActivityEvent int event) {
if (mUserActivityCounters == null) {
initUserActivityLocked();
}
- if (type >= 0 && type < NUM_USER_ACTIVITY_TYPES) {
- mUserActivityCounters[type].stepAtomic();
+ if (event >= 0 && event < NUM_USER_ACTIVITY_TYPES) {
+ mUserActivityCounters[event].stepAtomic();
} else {
- Slog.w(TAG, "Unknown user activity type " + type + " was specified.",
+ Slog.w(TAG, "Unknown user activity event " + event + " was specified.",
new Throwable());
}
}
@@ -11227,18 +10648,19 @@ public class BatteryStatsImpl extends BatteryStats {
UserInfoProvider userInfoProvider) {
init(clock);
+ mHandler = new MyHandler(handler.getLooper());
+ mConstants = new Constants(mHandler);
+
if (systemDir == null) {
mStatsFile = null;
- mBatteryStatsHistory = new BatteryStatsHistory(mHistoryBuffer);
+ mHistory = new BatteryStatsHistory(mStepDetailsCalculator, mClock);
} else {
mStatsFile = new AtomicFile(new File(systemDir, "batterystats.bin"));
- mBatteryStatsHistory = new BatteryStatsHistory(mHistoryBuffer, systemDir,
- this::getMaxHistoryFiles);
+ mHistory = new BatteryStatsHistory(systemDir, mConstants.MAX_HISTORY_FILES,
+ mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock);
}
mCheckinFile = new AtomicFile(new File(systemDir, "batterystats-checkin.bin"));
mDailyFile = new AtomicFile(new File(systemDir, "batterystats-daily.xml"));
- mHandler = new MyHandler(handler.getLooper());
- mConstants = new Constants(mHandler);
mStartCount++;
initTimersAndCounters();
mOnBattery = mOnBatteryInternal = false;
@@ -11247,7 +10669,6 @@ public class BatteryStatsImpl extends BatteryStats {
initTimes(uptimeUs, realtimeUs);
mStartPlatformVersion = mEndPlatformVersion = Build.ID;
initDischarge(realtimeUs);
- clearHistoryLocked();
updateDailyDeadlineLocked();
mPlatformIdleStateCallback = cb;
mMeasuredEnergyRetriever = energyStatsCb;
@@ -11258,12 +10679,6 @@ public class BatteryStatsImpl extends BatteryStats {
FrameworkStatsLog.write(FrameworkStatsLog.DEVICE_IDLE_MODE_STATE_CHANGED, mDeviceIdleMode);
}
- private int getMaxHistoryFiles() {
- synchronized (this) {
- return mConstants.MAX_HISTORY_FILES;
- }
- }
-
@VisibleForTesting
protected void initTimersAndCounters() {
mScreenOnTimer = new StopwatchTimer(mClock, null, -1, null, mOnBatteryTimeBase);
@@ -11345,7 +10760,7 @@ public class BatteryStatsImpl extends BatteryStats {
mDischargeUnplugLevel = 0;
mDischargePlugLevel = -1;
mDischargeCurrentLevel = 0;
- mCurrentBatteryLevel = 0;
+ mBatteryLevel = 0;
}
public void setPowerProfileLocked(PowerProfile profile) {
@@ -11732,7 +11147,7 @@ public class BatteryStatsImpl extends BatteryStats {
}
public int getHistoryUsedSize() {
- return mBatteryStatsHistory.getHistoryUsedSize();
+ return mHistory.getHistoryUsedSize();
}
@Override
@@ -11746,43 +11161,27 @@ public class BatteryStatsImpl extends BatteryStats {
*/
@VisibleForTesting
public BatteryStatsHistoryIterator createBatteryStatsHistoryIterator() {
- return new BatteryStatsHistoryIterator(mBatteryStatsHistory);
+ return mHistory.iterate();
}
@Override
public int getHistoryStringPoolSize() {
- return mHistoryTagPool.size();
+ return mHistory.getHistoryStringPoolSize();
}
@Override
public int getHistoryStringPoolBytes() {
- return mNumHistoryTagChars;
+ return mHistory.getHistoryStringPoolBytes();
}
@Override
public String getHistoryTagPoolString(int index) {
- ensureHistoryTagArray();
- HistoryTag historyTag = mHistoryTags.get(index);
- return historyTag != null ? historyTag.string : null;
+ return mHistory.getHistoryTagPoolString(index);
}
@Override
public int getHistoryTagPoolUid(int index) {
- ensureHistoryTagArray();
- HistoryTag historyTag = mHistoryTags.get(index);
- return historyTag != null ? historyTag.uid : Process.INVALID_UID;
- }
-
- private void ensureHistoryTagArray() {
- if (mHistoryTags != null) {
- return;
- }
-
- mHistoryTags = new SparseArray<>(mHistoryTagPool.size());
- for (Map.Entry<HistoryTag, Integer> entry: mHistoryTagPool.entrySet()) {
- mHistoryTags.put(entry.getValue() & ~BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG,
- entry.getKey());
- }
+ return mHistory.getHistoryTagPoolUid(index);
}
@Override
@@ -11792,15 +11191,11 @@ public class BatteryStatsImpl extends BatteryStats {
@Override
public void finishIteratingHistoryLocked() {
+ mBatteryStatsHistoryIterator.close();
mBatteryStatsHistoryIterator = null;
}
@Override
- public long getHistoryBaseTime() {
- return mHistoryBaseTimeMs;
- }
-
- @Override
public int getStartCount() {
return mStartCount;
}
@@ -11853,24 +11248,23 @@ public class BatteryStatsImpl extends BatteryStats {
long realtimeUs = mSecRealtime * 1000;
resetAllStatsLocked(mSecUptime, mSecRealtime, RESET_REASON_ADB_COMMAND);
pullPendingStateUpdatesLocked();
- addHistoryRecordLocked(mSecRealtime, mSecUptime);
- mDischargeCurrentLevel = mDischargeUnplugLevel = mDischargePlugLevel
- = mCurrentBatteryLevel = mHistoryCur.batteryLevel;
+ mHistory.writeHistoryItem(mSecRealtime, mSecUptime);
+ mDischargeCurrentLevel = mDischargeUnplugLevel = mDischargePlugLevel = mBatteryLevel;
mOnBatteryTimeBase.reset(uptimeUs, realtimeUs);
mOnBatteryScreenOffTimeBase.reset(uptimeUs, realtimeUs);
- if ((mHistoryCur.states&HistoryItem.STATE_BATTERY_PLUGGED_FLAG) == 0) {
+ if (!mBatteryPluggedIn) {
if (Display.isOnState(mScreenState)) {
- mDischargeScreenOnUnplugLevel = mHistoryCur.batteryLevel;
+ mDischargeScreenOnUnplugLevel = mBatteryLevel;
mDischargeScreenDozeUnplugLevel = 0;
mDischargeScreenOffUnplugLevel = 0;
} else if (Display.isDozeState(mScreenState)) {
mDischargeScreenOnUnplugLevel = 0;
- mDischargeScreenDozeUnplugLevel = mHistoryCur.batteryLevel;
+ mDischargeScreenDozeUnplugLevel = mBatteryLevel;
mDischargeScreenOffUnplugLevel = 0;
} else {
mDischargeScreenOnUnplugLevel = 0;
mDischargeScreenDozeUnplugLevel = 0;
- mDischargeScreenOffUnplugLevel = mHistoryCur.batteryLevel;
+ mDischargeScreenOffUnplugLevel = mBatteryLevel;
}
mDischargeAmountScreenOn = 0;
mDischargeAmountScreenOff = 0;
@@ -12014,27 +11408,12 @@ public class BatteryStatsImpl extends BatteryStats {
resetIfNotNull(mBinderThreadCpuTimesUs, false, elapsedRealtimeUs);
- mLastHistoryStepDetails = null;
- mLastStepCpuUserTimeMs = mLastStepCpuSystemTimeMs = 0;
- mCurStepCpuUserTimeMs = mCurStepCpuSystemTimeMs = 0;
- mLastStepCpuUserTimeMs = mCurStepCpuUserTimeMs = 0;
- mLastStepCpuSystemTimeMs = mCurStepCpuSystemTimeMs = 0;
- mLastStepStatUserTimeMs = mCurStepStatUserTimeMs = 0;
- mLastStepStatSystemTimeMs = mCurStepStatSystemTimeMs = 0;
- mLastStepStatIOWaitTimeMs = mCurStepStatIOWaitTimeMs = 0;
- mLastStepStatIrqTimeMs = mCurStepStatIrqTimeMs = 0;
- mLastStepStatSoftIrqTimeMs = mCurStepStatSoftIrqTimeMs = 0;
- mLastStepStatIdleTimeMs = mCurStepStatIdleTimeMs = 0;
-
mNumAllUidCpuTimeReads = 0;
mNumUidsRemoved = 0;
initDischarge(elapsedRealtimeUs);
- clearHistoryLocked();
- if (mBatteryStatsHistory != null) {
- mBatteryStatsHistory.resetAllFiles();
- }
+ mHistory.reset();
// Flush external data, gathering snapshots, but don't process it since it is pre-reset data
mIgnoreNextExternalStats = true;
@@ -12057,7 +11436,7 @@ public class BatteryStatsImpl extends BatteryStats {
for (HashMap.Entry<String, SparseIntArray> ent : active.entrySet()) {
SparseIntArray uids = ent.getValue();
for (int j=0; j<uids.size(); j++) {
- addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, i, ent.getKey(),
+ mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, i, ent.getKey(),
uids.keyAt(j));
}
}
@@ -12482,9 +11861,8 @@ public class BatteryStatsImpl extends BatteryStats {
(long) (mTmpRailStats.getWifiTotalEnergyUseduWs() / opVolt);
mWifiActivity.getMonitoredRailChargeConsumedMaMs().addCountLocked(
monitoredRailChargeConsumedMaMs);
- mHistoryCur.wifiRailChargeMah +=
- (monitoredRailChargeConsumedMaMs / MILLISECONDS_IN_HOUR);
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordWifiConsumedCharge(elapsedRealtimeMs, uptimeMs,
+ (monitoredRailChargeConsumedMaMs / MILLISECONDS_IN_HOUR));
mTmpRailStats.resetWifiTotalEnergyUsed();
if (uidEstimatedConsumptionMah != null) {
@@ -12597,9 +11975,8 @@ public class BatteryStatsImpl extends BatteryStats {
(long) (mTmpRailStats.getCellularTotalEnergyUseduWs() / opVolt);
mModemActivity.getMonitoredRailChargeConsumedMaMs().addCountLocked(
monitoredRailChargeConsumedMaMs);
- mHistoryCur.modemRailChargeMah +=
- (monitoredRailChargeConsumedMaMs / MILLISECONDS_IN_HOUR);
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordWifiConsumedCharge(elapsedRealtimeMs, uptimeMs,
+ (monitoredRailChargeConsumedMaMs / MILLISECONDS_IN_HOUR));
mTmpRailStats.resetCellularTotalEnergyUsed();
}
@@ -12867,8 +12244,8 @@ public class BatteryStatsImpl extends BatteryStats {
}
}
if (levelMaxTimeSpent == ModemActivityInfo.getNumTxPowerLevels() - 1) {
- mHistoryCur.states2 |= HistoryItem.STATE2_CELLULAR_HIGH_TX_POWER_FLAG;
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordState2StartEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE2_CELLULAR_HIGH_TX_POWER_FLAG);
}
}
@@ -14301,11 +13678,7 @@ public class BatteryStatsImpl extends BatteryStats {
mHandler.removeCallbacks(mDeferSetCharging);
if (mCharging != charging) {
mCharging = charging;
- if (charging) {
- mHistoryCur.states2 |= HistoryItem.STATE2_CHARGING_FLAG;
- } else {
- mHistoryCur.states2 &= ~HistoryItem.STATE2_CHARGING_FLAG;
- }
+ mHistory.setChargingState(charging);
mHandler.sendEmptyMessage(MSG_REPORT_CHARGING);
return true;
}
@@ -14319,6 +13692,15 @@ public class BatteryStatsImpl extends BatteryStats {
mSystemReady = true;
}
+ /**
+ * Force recording of all history events regardless of the "charging" state.
+ */
+ @VisibleForTesting
+ public void forceRecordAllHistory() {
+ mHistory.forceRecordAllHistory();
+ mRecordAllHistory = true;
+ }
+
@GuardedBy("this")
protected void setOnBatteryLocked(final long mSecRealtime, final long mSecUptime,
final boolean onBattery, final int oldStatus, final int level, final int chargeUah) {
@@ -14402,15 +13784,12 @@ public class BatteryStatsImpl extends BatteryStats {
mInitStepMode = mCurStepMode;
mModStepMode = 0;
pullPendingStateUpdatesLocked();
- mHistoryCur.batteryLevel = (byte)level;
- mHistoryCur.states &= ~HistoryItem.STATE_BATTERY_PLUGGED_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "Battery unplugged to: "
- + Integer.toHexString(mHistoryCur.states));
if (reset) {
- mRecordingHistory = true;
- startRecordingHistory(mSecRealtime, mSecUptime, reset);
+ mHistory.startRecordingHistory(mSecRealtime, mSecUptime, reset);
+ initActiveHistoryEventsLocked(mSecRealtime, mSecUptime);
}
- addHistoryRecordLocked(mSecRealtime, mSecUptime);
+ mBatteryPluggedIn = false;
+ mHistory.recordBatteryState(mSecRealtime, mSecUptime, level, mBatteryPluggedIn);
mDischargeCurrentLevel = mDischargeUnplugLevel = level;
if (Display.isOnState(screenState)) {
mDischargeScreenOnUnplugLevel = level;
@@ -14432,11 +13811,8 @@ public class BatteryStatsImpl extends BatteryStats {
} else {
mOnBattery = mOnBatteryInternal = false;
pullPendingStateUpdatesLocked();
- mHistoryCur.batteryLevel = (byte)level;
- mHistoryCur.states |= HistoryItem.STATE_BATTERY_PLUGGED_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "Battery plugged to: "
- + Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(mSecRealtime, mSecUptime);
+ mBatteryPluggedIn = true;
+ mHistory.recordBatteryState(mSecRealtime, mSecUptime, level, mBatteryPluggedIn);
mDischargeCurrentLevel = mDischargePlugLevel = level;
if (level < mDischargeUnplugLevel) {
mLowDischargeAmountSinceCharge += mDischargeUnplugLevel-level-1;
@@ -14451,45 +13827,12 @@ public class BatteryStatsImpl extends BatteryStats {
mModStepMode = 0;
}
if (doWrite || (mLastWriteTimeMs + (60 * 1000)) < mSecRealtime) {
- if (mStatsFile != null && mBatteryStatsHistory.getActiveFile() != null) {
+ if (mStatsFile != null && !mHistory.isReadOnly()) {
writeAsyncLocked();
}
}
}
- @GuardedBy("this")
- private void startRecordingHistory(final long elapsedRealtimeMs, final long uptimeMs,
- boolean reset) {
- mRecordingHistory = true;
- mHistoryCur.currentTime = mClock.currentTimeMillis();
- addHistoryBufferLocked(elapsedRealtimeMs,
- reset ? HistoryItem.CMD_RESET : HistoryItem.CMD_CURRENT_TIME,
- mHistoryCur);
- mHistoryCur.currentTime = 0;
- if (reset) {
- initActiveHistoryEventsLocked(elapsedRealtimeMs, uptimeMs);
- }
- }
-
- @GuardedBy("this")
- private void recordCurrentTimeChangeLocked(final long currentTimeMs,
- final long elapsedRealtimeMs, final long uptimeMs) {
- if (mRecordingHistory) {
- mHistoryCur.currentTime = currentTimeMs;
- addHistoryBufferLocked(elapsedRealtimeMs, HistoryItem.CMD_CURRENT_TIME, mHistoryCur);
- mHistoryCur.currentTime = 0;
- }
- }
-
- @GuardedBy("this")
- private void recordShutdownLocked(final long currentTimeMs, final long elapsedRealtimeMs) {
- if (mRecordingHistory) {
- mHistoryCur.currentTime = currentTimeMs;
- addHistoryBufferLocked(elapsedRealtimeMs, HistoryItem.CMD_SHUTDOWN, mHistoryCur);
- mHistoryCur.currentTime = 0;
- }
- }
-
private void scheduleSyncExternalStatsLocked(String reason, int updateFlags) {
if (mExternalSync != null) {
mExternalSync.scheduleSync(reason, updateFlags);
@@ -14507,8 +13850,7 @@ public class BatteryStatsImpl extends BatteryStats {
// Temperature is encoded without the signed bit, so clamp any negative temperatures to 0.
temp = Math.max(0, temp);
- reportChangesToStatsLog(mHaveBatteryLevel ? mHistoryCur : null,
- status, plugType, level);
+ reportChangesToStatsLog(status, plugType, level);
final boolean onBattery = isOnBattery(plugType, status);
if (!mHaveBatteryLevel) {
@@ -14518,52 +13860,47 @@ public class BatteryStatsImpl extends BatteryStats {
// plugged in, then twiddle our state to correctly reflect that
// since we won't be going through the full setOnBattery().
if (onBattery == mOnBattery) {
- if (onBattery) {
- mHistoryCur.states &= ~HistoryItem.STATE_BATTERY_PLUGGED_FLAG;
- } else {
- mHistoryCur.states |= HistoryItem.STATE_BATTERY_PLUGGED_FLAG;
- }
+ mHistory.setPluggedInState(!onBattery);
}
+ mBatteryStatus = status;
+ mBatteryLevel = level;
+ mBatteryChargeUah = chargeUah;
+
// Always start out assuming charging, that will be updated later.
- mHistoryCur.states2 |= HistoryItem.STATE2_CHARGING_FLAG;
- mHistoryCur.batteryStatus = (byte)status;
- mHistoryCur.batteryLevel = (byte)level;
- mHistoryCur.batteryChargeUah = chargeUah;
+ mHistory.setBatteryState(true /* charging */, status, level, chargeUah);
+
mMaxChargeStepLevel = mMinDischargeStepLevel =
mLastChargeStepLevel = mLastDischargeStepLevel = level;
- } else if (mCurrentBatteryLevel != level || mOnBattery != onBattery) {
+ } else if (mBatteryLevel != level || mOnBattery != onBattery) {
recordDailyStatsIfNeededLocked(level >= 100 && onBattery, currentTimeMs);
}
- int oldStatus = mHistoryCur.batteryStatus;
+ int oldStatus = mBatteryStatus;
if (onBattery) {
mDischargeCurrentLevel = level;
- if (!mRecordingHistory) {
- mRecordingHistory = true;
- startRecordingHistory(elapsedRealtimeMs, uptimeMs, true);
+ if (!mHistory.isRecordingHistory()) {
+ mHistory.startRecordingHistory(elapsedRealtimeMs, uptimeMs, true);
}
} else if (level < 96 &&
status != BatteryManager.BATTERY_STATUS_UNKNOWN) {
- if (!mRecordingHistory) {
- mRecordingHistory = true;
- startRecordingHistory(elapsedRealtimeMs, uptimeMs, true);
+ if (!mHistory.isRecordingHistory()) {
+ mHistory.startRecordingHistory(elapsedRealtimeMs, uptimeMs, true);
}
}
- mBatteryVoltageMv = voltageMv;
- mCurrentBatteryLevel = level;
if (mDischargePlugLevel < 0) {
mDischargePlugLevel = level;
}
if (onBattery != mOnBattery) {
- mHistoryCur.batteryLevel = (byte)level;
- mHistoryCur.batteryStatus = (byte)status;
- mHistoryCur.batteryHealth = (byte)health;
- mHistoryCur.batteryPlugType = (byte)plugType;
- mHistoryCur.batteryTemperature = (short)temp;
- mHistoryCur.batteryVoltage = (char) voltageMv;
- if (chargeUah < mHistoryCur.batteryChargeUah) {
+ mBatteryLevel = level;
+ mBatteryStatus = status;
+ mBatteryHealth = health;
+ mBatteryPlugType = plugType;
+ mBatteryTemperature = temp;
+ mBatteryVoltageMv = voltageMv;
+ mHistory.setBatteryState(status, level, health, plugType, temp, voltageMv, chargeUah);
+ if (chargeUah < mBatteryChargeUah) {
// Only record discharges
- final long chargeDiff = mHistoryCur.batteryChargeUah - chargeUah;
+ final long chargeDiff = (long) mBatteryChargeUah - chargeUah;
mDischargeCounter.addCountLocked(chargeDiff);
mDischargeScreenOffCounter.addCountLocked(chargeDiff);
if (Display.isDozeState(mScreenState)) {
@@ -14575,12 +13912,12 @@ public class BatteryStatsImpl extends BatteryStats {
mDischargeDeepDozeCounter.addCountLocked(chargeDiff);
}
}
- mHistoryCur.batteryChargeUah = chargeUah;
+ mBatteryChargeUah = chargeUah;
setOnBatteryLocked(elapsedRealtimeMs, uptimeMs, onBattery, oldStatus, level, chargeUah);
} else {
boolean changed = false;
- if (mHistoryCur.batteryLevel != level) {
- mHistoryCur.batteryLevel = (byte)level;
+ if (mBatteryLevel != level) {
+ mBatteryLevel = level;
changed = true;
// TODO(adamlesinski): Schedule the creation of a HistoryStepDetails record
@@ -14588,33 +13925,33 @@ public class BatteryStatsImpl extends BatteryStats {
mExternalSync.scheduleSyncDueToBatteryLevelChange(
mConstants.BATTERY_LEVEL_COLLECTION_DELAY_MS);
}
- if (mHistoryCur.batteryStatus != status) {
- mHistoryCur.batteryStatus = (byte)status;
+ if (mBatteryStatus != status) {
+ mBatteryStatus = status;
changed = true;
}
- if (mHistoryCur.batteryHealth != health) {
- mHistoryCur.batteryHealth = (byte)health;
+ if (mBatteryHealth != health) {
+ mBatteryHealth = health;
changed = true;
}
- if (mHistoryCur.batteryPlugType != plugType) {
- mHistoryCur.batteryPlugType = (byte)plugType;
+ if (mBatteryPlugType != plugType) {
+ mBatteryPlugType = plugType;
changed = true;
}
- if (temp >= (mHistoryCur.batteryTemperature+10)
- || temp <= (mHistoryCur.batteryTemperature-10)) {
- mHistoryCur.batteryTemperature = (short)temp;
+ if (temp >= (mBatteryTemperature + 10)
+ || temp <= (mBatteryTemperature - 10)) {
+ mBatteryTemperature = temp;
changed = true;
}
- if (voltageMv > (mHistoryCur.batteryVoltage + 20)
- || voltageMv < (mHistoryCur.batteryVoltage - 20)) {
- mHistoryCur.batteryVoltage = (char) voltageMv;
+ if (voltageMv > (mBatteryVoltageMv + 20)
+ || voltageMv < (mBatteryVoltageMv - 20)) {
+ mBatteryVoltageMv = voltageMv;
changed = true;
}
- if (chargeUah >= (mHistoryCur.batteryChargeUah + 10)
- || chargeUah <= (mHistoryCur.batteryChargeUah - 10)) {
- if (chargeUah < mHistoryCur.batteryChargeUah) {
+ if (chargeUah >= (mBatteryChargeUah + 10)
+ || chargeUah <= (mBatteryChargeUah - 10)) {
+ if (chargeUah < mBatteryChargeUah) {
// Only record discharges
- final long chargeDiff = mHistoryCur.batteryChargeUah - chargeUah;
+ final long chargeDiff = (long) mBatteryChargeUah - chargeUah;
mDischargeCounter.addCountLocked(chargeDiff);
mDischargeScreenOffCounter.addCountLocked(chargeDiff);
if (Display.isDozeState(mScreenState)) {
@@ -14626,9 +13963,10 @@ public class BatteryStatsImpl extends BatteryStats {
mDischargeDeepDozeCounter.addCountLocked(chargeDiff);
}
}
- mHistoryCur.batteryChargeUah = chargeUah;
+ mBatteryChargeUah = chargeUah;
changed = true;
}
+
long modeBits = (((long)mInitStepMode) << STEP_LEVEL_INITIAL_MODE_SHIFT)
| (((long)mModStepMode) << STEP_LEVEL_MODIFIED_MODE_SHIFT)
| (((long)(level&0xff)) << STEP_LEVEL_LEVEL_SHIFT);
@@ -14686,7 +14024,10 @@ public class BatteryStatsImpl extends BatteryStats {
mLastChargeStepLevel = level;
}
if (changed) {
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.setBatteryState(mBatteryStatus, mBatteryLevel, mBatteryHealth,
+ mBatteryPlugType, mBatteryTemperature, mBatteryVoltageMv,
+ mBatteryChargeUah);
+ mHistory.writeHistoryItem(elapsedRealtimeMs, uptimeMs);
}
}
if (!onBattery &&
@@ -14695,7 +14036,7 @@ public class BatteryStatsImpl extends BatteryStats {
// We don't record history while we are plugged in and fully charged
// (or when battery is not present). The next time we are
// unplugged, history will be cleared.
- mRecordingHistory = DEBUG;
+ mHistory.setHistoryRecordingEnabled(DEBUG);
}
mLastLearnedBatteryCapacityUah = chargeFullUah;
@@ -14714,17 +14055,18 @@ public class BatteryStatsImpl extends BatteryStats {
}
// Inform StatsLog of setBatteryState changes.
- // If this is the first reporting, pass in recentPast == null.
- private void reportChangesToStatsLog(HistoryItem recentPast,
- final int status, final int plugType, final int level) {
+ private void reportChangesToStatsLog(final int status, final int plugType, final int level) {
+ if (!mHaveBatteryLevel) {
+ return;
+ }
- if (recentPast == null || recentPast.batteryStatus != status) {
+ if (mBatteryStatus != status) {
FrameworkStatsLog.write(FrameworkStatsLog.CHARGING_STATE_CHANGED, status);
}
- if (recentPast == null || recentPast.batteryPlugType != plugType) {
+ if (mBatteryPlugType != plugType) {
FrameworkStatsLog.write(FrameworkStatsLog.PLUGGED_STATE_CHANGED, plugType);
}
- if (recentPast == null || recentPast.batteryLevel != level) {
+ if (mBatteryLevel != level) {
FrameworkStatsLog.write(FrameworkStatsLog.BATTERY_LEVEL_CHANGED, level);
}
}
@@ -14794,7 +14136,7 @@ public class BatteryStatsImpl extends BatteryStats {
if (msPerLevel <= 0) {
return -1;
}
- return (msPerLevel * mCurrentBatteryLevel) * 1000;
+ return (msPerLevel * mBatteryLevel) * 1000;
}
@Override
@@ -14824,7 +14166,7 @@ public class BatteryStatsImpl extends BatteryStats {
if (msPerLevel <= 0) {
return -1;
}
- return (msPerLevel * (100 - mCurrentBatteryLevel)) * 1000;
+ return (msPerLevel * (100 - mBatteryLevel)) * 1000;
}
/*@hide */
@@ -15255,7 +14597,8 @@ public class BatteryStatsImpl extends BatteryStats {
@GuardedBy("this")
public void shutdownLocked() {
- recordShutdownLocked(mClock.currentTimeMillis(), mClock.elapsedRealtime());
+ mHistory.recordShutdownEvent(mClock.elapsedRealtime(), mClock.uptimeMillis(),
+ mClock.currentTimeMillis());
writeSyncLocked();
mShuttingDown = true;
}
@@ -15463,7 +14806,6 @@ public class BatteryStatsImpl extends BatteryStats {
PROC_STATE_CHANGE_COLLECTION_DELAY_MS = mParser.getLong(
KEY_PROC_STATE_CHANGE_COLLECTION_DELAY_MS,
DEFAULT_PROC_STATE_CHANGE_COLLECTION_DELAY_MS);
-
MAX_HISTORY_FILES = mParser.getInt(KEY_MAX_HISTORY_FILES,
ActivityManager.isLowRamDeviceStatic() ?
DEFAULT_MAX_HISTORY_FILES_LOW_RAM_DEVICE
@@ -15474,9 +14816,20 @@ public class BatteryStatsImpl extends BatteryStats {
: DEFAULT_MAX_HISTORY_BUFFER_KB)
* 1024;
updateBatteryChargedDelayMsLocked();
+
+ onChange();
}
}
+ /**
+ * Propagates changes in constant values.
+ */
+ @VisibleForTesting
+ public void onChange() {
+ mHistory.setMaxHistoryFiles(MAX_HISTORY_FILES);
+ mHistory.setMaxHistoryBufferSize(MAX_HISTORY_BUFFER);
+ }
+
private void updateBatteryChargedDelayMsLocked() {
// a negative value indicates that we should ignore this override
final int delay = Settings.Global.getInt(mResolver,
@@ -15697,27 +15050,11 @@ public class BatteryStatsImpl extends BatteryStats {
}
private void writeHistoryLocked() {
- if (mBatteryStatsHistory.getActiveFile() == null) {
- Slog.w(TAG, "writeHistoryLocked: no history file associated with this instance");
- return;
- }
-
if (mShuttingDown) {
return;
}
- Parcel p = Parcel.obtain();
- try {
- final long start = SystemClock.uptimeMillis();
- writeHistoryBuffer(p, true);
- if (DEBUG) {
- Slog.d(TAG, "writeHistoryBuffer duration ms:"
- + (SystemClock.uptimeMillis() - start) + " bytes:" + p.dataSize());
- }
- writeParcelToFileLocked(p, mBatteryStatsHistory.getActiveFile());
- } finally {
- p.recycle();
- }
+ mHistory.writeHistory();
}
private final ReentrantLock mWriteLock = new ReentrantLock();
@@ -15756,13 +15093,6 @@ public class BatteryStatsImpl extends BatteryStats {
return;
}
- final AtomicFile activeHistoryFile = mBatteryStatsHistory.getActiveFile();
- if (activeHistoryFile == null) {
- Slog.w(TAG,
- "readLocked: no history file associated with this instance");
- return;
- }
-
mUidStats.clear();
Parcel stats = Parcel.obtain();
@@ -15775,7 +15105,7 @@ public class BatteryStatsImpl extends BatteryStats {
readSummaryFromParcel(stats);
if (DEBUG) {
Slog.d(TAG, "readLocked stats file:" + mStatsFile.getBaseFile().getPath()
- + " bytes:" + raw.length + " takes ms:" + (SystemClock.uptimeMillis()
+ + " bytes:" + raw.length + " took ms:" + (SystemClock.uptimeMillis()
- start));
}
}
@@ -15787,126 +15117,19 @@ public class BatteryStatsImpl extends BatteryStats {
stats.recycle();
}
- Parcel history = Parcel.obtain();
- try {
- final long start = SystemClock.uptimeMillis();
- if (activeHistoryFile.exists()) {
- byte[] raw = activeHistoryFile.readFully();
- if (raw.length > 0) {
- history.unmarshall(raw, 0, raw.length);
- history.setDataPosition(0);
- readHistoryBuffer(history);
- }
- if (DEBUG) {
- Slog.d(TAG, "readLocked history file::"
- + activeHistoryFile.getBaseFile().getPath()
- + " bytes:" + raw.length + " takes ms:" + (SystemClock.uptimeMillis()
- - start));
- }
- }
- } catch (Exception e) {
- Slog.e(TAG, "Error reading battery history", e);
- clearHistoryLocked();
- mBatteryStatsHistory.resetAllFiles();
- } finally {
- history.recycle();
+ if (!mHistory.readSummary()) {
+ resetAllStatsLocked(SystemClock.uptimeMillis(), SystemClock.elapsedRealtime(),
+ RESET_REASON_CORRUPT_FILE);
}
mEndPlatformVersion = Build.ID;
- if (mHistoryBuffer.dataPosition() > 0
- || mBatteryStatsHistory.getFilesNumbers().size() > 1) {
- mRecordingHistory = true;
- final long elapsedRealtimeMs = mClock.elapsedRealtime();
- final long uptimeMs = mClock.uptimeMillis();
- addHistoryBufferLocked(elapsedRealtimeMs, HistoryItem.CMD_START, mHistoryCur);
- startRecordingHistory(elapsedRealtimeMs, uptimeMs, false);
- }
+ mHistory.continueRecordingHistory();
recordDailyStatsIfNeededLocked(false, mClock.currentTimeMillis());
}
@GuardedBy("this")
- void readHistoryBuffer(Parcel in) throws ParcelFormatException {
- final int version = in.readInt();
- if (version != BatteryStatsHistory.VERSION) {
- Slog.w("BatteryStats", "readHistoryBuffer: version got " + version
- + ", expected " + BatteryStatsHistory.VERSION + "; erasing old stats");
- return;
- }
-
- final long historyBaseTime = in.readLong();
-
- mHistoryBuffer.setDataSize(0);
- mHistoryBuffer.setDataPosition(0);
-
- int bufSize = in.readInt();
- int curPos = in.dataPosition();
- if (bufSize >= (mConstants.MAX_HISTORY_BUFFER*100)) {
- throw new ParcelFormatException("File corrupt: history data buffer too large " +
- bufSize);
- } else if ((bufSize&~3) != bufSize) {
- throw new ParcelFormatException("File corrupt: history data buffer not aligned " +
- bufSize);
- } else {
- if (DEBUG_HISTORY) Slog.i(TAG, "***************** READING NEW HISTORY: " + bufSize
- + " bytes at " + curPos);
- mHistoryBuffer.appendFrom(in, curPos, bufSize);
- in.setDataPosition(curPos + bufSize);
- }
-
- if (DEBUG_HISTORY) {
- StringBuilder sb = new StringBuilder(128);
- sb.append("****************** OLD mHistoryBaseTimeMs: ");
- TimeUtils.formatDuration(mHistoryBaseTimeMs, sb);
- Slog.i(TAG, sb.toString());
- }
- mHistoryBaseTimeMs = historyBaseTime;
- if (DEBUG_HISTORY) {
- StringBuilder sb = new StringBuilder(128);
- sb.append("****************** NEW mHistoryBaseTimeMs: ");
- TimeUtils.formatDuration(mHistoryBaseTimeMs, sb);
- Slog.i(TAG, sb.toString());
- }
-
- // We are just arbitrarily going to insert 1 minute from the sample of
- // the last run until samples in this run.
- if (mHistoryBaseTimeMs > 0) {
- long oldnow = mClock.elapsedRealtime();
- mHistoryBaseTimeMs = mHistoryBaseTimeMs - oldnow + 1;
- if (DEBUG_HISTORY) {
- StringBuilder sb = new StringBuilder(128);
- sb.append("****************** ADJUSTED mHistoryBaseTimeMs: ");
- TimeUtils.formatDuration(mHistoryBaseTimeMs, sb);
- Slog.i(TAG, sb.toString());
- }
- }
- }
-
- void writeHistoryBuffer(Parcel out, boolean inclData) {
- if (DEBUG_HISTORY) {
- StringBuilder sb = new StringBuilder(128);
- sb.append("****************** WRITING mHistoryBaseTimeMs: ");
- TimeUtils.formatDuration(mHistoryBaseTimeMs, sb);
- sb.append(" mLastHistoryElapsedRealtimeMs: ");
- TimeUtils.formatDuration(mLastHistoryElapsedRealtimeMs, sb);
- Slog.i(TAG, sb.toString());
- }
- out.writeInt(BatteryStatsHistory.VERSION);
- out.writeLong(mHistoryBaseTimeMs + mLastHistoryElapsedRealtimeMs);
- if (!inclData) {
- out.writeInt(0);
- out.writeInt(0);
- return;
- }
-
- out.writeInt(mHistoryBuffer.dataSize());
- if (DEBUG_HISTORY) Slog.i(TAG, "***************** WRITING HISTORY: "
- + mHistoryBuffer.dataSize() + " bytes at " + out.dataPosition());
- out.appendFrom(mHistoryBuffer, 0, mHistoryBuffer.dataSize());
- }
-
- @GuardedBy("this")
public void readSummaryFromParcel(Parcel in) throws ParcelFormatException {
final int version = in.readInt();
@@ -15916,31 +15139,7 @@ public class BatteryStatsImpl extends BatteryStats {
return;
}
- boolean inclHistory = in.readBoolean();
- if (inclHistory) {
- readHistoryBuffer(in);
- mBatteryStatsHistory.readFromParcel(in);
- }
-
- mHistoryTagPool.clear();
- mNextHistoryTagIdx = 0;
- mNumHistoryTagChars = 0;
-
- int numTags = in.readInt();
- for (int i=0; i<numTags; i++) {
- int idx = in.readInt();
- String str = in.readString();
- int uid = in.readInt();
- HistoryTag tag = new HistoryTag();
- tag.string = str;
- tag.uid = uid;
- tag.poolIdx = idx;
- mHistoryTagPool.put(tag, idx);
- if (idx >= mNextHistoryTagIdx) {
- mNextHistoryTagIdx = idx+1;
- }
- mNumHistoryTagChars += tag.string.length() + 1;
- }
+ mHistory.readSummaryFromParcel(in);
mStartCount = in.readInt();
mUptimeUs = in.readLong();
@@ -15953,7 +15152,7 @@ public class BatteryStatsImpl extends BatteryStats {
mDischargeUnplugLevel = in.readInt();
mDischargePlugLevel = in.readInt();
mDischargeCurrentLevel = in.readInt();
- mCurrentBatteryLevel = in.readInt();
+ mBatteryLevel = in.readInt();
mEstimatedBatteryCapacityMah = in.readInt();
mLastLearnedBatteryCapacityUah = in.readInt();
mMinLearnedBatteryCapacityUah = in.readInt();
@@ -16456,19 +15655,7 @@ public class BatteryStatsImpl extends BatteryStats {
out.writeInt(VERSION);
- out.writeBoolean(inclHistory);
- if (inclHistory) {
- writeHistoryBuffer(out, true);
- mBatteryStatsHistory.writeToParcel(out);
- }
-
- out.writeInt(mHistoryTagPool.size());
- for (HashMap.Entry<HistoryTag, Integer> ent : mHistoryTagPool.entrySet()) {
- HistoryTag tag = ent.getKey();
- out.writeInt(ent.getValue());
- out.writeString(tag.string);
- out.writeInt(tag.uid);
- }
+ mHistory.writeSummaryToParcel(out, inclHistory);
out.writeInt(mStartCount);
out.writeLong(computeUptime(nowUptime, STATS_SINCE_CHARGED));
@@ -16481,7 +15668,7 @@ public class BatteryStatsImpl extends BatteryStats {
out.writeInt(mDischargeUnplugLevel);
out.writeInt(mDischargePlugLevel);
out.writeInt(mDischargeCurrentLevel);
- out.writeInt(mCurrentBatteryLevel);
+ out.writeInt(mBatteryLevel);
out.writeInt(mEstimatedBatteryCapacityMah);
out.writeInt(mLastLearnedBatteryCapacityUah);
out.writeInt(mMinLearnedBatteryCapacityUah);
diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
index 0cdd4d101459..c36d950b6cf6 100644
--- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
+++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
@@ -22,7 +22,6 @@ import android.os.BatteryConsumer;
import android.os.BatteryStats;
import android.os.BatteryUsageStats;
import android.os.BatteryUsageStatsQuery;
-import android.os.Parcel;
import android.os.Process;
import android.os.SystemClock;
import android.os.UidBatteryConsumer;
@@ -32,10 +31,8 @@ import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.os.BatteryStatsHistory;
import com.android.internal.os.PowerProfile;
-import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -220,18 +217,7 @@ public class BatteryUsageStatsProvider {
}
BatteryStatsImpl batteryStatsImpl = (BatteryStatsImpl) mStats;
-
- // Make a copy of battery history to avoid concurrent modification.
- Parcel historyBuffer = Parcel.obtain();
- historyBuffer.appendFrom(batteryStatsImpl.mHistoryBuffer, 0,
- batteryStatsImpl.mHistoryBuffer.dataSize());
-
- final File systemDir =
- batteryStatsImpl.mBatteryStatsHistory.getHistoryDirectory().getParentFile();
- final BatteryStatsHistory batteryStatsHistory =
- new BatteryStatsHistory(historyBuffer, systemDir, null);
-
- batteryUsageStatsBuilder.setBatteryHistory(batteryStatsHistory);
+ batteryUsageStatsBuilder.setBatteryHistory(batteryStatsImpl.copyHistory());
}
BatteryUsageStats stats = batteryUsageStatsBuilder.build();
diff --git a/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java b/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java
index 12e68b10c3df..eebd046b2601 100644
--- a/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java
@@ -96,6 +96,7 @@ abstract class AbstractVibratorStep extends Step {
"Turning off vibrator " + getVibratorId());
}
controller.off();
+ getVibration().stats().reportVibratorOff();
}
protected void changeAmplitude(float amplitude) {
@@ -104,6 +105,7 @@ abstract class AbstractVibratorStep extends Step {
"Amplitude changed on vibrator " + getVibratorId() + " to " + amplitude);
}
controller.setAmplitude(amplitude);
+ getVibration().stats().reportSetAmplitude();
}
/**
@@ -147,6 +149,8 @@ abstract class AbstractVibratorStep extends Step {
if (nextSegmentIndex >= effectSize && repeatIndex >= 0) {
// Count the loops that were played.
int loopSize = effectSize - repeatIndex;
+ int loopSegmentsPlayed = nextSegmentIndex - repeatIndex;
+ getVibration().stats().reportRepetition(loopSegmentsPlayed / loopSize);
nextSegmentIndex = repeatIndex + ((nextSegmentIndex - effectSize) % loopSize);
}
Step nextStep = conductor.nextVibrateStep(nextStartTime, controller, effect,
diff --git a/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java b/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java
index 3bc11c8f8322..f8b99265246a 100644
--- a/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/ComposePrimitivesVibratorStep.java
@@ -67,9 +67,10 @@ final class ComposePrimitivesVibratorStep extends AbstractVibratorStep {
Slog.d(VibrationThread.TAG, "Compose " + primitives + " primitives on vibrator "
+ controller.getVibratorInfo().getId());
}
- mVibratorOnResult = controller.on(
- primitives.toArray(new PrimitiveSegment[primitives.size()]),
- getVibration().id);
+ PrimitiveSegment[] primitivesArray =
+ primitives.toArray(new PrimitiveSegment[primitives.size()]);
+ mVibratorOnResult = controller.on(primitivesArray, getVibration().id);
+ getVibration().stats().reportComposePrimitives(mVibratorOnResult, primitivesArray);
return nextSteps(/* segmentsPlayed= */ primitives.size());
} finally {
diff --git a/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java b/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java
index 919f1be27ef9..81f52c912f28 100644
--- a/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/ComposePwleVibratorStep.java
@@ -68,8 +68,9 @@ final class ComposePwleVibratorStep extends AbstractVibratorStep {
Slog.d(VibrationThread.TAG, "Compose " + pwles + " PWLEs on vibrator "
+ controller.getVibratorInfo().getId());
}
- mVibratorOnResult = controller.on(pwles.toArray(new RampSegment[pwles.size()]),
- getVibration().id);
+ RampSegment[] pwlesArray = pwles.toArray(new RampSegment[pwles.size()]);
+ mVibratorOnResult = controller.on(pwlesArray, getVibration().id);
+ getVibration().stats().reportComposePwle(mVibratorOnResult, pwlesArray);
return nextSteps(/* segmentsPlayed= */ pwles.size());
} finally {
diff --git a/services/core/java/com/android/server/vibrator/PerformPrebakedVibratorStep.java b/services/core/java/com/android/server/vibrator/PerformPrebakedVibratorStep.java
index 601ae978f637..41902147838d 100644
--- a/services/core/java/com/android/server/vibrator/PerformPrebakedVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/PerformPrebakedVibratorStep.java
@@ -62,6 +62,7 @@ final class PerformPrebakedVibratorStep extends AbstractVibratorStep {
VibrationEffect fallback = getVibration().getFallback(prebaked.getEffectId());
mVibratorOnResult = controller.on(prebaked, getVibration().id);
+ getVibration().stats().reportPerformEffect(mVibratorOnResult, prebaked);
if (mVibratorOnResult == 0 && prebaked.shouldFallback()
&& (fallback instanceof VibrationEffect.Composed)) {
diff --git a/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java b/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java
index 1f0d2d71d25c..6fb9111793ea 100644
--- a/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/SetAmplitudeVibratorStep.java
@@ -148,7 +148,9 @@ final class SetAmplitudeVibratorStep extends AbstractVibratorStep {
"Turning on vibrator " + controller.getVibratorInfo().getId() + " for "
+ duration + "ms");
}
- return controller.on(duration, getVibration().id);
+ long vibratorOnResult = controller.on(duration, getVibration().id);
+ getVibration().stats().reportVibratorOn(vibratorOnResult);
+ return vibratorOnResult;
}
/**
diff --git a/services/core/java/com/android/server/vibrator/StartSequentialEffectStep.java b/services/core/java/com/android/server/vibrator/StartSequentialEffectStep.java
index 080a36cb2a6e..2c6fbbc945fa 100644
--- a/services/core/java/com/android/server/vibrator/StartSequentialEffectStep.java
+++ b/services/core/java/com/android/server/vibrator/StartSequentialEffectStep.java
@@ -93,10 +93,8 @@ final class StartSequentialEffectStep extends Step {
}
mVibratorsOnMaxDuration = startVibrating(effectMapping, nextSteps);
- if (mVibratorsOnMaxDuration > 0) {
- conductor.vibratorManagerHooks.noteVibratorOn(conductor.getVibration().uid,
- mVibratorsOnMaxDuration);
- }
+ conductor.vibratorManagerHooks.noteVibratorOn(conductor.getVibration().uid,
+ mVibratorsOnMaxDuration);
} finally {
if (mVibratorsOnMaxDuration >= 0) {
// It least one vibrator was started then add a finish step to wait for all
diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java
index d79837be3583..a375d0aceb54 100644
--- a/services/core/java/com/android/server/vibrator/Vibration.java
+++ b/services/core/java/com/android/server/vibrator/Vibration.java
@@ -16,10 +16,10 @@
package com.android.server.vibrator;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.CombinedVibration;
import android.os.IBinder;
-import android.os.SystemClock;
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.os.vibrator.PrebakedSegment;
@@ -30,48 +30,60 @@ import android.os.vibrator.VibrationEffectSegment;
import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;
+import com.android.internal.util.FrameworkStatsLog;
+
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
+import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.function.Function;
/** Represents a vibration request to the vibrator service. */
final class Vibration {
- private static final String TAG = "Vibration";
private static final SimpleDateFormat DEBUG_DATE_FORMAT =
new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
+ /** Vibration status with reference to values from vibratormanagerservice.proto for logging. */
enum Status {
- RUNNING,
- FINISHED,
- FINISHED_UNEXPECTED, // Didn't terminate in the usual way.
- FORWARDED_TO_INPUT_DEVICES,
- CANCELLED_BINDER_DIED,
- CANCELLED_BY_SCREEN_OFF,
- CANCELLED_BY_SETTINGS_UPDATE,
- CANCELLED_BY_USER,
- CANCELLED_BY_UNKNOWN_REASON,
- CANCELLED_SUPERSEDED,
- IGNORED_ERROR_APP_OPS,
- IGNORED_ERROR_CANCELLING,
- IGNORED_ERROR_SCHEDULING,
- IGNORED_ERROR_TOKEN,
- IGNORED_APP_OPS,
- IGNORED_BACKGROUND,
- IGNORED_UNKNOWN_VIBRATION,
- IGNORED_UNSUPPORTED,
- IGNORED_FOR_EXTERNAL,
- IGNORED_FOR_HIGHER_IMPORTANCE,
- IGNORED_FOR_ONGOING,
- IGNORED_FOR_POWER,
- IGNORED_FOR_RINGER_MODE,
- IGNORED_FOR_SETTINGS,
- IGNORED_SUPERSEDED,
+ UNKNOWN(VibrationProto.UNKNOWN),
+ RUNNING(VibrationProto.RUNNING),
+ FINISHED(VibrationProto.FINISHED),
+ FINISHED_UNEXPECTED(VibrationProto.FINISHED_UNEXPECTED),
+ FORWARDED_TO_INPUT_DEVICES(VibrationProto.FORWARDED_TO_INPUT_DEVICES),
+ CANCELLED_BINDER_DIED(VibrationProto.CANCELLED_BINDER_DIED),
+ CANCELLED_BY_SCREEN_OFF(VibrationProto.CANCELLED_BY_SCREEN_OFF),
+ CANCELLED_BY_SETTINGS_UPDATE(VibrationProto.CANCELLED_BY_SETTINGS_UPDATE),
+ CANCELLED_BY_USER(VibrationProto.CANCELLED_BY_USER),
+ CANCELLED_BY_UNKNOWN_REASON(VibrationProto.CANCELLED_BY_UNKNOWN_REASON),
+ CANCELLED_SUPERSEDED(VibrationProto.CANCELLED_SUPERSEDED),
+ IGNORED_ERROR_APP_OPS(VibrationProto.IGNORED_ERROR_APP_OPS),
+ IGNORED_ERROR_CANCELLING(VibrationProto.IGNORED_ERROR_CANCELLING),
+ IGNORED_ERROR_SCHEDULING(VibrationProto.IGNORED_ERROR_SCHEDULING),
+ IGNORED_ERROR_TOKEN(VibrationProto.IGNORED_ERROR_TOKEN),
+ IGNORED_APP_OPS(VibrationProto.IGNORED_APP_OPS),
+ IGNORED_BACKGROUND(VibrationProto.IGNORED_BACKGROUND),
+ IGNORED_UNKNOWN_VIBRATION(VibrationProto.IGNORED_UNKNOWN_VIBRATION),
+ IGNORED_UNSUPPORTED(VibrationProto.IGNORED_UNSUPPORTED),
+ IGNORED_FOR_EXTERNAL(VibrationProto.IGNORED_FOR_EXTERNAL),
+ IGNORED_FOR_HIGHER_IMPORTANCE(VibrationProto.IGNORED_FOR_HIGHER_IMPORTANCE),
+ IGNORED_FOR_ONGOING(VibrationProto.IGNORED_FOR_ONGOING),
+ IGNORED_FOR_POWER(VibrationProto.IGNORED_FOR_POWER),
+ IGNORED_FOR_RINGER_MODE(VibrationProto.IGNORED_FOR_RINGER_MODE),
+ IGNORED_FOR_SETTINGS(VibrationProto.IGNORED_FOR_SETTINGS),
+ IGNORED_SUPERSEDED(VibrationProto.IGNORED_SUPERSEDED);
+
+ private final int mProtoEnumValue;
+
+ Status(int value) {
+ mProtoEnumValue = value;
+ }
+
+ public int getProtoEnumValue() {
+ return mProtoEnumValue;
+ }
}
- /** Start time using {@link SystemClock#uptimeMillis()}, for calculations. */
- public final long startUptimeMillis;
public final VibrationAttributes attrs;
public final long id;
public final int uid;
@@ -91,17 +103,11 @@ final class Vibration {
@Nullable
private CombinedVibration mOriginalEffect;
- /**
- * Start/end times in unix epoch time. Only to be used for debugging purposes and to correlate
- * with other system events, any duration calculations should be done use
- * {@link #startUptimeMillis} so as not to be affected by discontinuities created by RTC
- * adjustments.
- */
- private final long mStartTimeDebug;
- private long mEndTimeDebug;
- /** End time using {@link SystemClock#uptimeMillis()}, for calculations. */
- private long mEndUptimeMillis;
- private Status mStatus;
+ /** Vibration status. */
+ private Vibration.Status mStatus;
+
+ /** Vibration runtime stats. */
+ private final VibrationStats mStats = new VibrationStats();
/** A {@link CountDownLatch} to enable waiting for completion. */
private final CountDownLatch mCompletionLatch = new CountDownLatch(1);
@@ -111,34 +117,35 @@ final class Vibration {
this.token = token;
this.mEffect = effect;
this.id = id;
- this.startUptimeMillis = SystemClock.uptimeMillis();
this.attrs = attrs;
this.uid = uid;
this.opPkg = opPkg;
this.reason = reason;
- mStartTimeDebug = System.currentTimeMillis();
- mStatus = Status.RUNNING;
+ mStatus = Vibration.Status.RUNNING;
+ }
+
+ VibrationStats stats() {
+ return mStats;
}
/**
- * Set the {@link Status} of this vibration and the current system time as this
+ * Set the {@link Status} of this vibration and reports the current system time as this
* vibration end time, for debugging purposes.
*
* <p>This method will only accept given value if the current status is {@link
* Status#RUNNING}.
*/
- public void end(Status status) {
+ public void end(EndInfo info) {
if (hasEnded()) {
// Vibration already ended, keep first ending status set and ignore this one.
return;
}
- mStatus = status;
- mEndUptimeMillis = SystemClock.uptimeMillis();
- mEndTimeDebug = System.currentTimeMillis();
+ mStatus = info.status;
+ mStats.reportEnded(info.endedByUid, info.endedByUsage);
mCompletionLatch.countDown();
}
- /** Waits indefinitely until another thread calls {@link #end(Status)} on this vibration. */
+ /** Waits indefinitely until another thread calls {@link #end} on this vibration. */
public void waitForEnd() throws InterruptedException {
mCompletionLatch.await();
}
@@ -228,16 +235,69 @@ final class Vibration {
/** Return {@link Vibration.DebugInfo} with read-only debug information about this vibration. */
public Vibration.DebugInfo getDebugInfo() {
- long durationMs = hasEnded() ? mEndUptimeMillis - startUptimeMillis : -1;
- return new Vibration.DebugInfo(
- mStartTimeDebug, mEndTimeDebug, durationMs, mEffect, mOriginalEffect,
- /* scale= */ 0, attrs, uid, opPkg, reason, mStatus);
+ return new Vibration.DebugInfo(mStatus, mStats, mEffect, mOriginalEffect, /* scale= */ 0,
+ attrs, uid, opPkg, reason);
+ }
+
+ /** Return {@link VibrationStats.StatsInfo} with read-only metrics about this vibration. */
+ public VibrationStats.StatsInfo getStatsInfo(long completionUptimeMillis) {
+ int vibrationType = isRepeating()
+ ? FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__REPEATED
+ : FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__SINGLE;
+ return new VibrationStats.StatsInfo(
+ uid, vibrationType, attrs.getUsage(), mStatus, mStats, completionUptimeMillis);
+ }
+
+ /** Immutable info passed as a signal to end a vibration. */
+ static final class EndInfo {
+ /** The {@link Status} to be set to the vibration when it ends with this info. */
+ @NonNull
+ public final Status status;
+ /** The UID that triggered the vibration that ended this, or -1 if undefined. */
+ public final int endedByUid;
+ /** The VibrationAttributes.USAGE_* of the vibration that ended this, or -1 if undefined. */
+ public final int endedByUsage;
+
+ EndInfo(@NonNull Vibration.Status status) {
+ this(status, /* endedByUid= */ -1, /* endedByUsage= */ -1);
+ }
+
+ EndInfo(@NonNull Vibration.Status status, int endedByUid, int endedByUsage) {
+ this.status = status;
+ this.endedByUid = endedByUid;
+ this.endedByUsage = endedByUsage;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof EndInfo)) return false;
+ EndInfo that = (EndInfo) o;
+ return endedByUid == that.endedByUid
+ && endedByUsage == that.endedByUsage
+ && status == that.status;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(status, endedByUid, endedByUsage);
+ }
+
+ @Override
+ public String toString() {
+ return "EndInfo{"
+ + "status=" + status
+ + ", endedByUid=" + endedByUid
+ + ", endedByUsage=" + endedByUsage
+ + '}';
+ }
}
/** Debug information about vibrations. */
static final class DebugInfo {
- private final long mStartTimeDebug;
- private final long mEndTimeDebug;
+ private final long mCreateTime;
+ private final long mStartTime;
+ private final long mEndTime;
private final long mDurationMs;
private final CombinedVibration mEffect;
private final CombinedVibration mOriginalEffect;
@@ -248,12 +308,13 @@ final class Vibration {
private final String mReason;
private final Status mStatus;
- DebugInfo(long startTimeDebug, long endTimeDebug, long durationMs,
- CombinedVibration effect, CombinedVibration originalEffect, float scale,
- VibrationAttributes attrs, int uid, String opPkg, String reason, Status status) {
- mStartTimeDebug = startTimeDebug;
- mEndTimeDebug = endTimeDebug;
- mDurationMs = durationMs;
+ DebugInfo(Status status, VibrationStats stats, @Nullable CombinedVibration effect,
+ @Nullable CombinedVibration originalEffect, float scale, VibrationAttributes attrs,
+ int uid, String opPkg, String reason) {
+ mCreateTime = stats.getCreateTimeDebug();
+ mStartTime = stats.getStartTimeDebug();
+ mEndTime = stats.getEndTimeDebug();
+ mDurationMs = stats.getDurationDebug();
mEffect = effect;
mOriginalEffect = originalEffect;
mScale = scale;
@@ -267,11 +328,13 @@ final class Vibration {
@Override
public String toString() {
return new StringBuilder()
- .append("startTime: ")
- .append(DEBUG_DATE_FORMAT.format(new Date(mStartTimeDebug)))
+ .append("createTime: ")
+ .append(DEBUG_DATE_FORMAT.format(new Date(mCreateTime)))
+ .append(", startTime: ")
+ .append(DEBUG_DATE_FORMAT.format(new Date(mStartTime)))
.append(", endTime: ")
- .append(mEndTimeDebug == 0 ? null
- : DEBUG_DATE_FORMAT.format(new Date(mEndTimeDebug)))
+ .append(mEndTime == 0 ? null
+ : DEBUG_DATE_FORMAT.format(new Date(mEndTime)))
.append(", durationMs: ")
.append(mDurationMs)
.append(", status: ")
@@ -296,8 +359,8 @@ final class Vibration {
/** Write this info into given {@code fieldId} on {@link ProtoOutputStream}. */
public void dumpProto(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
- proto.write(VibrationProto.START_TIME, mStartTimeDebug);
- proto.write(VibrationProto.END_TIME, mEndTimeDebug);
+ proto.write(VibrationProto.START_TIME, mStartTime);
+ proto.write(VibrationProto.END_TIME, mEndTime);
proto.write(VibrationProto.DURATION_MS, mDurationMs);
proto.write(VibrationProto.STATUS, mStatus.ordinal());
@@ -421,4 +484,5 @@ final class Vibration {
proto.end(token);
}
}
+
}
diff --git a/services/core/java/com/android/server/vibrator/VibrationStats.java b/services/core/java/com/android/server/vibrator/VibrationStats.java
new file mode 100644
index 000000000000..931be1d5d711
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/VibrationStats.java
@@ -0,0 +1,395 @@
+/*
+ * 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.vibrator;
+
+import android.os.SystemClock;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.RampSegment;
+import android.util.Slog;
+import android.util.SparseBooleanArray;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FrameworkStatsLog;
+
+/** Holds basic stats about the vibration playback and interaction with the vibrator HAL. */
+final class VibrationStats {
+ static final String TAG = "VibrationStats";
+
+ // Milestone timestamps, using SystemClock.uptimeMillis(), for calculations.
+ // - Create: time a vibration object was created, which is closer to when the service receives a
+ // vibrate request.
+ // - Start: time a vibration started to play, which is closer to the time that the
+ // VibrationEffect started playing the very first segment.
+ // - End: time a vibration ended, even if it never started to play. This can be as soon as the
+ // vibrator HAL reports it has finished the last command, or before it has even started
+ // when the vibration is ignored or cancelled.
+ // Create and end times set by VibratorManagerService only, guarded by its lock.
+ // Start times set by VibrationThread only (single-threaded).
+ private long mCreateUptimeMillis;
+ private long mStartUptimeMillis;
+ private long mEndUptimeMillis;
+
+ // Milestone timestamps, using unix epoch time, only to be used for debugging purposes and
+ // to correlate with other system events. Any duration calculations should be done with the
+ // {create/start/end}UptimeMillis counterparts so as not to be affected by discontinuities
+ // created by RTC adjustments.
+ // Set together with the *UptimeMillis counterparts.
+ private long mCreateTimeDebug;
+ private long mStartTimeDebug;
+ private long mEndTimeDebug;
+
+ // Vibration interruption tracking.
+ // Set by VibratorManagerService only, guarded by its lock.
+ private int mEndedByUid;
+ private int mEndedByUsage;
+ private int mInterruptedUsage;
+
+ // All following counters are set by VibrationThread only (single-threaded):
+ // Counts how many times the VibrationEffect was repeated.
+ private int mRepeatCount;
+ // Total duration, in milliseconds, the vibrator was active with non-zero amplitude.
+ private int mVibratorOnTotalDurationMillis;
+ // Total number of primitives used in compositions.
+ private int mVibrationCompositionTotalSize;
+ private int mVibrationPwleTotalSize;
+ // Counts how many times each IVibrator method was triggered by this vibration.
+ private int mVibratorOnCount;
+ private int mVibratorOffCount;
+ private int mVibratorSetAmplitudeCount;
+ private int mVibratorSetExternalControlCount;
+ private int mVibratorPerformCount;
+ private int mVibratorComposeCount;
+ private int mVibratorComposePwleCount;
+
+ // Ids of vibration effects and primitives used by this vibration, with support flag.
+ // Set by VibrationThread only (single-threaded).
+ private SparseBooleanArray mVibratorEffectsUsed = new SparseBooleanArray();
+ private SparseBooleanArray mVibratorPrimitivesUsed = new SparseBooleanArray();
+
+ VibrationStats() {
+ mCreateUptimeMillis = SystemClock.uptimeMillis();
+ mCreateTimeDebug = System.currentTimeMillis();
+ // Set invalid UID and VibrationAttributes.USAGE values to indicate fields are unset.
+ mEndedByUid = -1;
+ mEndedByUsage = -1;
+ mInterruptedUsage = -1;
+ }
+
+ long getCreateUptimeMillis() {
+ return mCreateUptimeMillis;
+ }
+
+ long getStartUptimeMillis() {
+ return mStartUptimeMillis;
+ }
+
+ long getEndUptimeMillis() {
+ return mEndUptimeMillis;
+ }
+
+ long getCreateTimeDebug() {
+ return mCreateTimeDebug;
+ }
+
+ long getStartTimeDebug() {
+ return mStartTimeDebug;
+ }
+
+ long getEndTimeDebug() {
+ return mEndTimeDebug;
+ }
+
+ /**
+ * Duration calculated for debugging purposes, between the creation of a vibration and the
+ * end time being reported, or -1 if the vibration has not ended.
+ */
+ long getDurationDebug() {
+ return hasEnded() ? (mEndUptimeMillis - mCreateUptimeMillis) : -1;
+ }
+
+ /** Return true if vibration reported it has ended. */
+ boolean hasEnded() {
+ return mEndUptimeMillis > 0;
+ }
+
+ /** Return true if vibration reported it has started triggering the vibrator. */
+ boolean hasStarted() {
+ return mStartUptimeMillis > 0;
+ }
+
+ /**
+ * Set the current system time as this vibration start time, for debugging purposes.
+ *
+ * <p>This indicates the vibration has started to interact with the vibrator HAL and the
+ * device may start vibrating after this point.
+ *
+ * <p>This method will only accept given value if the start timestamp was never set.
+ */
+ void reportStarted() {
+ if (hasEnded() || (mStartUptimeMillis != 0)) {
+ // Vibration already started or ended, keep first time set and ignore this one.
+ return;
+ }
+ mStartUptimeMillis = SystemClock.uptimeMillis();
+ mStartTimeDebug = System.currentTimeMillis();
+ }
+
+ /**
+ * Set status and end cause for this vibration to end, and the current system time as this
+ * vibration end time, for debugging purposes.
+ *
+ * <p>This might be triggered before {@link #reportStarted()}, which indicates this
+ * vibration was cancelled or ignored before it started triggering the vibrator.
+ *
+ * @return true if the status was accepted. This method will only accept given values if
+ * the end timestamp was never set.
+ */
+ boolean reportEnded(int endedByUid, int endedByUsage) {
+ if (hasEnded()) {
+ // Vibration already ended, keep first ending stats set and ignore this one.
+ return false;
+ }
+ mEndedByUid = endedByUid;
+ mEndedByUsage = endedByUsage;
+ mEndUptimeMillis = SystemClock.uptimeMillis();
+ mEndTimeDebug = System.currentTimeMillis();
+ return true;
+ }
+
+ /**
+ * Report this vibration has interrupted another vibration.
+ *
+ * <p>This method will only accept the first value as the one that was interrupted by this
+ * vibration, and will ignore all successive calls.
+ */
+ void reportInterruptedAnotherVibration(int interruptedUsage) {
+ if (mInterruptedUsage < 0) {
+ mInterruptedUsage = interruptedUsage;
+ }
+ }
+
+ /** Report the vibration has looped a few more times. */
+ void reportRepetition(int loops) {
+ mRepeatCount += loops;
+ }
+
+ /** Report a call to vibrator method to turn on for given duration. */
+ void reportVibratorOn(long halResult) {
+ mVibratorOnCount++;
+
+ if (halResult > 0) {
+ // If HAL result is positive then it represents the actual duration it will be ON.
+ mVibratorOnTotalDurationMillis += (int) halResult;
+ }
+ }
+
+ /** Report a call to vibrator method to turn off. */
+ void reportVibratorOff() {
+ mVibratorOffCount++;
+ }
+
+ /** Report a call to vibrator method to change the vibration amplitude. */
+ void reportSetAmplitude() {
+ mVibratorSetAmplitudeCount++;
+ }
+
+ /** Report a call to vibrator method to trigger a vibration effect. */
+ void reportPerformEffect(long halResult, PrebakedSegment prebaked) {
+ mVibratorPerformCount++;
+
+ if (halResult > 0) {
+ // If HAL result is positive then it represents the actual duration of the vibration.
+ mVibratorEffectsUsed.put(prebaked.getEffectId(), true);
+ mVibratorOnTotalDurationMillis += (int) halResult;
+ } else {
+ // Effect unsupported or request failed.
+ mVibratorEffectsUsed.put(prebaked.getEffectId(), false);
+ }
+ }
+
+ /** Report a call to vibrator method to trigger a vibration as a composition of primitives. */
+ void reportComposePrimitives(long halResult, PrimitiveSegment[] primitives) {
+ mVibratorComposeCount++;
+ mVibrationCompositionTotalSize += primitives.length;
+
+ if (halResult > 0) {
+ // If HAL result is positive then it represents the actual duration of the vibration.
+ // Remove the requested delays to update the total time the vibrator was ON.
+ for (PrimitiveSegment primitive : primitives) {
+ halResult -= primitive.getDelay();
+ mVibratorPrimitivesUsed.put(primitive.getPrimitiveId(), true);
+ }
+ if (halResult > 0) {
+ mVibratorOnTotalDurationMillis += (int) halResult;
+ }
+ } else {
+ // One or more primitives were unsupported, or request failed.
+ for (PrimitiveSegment primitive : primitives) {
+ mVibratorPrimitivesUsed.put(primitive.getPrimitiveId(), false);
+ }
+ }
+ }
+
+ /** Report a call to vibrator method to trigger a vibration as a PWLE. */
+ void reportComposePwle(long halResult, RampSegment[] segments) {
+ mVibratorComposePwleCount++;
+ mVibrationPwleTotalSize += segments.length;
+
+ if (halResult > 0) {
+ // If HAL result is positive then it represents the actual duration of the vibration.
+ // Remove the zero-amplitude segments to update the total time the vibrator was ON.
+ for (RampSegment ramp : segments) {
+ if ((ramp.getStartAmplitude() == 0) && (ramp.getEndAmplitude() == 0)) {
+ halResult -= ramp.getDuration();
+ }
+ }
+ if (halResult > 0) {
+ mVibratorOnTotalDurationMillis += (int) halResult;
+ }
+ }
+ }
+
+ /**
+ * Increment the stats for total number of times the {@code setExternalControl} method was
+ * triggered in the vibrator HAL.
+ */
+ void reportSetExternalControl() {
+ mVibratorSetExternalControlCount++;
+ }
+
+ /**
+ * Immutable metrics about this vibration, to be kept in memory until it can be pushed through
+ * {@link com.android.internal.util.FrameworkStatsLog} as a
+ * {@link com.android.internal.util.FrameworkStatsLog#VIBRATION_REPORTED}.
+ */
+ static final class StatsInfo {
+ public final int uid;
+ public final int vibrationType;
+ public final int usage;
+ public final int status;
+ public final boolean endedBySameUid;
+ public final int endedByUsage;
+ public final int interruptedUsage;
+ public final int repeatCount;
+ public final int totalDurationMillis;
+ public final int vibratorOnMillis;
+ public final int startLatencyMillis;
+ public final int endLatencyMillis;
+ public final int halComposeCount;
+ public final int halComposePwleCount;
+ public final int halOnCount;
+ public final int halOffCount;
+ public final int halPerformCount;
+ public final int halSetAmplitudeCount;
+ public final int halSetExternalControlCount;
+ public final int halCompositionSize;
+ public final int halPwleSize;
+ public final int[] halSupportedCompositionPrimitivesUsed;
+ public final int[] halSupportedEffectsUsed;
+ public final int[] halUnsupportedCompositionPrimitivesUsed;
+ public final int[] halUnsupportedEffectsUsed;
+ private boolean mIsWritten;
+
+ StatsInfo(int uid, int vibrationType, int usage, Vibration.Status status,
+ VibrationStats stats, long completionUptimeMillis) {
+ this.uid = uid;
+ this.vibrationType = vibrationType;
+ this.usage = usage;
+ this.status = status.getProtoEnumValue();
+ endedBySameUid = (uid == stats.mEndedByUid);
+ endedByUsage = stats.mEndedByUsage;
+ interruptedUsage = stats.mInterruptedUsage;
+ repeatCount = stats.mRepeatCount;
+
+ // This duration goes from the time this object was created until the time it was
+ // completed. We can use latencies to detect the times between first and last
+ // interaction with vibrator.
+ totalDurationMillis =
+ (int) Math.max(0, completionUptimeMillis - stats.mCreateUptimeMillis);
+ vibratorOnMillis = stats.mVibratorOnTotalDurationMillis;
+
+ if (stats.hasStarted()) {
+ // We only measure latencies for vibrations that actually triggered the vibrator.
+ startLatencyMillis =
+ (int) Math.max(0, stats.mStartUptimeMillis - stats.mCreateUptimeMillis);
+ endLatencyMillis =
+ (int) Math.max(0, completionUptimeMillis - stats.mEndUptimeMillis);
+ } else {
+ startLatencyMillis = endLatencyMillis = 0;
+ }
+
+ halComposeCount = stats.mVibratorComposeCount;
+ halComposePwleCount = stats.mVibratorComposePwleCount;
+ halOnCount = stats.mVibratorOnCount;
+ halOffCount = stats.mVibratorOffCount;
+ halPerformCount = stats.mVibratorPerformCount;
+ halSetAmplitudeCount = stats.mVibratorSetAmplitudeCount;
+ halSetExternalControlCount = stats.mVibratorSetExternalControlCount;
+ halCompositionSize = stats.mVibrationCompositionTotalSize;
+ halPwleSize = stats.mVibrationPwleTotalSize;
+ halSupportedCompositionPrimitivesUsed =
+ filteredKeys(stats.mVibratorPrimitivesUsed, /* supported= */ true);
+ halSupportedEffectsUsed =
+ filteredKeys(stats.mVibratorEffectsUsed, /* supported= */ true);
+ halUnsupportedCompositionPrimitivesUsed =
+ filteredKeys(stats.mVibratorPrimitivesUsed, /* supported= */ false);
+ halUnsupportedEffectsUsed =
+ filteredKeys(stats.mVibratorEffectsUsed, /* supported= */ false);
+ }
+
+ @VisibleForTesting
+ boolean isWritten() {
+ return mIsWritten;
+ }
+
+ void writeVibrationReported() {
+ if (mIsWritten) {
+ Slog.wtf(TAG, "Writing same vibration stats multiple times for uid=" + uid);
+ }
+ mIsWritten = true;
+ // Mapping from this MetricInfo representation and the atom proto VibrationReported.
+ FrameworkStatsLog.write_non_chained(
+ FrameworkStatsLog.VIBRATION_REPORTED,
+ uid, null, vibrationType, usage, status, endedBySameUid, endedByUsage,
+ interruptedUsage, repeatCount, totalDurationMillis, vibratorOnMillis,
+ startLatencyMillis, endLatencyMillis, halComposeCount, halComposePwleCount,
+ halOnCount, halOffCount, halPerformCount, halSetAmplitudeCount,
+ halSetExternalControlCount, halSupportedCompositionPrimitivesUsed,
+ halSupportedEffectsUsed, halUnsupportedCompositionPrimitivesUsed,
+ halUnsupportedEffectsUsed, halCompositionSize, halPwleSize);
+ }
+
+ private static int[] filteredKeys(SparseBooleanArray supportArray, boolean supported) {
+ int count = 0;
+ for (int i = 0; i < supportArray.size(); i++) {
+ if (supportArray.valueAt(i) == supported) count++;
+ }
+ if (count == 0) {
+ return null;
+ }
+ int pos = 0;
+ int[] res = new int[count];
+ for (int i = 0; i < supportArray.size(); i++) {
+ if (supportArray.valueAt(i) == supported) {
+ res[pos++] = supportArray.keyAt(i);
+ }
+ }
+ return res;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
index e3d806755c6e..0799b955b6f1 100644
--- a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
+++ b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
@@ -81,12 +81,12 @@ final class VibrationStepConductor implements IBinder.DeathRecipient {
private final IntArray mSignalVibratorsComplete;
@Nullable
@GuardedBy("mLock")
- private Vibration.Status mSignalCancelStatus = null;
+ private Vibration.EndInfo mSignalCancel = null;
@GuardedBy("mLock")
private boolean mSignalCancelImmediate = false;
@Nullable
- private Vibration.Status mCancelStatus = null;
+ private Vibration.EndInfo mCancelledVibrationEndInfo = null;
private boolean mCancelledImmediately = false; // hard stop
private int mPendingVibrateSteps;
private int mRemainingStartSequentialEffectSteps;
@@ -153,6 +153,9 @@ final class VibrationStepConductor implements IBinder.DeathRecipient {
// This count is decremented at the completion of the step, so we don't subtract one.
mRemainingStartSequentialEffectSteps = sequentialEffect.getEffects().size();
mNextSteps.offer(new StartSequentialEffectStep(this, sequentialEffect));
+ // Vibration will start playing in the Vibrator, following the effect timings and delays.
+ // Report current time as the vibration start time, for debugging.
+ mVibration.stats().reportStarted();
}
public Vibration getVibration() {
@@ -182,24 +185,25 @@ final class VibrationStepConductor implements IBinder.DeathRecipient {
* Calculate the {@link Vibration.Status} based on the current queue state and the expected
* number of {@link StartSequentialEffectStep} to be played.
*/
- public Vibration.Status calculateVibrationStatus() {
+ @Nullable
+ public Vibration.EndInfo calculateVibrationEndInfo() {
if (Build.IS_DEBUGGABLE) {
expectIsVibrationThread(true);
}
- if (mCancelStatus != null) {
- return mCancelStatus;
+ if (mCancelledVibrationEndInfo != null) {
+ return mCancelledVibrationEndInfo;
}
- if (mPendingVibrateSteps > 0
- || mRemainingStartSequentialEffectSteps > 0) {
- return Vibration.Status.RUNNING;
+ if (mPendingVibrateSteps > 0 || mRemainingStartSequentialEffectSteps > 0) {
+ // Vibration still running.
+ return null;
}
// No pending steps, and something happened.
if (mSuccessfulVibratorOnSteps > 0) {
- return Vibration.Status.FINISHED;
+ return new Vibration.EndInfo(Vibration.Status.FINISHED);
}
// If no step was able to turn the vibrator ON successfully.
- return Vibration.Status.IGNORED_UNSUPPORTED;
+ return new Vibration.EndInfo(Vibration.Status.IGNORED_UNSUPPORTED);
}
/**
@@ -305,45 +309,50 @@ final class VibrationStepConductor implements IBinder.DeathRecipient {
if (DEBUG) {
Slog.d(TAG, "Binder died, cancelling vibration...");
}
- notifyCancelled(Vibration.Status.CANCELLED_BINDER_DIED, /* immediate= */ false);
+ notifyCancelled(new Vibration.EndInfo(Vibration.Status.CANCELLED_BINDER_DIED),
+ /* immediate= */ false);
}
/**
* Notify the execution that cancellation is requested. This will be acted upon
* asynchronously in the VibrationThread.
*
+ * <p>Only the first cancel signal will be used to end a cancelled vibration, but subsequent
+ * calls with {@code immediate} flag set to true can still force the first cancel signal to
+ * take effect urgently.
+ *
* @param immediate indicates whether cancellation should abort urgently and skip cleanup steps.
*/
- public void notifyCancelled(@NonNull Vibration.Status cancelStatus, boolean immediate) {
+ public void notifyCancelled(@NonNull Vibration.EndInfo cancelInfo, boolean immediate) {
if (Build.IS_DEBUGGABLE) {
expectIsVibrationThread(false);
}
if (DEBUG) {
- Slog.d(TAG, "Vibration cancel requested with status=" + cancelStatus
+ Slog.d(TAG, "Vibration cancel requested with signal=" + cancelInfo
+ ", immediate=" + immediate);
}
- if ((cancelStatus == null) || !cancelStatus.name().startsWith("CANCEL")) {
- Slog.w(TAG, "Vibration cancel requested with bad status=" + cancelStatus
+ if ((cancelInfo == null) || !cancelInfo.status.name().startsWith("CANCEL")) {
+ Slog.w(TAG, "Vibration cancel requested with bad signal=" + cancelInfo
+ ", using CANCELLED_UNKNOWN_REASON to ensure cancellation.");
- cancelStatus = Vibration.Status.CANCELLED_BY_UNKNOWN_REASON;
+ cancelInfo = new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_UNKNOWN_REASON);
}
synchronized (mLock) {
- if (immediate && mSignalCancelImmediate || (mSignalCancelStatus != null)) {
+ if ((immediate && mSignalCancelImmediate) || (mSignalCancel != null)) {
if (DEBUG) {
Slog.d(TAG, "Vibration cancel request ignored as the vibration "
- + mVibration.id + "is already being cancelled with status="
- + mSignalCancelStatus + ", immediate=" + mSignalCancelImmediate);
+ + mVibration.id + "is already being cancelled with signal="
+ + mSignalCancel + ", immediate=" + mSignalCancelImmediate);
}
return;
}
mSignalCancelImmediate |= immediate;
- if (mSignalCancelStatus == null) {
- mSignalCancelStatus = cancelStatus;
+ if (mSignalCancel == null) {
+ mSignalCancel = cancelInfo;
} else {
if (DEBUG) {
- Slog.d(TAG, "Vibration cancel request new status=" + cancelStatus
- + " ignored as the vibration was already cancelled with status="
- + mSignalCancelStatus + ", but immediate flag was updated to "
+ Slog.d(TAG, "Vibration cancel request new signal=" + cancelInfo
+ + " ignored as the vibration was already cancelled with signal="
+ + mSignalCancel + ", but immediate flag was updated to "
+ mSignalCancelImmediate);
}
}
@@ -401,9 +410,9 @@ final class VibrationStepConductor implements IBinder.DeathRecipient {
if (Build.IS_DEBUGGABLE) {
expectIsVibrationThread(true); // Reads VibrationThread variables as well as signals.
}
- return (mSignalCancelStatus != mCancelStatus)
- || (mSignalCancelImmediate && !mCancelledImmediately)
- || (mSignalVibratorsComplete.size() > 0);
+ return (mSignalCancel != null && mCancelledVibrationEndInfo == null)
+ || (mSignalCancelImmediate && !mCancelledImmediately)
+ || (mSignalVibratorsComplete.size() > 0);
}
/**
@@ -416,7 +425,7 @@ final class VibrationStepConductor implements IBinder.DeathRecipient {
}
int[] vibratorsToProcess = null;
- Vibration.Status doCancelStatus = null;
+ Vibration.EndInfo doCancelInfo = null;
boolean doCancelImmediate = false;
// Collect signals to process, but don't keep the lock while processing them.
synchronized (mLock) {
@@ -426,10 +435,10 @@ final class VibrationStepConductor implements IBinder.DeathRecipient {
}
// This should only happen once.
doCancelImmediate = true;
- doCancelStatus = mSignalCancelStatus;
+ doCancelInfo = mSignalCancel;
}
- if (mSignalCancelStatus != mCancelStatus) {
- doCancelStatus = mSignalCancelStatus;
+ if ((mSignalCancel != null) && (mCancelledVibrationEndInfo == null)) {
+ doCancelInfo = mSignalCancel;
}
if (!doCancelImmediate && mSignalVibratorsComplete.size() > 0) {
// Swap out the queue of completions to process.
@@ -443,11 +452,11 @@ final class VibrationStepConductor implements IBinder.DeathRecipient {
// completion signals that were collected in this call, but we won't process them
// anyway as all steps are cancelled.
if (doCancelImmediate) {
- processCancelImmediately(doCancelStatus);
+ processCancelImmediately(doCancelInfo);
return;
}
- if (doCancelStatus != null) {
- processCancel(doCancelStatus);
+ if (doCancelInfo != null) {
+ processCancel(doCancelInfo);
}
if (vibratorsToProcess != null) {
processVibratorsComplete(vibratorsToProcess);
@@ -460,12 +469,12 @@ final class VibrationStepConductor implements IBinder.DeathRecipient {
* <p>This will remove all steps and replace them with respective results of
* {@link Step#cancel()}.
*/
- public void processCancel(Vibration.Status cancelStatus) {
+ public void processCancel(Vibration.EndInfo cancelInfo) {
if (Build.IS_DEBUGGABLE) {
expectIsVibrationThread(true);
}
- mCancelStatus = cancelStatus;
+ mCancelledVibrationEndInfo = cancelInfo;
// Vibrator callbacks should wait until all steps from the queue are properly cancelled
// and clean up steps are added back to the queue, so they can handle the callback.
List<Step> cleanUpSteps = new ArrayList<>();
@@ -483,13 +492,13 @@ final class VibrationStepConductor implements IBinder.DeathRecipient {
*
* <p>This will remove and trigger {@link Step#cancelImmediately()} in all steps, in order.
*/
- public void processCancelImmediately(Vibration.Status cancelStatus) {
+ public void processCancelImmediately(Vibration.EndInfo cancelInfo) {
if (Build.IS_DEBUGGABLE) {
expectIsVibrationThread(true);
}
mCancelledImmediately = true;
- mCancelStatus = cancelStatus;
+ mCancelledVibrationEndInfo = cancelInfo;
Step step;
while ((step = pollNext()) != null) {
step.cancelImmediately();
diff --git a/services/core/java/com/android/server/vibrator/VibrationThread.java b/services/core/java/com/android/server/vibrator/VibrationThread.java
index cecc5c04dedc..e824db105abc 100644
--- a/services/core/java/com/android/server/vibrator/VibrationThread.java
+++ b/services/core/java/com/android/server/vibrator/VibrationThread.java
@@ -76,7 +76,7 @@ final class VibrationThread extends Thread {
* cleanup tasks, and should not be given new work until {@link #onVibrationThreadReleased}
* is called.
*/
- void onVibrationCompleted(long vibrationId, Vibration.Status status);
+ void onVibrationCompleted(long vibrationId, @NonNull Vibration.EndInfo vibrationEndInfo);
/**
* Tells the manager that the VibrationThread is finished with the previous vibration and
@@ -237,7 +237,8 @@ final class VibrationThread extends Thread {
try {
runCurrentVibrationWithWakeLockAndDeathLink();
} finally {
- clientVibrationCompleteIfNotAlready(Vibration.Status.FINISHED_UNEXPECTED);
+ clientVibrationCompleteIfNotAlready(
+ new Vibration.EndInfo(Vibration.Status.FINISHED_UNEXPECTED));
}
} finally {
mWakeLock.release();
@@ -255,7 +256,8 @@ final class VibrationThread extends Thread {
vibrationBinderToken.linkToDeath(mExecutingConductor, 0);
} catch (RemoteException e) {
Slog.e(TAG, "Error linking vibration to token death", e);
- clientVibrationCompleteIfNotAlready(Vibration.Status.IGNORED_ERROR_TOKEN);
+ clientVibrationCompleteIfNotAlready(
+ new Vibration.EndInfo(Vibration.Status.IGNORED_ERROR_TOKEN));
return;
}
// Ensure that the unlink always occurs now.
@@ -274,11 +276,11 @@ final class VibrationThread extends Thread {
// Indicate that the vibration is complete. This can be called multiple times only for
// convenience of handling error conditions - an error after the client is complete won't
// affect the status.
- private void clientVibrationCompleteIfNotAlready(Vibration.Status completedStatus) {
+ private void clientVibrationCompleteIfNotAlready(@NonNull Vibration.EndInfo vibrationEndInfo) {
if (!mCalledVibrationCompleteCallback) {
mCalledVibrationCompleteCallback = true;
mVibratorManagerHooks.onVibrationCompleted(
- mExecutingConductor.getVibration().id, completedStatus);
+ mExecutingConductor.getVibration().id, vibrationEndInfo);
}
}
@@ -298,12 +300,15 @@ final class VibrationThread extends Thread {
mExecutingConductor.runNextStep();
}
- Vibration.Status status = mExecutingConductor.calculateVibrationStatus();
- // This block can only run once due to mCalledVibrationCompleteCallback.
- if (status != Vibration.Status.RUNNING && !mCalledVibrationCompleteCallback) {
- // First time vibration stopped running, start clean-up tasks and notify
- // callback immediately.
- clientVibrationCompleteIfNotAlready(status);
+ if (!mCalledVibrationCompleteCallback) {
+ // This block can only run once due to mCalledVibrationCompleteCallback.
+ Vibration.EndInfo vibrationEndInfo =
+ mExecutingConductor.calculateVibrationEndInfo();
+ if (vibrationEndInfo != null) {
+ // First time vibration stopped running, start clean-up tasks and notify
+ // callback immediately.
+ clientVibrationCompleteIfNotAlready(vibrationEndInfo);
+ }
}
}
} finally {
diff --git a/services/core/java/com/android/server/vibrator/VibratorFrameworkStatsLogger.java b/services/core/java/com/android/server/vibrator/VibratorFrameworkStatsLogger.java
new file mode 100644
index 000000000000..f600a2964cbc
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/VibratorFrameworkStatsLogger.java
@@ -0,0 +1,140 @@
+/*
+ * 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.vibrator;
+
+import android.os.Handler;
+import android.os.SystemClock;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FrameworkStatsLog;
+
+import java.util.ArrayDeque;
+import java.util.Queue;
+
+/** Helper class for async write of atoms to {@link FrameworkStatsLog} using a given Handler. */
+public class VibratorFrameworkStatsLogger {
+ private static final String TAG = "VibratorFrameworkStatsLogger";
+
+ // VibrationReported pushed atom needs to be throttled to at most one every 10ms.
+ private static final int VIBRATION_REPORTED_MIN_INTERVAL_MILLIS = 10;
+ // We accumulate events that should take 3s to write and drop excessive metrics.
+ private static final int VIBRATION_REPORTED_MAX_QUEUE_SIZE = 300;
+ // Warning about dropping entries after this amount of atoms were dropped by the throttle.
+ private static final int VIBRATION_REPORTED_WARNING_QUEUE_SIZE = 200;
+
+ private final Object mLock = new Object();
+ private final Handler mHandler;
+ private final long mVibrationReportedLogIntervalMillis;
+ private final long mVibrationReportedQueueMaxSize;
+ private final Runnable mConsumeVibrationStatsQueueRunnable =
+ () -> writeVibrationReportedFromQueue();
+
+ @GuardedBy("mLock")
+ private long mLastVibrationReportedLogUptime;
+ @GuardedBy("mLock")
+ private Queue<VibrationStats.StatsInfo> mVibrationStatsQueue = new ArrayDeque<>();
+
+ VibratorFrameworkStatsLogger(Handler handler) {
+ this(handler, VIBRATION_REPORTED_MIN_INTERVAL_MILLIS, VIBRATION_REPORTED_MAX_QUEUE_SIZE);
+ }
+
+ @VisibleForTesting
+ VibratorFrameworkStatsLogger(Handler handler, int vibrationReportedLogIntervalMillis,
+ int vibrationReportedQueueMaxSize) {
+ mHandler = handler;
+ mVibrationReportedLogIntervalMillis = vibrationReportedLogIntervalMillis;
+ mVibrationReportedQueueMaxSize = vibrationReportedQueueMaxSize;
+ }
+
+ /** Writes {@link FrameworkStatsLog#VIBRATOR_STATE_CHANGED} for state ON. */
+ public void writeVibratorStateOnAsync(int uid, long duration) {
+ mHandler.post(
+ () -> FrameworkStatsLog.write_non_chained(
+ FrameworkStatsLog.VIBRATOR_STATE_CHANGED, uid, null,
+ FrameworkStatsLog.VIBRATOR_STATE_CHANGED__STATE__ON, duration));
+ }
+
+ /** Writes {@link FrameworkStatsLog#VIBRATOR_STATE_CHANGED} for state OFF. */
+ public void writeVibratorStateOffAsync(int uid) {
+ mHandler.post(
+ () -> FrameworkStatsLog.write_non_chained(
+ FrameworkStatsLog.VIBRATOR_STATE_CHANGED, uid, null,
+ FrameworkStatsLog.VIBRATOR_STATE_CHANGED__STATE__OFF,
+ /* duration= */ 0));
+ }
+
+ /**
+ * Writes {@link FrameworkStatsLog#VIBRATION_REPORTED} for given vibration.
+ *
+ * <p>This atom is throttled to be pushed once every 10ms, so this logger can keep a queue of
+ * {@link VibrationStats.StatsInfo} entries to slowly write to statsd.
+ */
+ public void writeVibrationReportedAsync(VibrationStats.StatsInfo metrics) {
+ boolean needsScheduling;
+ long scheduleDelayMs;
+ int queueSize;
+
+ synchronized (mLock) {
+ queueSize = mVibrationStatsQueue.size();
+ needsScheduling = (queueSize == 0);
+
+ if (queueSize < mVibrationReportedQueueMaxSize) {
+ mVibrationStatsQueue.offer(metrics);
+ }
+
+ long nextLogUptime =
+ mLastVibrationReportedLogUptime + mVibrationReportedLogIntervalMillis;
+ scheduleDelayMs = Math.max(0, nextLogUptime - SystemClock.uptimeMillis());
+ }
+
+ if ((queueSize + 1) == VIBRATION_REPORTED_WARNING_QUEUE_SIZE) {
+ Slog.w(TAG, " Approaching vibration metrics queue limit, events might be dropped.");
+ }
+
+ if (needsScheduling) {
+ mHandler.postDelayed(mConsumeVibrationStatsQueueRunnable, scheduleDelayMs);
+ }
+ }
+
+ /** Writes next {@link FrameworkStatsLog#VIBRATION_REPORTED} from the queue. */
+ private void writeVibrationReportedFromQueue() {
+ boolean needsScheduling;
+ VibrationStats.StatsInfo stats;
+
+ synchronized (mLock) {
+ stats = mVibrationStatsQueue.poll();
+ needsScheduling = !mVibrationStatsQueue.isEmpty();
+
+ if (stats != null) {
+ mLastVibrationReportedLogUptime = SystemClock.uptimeMillis();
+ }
+ }
+
+ if (stats == null) {
+ Slog.w(TAG, "Unexpected vibration metric flush with empty queue. Ignoring.");
+ } else {
+ stats.writeVibrationReported();
+ }
+
+ if (needsScheduling) {
+ mHandler.postDelayed(mConsumeVibrationStatsQueueRunnable,
+ mVibrationReportedLogIntervalMillis);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 5ac2f4f27452..2f12a820eb81 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -129,6 +129,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
private final Context mContext;
private final PowerManager.WakeLock mWakeLock;
private final IBatteryStats mBatteryStatsService;
+ private final VibratorFrameworkStatsLogger mFrameworkStatsLogger;
private final Handler mHandler;
private final VibrationThread mVibrationThread;
private final AppOpsManager mAppOps;
@@ -163,10 +164,12 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
// When the system is entering a non-interactive state, we want to cancel
// vibrations in case a misbehaving app has abandoned them.
if (shouldCancelOnScreenOffLocked(mNextVibration)) {
- clearNextVibrationLocked(Vibration.Status.CANCELLED_BY_SCREEN_OFF);
+ clearNextVibrationLocked(
+ new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SCREEN_OFF));
}
if (shouldCancelOnScreenOffLocked(mCurrentVibration)) {
- mCurrentVibration.notifyCancelled(Vibration.Status.CANCELLED_BY_SCREEN_OFF,
+ mCurrentVibration.notifyCancelled(
+ new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SCREEN_OFF),
/* immediate= */ false);
}
}
@@ -207,6 +210,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
mVibratorManagerRecords = new VibratorManagerRecords(dumpLimit);
mBatteryStatsService = injector.getBatteryStatsService();
+ mFrameworkStatsLogger = injector.getFrameworkStatsLogger(mHandler);
mAppOps = mContext.getSystemService(AppOpsManager.class);
@@ -384,7 +388,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
* The Vibration is only returned if it is ongoing after this method returns.
*/
@Nullable
- private Vibration vibrateInternal(int uid, String opPkg, @NonNull CombinedVibration effect,
+ @VisibleForTesting
+ Vibration vibrateInternal(int uid, String opPkg, @NonNull CombinedVibration effect,
@Nullable VibrationAttributes attrs, String reason, IBinder token) {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "vibrate, reason = " + reason);
try {
@@ -399,6 +404,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
return null;
}
attrs = fixupVibrationAttributes(attrs, effect);
+ // Create Vibration.Stats as close to the received request as possible, for tracking.
Vibration vib = new Vibration(token, mNextVibrationId.getAndIncrement(), effect, attrs,
uid, opPkg, reason);
fillVibrationFallbacks(vib, effect);
@@ -413,32 +419,56 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
if (DEBUG) {
Slog.d(TAG, "Starting vibrate for vibration " + vib.id);
}
- Vibration.Status ignoreStatus = shouldIgnoreVibrationLocked(
- vib.uid, vib.opPkg, vib.attrs);
-
- if (ignoreStatus == null) {
- ignoreStatus = shouldIgnoreVibrationForOngoingLocked(vib);
+ int ignoredByUid = -1;
+ int ignoredByUsage = -1;
+ Vibration.Status status = null;
+
+ // Check if user settings or DnD is set to ignore this vibration.
+ status = shouldIgnoreVibrationLocked(vib.uid, vib.opPkg, vib.attrs);
+
+ // Check if something has external control, assume it's more important.
+ if ((status == null) && (mCurrentExternalVibration != null)) {
+ status = Vibration.Status.IGNORED_FOR_EXTERNAL;
+ ignoredByUid = mCurrentExternalVibration.externalVibration.getUid();
+ ignoredByUsage = mCurrentExternalVibration.externalVibration
+ .getVibrationAttributes().getUsage();
}
- if (ignoreStatus != null) {
- endVibrationLocked(vib, ignoreStatus);
- return vib;
+ // Check if ongoing vibration is more important than this vibration.
+ if (status == null) {
+ status = shouldIgnoreVibrationForOngoingLocked(vib);
+ if (status != null) {
+ ignoredByUid = mCurrentVibration.getVibration().uid;
+ ignoredByUsage = mCurrentVibration.getVibration().attrs.getUsage();
+ }
}
- final long ident = Binder.clearCallingIdentity();
- try {
- if (mCurrentVibration != null) {
- mCurrentVibration.notifyCancelled(Vibration.Status.CANCELLED_SUPERSEDED,
- /* immediate= */ false);
- }
- Vibration.Status status = startVibrationLocked(vib);
- if (status != Vibration.Status.RUNNING) {
- endVibrationLocked(vib, status);
+ // If not ignored so far then try to start this vibration.
+ if (status == null) {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ if (mCurrentVibration != null) {
+ vib.stats().reportInterruptedAnotherVibration(
+ mCurrentVibration.getVibration().attrs.getUsage());
+ mCurrentVibration.notifyCancelled(
+ new Vibration.EndInfo(
+ Vibration.Status.CANCELLED_SUPERSEDED, vib.uid,
+ vib.attrs.getUsage()),
+ /* immediate= */ false);
+ }
+ status = startVibrationLocked(vib);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
}
- return vib;
- } finally {
- Binder.restoreCallingIdentity(ident);
}
+
+ // Ignored or failed to start the vibration, end it and report metrics right away.
+ if (status != Vibration.Status.RUNNING) {
+ endVibrationLocked(vib,
+ new Vibration.EndInfo(status, ignoredByUid, ignoredByUsage),
+ /* shouldWriteStats= */ true);
+ }
+ return vib;
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
@@ -457,26 +487,28 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
if (DEBUG) {
Slog.d(TAG, "Canceling vibration");
}
+ Vibration.EndInfo cancelledByUserInfo =
+ new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER);
final long ident = Binder.clearCallingIdentity();
try {
if (mNextVibration != null
&& shouldCancelVibration(mNextVibration.getVibration(),
usageFilter, token)) {
- clearNextVibrationLocked(Vibration.Status.CANCELLED_BY_USER);
+ clearNextVibrationLocked(cancelledByUserInfo);
}
if (mCurrentVibration != null
&& shouldCancelVibration(mCurrentVibration.getVibration(),
usageFilter, token)) {
- mCurrentVibration.notifyCancelled(Vibration.Status.CANCELLED_BY_USER,
- /* immediate= */false);
+ mCurrentVibration.notifyCancelled(
+ cancelledByUserInfo, /* immediate= */false);
}
if (mCurrentExternalVibration != null
&& shouldCancelVibration(
mCurrentExternalVibration.externalVibration.getVibrationAttributes(),
usageFilter)) {
- mCurrentExternalVibration.externalVibration.mute();
- endExternalVibrateLocked(Vibration.Status.CANCELLED_BY_USER,
- /* continueExternalControl= */ false);
+ mCurrentExternalVibration.mute();
+ endExternalVibrateLocked(
+ cancelledByUserInfo, /* continueExternalControl= */ false);
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -604,15 +636,17 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
Slog.d(TAG, "Canceling vibration because settings changed: "
+ (inputDevicesChanged ? "input devices changed" : ignoreStatus));
}
- mCurrentVibration.notifyCancelled(Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE,
+ mCurrentVibration.notifyCancelled(
+ new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE),
/* immediate= */ false);
}
}
}
- private void setExternalControl(boolean externalControl) {
+ private void setExternalControl(boolean externalControl, VibrationStats vibrationStats) {
for (int i = 0; i < mVibrators.size(); i++) {
mVibrators.valueAt(i).setExternalControl(externalControl);
+ vibrationStats.reportSetExternalControl();
}
}
@@ -654,7 +688,9 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
}
// If there's already a vibration queued (waiting for the previous one to finish
// cancelling), end it cleanly and replace it with the new one.
- clearNextVibrationLocked(Vibration.Status.IGNORED_SUPERSEDED);
+ clearNextVibrationLocked(
+ new Vibration.EndInfo(Vibration.Status.IGNORED_SUPERSEDED,
+ vib.uid, vib.attrs.getUsage()));
mNextVibration = conductor;
return Vibration.Status.RUNNING;
} finally {
@@ -671,6 +707,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
switch (mode) {
case AppOpsManager.MODE_ALLOWED:
Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
+ // Make sure mCurrentVibration is set while triggering the VibrationThread.
mCurrentVibration = conductor;
if (!mVibrationThread.runVibrationOnVibrationThread(mCurrentVibration)) {
// Shouldn't happen. The method call already logs a wtf.
@@ -690,18 +727,26 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
}
@GuardedBy("mLock")
- private void endVibrationLocked(Vibration vib, Vibration.Status status) {
- vib.end(status);
- logVibrationStatus(vib.uid, vib.attrs, status);
+ private void endVibrationLocked(Vibration vib, Vibration.EndInfo vibrationEndInfo,
+ boolean shouldWriteStats) {
+ vib.end(vibrationEndInfo);
+ logVibrationStatus(vib.uid, vib.attrs, vibrationEndInfo.status);
mVibratorManagerRecords.record(vib);
+ if (shouldWriteStats) {
+ mFrameworkStatsLogger.writeVibrationReportedAsync(
+ vib.getStatsInfo(/* completionUptimeMillis= */ SystemClock.uptimeMillis()));
+ }
}
@GuardedBy("mLock")
- private void endVibrationLocked(ExternalVibrationHolder vib, Vibration.Status status) {
- vib.end(status);
+ private void endVibrationAndWriteStatsLocked(ExternalVibrationHolder vib,
+ Vibration.EndInfo vibrationEndInfo) {
+ vib.end(vibrationEndInfo);
logVibrationStatus(vib.externalVibration.getUid(),
- vib.externalVibration.getVibrationAttributes(), status);
+ vib.externalVibration.getVibrationAttributes(), vibrationEndInfo.status);
mVibratorManagerRecords.record(vib);
+ mFrameworkStatsLogger.writeVibrationReportedAsync(
+ vib.getStatsInfo(/* completionUptimeMillis= */ SystemClock.uptimeMillis()));
}
private void logVibrationStatus(int uid, VibrationAttributes attrs, Vibration.Status status) {
@@ -744,15 +789,17 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
}
@GuardedBy("mLock")
- private void reportFinishedVibrationLocked(Vibration.Status status) {
+ private void reportFinishedVibrationLocked(Vibration.EndInfo vibrationEndInfo) {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "reportFinishVibrationLocked");
Trace.asyncTraceEnd(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
try {
Vibration vib = mCurrentVibration.getVibration();
if (DEBUG) {
- Slog.d(TAG, "Reporting vibration " + vib.id + " finished with status " + status);
+ Slog.d(TAG, "Reporting vibration " + vib.id + " finished with " + vibrationEndInfo);
}
- endVibrationLocked(vib, status);
+ // DO NOT write metrics at this point, wait for the VibrationThread to report the
+ // vibration was released, after all cleanup. The metrics will be reported then.
+ endVibrationLocked(vib, vibrationEndInfo, /* shouldWriteStats= */ false);
finishAppOpModeLocked(vib.uid, vib.opPkg);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
@@ -791,11 +838,6 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
@GuardedBy("mLock")
@Nullable
private Vibration.Status shouldIgnoreVibrationForOngoingLocked(Vibration vib) {
- if (mCurrentExternalVibration != null) {
- // If something has external control of the vibrator, assume that it's more important.
- return Vibration.Status.IGNORED_FOR_EXTERNAL;
- }
-
if (mCurrentVibration == null || vib.isRepeating()) {
// Incoming repeating vibrations always take precedence over ongoing vibrations.
return null;
@@ -1122,7 +1164,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
}
Vibration vib = conductor.getVibration();
return mVibrationSettings.shouldCancelVibrationOnScreenOff(
- vib.uid, vib.opPkg, vib.attrs.getUsage(), vib.startUptimeMillis);
+ vib.uid, vib.opPkg, vib.attrs.getUsage(), vib.stats().getCreateUptimeMillis());
}
@GuardedBy("mLock")
@@ -1158,6 +1200,10 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
BatteryStats.SERVICE_NAME));
}
+ VibratorFrameworkStatsLogger getFrameworkStatsLogger(Handler handler) {
+ return new VibratorFrameworkStatsLogger(handler);
+ }
+
VibratorController createVibratorController(int vibratorId,
VibratorController.OnVibrationCompleteListener listener) {
return new VibratorController(vibratorId, listener);
@@ -1197,6 +1243,10 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
public void noteVibratorOn(int uid, long duration) {
try {
if (duration <= 0) {
+ // Tried to turn vibrator ON and got:
+ // duration == 0: Unsupported effect/method or zero-amplitude segment.
+ // duration < 0: Unexpected error triggering the vibrator.
+ // Skip battery stats and atom metric for VibratorStageChanged to ON.
return;
}
if (duration == Long.MAX_VALUE) {
@@ -1205,10 +1255,9 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
duration = BATTERY_STATS_REPEATING_VIBRATION_DURATION;
}
mBatteryStatsService.noteVibratorOn(uid, duration);
- FrameworkStatsLog.write_non_chained(FrameworkStatsLog.VIBRATOR_STATE_CHANGED,
- uid, null, FrameworkStatsLog.VIBRATOR_STATE_CHANGED__STATE__ON,
- duration);
+ mFrameworkStatsLogger.writeVibratorStateOnAsync(uid, duration);
} catch (RemoteException e) {
+ Slog.e(TAG, "Error logging VibratorStateChanged to ON", e);
}
}
@@ -1216,22 +1265,21 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
public void noteVibratorOff(int uid) {
try {
mBatteryStatsService.noteVibratorOff(uid);
- FrameworkStatsLog.write_non_chained(FrameworkStatsLog.VIBRATOR_STATE_CHANGED,
- uid, null, FrameworkStatsLog.VIBRATOR_STATE_CHANGED__STATE__OFF,
- /* duration= */ 0);
+ mFrameworkStatsLogger.writeVibratorStateOffAsync(uid);
} catch (RemoteException e) {
+ Slog.e(TAG, "Error logging VibratorStateChanged to OFF", e);
}
}
@Override
- public void onVibrationCompleted(long vibrationId, Vibration.Status status) {
+ public void onVibrationCompleted(long vibrationId, Vibration.EndInfo vibrationEndInfo) {
if (DEBUG) {
- Slog.d(TAG, "Vibration " + vibrationId + " finished with status " + status);
+ Slog.d(TAG, "Vibration " + vibrationId + " finished with " + vibrationEndInfo);
}
synchronized (mLock) {
if (mCurrentVibration != null
&& mCurrentVibration.getVibration().id == vibrationId) {
- reportFinishedVibrationLocked(status);
+ reportFinishedVibrationLocked(vibrationEndInfo);
}
}
}
@@ -1251,13 +1299,21 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
"VibrationId mismatch on release. expected=%d, released=%d",
mCurrentVibration.getVibration().id, vibrationId));
}
- mCurrentVibration = null;
+ if (mCurrentVibration != null) {
+ // This is when we consider the current vibration complete, so report metrics.
+ mFrameworkStatsLogger.writeVibrationReportedAsync(
+ mCurrentVibration.getVibration().getStatsInfo(
+ /* completionUptimeMillis= */ SystemClock.uptimeMillis()));
+ mCurrentVibration = null;
+ }
if (mNextVibration != null) {
VibrationStepConductor nextConductor = mNextVibration;
mNextVibration = null;
Vibration.Status status = startVibrationOnThreadLocked(nextConductor);
if (status != Vibration.Status.RUNNING) {
- endVibrationLocked(nextConductor.getVibration(), status);
+ // Failed to start the vibration, end it and report metrics right away.
+ endVibrationLocked(nextConductor.getVibration(),
+ new Vibration.EndInfo(status), /* shouldWriteStats= */ true);
}
}
}
@@ -1325,31 +1381,48 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
private final class ExternalVibrationHolder implements IBinder.DeathRecipient {
public final ExternalVibration externalVibration;
+ public final VibrationStats stats = new VibrationStats();
public int scale;
- private final long mStartUptimeMillis;
- private final long mStartTimeDebug;
-
- private long mEndUptimeMillis;
- private long mEndTimeDebug;
private Vibration.Status mStatus;
private ExternalVibrationHolder(ExternalVibration externalVibration) {
this.externalVibration = externalVibration;
this.scale = IExternalVibratorService.SCALE_NONE;
- mStartUptimeMillis = SystemClock.uptimeMillis();
- mStartTimeDebug = System.currentTimeMillis();
mStatus = Vibration.Status.RUNNING;
}
- public void end(Vibration.Status status) {
+ public void mute() {
+ externalVibration.mute();
+ }
+
+ public void linkToDeath() {
+ externalVibration.linkToDeath(this);
+ }
+
+ public void unlinkToDeath() {
+ externalVibration.unlinkToDeath(this);
+ }
+
+ public boolean isHoldingSameVibration(ExternalVibration externalVibration) {
+ return this.externalVibration.equals(externalVibration);
+ }
+
+ public void end(Vibration.EndInfo info) {
if (mStatus != Vibration.Status.RUNNING) {
- // Vibration already ended, keep first ending status set and ignore this one.
+ // Already ended, ignore this call
return;
}
- mStatus = status;
- mEndUptimeMillis = SystemClock.uptimeMillis();
- mEndTimeDebug = System.currentTimeMillis();
+ mStatus = info.status;
+ stats.reportEnded(info.endedByUid, info.endedByUsage);
+
+ if (stats.hasStarted()) {
+ // External vibration doesn't have feedback from total time the vibrator was playing
+ // with non-zero amplitude, so we use the duration between start and end times of
+ // the vibration as the time the vibrator was ON, since the haptic channels are
+ // open for this duration and can receive vibration waveform data.
+ stats.reportVibratorOn(stats.getEndUptimeMillis() - stats.getStartUptimeMillis());
+ }
}
public void binderDied() {
@@ -1358,19 +1431,26 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
if (DEBUG) {
Slog.d(TAG, "External vibration finished because binder died");
}
- endExternalVibrateLocked(Vibration.Status.CANCELLED_BINDER_DIED,
+ endExternalVibrateLocked(
+ new Vibration.EndInfo(Vibration.Status.CANCELLED_BINDER_DIED),
/* continueExternalControl= */ false);
}
}
}
public Vibration.DebugInfo getDebugInfo() {
- long durationMs = mEndUptimeMillis == 0 ? -1 : mEndUptimeMillis - mStartUptimeMillis;
return new Vibration.DebugInfo(
- mStartTimeDebug, mEndTimeDebug, durationMs,
- /* effect= */ null, /* originalEffect= */ null, scale,
+ mStatus, stats, /* effect= */ null, /* originalEffect= */ null, scale,
externalVibration.getVibrationAttributes(), externalVibration.getUid(),
- externalVibration.getPackage(), /* reason= */ null, mStatus);
+ externalVibration.getPackage(), /* reason= */ null);
+ }
+
+ public VibrationStats.StatsInfo getStatsInfo(long completionUptimeMillis) {
+ return new VibrationStats.StatsInfo(
+ externalVibration.getUid(),
+ FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__EXTERNAL,
+ externalVibration.getVibrationAttributes().getUsage(), mStatus, stats,
+ completionUptimeMillis);
}
}
@@ -1500,9 +1580,11 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
/** Clears mNextVibration if set, ending it cleanly */
@GuardedBy("mLock")
- private void clearNextVibrationLocked(Vibration.Status endStatus) {
+ private void clearNextVibrationLocked(Vibration.EndInfo vibrationEndInfo) {
if (mNextVibration != null) {
- endVibrationLocked(mNextVibration.getVibration(), endStatus);
+ // Clearing next vibration before playing it, end it and report metrics right away.
+ endVibrationLocked(mNextVibration.getVibration(), vibrationEndInfo,
+ /* shouldWriteStats= */ true);
mNextVibration = null;
}
}
@@ -1510,25 +1592,25 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
/**
* Ends the external vibration, and clears related service state.
*
- * @param status the status to end the associated Vibration with
+ * @param vibrationEndInfo the status and related info to end the associated Vibration with
* @param continueExternalControl indicates whether external control will continue. If not, the
* HAL will have external control turned off.
*/
@GuardedBy("mLock")
- private void endExternalVibrateLocked(Vibration.Status status,
+ private void endExternalVibrateLocked(Vibration.EndInfo vibrationEndInfo,
boolean continueExternalControl) {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "endExternalVibrateLocked");
try {
if (mCurrentExternalVibration == null) {
return;
}
- endVibrationLocked(mCurrentExternalVibration, status);
- mCurrentExternalVibration.externalVibration.unlinkToDeath(
- mCurrentExternalVibration);
- mCurrentExternalVibration = null;
+ mCurrentExternalVibration.unlinkToDeath();
if (!continueExternalControl) {
- setExternalControl(false);
+ setExternalControl(false, mCurrentExternalVibration.stats);
}
+ // The external control was turned off, end it and report metrics right away.
+ endVibrationAndWriteStatsLocked(mCurrentExternalVibration, vibrationEndInfo);
+ mCurrentExternalVibration = null;
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
}
@@ -1552,6 +1634,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
return IExternalVibratorService.SCALE_MUTE;
}
+ // Create Vibration.Stats as close to the received request as possible, for tracking.
+ ExternalVibrationHolder vibHolder = new ExternalVibrationHolder(vib);
VibrationAttributes attrs = fixupVibrationAttributes(vib.getVibrationAttributes(),
/* effect= */ null);
if (attrs.isFlagSet(VibrationAttributes.FLAG_INVALIDATE_SETTINGS_CACHE)) {
@@ -1562,18 +1646,17 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
boolean alreadyUnderExternalControl = false;
boolean waitForCompletion = false;
- int scale;
synchronized (mLock) {
Vibration.Status ignoreStatus = shouldIgnoreVibrationLocked(
vib.getUid(), vib.getPackage(), attrs);
if (ignoreStatus != null) {
- ExternalVibrationHolder vibHolder = new ExternalVibrationHolder(vib);
vibHolder.scale = IExternalVibratorService.SCALE_MUTE;
- endVibrationLocked(vibHolder, ignoreStatus);
+ // Failed to start the vibration, end it and report metrics right away.
+ endVibrationAndWriteStatsLocked(vibHolder, new Vibration.EndInfo(ignoreStatus));
return vibHolder.scale;
}
if (mCurrentExternalVibration != null
- && mCurrentExternalVibration.externalVibration.equals(vib)) {
+ && mCurrentExternalVibration.isHoldingSameVibration(vib)) {
// We are already playing this external vibration, so we can return the same
// scale calculated in the previous call to this method.
return mCurrentExternalVibration.scale;
@@ -1582,8 +1665,14 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
// If we're not under external control right now, then cancel any normal
// vibration that may be playing and ready the vibrator for external control.
if (mCurrentVibration != null) {
- clearNextVibrationLocked(Vibration.Status.IGNORED_FOR_EXTERNAL);
- mCurrentVibration.notifyCancelled(Vibration.Status.CANCELLED_SUPERSEDED,
+ vibHolder.stats.reportInterruptedAnotherVibration(
+ mCurrentVibration.getVibration().attrs.getUsage());
+ clearNextVibrationLocked(
+ new Vibration.EndInfo(Vibration.Status.IGNORED_FOR_EXTERNAL,
+ vib.getUid(), attrs.getUsage()));
+ mCurrentVibration.notifyCancelled(
+ new Vibration.EndInfo(Vibration.Status.CANCELLED_SUPERSEDED,
+ vib.getUid(), attrs.getUsage()),
/* immediate= */ true);
waitForCompletion = true;
}
@@ -1597,22 +1686,27 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
// Note that this doesn't support multiple concurrent external controls, as we
// would need to mute the old one still if it came from a different controller.
alreadyUnderExternalControl = true;
- mCurrentExternalVibration.externalVibration.mute();
- endExternalVibrateLocked(Vibration.Status.CANCELLED_SUPERSEDED,
+ mCurrentExternalVibration.mute();
+ vibHolder.stats.reportInterruptedAnotherVibration(
+ mCurrentExternalVibration.externalVibration
+ .getVibrationAttributes().getUsage());
+ endExternalVibrateLocked(
+ new Vibration.EndInfo(Vibration.Status.CANCELLED_SUPERSEDED,
+ vib.getUid(), attrs.getUsage()),
/* continueExternalControl= */ true);
}
- mCurrentExternalVibration = new ExternalVibrationHolder(vib);
- vib.linkToDeath(mCurrentExternalVibration);
- mCurrentExternalVibration.scale = mVibrationScaler.getExternalVibrationScale(
- attrs.getUsage());
- scale = mCurrentExternalVibration.scale;
+ mCurrentExternalVibration = vibHolder;
+ vibHolder.linkToDeath();
+ vibHolder.scale = mVibrationScaler.getExternalVibrationScale(attrs.getUsage());
}
if (waitForCompletion) {
if (!mVibrationThread.waitForThreadIdle(VIBRATION_CANCEL_WAIT_MILLIS)) {
Slog.e(TAG, "Timed out waiting for vibration to cancel");
synchronized (mLock) {
- endExternalVibrateLocked(Vibration.Status.IGNORED_ERROR_CANCELLING,
+ // Trigger endExternalVibrateLocked to unlink to death recipient.
+ endExternalVibrateLocked(
+ new Vibration.EndInfo(Vibration.Status.IGNORED_ERROR_CANCELLING),
/* continueExternalControl= */ false);
}
return IExternalVibratorService.SCALE_MUTE;
@@ -1622,23 +1716,27 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
if (DEBUG) {
Slog.d(TAG, "Vibrator going under external control.");
}
- setExternalControl(true);
+ setExternalControl(true, vibHolder.stats);
}
if (DEBUG) {
Slog.e(TAG, "Playing external vibration: " + vib);
}
- return scale;
+ // Vibrator will start receiving data from external channels after this point.
+ // Report current time as the vibration start time, for debugging.
+ vibHolder.stats.reportStarted();
+ return vibHolder.scale;
}
@Override
public void onExternalVibrationStop(ExternalVibration vib) {
synchronized (mLock) {
if (mCurrentExternalVibration != null
- && mCurrentExternalVibration.externalVibration.equals(vib)) {
+ && mCurrentExternalVibration.isHoldingSameVibration(vib)) {
if (DEBUG) {
Slog.e(TAG, "Stopping external vibration" + vib);
}
- endExternalVibrateLocked(Vibration.Status.FINISHED,
+ endExternalVibrateLocked(
+ new Vibration.EndInfo(Vibration.Status.FINISHED),
/* continueExternalControl= */ false);
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index a21919cdb960..f8cbd8b3dd48 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -1,5 +1,6 @@
package com.android.server.wm;
+import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
import static android.app.ActivityManager.START_SUCCESS;
import static android.app.ActivityManager.START_TASK_TO_FRONT;
import static android.app.ActivityManager.processStateAmToProto;
@@ -69,6 +70,7 @@ import static com.android.internal.util.FrameworkStatsLog.CAMERA_COMPAT_CONTROL_
import static com.android.internal.util.FrameworkStatsLog.CAMERA_COMPAT_CONTROL_EVENT_REPORTED__EVENT__CLICKED_REVERT_TREATMENT;
import static com.android.server.am.MemoryStatUtil.MemoryStat;
import static com.android.server.am.MemoryStatUtil.readMemoryStatFromFilesystem;
+import static com.android.server.am.ProcessList.INVALID_ADJ;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_METRICS;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -274,6 +276,10 @@ class ActivityMetricsLogger {
final boolean mProcessRunning;
/** whether the process of the launching activity didn't have any active activity. */
final boolean mProcessSwitch;
+ /** The process state of the launching activity prior to the launch */
+ final int mProcessState;
+ /** The oom adj score of the launching activity prior to the launch */
+ final int mProcessOomAdj;
/** Whether the last launched activity has reported drawn. */
boolean mIsDrawn;
/** The latest activity to have been launched. */
@@ -309,8 +315,8 @@ class ActivityMetricsLogger {
@Nullable
static TransitionInfo create(@NonNull ActivityRecord r,
@NonNull LaunchingState launchingState, @Nullable ActivityOptions options,
- boolean processRunning, boolean processSwitch, boolean newActivityCreated,
- int startResult) {
+ boolean processRunning, boolean processSwitch, int processState, int processOomAdj,
+ boolean newActivityCreated, int startResult) {
if (startResult != START_SUCCESS && startResult != START_TASK_TO_FRONT) {
return null;
}
@@ -325,18 +331,20 @@ class ActivityMetricsLogger {
transitionType = TYPE_TRANSITION_COLD_LAUNCH;
}
return new TransitionInfo(r, launchingState, options, transitionType, processRunning,
- processSwitch);
+ processSwitch, processState, processOomAdj);
}
/** Use {@link TransitionInfo#create} instead to ensure the transition type is valid. */
private TransitionInfo(ActivityRecord r, LaunchingState launchingState,
ActivityOptions options, int transitionType, boolean processRunning,
- boolean processSwitch) {
+ boolean processSwitch, int processState, int processOomAdj) {
mLaunchingState = launchingState;
mTransitionStartTimeNs = launchingState.mCurrentTransitionStartTimeNs;
mTransitionType = transitionType;
mProcessRunning = processRunning;
mProcessSwitch = processSwitch;
+ mProcessState = processState;
+ mProcessOomAdj = processOomAdj;
mTransitionDeviceUptimeMs = launchingState.mCurrentUpTimeMs;
setLatestLaunchedActivity(r);
// The launching state can be reused by consecutive launch. Its original association
@@ -640,12 +648,23 @@ class ActivityMetricsLogger {
// interesting.
final boolean processSwitch = !processRunning
|| !processRecord.hasStartedActivity(launchedActivity);
+ final int processState;
+ final int processOomAdj;
+ if (processRunning) {
+ processState = processRecord.getCurrentProcState();
+ processOomAdj = processRecord.getCurrentAdj();
+ } else {
+ processState = PROCESS_STATE_NONEXISTENT;
+ processOomAdj = INVALID_ADJ;
+ }
final TransitionInfo info = launchingState.mAssociatedTransitionInfo;
if (DEBUG_METRICS) {
Slog.i(TAG, "notifyActivityLaunched" + " resultCode=" + resultCode
+ " launchedActivity=" + launchedActivity + " processRunning=" + processRunning
+ " processSwitch=" + processSwitch
+ + " processState=" + processState
+ + " processOomAdj=" + processOomAdj
+ " newActivityCreated=" + newActivityCreated + " info=" + info);
}
@@ -681,7 +700,8 @@ class ActivityMetricsLogger {
}
final TransitionInfo newInfo = TransitionInfo.create(launchedActivity, launchingState,
- options, processRunning, processSwitch, newActivityCreated, resultCode);
+ options, processRunning, processSwitch, processState, processOomAdj,
+ newActivityCreated, resultCode);
if (newInfo == null) {
abort(launchingState, "unrecognized launch");
return;
@@ -996,8 +1016,11 @@ class ActivityMetricsLogger {
final long timestamp = info.mTransitionStartTimeNs;
final long uptime = info.mTransitionDeviceUptimeMs;
final int transitionDelay = info.mCurrentTransitionDelayMs;
+ final int processState = info.mProcessState;
+ final int processOomAdj = info.mProcessOomAdj;
mLoggerHandler.post(() -> logAppTransition(
- timestamp, uptime, transitionDelay, infoSnapshot, isHibernating));
+ timestamp, uptime, transitionDelay, infoSnapshot, isHibernating,
+ processState, processOomAdj));
}
mLoggerHandler.post(() -> logAppDisplayed(infoSnapshot));
if (info.mPendingFullyDrawn != null) {
@@ -1009,7 +1032,8 @@ class ActivityMetricsLogger {
// This gets called on another thread without holding the activity manager lock.
private void logAppTransition(long transitionStartTimeNs, long transitionDeviceUptimeMs,
- int currentTransitionDelayMs, TransitionInfoSnapshot info, boolean isHibernating) {
+ int currentTransitionDelayMs, TransitionInfoSnapshot info, boolean isHibernating,
+ int processState, int processOomAdj) {
final LogMaker builder = new LogMaker(APP_TRANSITION);
builder.setPackageName(info.packageName);
builder.setType(info.type);
@@ -1075,7 +1099,9 @@ class ActivityMetricsLogger {
isIncremental,
isLoading,
info.launchedActivityName.hashCode(),
- TimeUnit.NANOSECONDS.toMillis(transitionStartTimeNs));
+ TimeUnit.NANOSECONDS.toMillis(transitionStartTimeNs),
+ processState,
+ processOomAdj);
if (DEBUG_METRICS) {
Slog.i(TAG, String.format("APP_START_OCCURRED(%s, %s, %s, %s, %s)",
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index c64e5255c78c..55175c5fbfb1 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1566,7 +1566,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
if (task == mLastParentBeforePip && task != null) {
// Notify the TaskFragmentOrganizer that the activity is reparented back from pip.
mAtmService.mWindowOrganizerController.mTaskFragmentOrganizerController
- .onActivityReparentToTask(this);
+ .onActivityReparentedToTask(this);
// Activity's reparented back from pip, clear the links once established
clearLastParentBeforePip();
}
@@ -5640,7 +5640,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
ProtoLog.v(WM_DEBUG_ADD_REMOVE, "notifyAppResumed: wasStopped=%b %s",
wasStopped, this);
mAppStopped = false;
- // Allow the window to turn the screen on once the app is resumed again.
+ // Allow the window to turn the screen on once the app is started and resumed.
if (mAtmService.getActivityStartController().isInExecution()) {
setCurrentLaunchCanTurnScreenOn(true);
}
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index 77d6097a9c69..8f18064b4723 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -94,6 +94,7 @@ public class ActivityStartController {
boolean mCheckedForSetup = false;
+ /** Whether an {@link ActivityStarter} is currently executing (starting an Activity). */
private boolean mInExecution = false;
/**
@@ -129,7 +130,7 @@ public class ActivityStartController {
return mFactory.obtain().setIntent(intent).setReason(reason);
}
- void onExecutionStarted(ActivityStarter starter) {
+ void onExecutionStarted() {
mInExecution = true;
}
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index abedd96d1cde..619d693068d4 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1267,7 +1267,7 @@ class ActivityStarter {
}
private void onExecutionStarted() {
- mController.onExecutionStarted(this);
+ mController.onExecutionStarted();
}
private boolean isHomeApp(int uid, @Nullable String packageName) {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 87a4fe9b9817..0a4bc60d7ece 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -311,7 +311,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
/**
* The duration to keep a process in animating state (top scheduling group) when the
- * wakefulness is changing from awake to doze or sleep.
+ * wakefulness is dozing (unlocking) or changing from awake to doze or sleep (locking).
*/
private static final long DOZE_ANIMATING_STATE_RETAIN_TIME_MS = 2000;
@@ -2927,12 +2927,14 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
mDemoteTopAppReasons &= ~DEMOTE_TOP_REASON_DURING_UNLOCKING;
final WindowState notificationShade = mRootWindowContainer.getDefaultDisplay()
.getDisplayPolicy().getNotificationShade();
- proc = notificationShade != null
- ? mProcessMap.getProcess(notificationShade.mSession.mPid) : null;
- }
- if (proc == null) {
- return;
+ proc = notificationShade != null ? notificationShade.getProcess() : null;
}
+ setProcessAnimatingWhileDozing(proc);
+ }
+
+ // The caller MUST NOT hold the global lock because it calls AM method directly.
+ void setProcessAnimatingWhileDozing(WindowProcessController proc) {
+ if (proc == null) return;
// Set to activity manager directly to make sure the state can be seen by the subsequent
// update of scheduling group.
proc.setRunningAnimationUnsafe();
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index be995a887fc2..5a1afc49c62b 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -2654,12 +2654,12 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
@Override
public void accept(ActivityRecord r) {
- if (r.finishing) {
- return;
- }
if (r.mLaunchCookie != null) {
mInfo.addLaunchCookie(r.mLaunchCookie);
}
+ if (r.finishing) {
+ return;
+ }
mInfo.numActivities++;
mInfo.baseActivity = r.mActivityComponent;
if (mTopRunning == null) {
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 55d6b2fe8226..95169dbd7092 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -375,9 +375,6 @@ public class AppTransition implements Dump {
final AnimationAdapter topOpeningAnim = wc != null ? wc.getAnimation() : null;
int redoLayout = notifyAppTransitionStartingLocked(
- AppTransition.isKeyguardGoingAwayTransitOld(transit),
- AppTransition.isKeyguardOccludeTransitOld(transit),
- topOpeningAnim != null ? topOpeningAnim.getDurationHint() : 0,
topOpeningAnim != null
? topOpeningAnim.getStatusBarTransitionsStartTime()
: SystemClock.uptimeMillis(),
@@ -416,8 +413,11 @@ public class AppTransition implements Dump {
}
void freeze() {
- final boolean keyguardGoingAway = mNextAppTransitionRequests.contains(
+ final boolean keyguardGoingAwayCancelled = mNextAppTransitionRequests.contains(
TRANSIT_KEYGUARD_GOING_AWAY);
+ final boolean keyguardOccludedCancelled =
+ mNextAppTransitionRequests.contains(TRANSIT_KEYGUARD_OCCLUDE)
+ || mNextAppTransitionRequests.contains(TRANSIT_KEYGUARD_UNOCCLUDE);
// The RemoteAnimationControl didn't register AppTransitionListener and
// only initialized the finish and timeout callback when goodToGo().
@@ -429,7 +429,7 @@ public class AppTransition implements Dump {
mNextAppTransitionRequests.clear();
clear();
setReady();
- notifyAppTransitionCancelledLocked(keyguardGoingAway);
+ notifyAppTransitionCancelledLocked(keyguardGoingAwayCancelled, keyguardOccludedCancelled);
}
private void setAppTransitionState(int state) {
@@ -479,9 +479,11 @@ public class AppTransition implements Dump {
}
}
- private void notifyAppTransitionCancelledLocked(boolean keyguardGoingAway) {
+ private void notifyAppTransitionCancelledLocked(boolean keyguardGoingAwayCancelled,
+ boolean keyguardOccludedCancelled) {
for (int i = 0; i < mListeners.size(); i++) {
- mListeners.get(i).onAppTransitionCancelledLocked(keyguardGoingAway);
+ mListeners.get(i).onAppTransitionCancelledLocked(keyguardGoingAwayCancelled,
+ keyguardOccludedCancelled);
}
}
@@ -491,14 +493,12 @@ public class AppTransition implements Dump {
}
}
- private int notifyAppTransitionStartingLocked(boolean keyguardGoingAway,
- boolean keyguardOcclude, long duration, long statusBarAnimationStartTime,
+ private int notifyAppTransitionStartingLocked(long statusBarAnimationStartTime,
long statusBarAnimationDuration) {
int redoLayout = 0;
for (int i = 0; i < mListeners.size(); i++) {
- redoLayout |= mListeners.get(i).onAppTransitionStartingLocked(keyguardGoingAway,
- keyguardOcclude, duration, statusBarAnimationStartTime,
- statusBarAnimationDuration);
+ redoLayout |= mListeners.get(i).onAppTransitionStartingLocked(
+ statusBarAnimationStartTime, statusBarAnimationDuration);
}
return redoLayout;
}
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index 963345f2f49f..a645e89d700b 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -20,10 +20,6 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FLAG_APP_CRASHED;
-import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION;
-import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION;
-import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE;
-import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER;
import static android.view.WindowManager.TRANSIT_FLAG_OPEN_BEHIND;
import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE;
@@ -93,7 +89,6 @@ import android.view.WindowManager.LayoutParams;
import android.view.WindowManager.TransitionFlags;
import android.view.WindowManager.TransitionOldType;
import android.view.WindowManager.TransitionType;
-import android.view.animation.Animation;
import android.window.ITaskFragmentOrganizer;
import com.android.internal.annotations.VisibleForTesting;
@@ -295,7 +290,6 @@ public class AppTransitionController {
final int flags = appTransition.getTransitFlags();
layoutRedo = appTransition.goodToGo(transit, topOpeningApp);
- handleNonAppWindowsInTransition(transit, flags);
appTransition.postAnimationCallback();
appTransition.clear();
} finally {
@@ -1143,30 +1137,6 @@ public class AppTransitionController {
}
}
- private void handleNonAppWindowsInTransition(@TransitionOldType int transit, int flags) {
- if (transit == TRANSIT_OLD_KEYGUARD_GOING_AWAY
- && !WindowManagerService.sEnableRemoteKeyguardGoingAwayAnimation) {
- if ((flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER) != 0
- && (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION) == 0
- && (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION) == 0) {
- Animation anim = mService.mPolicy.createKeyguardWallpaperExit(
- (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE) != 0);
- if (anim != null) {
- anim.scaleCurrentDuration(mService.getTransitionAnimationScaleLocked());
- mDisplayContent.mWallpaperController.startWallpaperAnimation(anim);
- }
- }
- }
- if ((transit == TRANSIT_OLD_KEYGUARD_GOING_AWAY
- || transit == TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER)
- && !WindowManagerService.sEnableRemoteKeyguardGoingAwayAnimation) {
- mDisplayContent.startKeyguardExitOnNonAppWindows(
- transit == TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER,
- (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE) != 0,
- (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION) != 0);
- }
- }
-
private boolean transitionGoodToGo(ArraySet<? extends WindowContainer> apps,
ArrayMap<WindowContainer, Integer> outReasons) {
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index fccf54d83198..721497ea884e 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -53,7 +53,6 @@ import static android.view.Surface.ROTATION_0;
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
import static android.view.View.GONE;
-import static android.view.ViewRootImpl.LOCAL_LAYOUT;
import static android.view.WindowInsets.Type.displayCutout;
import static android.view.WindowInsets.Type.ime;
import static android.view.WindowInsets.Type.systemBars;
@@ -950,7 +949,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
final int preferredModeId = getDisplayPolicy().getRefreshRatePolicy()
.getPreferredModeId(w);
- if (mTmpApplySurfaceChangesTransactionState.preferredModeId == 0
+ if (w.isFocused() && mTmpApplySurfaceChangesTransactionState.preferredModeId == 0
&& preferredModeId != 0) {
mTmpApplySurfaceChangesTransactionState.preferredModeId = preferredModeId;
}
@@ -2710,25 +2709,22 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
mCurrentPrivacyIndicatorBounds =
mCurrentPrivacyIndicatorBounds.updateStaticBounds(staticBounds);
if (!Objects.equals(oldBounds, mCurrentPrivacyIndicatorBounds)) {
- updateDisplayFrames(false /* insetsSourceMayChange */, true /* notifyInsetsChange */);
+ updateDisplayFrames(true /* notifyInsetsChange */);
}
}
void onDisplayInfoChanged() {
- updateDisplayFrames(LOCAL_LAYOUT, LOCAL_LAYOUT);
+ updateDisplayFrames(false /* notifyInsetsChange */);
mMinSizeOfResizeableTaskDp = getMinimalTaskSizeDp();
mInputMonitor.layoutInputConsumers(mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight);
mDisplayPolicy.onDisplayInfoChanged(mDisplayInfo);
}
- private void updateDisplayFrames(boolean insetsSourceMayChange, boolean notifyInsetsChange) {
+ private void updateDisplayFrames(boolean notifyInsetsChange) {
if (mDisplayFrames.update(mDisplayInfo,
calculateDisplayCutoutForRotation(mDisplayInfo.rotation),
calculateRoundedCornersForRotation(mDisplayInfo.rotation),
calculatePrivacyIndicatorBoundsForRotation(mDisplayInfo.rotation))) {
- if (insetsSourceMayChange) {
- mDisplayPolicy.updateInsetsSourceFramesExceptIme(mDisplayFrames);
- }
mInsetsStateController.onDisplayFramesUpdated(notifyInsetsChange);
}
}
@@ -6602,7 +6598,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
@Override
- public void onAppTransitionCancelledLocked(boolean keyguardGoingAway) {
+ public void onAppTransitionCancelledLocked(boolean keyguardGoingAwayCancelled,
+ boolean keyguardOccludedCancelled) {
// It is only needed when freezing display in legacy transition.
if (mTransitionController.isShellTransitionsEnabled()) return;
continueUpdateOrientationForDiffOrienLaunchingApp();
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 07694065cbd7..24cef31dde7c 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -258,7 +258,7 @@ public class DisplayPolicy {
private volatile boolean mWindowManagerDrawComplete;
private WindowState mStatusBar = null;
- private WindowState mNotificationShade = null;
+ private volatile WindowState mNotificationShade;
private final int[] mStatusBarHeightForRotation = new int[4];
private WindowState mNavigationBar = null;
@NavigationBarPosition
@@ -602,9 +602,8 @@ public class DisplayPolicy {
}
@Override
- public int onAppTransitionStartingLocked(boolean keyguardGoingAway,
- boolean keyguardOccluding, long duration,
- long statusBarAnimationStartTime, long statusBarAnimationDuration) {
+ public int onAppTransitionStartingLocked(long statusBarAnimationStartTime,
+ long statusBarAnimationDuration) {
mHandler.post(() -> {
StatusBarManagerInternal statusBar = getStatusBarManagerInternal();
if (statusBar != null) {
@@ -616,7 +615,8 @@ public class DisplayPolicy {
}
@Override
- public void onAppTransitionCancelledLocked(boolean keyguardGoingAway) {
+ public void onAppTransitionCancelledLocked(boolean keyguardGoingAwayCancelled,
+ boolean keyguardOccludedCancelled) {
mHandler.post(mAppTransitionCancelled);
}
@@ -1576,19 +1576,6 @@ public class DisplayPolicy {
}
}
- void updateInsetsSourceFramesExceptIme(DisplayFrames displayFrames) {
- sTmpClientFrames.attachedFrame = null;
- for (int i = mInsetsSourceWindowsExceptIme.size() - 1; i >= 0; i--) {
- final WindowState win = mInsetsSourceWindowsExceptIme.valueAt(i);
- mWindowLayout.computeFrames(win.mAttrs.forRotation(displayFrames.mRotation),
- displayFrames.mInsetsState, displayFrames.mDisplayCutoutSafe,
- displayFrames.mUnrestricted, win.getWindowingMode(), UNSPECIFIED_LENGTH,
- UNSPECIFIED_LENGTH, win.getRequestedVisibilities(), win.mGlobalScale,
- sTmpClientFrames);
- win.updateSourceFrame(sTmpClientFrames.frame);
- }
- }
-
void onDisplayInfoChanged(DisplayInfo info) {
mSystemGestures.onDisplayInfoChanged(info);
}
@@ -2384,18 +2371,19 @@ public class DisplayPolicy {
return;
}
- // The immersive mode confirmation should never affect the system bar visibility, otherwise
+ // Immersive mode confirmation should never affect the system bar visibility, otherwise
// it will unhide the navigation bar and hide itself.
if (winCandidate.getAttrs().token == mImmersiveModeConfirmation.getWindowToken()) {
-
- // The immersive mode confirmation took the focus from mLastFocusedWindow which was
- // controlling the system ui visibility. So if mLastFocusedWindow can still receive
- // keys, we let it keep controlling the visibility.
- final boolean lastFocusCanReceiveKeys =
- (mLastFocusedWindow != null && mLastFocusedWindow.canReceiveKeys());
- winCandidate = isKeyguardShowing() && !isKeyguardOccluded() ? mNotificationShade
- : lastFocusCanReceiveKeys ? mLastFocusedWindow
- : mTopFullscreenOpaqueWindowState;
+ if (mNotificationShade != null && mNotificationShade.canReceiveKeys()) {
+ // Let notification shade control the system bar visibility.
+ winCandidate = mNotificationShade;
+ } else if (mLastFocusedWindow != null && mLastFocusedWindow.canReceiveKeys()) {
+ // Immersive mode confirmation took the focus from mLastFocusedWindow which was
+ // controlling the system bar visibility. Let it keep controlling the visibility.
+ winCandidate = mLastFocusedWindow;
+ } else {
+ winCandidate = mTopFullscreenOpaqueWindowState;
+ }
if (winCandidate == null) {
return;
}
@@ -2758,6 +2746,19 @@ public class DisplayPolicy {
mImmersiveModeConfirmation.onLockTaskModeChangedLw(lockTaskState);
}
+ /** Called when a {@link android.os.PowerManager#USER_ACTIVITY_EVENT_TOUCH} is sent. */
+ public void onUserActivityEventTouch() {
+ // If there is keyguard, it may use INPUT_FEATURE_DISABLE_USER_ACTIVITY (InputDispatcher
+ // won't trigger user activity for touch). So while the device is not interactive, the user
+ // event is only sent explicitly from SystemUI.
+ if (mAwake) return;
+ // If the event is triggered while the display is not awake, the screen may be showing
+ // dozing UI such as AOD or overlay UI of under display fingerprint. Then set the animating
+ // state temporarily to make the process more responsive.
+ final WindowState w = mNotificationShade;
+ mService.mAtmService.setProcessAnimatingWhileDozing(w != null ? w.getProcess() : null);
+ }
+
boolean onSystemUiSettingsChanged() {
return mImmersiveModeConfirmation.onSettingChanged(mService.mCurrentUserId);
}
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 3d91921e3ab7..8dd58506ef0b 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -399,9 +399,8 @@ public class DisplayRotation {
return false;
}
- final ScreenRotationAnimation screenRotationAnimation =
- mDisplayContent.getRotationAnimation();
- if (screenRotationAnimation != null && screenRotationAnimation.isAnimating()) {
+ if (mDisplayContent.inTransition()
+ && !mDisplayContent.mTransitionController.useShellTransitionsRotation()) {
// Rotation updates cannot be performed while the previous rotation change animation
// is still in progress. Skip this update. We will try updating again after the
// animation is finished and the display is unfrozen.
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index 7a9e6a977dfa..f3bd1a15cc94 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -761,7 +761,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 */);
+ false /* disable */, 0 /* floatingImeBottomInsets */, null);
mFinishCallback = finishCallback;
mControlCallbacks = new InsetsPolicyAnimationControlCallbacks(this);
}
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index 5b702eac7059..db79eae1d80c 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -161,15 +161,15 @@ public class RecentsAnimationController implements DeathRecipient {
*/
final AppTransitionListener mAppTransitionListener = new AppTransitionListener() {
@Override
- public int onAppTransitionStartingLocked(boolean keyguardGoingAway,
- boolean keyguardOccluding, long duration, long statusBarAnimationStartTime,
+ public int onAppTransitionStartingLocked(long statusBarAnimationStartTime,
long statusBarAnimationDuration) {
continueDeferredCancel();
return 0;
}
@Override
- public void onAppTransitionCancelledLocked(boolean keyguardGoingAway) {
+ public void onAppTransitionCancelledLocked(boolean keyguardGoingAwayCancelled,
+ boolean keyguardOccludedCancelled) {
continueDeferredCancel();
}
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 0128c187bbdd..fb68fe666c0b 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -242,16 +242,17 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
@Override
public int relayout(IWindow window, WindowManager.LayoutParams attrs,
- int requestedWidth, int requestedHeight, int viewFlags, int flags,
- ClientWindowFrames outFrames, MergedConfiguration mergedConfiguration,
- SurfaceControl outSurfaceControl, InsetsState outInsetsState,
- InsetsSourceControl[] outActiveControls, Bundle outSyncSeqIdBundle) {
+ int requestedWidth, int requestedHeight, int viewFlags, int flags, int seq,
+ int lastSyncSeqId, ClientWindowFrames outFrames,
+ MergedConfiguration mergedConfiguration, SurfaceControl outSurfaceControl,
+ InsetsState outInsetsState, InsetsSourceControl[] outActiveControls,
+ Bundle outSyncSeqIdBundle) {
if (false) Slog.d(TAG_WM, ">>>>>> ENTERED relayout from "
+ Binder.getCallingPid());
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, mRelayoutTag);
int res = mService.relayoutWindow(this, window, attrs,
- requestedWidth, requestedHeight, viewFlags, flags,
- outFrames, mergedConfiguration, outSurfaceControl, outInsetsState,
+ requestedWidth, requestedHeight, viewFlags, flags, seq,
+ lastSyncSeqId, outFrames, mergedConfiguration, outSurfaceControl, outInsetsState,
outActiveControls, outSyncSeqIdBundle);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
if (false) Slog.d(TAG_WM, "<<<<<< EXITING relayout to "
@@ -260,6 +261,16 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
}
@Override
+ public void relayoutAsync(IWindow window, WindowManager.LayoutParams attrs,
+ int requestedWidth, int requestedHeight, int viewFlags, int flags, int seq,
+ int lastSyncSeqId) {
+ relayout(window, attrs, requestedWidth, requestedHeight, viewFlags, flags, seq,
+ lastSyncSeqId, null /* outFrames */, null /* mergedConfiguration */,
+ null /* outSurfaceControl */, null /* outInsetsState */,
+ null /* outActiveControls */, null /* outSyncIdBundle */);
+ }
+
+ @Override
public boolean outOfMemory(IWindow window) {
return mService.outOfMemoryWindow(this, window);
}
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index c1f06e442294..44b5b886a5f6 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -2340,7 +2340,10 @@ class TaskFragment extends WindowContainer<WindowContainer> {
return false;
}
- return !startBounds.equals(getBounds());
+ // Only take snapshot if the bounds are resized.
+ final Rect endBounds = getConfiguration().windowConfiguration.getBounds();
+ return endBounds.width() != startBounds.width()
+ || endBounds.height() != startBounds.height();
}
boolean canHaveEmbeddingActivityTransition(@NonNull ActivityRecord child) {
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index 6ca564833d41..88059e1a0d04 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -18,7 +18,7 @@ package com.android.server.wm;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.window.TaskFragmentOrganizer.putErrorInfoInBundle;
-import static android.window.TaskFragmentTransaction.TYPE_ACTIVITY_REPARENT_TO_TASK;
+import static android.window.TaskFragmentTransaction.TYPE_ACTIVITY_REPARENTED_TO_TASK;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_APPEARED;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_ERROR;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_INFO_CHANGED;
@@ -277,7 +277,7 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr
}
@Nullable
- TaskFragmentTransaction.Change prepareActivityReparentToTask(
+ TaskFragmentTransaction.Change prepareActivityReparentedToTask(
@NonNull ActivityRecord activity) {
if (activity.finishing) {
Slog.d(TAG, "Reparent activity=" + activity.token + " is finishing");
@@ -315,7 +315,7 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr
}
ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Activity=%s reparent to taskId=%d",
activity.token, task.mTaskId);
- return new TaskFragmentTransaction.Change(TYPE_ACTIVITY_REPARENT_TO_TASK)
+ return new TaskFragmentTransaction.Change(TYPE_ACTIVITY_REPARENTED_TO_TASK)
.setTaskId(task.mTaskId)
.setActivityIntent(activity.intent)
.setActivityToken(activityToken);
@@ -521,7 +521,7 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr
mAtmService.mWindowManager.mWindowPlacerLocked.requestTraversal();
}
- void onActivityReparentToTask(@NonNull ActivityRecord activity) {
+ void onActivityReparentedToTask(@NonNull ActivityRecord activity) {
final ITaskFragmentOrganizer organizer;
if (activity.mLastTaskFragmentOrganizerBeforePip != null) {
// If the activity is previously embedded in an organized TaskFragment.
@@ -547,7 +547,7 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr
return;
}
addPendingEvent(new PendingTaskFragmentEvent.Builder(
- PendingTaskFragmentEvent.EVENT_ACTIVITY_REPARENT_TO_TASK, organizer)
+ PendingTaskFragmentEvent.EVENT_ACTIVITY_REPARENTED_TO_TASK, organizer)
.setActivity(activity)
.build());
}
@@ -601,7 +601,7 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr
static final int EVENT_INFO_CHANGED = 2;
static final int EVENT_PARENT_INFO_CHANGED = 3;
static final int EVENT_ERROR = 4;
- static final int EVENT_ACTIVITY_REPARENT_TO_TASK = 5;
+ static final int EVENT_ACTIVITY_REPARENTED_TO_TASK = 5;
@IntDef(prefix = "EVENT_", value = {
EVENT_APPEARED,
@@ -609,7 +609,7 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr
EVENT_INFO_CHANGED,
EVENT_PARENT_INFO_CHANGED,
EVENT_ERROR,
- EVENT_ACTIVITY_REPARENT_TO_TASK
+ EVENT_ACTIVITY_REPARENTED_TO_TASK
})
@Retention(RetentionPolicy.SOURCE)
public @interface EventType {}
@@ -900,8 +900,8 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr
case PendingTaskFragmentEvent.EVENT_ERROR:
return state.prepareTaskFragmentError(event.mErrorCallbackToken, taskFragment,
event.mOpType, event.mException);
- case PendingTaskFragmentEvent.EVENT_ACTIVITY_REPARENT_TO_TASK:
- return state.prepareActivityReparentToTask(event.mActivity);
+ case PendingTaskFragmentEvent.EVENT_ACTIVITY_REPARENTED_TO_TASK:
+ return state.prepareActivityReparentedToTask(event.mActivity);
default:
throw new IllegalArgumentException("Unknown TaskFragmentEvent=" + event.mEventType);
}
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index bbc95a1dd70f..3a17e48f14a8 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -31,13 +31,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FLAG_IS_RECENTS;
-import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
-import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION;
-import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION;
-import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE;
-import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED;
-import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
@@ -72,7 +66,6 @@ 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;
@@ -80,7 +73,6 @@ import android.util.Slog;
import android.util.SparseArray;
import android.view.SurfaceControl;
import android.view.WindowManager;
-import android.view.animation.Animation;
import android.window.RemoteTransition;
import android.window.TransitionInfo;
@@ -622,9 +614,11 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
throw new IllegalStateException("Can't finish a non-playing transition " + mSyncId);
}
+ boolean hasParticipatedDisplay = false;
// Commit all going-invisible containers
for (int i = 0; i < mParticipants.size(); ++i) {
- final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord();
+ final WindowContainer<?> participant = mParticipants.valueAt(i);
+ final ActivityRecord ar = participant.asActivityRecord();
if (ar != null) {
boolean visibleAtTransitionEnd = mVisibleAtTransitionEndTokens.contains(ar);
// We need both the expected visibility AND current requested-visibility to be
@@ -656,8 +650,13 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
// Legacy dispatch relies on this (for now).
ar.mEnteringAnimation = visibleAtTransitionEnd;
}
+ continue;
+ }
+ if (participant.asDisplayContent() != null) {
+ hasParticipatedDisplay = true;
+ continue;
}
- final WallpaperWindowToken wt = mParticipants.valueAt(i).asWallpaperToken();
+ final WallpaperWindowToken wt = participant.asWallpaperToken();
if (wt != null) {
final boolean visibleAtTransitionEnd = mVisibleAtTransitionEndTokens.contains(wt);
if (!visibleAtTransitionEnd && !wt.isVisibleRequested()) {
@@ -737,6 +736,12 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
mState = STATE_FINISHED;
mController.mTransitionTracer.logState(this);
+ // Rotation change may be deferred while there is a display change transition, so check
+ // again in case there is a new pending change.
+ if (hasParticipatedDisplay && !mController.useShellTransitionsRotation()) {
+ mController.mAtm.mWindowManager.updateRotation(false /* alwaysSendConfiguration */,
+ false /* forceRelayout */);
+ }
}
void abort() {
@@ -1053,36 +1058,9 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
private void handleNonAppWindowsInTransition(@NonNull DisplayContent dc,
@TransitionType int transit, @TransitionFlags int flags) {
- if ((transit == TRANSIT_KEYGUARD_GOING_AWAY
- || (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY) != 0)
- && !WindowManagerService.sEnableRemoteKeyguardGoingAwayAnimation) {
- if ((flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER) != 0
- && (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION) == 0
- && (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION) == 0) {
- Animation anim = mController.mAtm.mWindowManager.mPolicy
- .createKeyguardWallpaperExit(
- (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE) != 0);
- if (anim != null) {
- anim.scaleCurrentDuration(
- mController.mAtm.mWindowManager.getTransitionAnimationScaleLocked());
- dc.mWallpaperController.startWallpaperAnimation(anim);
- }
- }
- dc.startKeyguardExitOnNonAppWindows(
- (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER) != 0,
- (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE) != 0,
- (flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION) != 0);
- if (!WindowManagerService.sEnableRemoteKeyguardGoingAwayAnimation) {
- // When remote animation is enabled for KEYGUARD_GOING_AWAY transition, SysUI
- // receives IRemoteAnimationRunner#onAnimationStart to start animation, so we don't
- // need to call IKeyguardService#keyguardGoingAway here.
- mController.mAtm.mWindowManager.mPolicy.startKeyguardExitAnimation(
- SystemClock.uptimeMillis(), 0 /* duration */);
- }
- }
if ((flags & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0) {
mController.mAtm.mWindowManager.mPolicy.applyKeyguardOcclusionChange(
- true /* keyguardOccludingStarted */);
+ false /* notify */);
}
}
@@ -1192,7 +1170,14 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
return false;
}
- final @TransitionInfo.TransitionMode int mode = changes.get(target).getTransitMode(target);
+ final ChangeInfo change = changes.get(target);
+ if (change.mStartParent != null && target.getParent() != change.mStartParent) {
+ // When a window is reparented, the state change won't fit into any of the parents.
+ // Don't promote such change so that we can animate the reparent if needed.
+ return false;
+ }
+
+ final @TransitionInfo.TransitionMode int mode = change.getTransitMode(target);
for (int i = parent.getChildCount() - 1; i >= 0; --i) {
final WindowContainer<?> sibling = parent.getChildAt(i);
if (target == sibling) continue;
@@ -1332,14 +1317,14 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
// Intermediate parents must be those that has window to be managed by Shell.
continue;
}
- if (parentChange.mParent != null && !skipIntermediateReports) {
- changes.get(wc).mParent = p;
+ if (parentChange.mEndParent != null && !skipIntermediateReports) {
+ changes.get(wc).mEndParent = p;
// The chain above the parent was processed.
break;
}
if (targetList.contains(p)) {
if (skipIntermediateReports) {
- changes.get(wc).mParent = p;
+ changes.get(wc).mEndParent = p;
} else {
intermediates.add(p);
}
@@ -1351,10 +1336,10 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
}
if (!foundParentInTargets || intermediates.isEmpty()) continue;
// Add any always-report parents along the way.
- changes.get(wc).mParent = intermediates.get(0);
+ changes.get(wc).mEndParent = intermediates.get(0);
for (int j = 0; j < intermediates.size() - 1; j++) {
final WindowContainer<?> intermediate = intermediates.get(j);
- changes.get(intermediate).mParent = intermediates.get(j + 1);
+ changes.get(intermediate).mEndParent = intermediates.get(j + 1);
targets.add(intermediate);
}
}
@@ -1467,8 +1452,8 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
target.mRemoteToken != null ? target.mRemoteToken.toWindowContainerToken()
: null, getLeashSurface(target, startT));
// TODO(shell-transitions): Use leash for non-organized windows.
- if (info.mParent != null) {
- change.setParent(info.mParent.mRemoteToken.toWindowContainerToken());
+ if (info.mEndParent != null) {
+ change.setParent(info.mEndParent.mRemoteToken.toWindowContainerToken());
}
change.setMode(info.getTransitMode(target));
change.setStartAbsBounds(info.mAbsoluteBounds);
@@ -1651,7 +1636,9 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
@interface Flag {}
// Usually "post" change state.
- WindowContainer mParent;
+ WindowContainer mEndParent;
+ // Parent before change state.
+ WindowContainer mStartParent;
// State tracking
boolean mExistenceChanged = false;
@@ -1672,6 +1659,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
mAbsoluteBounds.set(origState.getBounds());
mShowWallpaper = origState.showWallpaper();
mRotation = origState.getWindowConfiguration().getRotation();
+ mStartParent = origState.getParent();
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 4f324f22aa5e..79dfaa961bf2 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -638,11 +638,9 @@ class TransitionController {
}
void dispatchLegacyAppTransitionStarting(TransitionInfo info, long statusBarTransitionDelay) {
- final boolean keyguardGoingAway = info.isKeyguardGoingAway();
for (int i = 0; i < mLegacyListeners.size(); ++i) {
// TODO(shell-transitions): handle (un)occlude transition.
- mLegacyListeners.get(i).onAppTransitionStartingLocked(keyguardGoingAway,
- false /* keyguardOcclude */, 0 /* durationHint */,
+ mLegacyListeners.get(i).onAppTransitionStartingLocked(
SystemClock.uptimeMillis() + statusBarTransitionDelay,
AnimationAdapter.STATUS_BAR_TRANSITION_DURATION);
}
@@ -657,7 +655,7 @@ class TransitionController {
void dispatchLegacyAppTransitionCancelled() {
for (int i = 0; i < mLegacyListeners.size(); ++i) {
mLegacyListeners.get(i).onAppTransitionCancelledLocked(
- false /* keyguardGoingAway */);
+ false /* keyguardGoingAwayCancelled */, false /* keyguardOccludedCancelled */);
}
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index da5f5d0e29fd..4b5b6dad4fc4 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -220,9 +220,13 @@ public abstract class WindowManagerInternal {
/**
* Called when a pending app transition gets cancelled.
*
- * @param keyguardGoingAway true if keyguard going away transition got cancelled.
+ * @param keyguardGoingAwayCancelled {@code true} if keyguard going away transition was
+ * cancelled.
+ * @param keyguardOccludedCancelled {@code true} if keyguard (un)occluded transition was
+ * cancelled.
*/
- public void onAppTransitionCancelledLocked(boolean keyguardGoingAway) {}
+ public void onAppTransitionCancelledLocked(boolean keyguardGoingAwayCancelled,
+ boolean keyguardOccludedCancelled) {}
/**
* Called when an app transition is timed out.
@@ -232,9 +236,6 @@ public abstract class WindowManagerInternal {
/**
* Called when an app transition gets started
*
- * @param keyguardGoingAway true if keyguard going away transition is started.
- * @param keyguardOccluding true if keyguard (un)occlude transition is started.
- * @param duration the total duration of the transition
* @param statusBarAnimationStartTime the desired start time for all visual animations in
* the status bar caused by this app transition in uptime millis
* @param statusBarAnimationDuration the duration for all visual animations in the status
@@ -245,8 +246,7 @@ public abstract class WindowManagerInternal {
* {@link WindowManagerPolicy#FINISH_LAYOUT_REDO_WALLPAPER},
* or {@link WindowManagerPolicy#FINISH_LAYOUT_REDO_ANIM}.
*/
- public int onAppTransitionStartingLocked(boolean keyguardGoingAway,
- boolean keyguardOccluding, long duration, long statusBarAnimationStartTime,
+ public int onAppTransitionStartingLocked(long statusBarAnimationStartTime,
long statusBarAnimationDuration) {
return 0;
}
@@ -810,12 +810,17 @@ public abstract class WindowManagerInternal {
*/
public final String imeLayerTargetName;
+ /** The surface parent of the IME container. */
+ public final String imeSurfaceParentName;
+
public ImeTargetInfo(String focusedWindowName, String requestWindowName,
- String imeControlTargetName, String imeLayerTargetName) {
+ String imeControlTargetName, String imeLayerTargetName,
+ String imeSurfaceParentName) {
this.focusedWindowName = focusedWindowName;
this.requestWindowName = requestWindowName;
this.imeControlTargetName = imeControlTargetName;
this.imeLayerTargetName = imeLayerTargetName;
+ this.imeSurfaceParentName = imeSurfaceParentName;
}
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index fd54f78b22fe..298f93d75e7a 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -88,6 +88,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import static android.view.WindowManager.REMOVE_CONTENT_MODE_UNDEFINED;
import static android.view.WindowManager.TRANSIT_NONE;
import static android.view.WindowManager.TRANSIT_RELAUNCH;
+import static android.view.WindowManager.fixScale;
import static android.view.WindowManagerGlobal.ADD_OKAY;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_CANCEL_AND_REDRAW;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED;
@@ -424,32 +425,6 @@ public class WindowManagerService extends IWindowManager.Stub
SystemProperties.getBoolean(ENABLE_SHELL_TRANSITIONS, false);
/**
- * Run Keyguard animation as remote animation in System UI instead of local animation in
- * the server process.
- *
- * 0: Runs all keyguard animation as local animation
- * 1: Only runs keyguard going away animation as remote animation
- * 2: Runs all keyguard animation as remote animation
- */
- private static final String ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY =
- "persist.wm.enable_remote_keyguard_animation";
-
- private static final int sEnableRemoteKeyguardAnimation =
- SystemProperties.getInt(ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY, 2);
-
- /**
- * @see #ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY
- */
- public static final boolean sEnableRemoteKeyguardGoingAwayAnimation =
- sEnableRemoteKeyguardAnimation >= 1;
-
- /**
- * @see #ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY
- */
- public static final boolean sEnableRemoteKeyguardOccludeAnimation =
- sEnableRemoteKeyguardAnimation >= 2;
-
- /**
* Allows a fullscreen windowing mode activity to launch in its desired orientation directly
* when the display has different orientation.
*/
@@ -1117,7 +1092,8 @@ public class WindowManagerService extends IWindowManager.Stub
= new WindowManagerInternal.AppTransitionListener() {
@Override
- public void onAppTransitionCancelledLocked(boolean keyguardGoingAway) {
+ public void onAppTransitionCancelledLocked(boolean keyguardGoingAwayCancelled,
+ boolean keyguardOccludedCancelled) {
}
@Override
@@ -1326,15 +1302,10 @@ public class WindowManagerService extends IWindowManager.Stub
}, UserHandle.ALL, suspendPackagesFilter, null, null);
// Get persisted window scale setting
- mWindowAnimationScaleSetting = Settings.Global.getFloat(resolver,
- Settings.Global.WINDOW_ANIMATION_SCALE, mWindowAnimationScaleSetting);
- mTransitionAnimationScaleSetting = Settings.Global.getFloat(resolver,
- Settings.Global.TRANSITION_ANIMATION_SCALE,
- context.getResources().getFloat(
- R.dimen.config_appTransitionAnimationDurationScaleDefault));
-
- setAnimatorDurationScale(Settings.Global.getFloat(resolver,
- Settings.Global.ANIMATOR_DURATION_SCALE, mAnimatorDurationScaleSetting));
+ mWindowAnimationScaleSetting = getWindowAnimationScaleSetting();
+ mTransitionAnimationScaleSetting = getTransitionAnimationScaleSetting();
+
+ setAnimatorDurationScale(getAnimatorDurationScaleSetting());
mForceDesktopModeOnExternalDisplays = Settings.Global.getInt(resolver,
DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, 0) != 0;
@@ -1408,6 +1379,22 @@ public class WindowManagerService extends IWindowManager.Stub
lightRadius);
}
+ private float getTransitionAnimationScaleSetting() {
+ return fixScale(Settings.Global.getFloat(mContext.getContentResolver(),
+ Settings.Global.TRANSITION_ANIMATION_SCALE, mContext.getResources().getFloat(
+ R.dimen.config_appTransitionAnimationDurationScaleDefault)));
+ }
+
+ private float getAnimatorDurationScaleSetting() {
+ return fixScale(Settings.Global.getFloat(mContext.getContentResolver(),
+ Settings.Global.ANIMATOR_DURATION_SCALE, mAnimatorDurationScaleSetting));
+ }
+
+ private float getWindowAnimationScaleSetting() {
+ return fixScale(Settings.Global.getFloat(mContext.getContentResolver(),
+ Settings.Global.WINDOW_ANIMATION_SCALE, mWindowAnimationScaleSetting));
+ }
+
/**
* Called after all entities (such as the {@link ActivityManagerService}) have been set up and
* associated with the {@link WindowManagerService}.
@@ -1869,7 +1856,7 @@ public class WindowManagerService extends IWindowManager.Stub
displayContent.getInsetsStateController().updateAboveInsetsState(
false /* notifyInsetsChanged */);
- outInsetsState.set(win.getCompatInsetsState(), win.isClientLocal());
+ outInsetsState.set(win.getCompatInsetsState(), true /* copySources */);
getInsetsSourceControls(win, outActiveControls);
if (win.mLayoutAttached) {
@@ -2248,11 +2235,14 @@ public class WindowManagerService extends IWindowManager.Stub
}
public int relayoutWindow(Session session, IWindow client, LayoutParams attrs,
- int requestedWidth, int requestedHeight, int viewVisibility, int flags,
- ClientWindowFrames outFrames, MergedConfiguration mergedConfiguration,
- SurfaceControl outSurfaceControl, InsetsState outInsetsState,
- InsetsSourceControl[] outActiveControls, Bundle outSyncIdBundle) {
- Arrays.fill(outActiveControls, null);
+ int requestedWidth, int requestedHeight, int viewVisibility, int flags, int seq,
+ int lastSyncSeqId, ClientWindowFrames outFrames,
+ MergedConfiguration outMergedConfiguration, SurfaceControl outSurfaceControl,
+ InsetsState outInsetsState, InsetsSourceControl[] outActiveControls,
+ Bundle outSyncIdBundle) {
+ if (outActiveControls != null) {
+ Arrays.fill(outActiveControls, null);
+ }
int result = 0;
boolean configChanged;
final int pid = Binder.getCallingPid();
@@ -2263,8 +2253,15 @@ public class WindowManagerService extends IWindowManager.Stub
if (win == null) {
return 0;
}
+ if (win.mRelayoutSeq < seq) {
+ win.mRelayoutSeq = seq;
+ } else if (win.mRelayoutSeq > seq) {
+ return 0;
+ }
- if (win.cancelAndRedraw() && win.mPrepareSyncSeqId <= win.mLastSeqIdSentToRelayout) {
+ if (win.cancelAndRedraw() && win.mPrepareSyncSeqId <= lastSyncSeqId) {
+ // The client has reported the sync draw, but we haven't finished it yet.
+ // Don't let the client perform a non-sync draw at this time.
result |= RELAYOUT_RES_CANCEL_AND_REDRAW;
}
@@ -2427,7 +2424,7 @@ public class WindowManagerService extends IWindowManager.Stub
// Create surfaceControl before surface placement otherwise layout will be skipped
// (because WS.isGoneForLayout() is true when there is no surface.
- if (shouldRelayout) {
+ if (shouldRelayout && outSurfaceControl != null) {
try {
result = createSurfaceControl(outSurfaceControl, result, win, winAnimator);
} catch (Exception e) {
@@ -2466,22 +2463,25 @@ public class WindowManagerService extends IWindowManager.Stub
winAnimator.mEnterAnimationPending = false;
winAnimator.mEnteringAnimation = false;
- if (viewVisibility == View.VISIBLE && winAnimator.hasSurface()) {
- // We already told the client to go invisible, but the message may not be
- // handled yet, or it might want to draw a last frame. If we already have a
- // surface, let the client use that, but don't create new surface at this point.
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "relayoutWindow: getSurface");
- winAnimator.mSurfaceController.getSurfaceControl(outSurfaceControl);
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- } else {
- if (DEBUG_VISIBILITY) Slog.i(TAG_WM, "Releasing surface in: " + win);
-
- try {
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "wmReleaseOutSurface_"
- + win.mAttrs.getTitle());
- outSurfaceControl.release();
- } finally {
+ if (outSurfaceControl != null) {
+ if (viewVisibility == View.VISIBLE && winAnimator.hasSurface()) {
+ // We already told the client to go invisible, but the message may not be
+ // handled yet, or it might want to draw a last frame. If we already have a
+ // surface, let the client use that, but don't create new surface at this
+ // point.
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "relayoutWindow: getSurface");
+ winAnimator.mSurfaceController.getSurfaceControl(outSurfaceControl);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+ } else {
+ if (DEBUG_VISIBILITY) Slog.i(TAG_WM, "Releasing surface in: " + win);
+
+ try {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "wmReleaseOutSurface_"
+ + win.mAttrs.getTitle());
+ outSurfaceControl.release();
+ } finally {
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+ }
}
}
@@ -2534,20 +2534,16 @@ public class WindowManagerService extends IWindowManager.Stub
win.mResizedWhileGone = false;
}
- win.fillClientWindowFramesAndConfiguration(outFrames, mergedConfiguration,
- false /* useLatestConfig */, shouldRelayout);
+ if (outFrames != null && outMergedConfiguration != null) {
+ win.fillClientWindowFramesAndConfiguration(outFrames, outMergedConfiguration,
+ false /* useLatestConfig */, shouldRelayout);
- // Set resize-handled here because the values are sent back to the client.
- win.onResizeHandled();
+ // Set resize-handled here because the values are sent back to the client.
+ win.onResizeHandled();
+ }
- outInsetsState.set(win.getCompatInsetsState(), win.isClientLocal());
- if (DEBUG) {
- Slog.v(TAG_WM, "Relayout given client " + client.asBinder()
- + ", requestedWidth=" + requestedWidth
- + ", requestedHeight=" + requestedHeight
- + ", viewVisibility=" + viewVisibility
- + "\nRelayout returning frame=" + outFrames.frame
- + ", surface=" + outSurfaceControl);
+ if (outInsetsState != null) {
+ outInsetsState.set(win.getCompatInsetsState(), true /* copySources */);
}
ProtoLog.v(WM_DEBUG_FOCUS, "Relayout of %s: focusMayChange=%b",
@@ -2558,14 +2554,16 @@ public class WindowManagerService extends IWindowManager.Stub
}
win.mInRelayout = false;
- if (USE_BLAST_SYNC && win.useBLASTSync() && viewVisibility != View.GONE
- && (win.mSyncSeqId > win.mLastSeqIdSentToRelayout)) {
- win.markRedrawForSyncReported();
-
- win.mLastSeqIdSentToRelayout = win.mSyncSeqId;
- outSyncIdBundle.putInt("seqid", win.mSyncSeqId);
- } else {
- outSyncIdBundle.putInt("seqid", -1);
+ if (outSyncIdBundle != null) {
+ final int maybeSyncSeqId;
+ if (USE_BLAST_SYNC && win.useBLASTSync() && viewVisibility != View.GONE
+ && win.mSyncSeqId > lastSyncSeqId) {
+ maybeSyncSeqId = win.mSyncSeqId;
+ win.markRedrawForSyncReported();
+ } else {
+ maybeSyncSeqId = -1;
+ }
+ outSyncIdBundle.putInt("seqid", maybeSyncSeqId);
}
if (configChanged) {
@@ -2574,7 +2572,9 @@ public class WindowManagerService extends IWindowManager.Stub
displayContent.sendNewConfiguration();
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
- getInsetsSourceControls(win, outActiveControls);
+ if (outActiveControls != null) {
+ getInsetsSourceControls(win, outActiveControls);
+ }
}
Binder.restoreCallingIdentity(origId);
@@ -2610,27 +2610,32 @@ public class WindowManagerService extends IWindowManager.Stub
transit = WindowManagerPolicy.TRANSIT_PREVIEW_DONE;
}
- String reason = null;
- if (win.isWinVisibleLw() && winAnimator.applyAnimationLocked(transit, false)) {
- reason = "applyAnimation";
- focusMayChange = true;
- win.mAnimatingExit = true;
- } else if (win.mDisplayContent.okToAnimate() && win.isExitAnimationRunningSelfOrParent()) {
- // Currently in a hide animation... turn this into
- // an exit.
- win.mAnimatingExit = true;
- } else if (win.mDisplayContent.okToAnimate()
- && win.mDisplayContent.mWallpaperController.isWallpaperTarget(win)
- && win.mAttrs.type != TYPE_NOTIFICATION_SHADE) {
- reason = "isWallpaperTarget";
- // If the wallpaper is currently behind this app window, we need to change both of them
- // inside of a transaction to avoid artifacts.
- // For NotificationShade, sysui is in charge of running window animation and it updates
- // the client view visibility only after both NotificationShade and the wallpaper are
- // hidden. So we don't need to care about exit animation, but can destroy its surface
- // immediately.
- win.mAnimatingExit = true;
- } else {
+ if (win.isWinVisibleLw() && win.mDisplayContent.okToAnimate()) {
+ String reason = null;
+ if (winAnimator.applyAnimationLocked(transit, false)) {
+ reason = "applyAnimation";
+ focusMayChange = true;
+ win.mAnimatingExit = true;
+ } else if (win.isExitAnimationRunningSelfOrParent()) {
+ reason = "animating";
+ win.mAnimatingExit = true;
+ } else if (win.mDisplayContent.mWallpaperController.isWallpaperTarget(win)
+ && win.mAttrs.type != TYPE_NOTIFICATION_SHADE) {
+ reason = "isWallpaperTarget";
+ // If the wallpaper is currently behind this app window, they should be updated
+ // in a transaction to avoid artifacts.
+ // For NotificationShade, sysui is in charge of running window animation and it
+ // updates the client view visibility only after both NotificationShade and the
+ // wallpaper are hidden. So the exit animation is not needed and can destroy its
+ // surface immediately.
+ win.mAnimatingExit = true;
+ }
+ if (reason != null) {
+ ProtoLog.d(WM_DEBUG_ANIM,
+ "Set animatingExit: reason=startExitingAnimation/%s win=%s", reason, win);
+ }
+ }
+ if (!win.mAnimatingExit) {
boolean stopped = win.mActivityRecord == null || win.mActivityRecord.mAppStopped;
// We set mDestroying=true so ActivityRecord#notifyAppStopped in-to destroy surfaces
// will later actually destroy the surface if we do not do so here. Normally we leave
@@ -2638,10 +2643,6 @@ public class WindowManagerService extends IWindowManager.Stub
win.mDestroying = true;
win.destroySurface(false, stopped);
}
- if (reason != null) {
- ProtoLog.d(WM_DEBUG_ANIM, "Set animatingExit: reason=startExitingAnimation/%s win=%s",
- reason, win);
- }
if (mAccessibilityController.hasCallbacks()) {
mAccessibilityController.onWindowTransition(win, transit);
}
@@ -3393,11 +3394,6 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
- static float fixScale(float scale) {
- if (scale < 0) scale = 0;
- else if (scale > 20) scale = 20;
- return Math.abs(scale);
- }
@Override
public void setAnimationScale(int which, float scale) {
@@ -4289,7 +4285,9 @@ public class WindowManagerService extends IWindowManager.Stub
// Even if alwaysSend, we are waiting for a transition or remote to provide
// updated configuration, so we can't update configuration yet.
if (!pendingRemoteDisplayChange) {
- if (!rotationChanged || forceRelayout) {
+ // The layout-needed flag will be set if there is a rotation change, so
+ // only set it if the caller requests to force relayout.
+ if (forceRelayout) {
displayContent.setLayoutNeeded();
layoutNeeded = true;
}
@@ -5323,24 +5321,16 @@ public class WindowManagerService extends IWindowManager.Stub
final int mode = msg.arg1;
switch (mode) {
case WINDOW_ANIMATION_SCALE: {
- mWindowAnimationScaleSetting = Settings.Global.getFloat(
- mContext.getContentResolver(),
- Settings.Global.WINDOW_ANIMATION_SCALE,
- mWindowAnimationScaleSetting);
+ mWindowAnimationScaleSetting = getWindowAnimationScaleSetting();
break;
}
case TRANSITION_ANIMATION_SCALE: {
- mTransitionAnimationScaleSetting = Settings.Global.getFloat(
- mContext.getContentResolver(),
- Settings.Global.TRANSITION_ANIMATION_SCALE,
- mTransitionAnimationScaleSetting);
+ mTransitionAnimationScaleSetting =
+ getTransitionAnimationScaleSetting();
break;
}
case ANIMATION_DURATION_SCALE: {
- mAnimatorDurationScaleSetting = Settings.Global.getFloat(
- mContext.getContentResolver(),
- Settings.Global.ANIMATOR_DURATION_SCALE,
- mAnimatorDurationScaleSetting);
+ mAnimatorDurationScaleSetting = getAnimatorDurationScaleSetting();
dispatchNewAnimatorScaleLocked(null);
break;
}
@@ -8202,6 +8192,7 @@ public class WindowManagerService extends IWindowManager.Stub
final String requestWindowName;
final String imeControlTargetName;
final String imeLayerTargetName;
+ final String imeSurfaceParentName;
synchronized (mGlobalLock) {
final WindowState focusedWin = mWindowMap.get(focusedToken);
focusedWindowName = focusedWin != null ? focusedWin.getName() : "null";
@@ -8218,15 +8209,17 @@ public class WindowManagerService extends IWindowManager.Stub
}
final InsetsControlTarget target = dc.getImeTarget(IME_TARGET_LAYERING);
imeLayerTargetName = target != null ? target.getWindow().getName() : "null";
+ final SurfaceControl imeParent = dc.mInputMethodSurfaceParent;
+ imeSurfaceParentName = imeParent != null ? imeParent.toString() : "null";
if (show) {
dc.onShowImeRequested();
}
} else {
- imeControlTargetName = imeLayerTargetName = "no-display";
+ imeControlTargetName = imeLayerTargetName = imeSurfaceParentName = "no-display";
}
}
return new ImeTargetInfo(focusedWindowName, requestWindowName, imeControlTargetName,
- imeLayerTargetName);
+ imeLayerTargetName, imeSurfaceParentName);
}
@Override
@@ -8902,7 +8895,6 @@ public class WindowManagerService extends IWindowManager.Stub
@Override
public boolean getWindowInsets(WindowManager.LayoutParams attrs, int displayId,
InsetsState outInsetsState) {
- final boolean fromLocal = Binder.getCallingPid() == MY_PID;
final int uid = Binder.getCallingUid();
final long origId = Binder.clearCallingIdentity();
try {
@@ -8916,10 +8908,8 @@ public class WindowManagerService extends IWindowManager.Stub
final float overrideScale = mAtmService.mCompatModePackages.getCompatScale(
attrs.packageName, uid);
final InsetsState state = dc.getInsetsPolicy().getInsetsForWindowMetrics(attrs);
- final boolean hasCompatScale =
- WindowState.hasCompatScale(attrs, token, overrideScale);
- outInsetsState.set(state, hasCompatScale || fromLocal);
- if (hasCompatScale) {
+ outInsetsState.set(state, true /* copySources */);
+ if (WindowState.hasCompatScale(attrs, token, overrideScale)) {
final float compatScale = token != null && token.hasSizeCompatBounds()
? token.getSizeCompatScale() * overrideScale
: overrideScale;
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 95b06453781e..8f63e9317962 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -27,6 +27,7 @@ import static android.view.WindowManager.TRANSIT_FLAG_APP_CRASHED;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
import static com.android.internal.util.Preconditions.checkArgument;
+import static com.android.server.am.ProcessList.INVALID_ADJ;
import static com.android.server.wm.ActivityRecord.State.DESTROYED;
import static com.android.server.wm.ActivityRecord.State.DESTROYING;
import static com.android.server.wm.ActivityRecord.State.PAUSED;
@@ -125,6 +126,8 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
private volatile int mCurProcState = PROCESS_STATE_NONEXISTENT;
// Last reported process state;
private volatile int mRepProcState = PROCESS_STATE_NONEXISTENT;
+ // Currently computed oom adj score
+ private volatile int mCurAdj = INVALID_ADJ;
// are we in the process of crashing?
private volatile boolean mCrashing;
// does the app have a not responding dialog?
@@ -319,6 +322,14 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
return mCurProcState;
}
+ public void setCurrentAdj(int curAdj) {
+ mCurAdj = curAdj;
+ }
+
+ int getCurrentAdj() {
+ return mCurAdj;
+ }
+
/**
* Sets the computed process state from the oom adjustment calculation. This is frequently
* called in activity manager's lock, so don't use window manager lock here.
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 283010ea5767..a85dcbf24361 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -389,7 +389,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
* examine the git commit message introducing this comment and variable.2
*/
int mSyncSeqId = 0;
- int mLastSeqIdSentToRelayout = 0;
/** The last syncId associated with a prepareSync or 0 when no sync is active. */
int mPrepareSyncSeqId = 0;
@@ -425,6 +424,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
boolean mHaveFrame;
boolean mObscured;
+ int mRelayoutSeq = -1;
int mLayoutSeq = -1;
/**
@@ -1349,29 +1349,15 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
final WindowFrames windowFrames = mWindowFrames;
mTmpRect.set(windowFrames.mParentFrame);
- if (LOCAL_LAYOUT) {
- windowFrames.mCompatFrame.set(clientWindowFrames.frame);
-
- windowFrames.mFrame.set(clientWindowFrames.frame);
- windowFrames.mDisplayFrame.set(clientWindowFrames.displayFrame);
- windowFrames.mParentFrame.set(clientWindowFrames.parentFrame);
- if (mGlobalScale != 1f) {
- // The frames sent from the client need to be adjusted to the real coordinate space.
- windowFrames.mFrame.scale(mGlobalScale);
- windowFrames.mDisplayFrame.scale(mGlobalScale);
- windowFrames.mParentFrame.scale(mGlobalScale);
- }
- } else {
- windowFrames.mDisplayFrame.set(clientWindowFrames.displayFrame);
- windowFrames.mParentFrame.set(clientWindowFrames.parentFrame);
- windowFrames.mFrame.set(clientWindowFrames.frame);
+ windowFrames.mDisplayFrame.set(clientWindowFrames.displayFrame);
+ windowFrames.mParentFrame.set(clientWindowFrames.parentFrame);
+ windowFrames.mFrame.set(clientWindowFrames.frame);
- windowFrames.mCompatFrame.set(windowFrames.mFrame);
- if (mInvGlobalScale != 1f) {
- // Also, the scaled frame that we report to the app needs to be adjusted to be in
- // its coordinate space.
- windowFrames.mCompatFrame.scale(mInvGlobalScale);
- }
+ windowFrames.mCompatFrame.set(windowFrames.mFrame);
+ if (mInvGlobalScale != 1f) {
+ // Also, the scaled frame that we report to the app needs to be adjusted to be in
+ // its coordinate space.
+ windowFrames.mCompatFrame.scale(mInvGlobalScale);
}
windowFrames.setParentFrameWasClippedByDisplayCutout(
clientWindowFrames.isParentFrameClippedByDisplayCutout);
@@ -1415,13 +1401,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
updateSourceFrame(windowFrames.mFrame);
- if (LOCAL_LAYOUT) {
- if (!mHaveFrame) {
- // The first frame should not be considered as moved.
- updateLastFrames();
- }
- }
-
if (mActivityRecord != null && !mIsChildWindow) {
mActivityRecord.layoutLetterbox(this);
}
@@ -3823,6 +3802,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
return wpc != null && wpc.registeredForDisplayAreaConfigChanges();
}
+ WindowProcessController getProcess() {
+ return mWpcForDisplayAreaConfigChanges;
+ }
+
/**
* Fills the given window frames and merged configuration for the client.
*
diff --git a/services/core/xsd/display-device-config/autobrightness.xsd b/services/core/xsd/display-device-config/autobrightness.xsd
new file mode 100644
index 000000000000..477625a36cbd
--- /dev/null
+++ b/services/core/xsd/display-device-config/autobrightness.xsd
@@ -0,0 +1,33 @@
+<!--
+ 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.
+-->
+<xs:schema version="2.0"
+ elementFormDefault="qualified"
+ xmlns:xs="http://www.w3.org/2001/XMLSchema">
+ <xs:complexType name="autoBrightness">
+ <xs:sequence>
+ <!-- Sets the debounce for autoBrightness brightening in millis-->
+ <xs:element name="brighteningLightDebounceMillis" type="xs:nonNegativeInteger"
+ minOccurs="0" maxOccurs="1">
+ <xs:annotation name="final"/>
+ </xs:element>
+ <!-- Sets the debounce for autoBrightness darkening in millis-->
+ <xs:element name="darkeningLightDebounceMillis" type="xs:nonNegativeInteger"
+ minOccurs="0" maxOccurs="1">
+ <xs:annotation name="final"/>
+ </xs:element>
+ </xs:sequence>
+ </xs:complexType>
+</xs:schema> \ No newline at end of file
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index 073b131cc819..bea5e2c2de74 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -23,6 +23,7 @@
<xs:schema version="2.0"
elementFormDefault="qualified"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
+ <xs:include schemaLocation="autobrightness.xsd" />
<xs:element name="displayConfiguration">
<xs:complexType>
<xs:sequence>
@@ -102,22 +103,6 @@
</xs:element>
<!-- Type definitions -->
-
- <xs:complexType name="autoBrightness">
- <xs:sequence>
- <!-- Sets the debounce for autoBrightness brightening in millis-->
- <xs:element name="brighteningLightDebounceMillis" type="xs:nonNegativeInteger"
- minOccurs="0" maxOccurs="1">
- <xs:annotation name="final"/>
- </xs:element>
- <!-- Sets the debounce for autoBrightness darkening in millis-->
- <xs:element name="darkeningLightDebounceMillis" type="xs:nonNegativeInteger"
- minOccurs="0" maxOccurs="1">
- <xs:annotation name="final"/>
- </xs:element>
- </xs:sequence>
- </xs:complexType>
-
<xs:complexType name="displayQuirks">
<xs:sequence>
<xs:element name="quirk" type="xs:string" minOccurs="0" maxOccurs="unbounded" />
diff --git a/services/people/java/com/android/server/people/data/ConversationInfo.java b/services/people/java/com/android/server/people/data/ConversationInfo.java
index 16c4c29a798e..6ead44a8cd1b 100644
--- a/services/people/java/com/android/server/people/data/ConversationInfo.java
+++ b/services/people/java/com/android/server/people/data/ConversationInfo.java
@@ -26,6 +26,7 @@ import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutInfo.ShortcutFlags;
import android.net.Uri;
import android.text.TextUtils;
+import android.util.Log;
import android.util.Slog;
import android.util.proto.ProtoInputStream;
import android.util.proto.ProtoOutputStream;
@@ -37,6 +38,7 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
+import java.io.EOFException;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -50,6 +52,11 @@ import java.util.Objects;
* Represents a conversation that is provided by the app based on {@link ShortcutInfo}.
*/
public class ConversationInfo {
+ private static final boolean DEBUG = false;
+
+ // Schema version for the backup payload. Must be incremented whenever fields are added in
+ // backup payload.
+ private static final int VERSION = 1;
private static final String TAG = ConversationInfo.class.getSimpleName();
@@ -100,6 +107,8 @@ public class ConversationInfo {
private long mLastEventTimestamp;
+ private long mCreationTimestamp;
+
@ShortcutFlags
private int mShortcutFlags;
@@ -116,6 +125,7 @@ public class ConversationInfo {
mNotificationChannelId = builder.mNotificationChannelId;
mParentNotificationChannelId = builder.mParentNotificationChannelId;
mLastEventTimestamp = builder.mLastEventTimestamp;
+ mCreationTimestamp = builder.mCreationTimestamp;
mShortcutFlags = builder.mShortcutFlags;
mConversationFlags = builder.mConversationFlags;
mCurrStatuses = builder.mCurrStatuses;
@@ -170,6 +180,13 @@ public class ConversationInfo {
return mLastEventTimestamp;
}
+ /**
+ * Timestamp of the creation of the conversationInfo.
+ */
+ long getCreationTimestamp() {
+ return mCreationTimestamp;
+ }
+
/** Whether the shortcut for this conversation is set long-lived by the app. */
public boolean isShortcutLongLived() {
return hasShortcutFlags(ShortcutInfo.FLAG_LONG_LIVED);
@@ -241,6 +258,7 @@ public class ConversationInfo {
&& Objects.equals(mNotificationChannelId, other.mNotificationChannelId)
&& Objects.equals(mParentNotificationChannelId, other.mParentNotificationChannelId)
&& Objects.equals(mLastEventTimestamp, other.mLastEventTimestamp)
+ && mCreationTimestamp == other.mCreationTimestamp
&& mShortcutFlags == other.mShortcutFlags
&& mConversationFlags == other.mConversationFlags
&& Objects.equals(mCurrStatuses, other.mCurrStatuses);
@@ -250,7 +268,7 @@ public class ConversationInfo {
public int hashCode() {
return Objects.hash(mShortcutId, mLocusId, mContactUri, mContactPhoneNumber,
mNotificationChannelId, mParentNotificationChannelId, mLastEventTimestamp,
- mShortcutFlags, mConversationFlags, mCurrStatuses);
+ mCreationTimestamp, mShortcutFlags, mConversationFlags, mCurrStatuses);
}
@Override
@@ -264,6 +282,7 @@ public class ConversationInfo {
sb.append(", notificationChannelId=").append(mNotificationChannelId);
sb.append(", parentNotificationChannelId=").append(mParentNotificationChannelId);
sb.append(", lastEventTimestamp=").append(mLastEventTimestamp);
+ sb.append(", creationTimestamp=").append(mCreationTimestamp);
sb.append(", statuses=").append(mCurrStatuses);
sb.append(", shortcutFlags=0x").append(Integer.toHexString(mShortcutFlags));
sb.append(" [");
@@ -329,6 +348,7 @@ public class ConversationInfo {
mParentNotificationChannelId);
}
protoOutputStream.write(ConversationInfoProto.LAST_EVENT_TIMESTAMP, mLastEventTimestamp);
+ protoOutputStream.write(ConversationInfoProto.CREATION_TIMESTAMP, mCreationTimestamp);
protoOutputStream.write(ConversationInfoProto.SHORTCUT_FLAGS, mShortcutFlags);
protoOutputStream.write(ConversationInfoProto.CONVERSATION_FLAGS, mConversationFlags);
if (mContactPhoneNumber != null) {
@@ -352,6 +372,8 @@ public class ConversationInfo {
out.writeUTF(mContactPhoneNumber != null ? mContactPhoneNumber : "");
out.writeUTF(mParentNotificationChannelId != null ? mParentNotificationChannelId : "");
out.writeLong(mLastEventTimestamp);
+ out.writeInt(VERSION);
+ out.writeLong(mCreationTimestamp);
// ConversationStatus is a transient object and not persisted
} catch (IOException e) {
Slog.e(TAG, "Failed to write fields to backup payload.", e);
@@ -399,6 +421,9 @@ public class ConversationInfo {
builder.setLastEventTimestamp(protoInputStream.readLong(
ConversationInfoProto.LAST_EVENT_TIMESTAMP));
break;
+ case (int) ConversationInfoProto.CREATION_TIMESTAMP:
+ builder.setCreationTimestamp(protoInputStream.readLong(
+ ConversationInfoProto.CREATION_TIMESTAMP));
case (int) ConversationInfoProto.SHORTCUT_FLAGS:
builder.setShortcutFlags(protoInputStream.readInt(
ConversationInfoProto.SHORTCUT_FLAGS));
@@ -448,6 +473,10 @@ public class ConversationInfo {
builder.setParentNotificationChannelId(parentNotificationChannelId);
}
builder.setLastEventTimestamp(in.readLong());
+ int payloadVersion = maybeReadVersion(in);
+ if (payloadVersion == 1) {
+ builder.setCreationTimestamp(in.readLong());
+ }
} catch (IOException e) {
Slog.e(TAG, "Failed to read conversation info fields from backup payload.", e);
return null;
@@ -455,6 +484,16 @@ public class ConversationInfo {
return builder.build();
}
+ private static int maybeReadVersion(DataInputStream in) throws IOException {
+ try {
+ return in.readInt();
+ } catch (EOFException eofException) {
+ // EOF is expected if using old backup payload protocol.
+ if (DEBUG) Log.d(TAG, "Eof reached for data stream, missing version number");
+ return 0;
+ }
+ }
+
/**
* Builder class for {@link ConversationInfo} objects.
*/
@@ -479,6 +518,8 @@ public class ConversationInfo {
private long mLastEventTimestamp;
+ private long mCreationTimestamp;
+
@ShortcutFlags
private int mShortcutFlags;
@@ -502,6 +543,7 @@ public class ConversationInfo {
mNotificationChannelId = conversationInfo.mNotificationChannelId;
mParentNotificationChannelId = conversationInfo.mParentNotificationChannelId;
mLastEventTimestamp = conversationInfo.mLastEventTimestamp;
+ mCreationTimestamp = conversationInfo.mCreationTimestamp;
mShortcutFlags = conversationInfo.mShortcutFlags;
mConversationFlags = conversationInfo.mConversationFlags;
mCurrStatuses = conversationInfo.mCurrStatuses;
@@ -542,6 +584,11 @@ public class ConversationInfo {
return this;
}
+ Builder setCreationTimestamp(long creationTimestamp) {
+ mCreationTimestamp = creationTimestamp;
+ return this;
+ }
+
Builder setShortcutFlags(@ShortcutFlags int shortcutFlags) {
mShortcutFlags = shortcutFlags;
return this;
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 d305fc5d7dc4..693f3a0cf8a0 100644
--- a/services/people/java/com/android/server/people/data/DataManager.java
+++ b/services/people/java/com/android/server/people/data/DataManager.java
@@ -816,10 +816,18 @@ public class DataManager {
}
private boolean isCachedRecentConversation(ConversationInfo conversationInfo) {
+ return isEligibleForCleanUp(conversationInfo)
+ && conversationInfo.getLastEventTimestamp() > 0L;
+ }
+
+ /**
+ * Conversations that are cached and not customized are eligible for clean-up, even if they
+ * don't have an associated notification event with them.
+ */
+ private boolean isEligibleForCleanUp(ConversationInfo conversationInfo) {
return conversationInfo.isShortcutCachedForNotification()
&& Objects.equals(conversationInfo.getNotificationChannelId(),
- conversationInfo.getParentNotificationChannelId())
- && conversationInfo.getLastEventTimestamp() > 0L;
+ conversationInfo.getParentNotificationChannelId());
}
private boolean hasActiveNotifications(String packageName, @UserIdInt int userId,
@@ -842,14 +850,14 @@ public class DataManager {
}
// pair of <package name, conversation info>
List<Pair<String, ConversationInfo>> cachedConvos = new ArrayList<>();
- userData.forAllPackages(packageData ->
+ userData.forAllPackages(packageData -> {
packageData.forAllConversations(conversationInfo -> {
- if (isCachedRecentConversation(conversationInfo)) {
+ if (isEligibleForCleanUp(conversationInfo)) {
cachedConvos.add(
Pair.create(packageData.getPackageName(), conversationInfo));
}
- })
- );
+ });
+ });
if (cachedConvos.size() <= targetCachedCount) {
return;
}
@@ -858,7 +866,9 @@ public class DataManager {
PriorityQueue<Pair<String, ConversationInfo>> maxHeap = new PriorityQueue<>(
numToUncache + 1,
Comparator.comparingLong((Pair<String, ConversationInfo> pair) ->
- pair.second.getLastEventTimestamp()).reversed());
+ Math.max(
+ pair.second.getLastEventTimestamp(),
+ pair.second.getCreationTimestamp())).reversed());
for (Pair<String, ConversationInfo> cached : cachedConvos) {
if (hasActiveNotifications(cached.first, userId, cached.second.getShortcutId())) {
continue;
@@ -893,7 +903,7 @@ public class DataManager {
}
ConversationInfo.Builder builder = oldConversationInfo != null
? new ConversationInfo.Builder(oldConversationInfo)
- : new ConversationInfo.Builder();
+ : new ConversationInfo.Builder().setCreationTimestamp(System.currentTimeMillis());
builder.setShortcutId(shortcutInfo.getId());
builder.setLocusId(shortcutInfo.getLocusId());
@@ -1326,7 +1336,8 @@ public class DataManager {
}
}
- private void updateConversationStoreThenNotifyListeners(ConversationStore cs,
+ @VisibleForTesting
+ void updateConversationStoreThenNotifyListeners(ConversationStore cs,
ConversationInfo modifiedConv,
String packageName, int userId) {
cs.addOrUpdate(modifiedConv);
diff --git a/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java b/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java
index 297538ad7b4b..159285a5ce5e 100644
--- a/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java
@@ -61,8 +61,8 @@ import com.android.server.backup.testing.BackupManagerServiceTestUtils;
import com.android.server.backup.testing.TransportData;
import com.android.server.backup.testing.TransportTestUtils.TransportMock;
import com.android.server.backup.transport.TransportNotRegisteredException;
-import com.android.server.testing.shadows.ShadowBackupEligibilityRules;
import com.android.server.testing.shadows.ShadowApplicationPackageManager;
+import com.android.server.testing.shadows.ShadowBackupEligibilityRules;
import com.android.server.testing.shadows.ShadowBinder;
import com.android.server.testing.shadows.ShadowKeyValueBackupJob;
import com.android.server.testing.shadows.ShadowKeyValueBackupTask;
@@ -361,6 +361,26 @@ public class UserBackupManagerServiceTest {
}
/**
+ * Test verifying that {@link UserBackupManagerService#selectBackupTransport(String)} does not
+ * switch the current transport to the inputted transport, when the inputted transport is not
+ * registered.
+ */
+ @Test
+ public void testSelectBackupTransport_nonRegisteredTransport() throws Exception {
+ setUpForSelectTransport();
+ mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
+ when(mTransportManager.isTransportRegistered(eq(mNewTransport.transportName)))
+ .thenReturn(false);
+ UserBackupManagerService backupManagerService = createUserBackupManagerServiceAndRunTasks();
+
+ String oldTransport = backupManagerService.selectBackupTransport(
+ mNewTransport.transportName);
+
+ assertThat(getSettingsTransport()).isNotEqualTo(mNewTransport.transportName);
+ assertThat(oldTransport).isEqualTo(null);
+ }
+
+ /**
* Test verifying that {@link UserBackupManagerService#selectBackupTransport(String)} throws a
* {@link SecurityException} if the caller does not have backup permission.
*/
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
index b53a2c69708d..9022db83d5ce 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
@@ -17,8 +17,10 @@
package com.android.server.app;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.server.app.GameManagerService.WRITE_SETTINGS;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
@@ -35,11 +37,15 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.Manifest;
+import android.annotation.Nullable;
import android.app.GameManager;
import android.app.GameModeInfo;
import android.app.GameState;
+import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
@@ -97,6 +103,7 @@ public class GameManagerServiceTests {
private PowerManagerInternal mMockPowerManager;
@Mock
private UserManager mMockUserManager;
+ private BroadcastReceiver mShutDownActionReceiver;
// Stolen from ConnectivityServiceTest.MockContext
class MockContext extends ContextWrapper {
@@ -165,6 +172,12 @@ public class GameManagerServiceTests {
}
throw new UnsupportedOperationException("Couldn't find system service: " + name);
}
+
+ @Override
+ public Intent registerReceiver(@Nullable BroadcastReceiver receiver, IntentFilter filter) {
+ mShutDownActionReceiver = receiver;
+ return null;
+ }
}
@Before
@@ -200,15 +213,16 @@ public class GameManagerServiceTests {
@After
public void tearDown() throws Exception {
LocalServices.removeServiceForTest(PowerManagerInternal.class);
- GameManagerService gameManagerService = new GameManagerService(mMockContext);
if (mMockingSession != null) {
mMockingSession.finishMocking();
}
+ deleteFolder(InstrumentationRegistry.getTargetContext().getFilesDir());
}
private void startUser(GameManagerService gameManagerService, int userId) {
UserInfo userInfo = new UserInfo(userId, "name", 0);
- gameManagerService.onUserStarting(new SystemService.TargetUser(userInfo));
+ gameManagerService.onUserStarting(new SystemService.TargetUser(userInfo),
+ InstrumentationRegistry.getContext().getFilesDir());
mTestLooper.dispatchAll();
}
@@ -584,7 +598,7 @@ public class GameManagerServiceTests {
gameManagerService.updateConfigsForUser(USER_ID_1, true, mPackageName);
}
GameManagerService.GamePackageConfiguration config =
- gameManagerService.getConfig(mPackageName);
+ gameManagerService.getConfig(mPackageName, USER_ID_1);
assertEquals(scaling, config.getGameModeConfiguration(gameMode).getScaling(), 0.01f);
}
@@ -594,7 +608,7 @@ public class GameManagerServiceTests {
// Validate GamePackageConfiguration returns the correct value.
GameManagerService.GamePackageConfiguration config =
- gameManagerService.getConfig(mPackageName);
+ gameManagerService.getConfig(mPackageName, USER_ID_1);
assertEquals(config.getGameModeConfiguration(gameMode).getUseAngle(), angleEnabled);
// Validate GameManagerService.isAngleEnabled() returns the correct value.
@@ -607,7 +621,7 @@ public class GameManagerServiceTests {
// Validate GamePackageConfiguration returns the correct value.
GameManagerService.GamePackageConfiguration config =
- gameManagerService.getConfig(mPackageName);
+ gameManagerService.getConfig(mPackageName, USER_ID_1);
assertEquals(
loadingBoost, config.getGameModeConfiguration(gameMode).getLoadingBoostDuration());
@@ -623,7 +637,7 @@ public class GameManagerServiceTests {
gameManagerService.updateConfigsForUser(USER_ID_1, true, mPackageName);
}
GameManagerService.GamePackageConfiguration config =
- gameManagerService.getConfig(mPackageName);
+ gameManagerService.getConfig(mPackageName, USER_ID_1);
assertEquals(fps, config.getGameModeConfiguration(gameMode).getFps());
}
@@ -1049,7 +1063,7 @@ public class GameManagerServiceTests {
mTestLooper.getLooper());
startUser(gameManagerService, USER_ID_1);
GameManagerService.GamePackageConfiguration config =
- gameManagerService.getConfig(mPackageName);
+ gameManagerService.getConfig(mPackageName, USER_ID_1);
assertEquals(90,
config.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE).getFps());
assertEquals(30, config.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY).getFps());
@@ -1064,7 +1078,7 @@ public class GameManagerServiceTests {
mTestLooper.getLooper());
startUser(gameManagerService, USER_ID_1);
GameManagerService.GamePackageConfiguration config =
- gameManagerService.getConfig(mPackageName);
+ gameManagerService.getConfig(mPackageName, USER_ID_1);
assertEquals(0,
config.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE).getFps());
assertEquals(0, config.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY).getFps());
@@ -1092,7 +1106,7 @@ public class GameManagerServiceTests {
startUser(gameManagerService, USER_ID_1);
gameManagerService.updateConfigsForUser(USER_ID_1, true, mPackageName);
GameManagerService.GamePackageConfiguration config =
- gameManagerService.getConfig(mPackageName);
+ gameManagerService.getConfig(mPackageName, USER_ID_1);
assertNull(config.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE));
}
@@ -1339,10 +1353,15 @@ public class GameManagerServiceTests {
mTestLooper.dispatchAll();
/* Expected fileOutput (order may vary)
+ # user 1001:
com.android.app2 <UID> 0 2 angle=0,scaling=0.5,fps=90 3 angle=0,scaling=0.5,fps=60
com.android.app1 <UID> 1 2 angle=0,scaling=0.5,fps=90 3 angle=0,scaling=0.7,fps=30
com.android.app0 <UID> 0 2 angle=0,scaling=0.6,fps=120 3 angle=0,scaling=0.7,fps=30
+ # user 1002:
+ com.android.app2 <UID> 0 2 angle=0,scaling=0.5,fps=90 3 angle=0,scaling=0.7,fps=30
+ com.android.app1 <UID> 1 2 angle=0,scaling=0.5,fps=90 3 angle=0,scaling=0.7,fps=30
+ com.android.app0 <UID> 0 2 angle=0,scaling=0.5,fps=90 3 angle=0,scaling=0.7,fps=30
The current game mode would only be set to non-zero if the current user have that game
installed.
*/
@@ -1386,7 +1405,7 @@ public class GameManagerServiceTests {
assertEquals(splitLine[3], "2");
assertEquals(splitLine[4], "angle=0,scaling=0.5,fps=90");
assertEquals(splitLine[5], "3");
- assertEquals(splitLine[6], "angle=0,scaling=0.5,fps=60");
+ assertEquals(splitLine[6], "angle=0,scaling=0.7,fps=30");
splitLine = fileOutput.get(1).split("\\s+");
assertEquals(splitLine[0], "com.android.app1");
assertEquals(splitLine[2], "3");
@@ -1398,7 +1417,7 @@ public class GameManagerServiceTests {
assertEquals(splitLine[0], "com.android.app0");
assertEquals(splitLine[2], "0");
assertEquals(splitLine[3], "2");
- assertEquals(splitLine[4], "angle=0,scaling=0.6,fps=120");
+ assertEquals(splitLine[4], "angle=0,scaling=0.5,fps=90");
assertEquals(splitLine[5], "3");
assertEquals(splitLine[6], "angle=0,scaling=0.7,fps=30");
@@ -1493,12 +1512,52 @@ public class GameManagerServiceTests {
@Test
public void testGetResolutionScalingFactor_noUserId() {
- mockModifyGameModeDenied();
+ mockModifyGameModeGranted();
mockDeviceConfigAll();
GameManagerService gameManagerService =
new GameManagerService(mMockContext, mTestLooper.getLooper());
startUser(gameManagerService, USER_ID_2);
- assertEquals(-1f, gameManagerService.getResolutionScalingFactor(mPackageName,
- GameManager.GAME_MODE_BATTERY, USER_ID_1), 0.001f);
+ assertThrows(IllegalArgumentException.class, () -> {
+ gameManagerService.getResolutionScalingFactor(mPackageName,
+ GameManager.GAME_MODE_BATTERY, USER_ID_1);
+ });
+ }
+
+ @Test
+ public void testWritingSettingFile_onShutdown() throws InterruptedException {
+ mockModifyGameModeGranted();
+ mockDeviceConfigAll();
+ GameManagerService gameManagerService = new GameManagerService(mMockContext);
+ gameManagerService.onBootCompleted();
+ startUser(gameManagerService, USER_ID_1);
+ Thread.sleep(500);
+ gameManagerService.setGameModeConfigOverride("com.android.app1", USER_ID_1,
+ GameManager.GAME_MODE_BATTERY, "60", "0.5");
+ gameManagerService.setGameMode("com.android.app1", USER_ID_1,
+ GameManager.GAME_MODE_PERFORMANCE);
+ GameManagerSettings settings = new GameManagerSettings(
+ InstrumentationRegistry.getContext().getFilesDir());
+ Thread.sleep(500);
+ // no data written as delayed messages are queued
+ assertFalse(settings.readPersistentDataLocked());
+ assertTrue(gameManagerService.mHandler.hasEqualMessages(WRITE_SETTINGS, USER_ID_1));
+ Intent shutdown = new Intent();
+ shutdown.setAction(Intent.ACTION_SHUTDOWN);
+ mShutDownActionReceiver.onReceive(mMockContext, shutdown);
+ Thread.sleep(500);
+ // data is written on processing new message with no delay on shutdown,
+ // and all queued messages should be removed
+ assertTrue(settings.readPersistentDataLocked());
+ assertFalse(gameManagerService.mHandler.hasEqualMessages(WRITE_SETTINGS, USER_ID_1));
+ }
+
+ private static void deleteFolder(File folder) {
+ File[] files = folder.listFiles();
+ if (files != null) {
+ for (File file : files) {
+ deleteFolder(file);
+ }
+ }
+ folder.delete();
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java
index 427c3b3409d2..4a631a1251d5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java
@@ -34,7 +34,6 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManagerInternal;
-import android.content.pm.ServiceInfo;
import android.os.BatteryManagerInternal;
import android.os.RemoteException;
import android.util.ArraySet;
@@ -190,7 +189,7 @@ public class BatteryControllerTest {
JobInfo jobInfo) {
JobStatus js = JobStatus.createFromJobInfo(
jobInfo, callingUid, packageName, SOURCE_USER_ID, testTag);
- js.serviceInfo = mock(ServiceInfo.class);
+ js.serviceProcessName = "testProcess";
// Make sure tests aren't passing just because the default bucket is likely ACTIVE.
js.setStandbyBucket(FREQUENT_INDEX);
return js;
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
index 35e6db947ac3..1b39add3b421 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
@@ -24,13 +24,17 @@ import static android.text.format.DateUtils.HOUR_IN_MILLIS;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
-import static com.android.server.job.controllers.FlexibilityController.DEFAULT_FLEXIBILITY_DEADLINE;
-import static com.android.server.job.controllers.FlexibilityController.FcConstants.KEY_FLEXIBILITY_ENABLED;
+import static com.android.server.job.controllers.FlexibilityController.FcConfig.DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS;
+import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_DEADLINE_PROXIMITY_LIMIT;
+import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_FALLBACK_FLEXIBILITY_DEADLINE;
+import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_FLEXIBILITY_ENABLED;
+import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS;
import static com.android.server.job.controllers.JobStatus.CONSTRAINT_BATTERY_NOT_LOW;
import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CHARGING;
import static com.android.server.job.controllers.JobStatus.CONSTRAINT_FLEXIBLE;
import static com.android.server.job.controllers.JobStatus.CONSTRAINT_IDLE;
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -76,7 +80,7 @@ public class FlexibilityControllerTest {
private FlexibilityController mFlexibilityController;
private DeviceConfig.Properties.Builder mDeviceConfigPropertiesBuilder;
private JobStore mJobStore;
- private FlexibilityController.FcConstants mFcConstants;
+ private FlexibilityController.FcConfig mFcConfig;
@Mock
private AlarmManager mAlarmManager;
@@ -129,8 +133,9 @@ public class FlexibilityControllerTest {
// Initialize real objects.
mFlexibilityController = new FlexibilityController(mJobSchedulerService,
mPrefetchController);
- mFcConstants = mFlexibilityController.getFcConstants();
+ mFcConfig = mFlexibilityController.getFcConfig();
+ setDeviceConfigLong(KEY_DEADLINE_PROXIMITY_LIMIT, 0L);
setDeviceConfigBoolean(KEY_FLEXIBILITY_ENABLED, true);
}
@@ -145,7 +150,25 @@ public class FlexibilityControllerTest {
mDeviceConfigPropertiesBuilder.setBoolean(key, val);
synchronized (mFlexibilityController.mLock) {
mFlexibilityController.prepareForUpdatedConstantsLocked();
- mFcConstants.processConstantLocked(mDeviceConfigPropertiesBuilder.build(), key);
+ mFcConfig.processConstantLocked(mDeviceConfigPropertiesBuilder.build(), key);
+ mFlexibilityController.onConstantsUpdatedLocked();
+ }
+ }
+
+ private void setDeviceConfigLong(String key, Long val) {
+ mDeviceConfigPropertiesBuilder.setLong(key, val);
+ synchronized (mFlexibilityController.mLock) {
+ mFlexibilityController.prepareForUpdatedConstantsLocked();
+ mFcConfig.processConstantLocked(mDeviceConfigPropertiesBuilder.build(), key);
+ mFlexibilityController.onConstantsUpdatedLocked();
+ }
+ }
+
+ private void setDeviceConfigString(String key, String val) {
+ mDeviceConfigPropertiesBuilder.setString(key, val);
+ synchronized (mFlexibilityController.mLock) {
+ mFlexibilityController.prepareForUpdatedConstantsLocked();
+ mFcConfig.processConstantLocked(mDeviceConfigPropertiesBuilder.build(), key);
mFlexibilityController.onConstantsUpdatedLocked();
}
}
@@ -162,6 +185,81 @@ public class FlexibilityControllerTest {
return js;
}
+ /**
+ * Tests that the there are equally many percents to drop constraints as there are constraints
+ */
+ @Test
+ public void testDefaultVariableValues() {
+ assertEquals(FlexibilityController.NUM_FLEXIBLE_CONSTRAINTS,
+ mFlexibilityController.mFcConfig.DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS.length
+ );
+ }
+
+ @Test
+ public void testOnConstantsUpdated_DefaultFlexibility() {
+ JobStatus js = createJobStatus("testDefaultFlexibilityConfig", createJob(0));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(js));
+ setDeviceConfigBoolean(KEY_FLEXIBILITY_ENABLED, false);
+ assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(js));
+ setDeviceConfigBoolean(KEY_FLEXIBILITY_ENABLED, true);
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(js));
+ }
+
+ @Test
+ public void testOnConstantsUpdated_DeadlineProximity() {
+ JobStatus js = createJobStatus("testDeadlineProximityConfig", createJob(0));
+ setDeviceConfigLong(KEY_DEADLINE_PROXIMITY_LIMIT, Long.MAX_VALUE);
+ mFlexibilityController.mFlexibilityAlarmQueue.scheduleDropNumConstraintsAlarm(js);
+ assertEquals(0, js.getNumRequiredFlexibleConstraints());
+ }
+
+ @Test
+ public void testOnConstantsUpdated_FallbackDeadline() {
+ JobStatus js = createJobStatus("testFallbackDeadlineConfig", createJob(0));
+ assertEquals(DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS,
+ mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0L));
+ setDeviceConfigLong(KEY_FALLBACK_FLEXIBILITY_DEADLINE, 100L);
+ assertEquals(100L, mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0L));
+ }
+
+ @Test
+ public void testOnConstantsUpdated_PercentsToDropConstraints() {
+ JobInfo.Builder jb = createJob(0).setOverrideDeadline(100L);
+ JobStatus js = createJobStatus("testPercentsToDropConstraintsConfig", jb);
+ assertEquals(150L,
+ mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
+ setDeviceConfigString(KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS, "10,20,30,40");
+ assertArrayEquals(
+ mFlexibilityController.mFcConfig.PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS,
+ new int[] {10, 20, 30, 40});
+ assertEquals(110L,
+ mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
+ js.adjustNumRequiredFlexibleConstraints(-1);
+ assertEquals(120L,
+ mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
+ js.adjustNumRequiredFlexibleConstraints(-1);
+ assertEquals(130L,
+ mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
+ }
+
+ @Test
+ public void testOnConstantsUpdated_PercentsToDropConstraintsInvalidValues() {
+ JobInfo.Builder jb = createJob(0).setOverrideDeadline(100L);
+ JobStatus js = createJobStatus("testPercentsToDropConstraintsConfig", jb);
+ js.enqueueTime = 100L;
+ assertEquals(150L,
+ mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
+ setDeviceConfigString(KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS, "10,20a,030,40");
+ assertEquals(150L,
+ mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
+ setDeviceConfigString(KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS, "10,40");
+ assertEquals(150L,
+ mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
+ setDeviceConfigString(KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS, "50,40,10,40");
+ assertEquals(150L,
+ mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
+ }
+
@Test
public void testGetNextConstraintDropTimeElapsedLocked() {
long nextTimeToDropNumConstraints;
@@ -347,7 +445,7 @@ public class FlexibilityControllerTest {
// no deadline
jb = createJob(0);
js = createJobStatus("time", jb);
- assertEquals(100L + DEFAULT_FLEXIBILITY_DEADLINE,
+ assertEquals(100L + DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS,
mFlexibilityController.getLifeCycleEndElapsedLocked(js, 100L));
}
@@ -431,8 +529,8 @@ public class FlexibilityControllerTest {
assertEquals(0, trackedJobs.get(3).size());
JobSchedulerService.sElapsedRealtimeClock =
- Clock.fixed(Instant.ofEpochMilli(
- (DEFAULT_FLEXIBILITY_DEADLINE / 2) + HOUR_IN_MILLIS), ZoneOffset.UTC);
+ Clock.fixed(Instant.ofEpochMilli((DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS / 2)
+ + HOUR_IN_MILLIS), ZoneOffset.UTC);
flexTracker.resetJobNumDroppedConstraints(jobs[0]);
assertEquals(0, trackedJobs.get(0).size());
@@ -577,19 +675,76 @@ public class FlexibilityControllerTest {
}
@Test
+ public void testResetJobNumDroppedConstraints() {
+ JobInfo.Builder jb = createJob(22).setOverrideDeadline(100L);
+ JobStatus js = createJobStatus("testResetJobNumDroppedConstraints", jb);
+ js.adjustNumRequiredFlexibleConstraints(3);
+
+ mFlexibilityController.mFlexibilityTracker.add(js);
+
+ assertEquals(3, js.getNumRequiredFlexibleConstraints());
+ assertEquals(0, js.getNumDroppedFlexibleConstraints());
+ assertEquals(1, mFlexibilityController
+ .mFlexibilityTracker.getJobsByNumRequiredConstraints(3).size());
+
+ JobSchedulerService.sElapsedRealtimeClock =
+ Clock.fixed(Instant.ofEpochMilli(155L), ZoneOffset.UTC);
+
+ mFlexibilityController.mFlexibilityTracker.adjustJobsRequiredConstraints(js, -1);
+
+ assertEquals(2, js.getNumRequiredFlexibleConstraints());
+ assertEquals(1, js.getNumDroppedFlexibleConstraints());
+ assertEquals(1, mFlexibilityController
+ .mFlexibilityTracker.getJobsByNumRequiredConstraints(2).size());
+
+ mFlexibilityController.mFlexibilityTracker.resetJobNumDroppedConstraints(js);
+
+ assertEquals(2, js.getNumRequiredFlexibleConstraints());
+ assertEquals(1, js.getNumDroppedFlexibleConstraints());
+ assertEquals(1, mFlexibilityController
+ .mFlexibilityTracker.getJobsByNumRequiredConstraints(2).size());
+
+ JobSchedulerService.sElapsedRealtimeClock =
+ Clock.fixed(Instant.ofEpochMilli(140L), ZoneOffset.UTC);
+
+ mFlexibilityController.mFlexibilityTracker.resetJobNumDroppedConstraints(js);
+
+ assertEquals(3, js.getNumRequiredFlexibleConstraints());
+ assertEquals(0, js.getNumDroppedFlexibleConstraints());
+ assertEquals(1, mFlexibilityController
+ .mFlexibilityTracker.getJobsByNumRequiredConstraints(3).size());
+
+ JobSchedulerService.sElapsedRealtimeClock =
+ Clock.fixed(Instant.ofEpochMilli(175), ZoneOffset.UTC);
+
+ mFlexibilityController.mFlexibilityTracker.resetJobNumDroppedConstraints(js);
+
+ assertEquals(0, js.getNumRequiredFlexibleConstraints());
+ assertEquals(3, js.getNumDroppedFlexibleConstraints());
+
+ JobSchedulerService.sElapsedRealtimeClock =
+ Clock.fixed(Instant.ofEpochMilli(165L), ZoneOffset.UTC);
+
+ mFlexibilityController.mFlexibilityTracker.resetJobNumDroppedConstraints(js);
+
+ assertEquals(1, js.getNumRequiredFlexibleConstraints());
+ assertEquals(2, js.getNumDroppedFlexibleConstraints());
+ assertEquals(1, mFlexibilityController
+ .mFlexibilityTracker.getJobsByNumRequiredConstraints(1).size());
+ }
+
+ @Test
public void testOnPrefetchCacheUpdated() {
ArraySet<JobStatus> jobs = new ArraySet<JobStatus>();
JobInfo.Builder jb = createJob(22).setPrefetch(true);
JobStatus js = createJobStatus("onPrefetchCacheUpdated", jb);
jobs.add(js);
when(mPrefetchController.getLaunchTimeThresholdMs()).thenReturn(7 * HOUR_IN_MILLIS);
-
- mFlexibilityController.maybeStartTrackingJobLocked(js, null);
- mFlexibilityController.mFlexibilityTracker.adjustJobsRequiredConstraints(js, -1);
-
when(mPrefetchController.getNextEstimatedLaunchTimeLocked(js)).thenReturn(
1150L + mFlexibilityController.mConstants.PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS);
+ mFlexibilityController.maybeStartTrackingJobLocked(js, null);
+
JobSchedulerService.sElapsedRealtimeClock =
Clock.fixed(Instant.ofEpochMilli(150L), ZoneOffset.UTC);
@@ -604,9 +759,9 @@ public class FlexibilityControllerTest {
assertEquals(1150L,
mFlexibilityController.getLifeCycleEndElapsedLocked(js, 150L));
assertEquals(0, mFlexibilityController.getCurPercentOfLifecycleLocked(js));
- assertEquals(0, js.getNumDroppedFlexibleConstraints());
assertEquals(650L, mFlexibilityController
.getNextConstraintDropTimeElapsedLocked(js));
+ assertEquals(3, js.getNumRequiredFlexibleConstraints());
assertEquals(1, mFlexibilityController
.mFlexibilityTracker.getJobsByNumRequiredConstraints(3).size());
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
index f15e60f32fb7..149ae0b81740 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
@@ -33,6 +33,7 @@ import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CONNECTIVI
import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CONTENT_TRIGGER;
import static com.android.server.job.controllers.JobStatus.CONSTRAINT_DEADLINE;
import static com.android.server.job.controllers.JobStatus.CONSTRAINT_DEVICE_NOT_DOZING;
+import static com.android.server.job.controllers.JobStatus.CONSTRAINT_FLEXIBLE;
import static com.android.server.job.controllers.JobStatus.CONSTRAINT_IDLE;
import static com.android.server.job.controllers.JobStatus.CONSTRAINT_STORAGE_NOT_LOW;
import static com.android.server.job.controllers.JobStatus.CONSTRAINT_TIMING_DELAY;
@@ -50,7 +51,6 @@ import android.app.job.JobInfo;
import android.app.usage.UsageStatsManagerInternal;
import android.content.ComponentName;
import android.content.pm.PackageManagerInternal;
-import android.content.pm.ServiceInfo;
import android.net.Uri;
import android.os.SystemClock;
import android.provider.MediaStore;
@@ -790,6 +790,83 @@ public class JobStatusTest {
assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_BACKGROUND_NOT_RESTRICTED));
}
+ @Test
+ public void testWouldBeReadyWithConstraint_FlexibilityDoesNotAffectReadiness() {
+ final JobStatus job = createJobStatus(
+ new JobInfo.Builder(101, new ComponentName("foo", "bar")).build());
+
+ markImplicitConstraintsSatisfied(job, false);
+ job.setFlexibilityConstraintSatisfied(0, false);
+ assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_CHARGING));
+ assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_IDLE));
+ assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_BATTERY_NOT_LOW));
+ assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_STORAGE_NOT_LOW));
+ assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_TIMING_DELAY));
+ assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_DEADLINE));
+ assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_CONNECTIVITY));
+ assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_CONTENT_TRIGGER));
+ assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_FLEXIBLE));
+
+ markImplicitConstraintsSatisfied(job, true);
+ job.setFlexibilityConstraintSatisfied(0, false);
+ assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_CHARGING));
+ assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_IDLE));
+ assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_BATTERY_NOT_LOW));
+ assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_STORAGE_NOT_LOW));
+ assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_TIMING_DELAY));
+ assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_DEADLINE));
+ assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_CONNECTIVITY));
+ assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_CONTENT_TRIGGER));
+ assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_FLEXIBLE));
+
+ markImplicitConstraintsSatisfied(job, false);
+ job.setFlexibilityConstraintSatisfied(0, true);
+ assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_CHARGING));
+ assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_IDLE));
+ assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_BATTERY_NOT_LOW));
+ assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_STORAGE_NOT_LOW));
+ assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_TIMING_DELAY));
+ assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_DEADLINE));
+ assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_CONNECTIVITY));
+ assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_CONTENT_TRIGGER));
+ assertFalse(job.wouldBeReadyWithConstraint(CONSTRAINT_FLEXIBLE));
+
+ markImplicitConstraintsSatisfied(job, true);
+ job.setFlexibilityConstraintSatisfied(0, true);
+ assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_CHARGING));
+ assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_IDLE));
+ assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_BATTERY_NOT_LOW));
+ assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_STORAGE_NOT_LOW));
+ assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_TIMING_DELAY));
+ assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_DEADLINE));
+ assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_CONNECTIVITY));
+ assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_CONTENT_TRIGGER));
+ assertTrue(job.wouldBeReadyWithConstraint(CONSTRAINT_FLEXIBLE));
+ }
+
+ @Test
+ public void testReadinessStatusWithConstraint_FlexibilityConstraint() {
+ final JobStatus job = createJobStatus(
+ new JobInfo.Builder(101, new ComponentName("foo", "bar")).build());
+ job.setConstraintSatisfied(CONSTRAINT_FLEXIBLE, sElapsedRealtimeClock.millis(), false);
+ markImplicitConstraintsSatisfied(job, true);
+ assertTrue(job.readinessStatusWithConstraint(CONSTRAINT_FLEXIBLE, true));
+ assertFalse(job.readinessStatusWithConstraint(CONSTRAINT_FLEXIBLE, false));
+
+ markImplicitConstraintsSatisfied(job, false);
+ assertFalse(job.readinessStatusWithConstraint(CONSTRAINT_FLEXIBLE, true));
+ assertFalse(job.readinessStatusWithConstraint(CONSTRAINT_FLEXIBLE, false));
+
+ job.setConstraintSatisfied(CONSTRAINT_FLEXIBLE, sElapsedRealtimeClock.millis(), true);
+ markImplicitConstraintsSatisfied(job, true);
+ assertTrue(job.readinessStatusWithConstraint(CONSTRAINT_FLEXIBLE, true));
+ assertFalse(job.readinessStatusWithConstraint(CONSTRAINT_FLEXIBLE, false));
+
+ markImplicitConstraintsSatisfied(job, false);
+ assertFalse(job.readinessStatusWithConstraint(CONSTRAINT_FLEXIBLE, true));
+ assertFalse(job.readinessStatusWithConstraint(CONSTRAINT_FLEXIBLE, false));
+ }
+
private void markImplicitConstraintsSatisfied(JobStatus job, boolean isSatisfied) {
job.setQuotaConstraintSatisfied(sElapsedRealtimeClock.millis(), isSatisfied);
job.setTareWealthConstraintSatisfied(sElapsedRealtimeClock.millis(), isSatisfied);
@@ -797,7 +874,6 @@ public class JobStatusTest {
sElapsedRealtimeClock.millis(), isSatisfied, false);
job.setBackgroundNotRestrictedConstraintSatisfied(
sElapsedRealtimeClock.millis(), isSatisfied, false);
- job.setFlexibilityConstraintSatisfied(sElapsedRealtimeClock.millis(), isSatisfied);
}
private static JobStatus createJobStatus(long earliestRunTimeElapsedMillis,
@@ -810,7 +886,7 @@ public class JobStatusTest {
private static JobStatus createJobStatus(JobInfo job) {
JobStatus jobStatus = JobStatus.createFromJobInfo(job, 0, null, -1, "JobStatusTest");
- jobStatus.serviceInfo = mock(ServiceInfo.class);
+ jobStatus.serviceProcessName = "testProcess";
return jobStatus;
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java
index 7242b1bf1ccb..969389c27e70 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java
@@ -47,7 +47,6 @@ import android.app.usage.UsageStatsManagerInternal.EstimatedLaunchTimeChangedLis
import android.appwidget.AppWidgetManager;
import android.content.ComponentName;
import android.content.Context;
-import android.content.pm.ServiceInfo;
import android.os.Looper;
import android.os.Process;
import android.os.SystemClock;
@@ -185,7 +184,7 @@ public class PrefetchControllerTest {
JobInfo jobInfo) {
JobStatus js = JobStatus.createFromJobInfo(
jobInfo, callingUid, packageName, SOURCE_USER_ID, testTag);
- js.serviceInfo = mock(ServiceInfo.class);
+ js.serviceProcessName = "testProcess";
js.setStandbyBucket(FREQUENT_INDEX);
// Make sure Doze and background-not-restricted don't affect tests.
js.setDeviceNotDozingConstraintSatisfied(/* nowElapsed */ sElapsedRealtimeClock.millis(),
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
index 7048fb2379a0..9407968dbb56 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
@@ -70,7 +70,6 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
-import android.content.pm.ServiceInfo;
import android.os.BatteryManagerInternal;
import android.os.Handler;
import android.os.Looper;
@@ -385,7 +384,7 @@ public class QuotaControllerTest {
JobInfo jobInfo) {
JobStatus js = JobStatus.createFromJobInfo(
jobInfo, callingUid, packageName, SOURCE_USER_ID, testTag);
- js.serviceInfo = mock(ServiceInfo.class);
+ js.serviceProcessName = "testProcess";
// Make sure tests aren't passing just because the default bucket is likely ACTIVE.
js.setStandbyBucket(FREQUENT_INDEX);
// Make sure Doze and background-not-restricted don't affect tests.
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/listeners/ListenerMultiplexerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/listeners/ListenerMultiplexerTest.java
index d7fef604d25b..5b927061a655 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/listeners/ListenerMultiplexerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/listeners/ListenerMultiplexerTest.java
@@ -58,8 +58,10 @@ public class ListenerMultiplexerTest {
void onRegistrationAdded(Consumer<TestListenerRegistration> consumer,
TestListenerRegistration registration);
- void onRegistrationReplaced(Consumer<TestListenerRegistration> consumer,
- TestListenerRegistration oldRegistration, TestListenerRegistration newRegistration);
+ void onRegistrationReplaced(Consumer<TestListenerRegistration> oldConsumer,
+ TestListenerRegistration oldRegistration,
+ Consumer<TestListenerRegistration> newConsumer,
+ TestListenerRegistration newRegistration);
void onRegistrationRemoved(Consumer<TestListenerRegistration> consumer,
TestListenerRegistration registration);
@@ -93,10 +95,10 @@ public class ListenerMultiplexerTest {
assertThat(mMultiplexer.mMergedRequest).isEqualTo(0);
mMultiplexer.addListener(1, consumer);
- mInOrder.verify(mCallbacks).onRegistrationRemoved(eq(consumer),
- any(TestListenerRegistration.class));
mInOrder.verify(mCallbacks).onRegistrationReplaced(eq(consumer),
- any(TestListenerRegistration.class), any(TestListenerRegistration.class));
+ any(TestListenerRegistration.class),
+ eq(consumer),
+ any(TestListenerRegistration.class));
assertThat(mMultiplexer.mRegistered).isTrue();
assertThat(mMultiplexer.mMergedRequest).isEqualTo(1);
@@ -115,10 +117,10 @@ public class ListenerMultiplexerTest {
any(TestListenerRegistration.class));
mInOrder.verify(mCallbacks).onActive();
mMultiplexer.replaceListener(1, oldConsumer, consumer);
- mInOrder.verify(mCallbacks).onRegistrationRemoved(eq(oldConsumer),
+ mInOrder.verify(mCallbacks).onRegistrationReplaced(eq(oldConsumer),
+ any(TestListenerRegistration.class),
+ eq(consumer),
any(TestListenerRegistration.class));
- mInOrder.verify(mCallbacks).onRegistrationReplaced(eq(consumer),
- any(TestListenerRegistration.class), any(TestListenerRegistration.class));
assertThat(mMultiplexer.mRegistered).isTrue();
assertThat(mMultiplexer.mMergedRequest).isEqualTo(1);
@@ -352,13 +354,19 @@ public class ListenerMultiplexerTest {
}
private static class TestListenerRegistration extends
- RequestListenerRegistration<Integer, Consumer<TestListenerRegistration>> {
+ ListenerRegistration<Consumer<TestListenerRegistration>> {
+ private final Integer mInteger;
boolean mActive = true;
protected TestListenerRegistration(Integer integer,
Consumer<TestListenerRegistration> consumer) {
- super(DIRECT_EXECUTOR, integer, consumer);
+ super(DIRECT_EXECUTOR, consumer);
+ mInteger = integer;
+ }
+
+ public Integer getInteger() {
+ return mInteger;
}
}
@@ -375,11 +383,6 @@ public class ListenerMultiplexerTest {
mCallbacks = callbacks;
}
- @Override
- public String getTag() {
- return "TestMultiplexer";
- }
-
public void addListener(Integer request, Consumer<TestListenerRegistration> consumer) {
putRegistration(consumer, new TestListenerRegistration(request, consumer));
}
@@ -399,9 +402,9 @@ public class ListenerMultiplexerTest {
removeRegistration(consumer, registration);
}
- public void setActive(Integer request, boolean active) {
+ public void setActive(Integer integer, boolean active) {
updateRegistrations(testRegistration -> {
- if (testRegistration.getRequest().equals(request)) {
+ if (testRegistration.getInteger().equals(integer)) {
testRegistration.mActive = active;
return true;
}
@@ -458,10 +461,11 @@ public class ListenerMultiplexerTest {
}
@Override
- protected void onRegistrationReplaced(Consumer<TestListenerRegistration> consumer,
+ protected void onRegistrationReplaced(Consumer<TestListenerRegistration> oldKey,
TestListenerRegistration oldRegistration,
+ Consumer<TestListenerRegistration> newKey,
TestListenerRegistration newRegistration) {
- mCallbacks.onRegistrationReplaced(consumer, oldRegistration, newRegistration);
+ mCallbacks.onRegistrationReplaced(oldKey, oldRegistration, newKey, newRegistration);
}
@Override
@@ -475,8 +479,8 @@ public class ListenerMultiplexerTest {
Collection<TestListenerRegistration> testRegistrations) {
int max = Integer.MIN_VALUE;
for (TestListenerRegistration registration : testRegistrations) {
- if (registration.getRequest() > max) {
- max = registration.getRequest();
+ if (registration.getInteger() > max) {
+ max = registration.getInteger();
}
}
mMergeCount++;
@@ -493,7 +497,7 @@ public class ListenerMultiplexerTest {
@Override
protected void onRegistrationAdded(Consumer<TestListenerRegistration> consumer,
TestListenerRegistration registration) {
- addListener(registration.getRequest(), consumer);
+ addListener(registration.getInteger(), consumer);
}
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
index 71cc65b484ee..0ac14432d113 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
@@ -32,7 +32,6 @@ import static com.android.server.location.LocationPermissions.PERMISSION_COARSE;
import static com.android.server.location.LocationPermissions.PERMISSION_FINE;
import static com.android.server.location.LocationUtils.createLocation;
import static com.android.server.location.LocationUtils.createLocationResult;
-import static com.android.server.location.listeners.RemoteListenerRegistration.IN_PROCESS_EXECUTOR;
import static com.google.common.truth.Truth.assertThat;
@@ -534,7 +533,7 @@ public class LocationProviderManagerTest {
listener);
CountDownLatch blocker = new CountDownLatch(1);
- IN_PROCESS_EXECUTOR.execute(() -> {
+ FgThread.getExecutor().execute(() -> {
try {
blocker.await();
} catch (InterruptedException e) {
@@ -661,7 +660,7 @@ public class LocationProviderManagerTest {
listener);
CountDownLatch blocker = new CountDownLatch(1);
- IN_PROCESS_EXECUTOR.execute(() -> {
+ FgThread.getExecutor().execute(() -> {
try {
blocker.await();
} catch (InterruptedException e) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/BroadcastHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/BroadcastHelperTest.kt
deleted file mode 100644
index d25649edb816..000000000000
--- a/services/tests/mockingservicestests/src/com/android/server/pm/BroadcastHelperTest.kt
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.pm
-
-import com.android.server.testutils.whenever
-import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations
-
-@RunWith(JUnit4::class)
-class BroadcastHelperTest {
-
- companion object {
- const val TEST_PACKAGE_1 = "com.android.test.package1"
- const val TEST_PACKAGE_2 = "com.android.test.package2"
- const val TEST_UID_1 = 10100
- const val TEST_UID_2 = 10101
- const val TEST_USER_ID = 0
- }
-
- lateinit var broadcastHelper: BroadcastHelper
- lateinit var packagesToChange: Array<String>
- lateinit var uidsToChange: IntArray
-
- @Mock
- lateinit var snapshot: Computer
-
- @Rule
- @JvmField
- val rule = MockSystemRule()
-
- @Before
- open fun setup() {
- MockitoAnnotations.initMocks(this)
- rule.system().stageNominalSystemState()
- broadcastHelper = BroadcastHelper(rule.mocks().injector)
- packagesToChange = arrayOf(TEST_PACKAGE_1, TEST_PACKAGE_2)
- uidsToChange = intArrayOf(TEST_UID_1, TEST_UID_2)
- }
-
- @Test
- fun getBroadcastParams_withSameVisibilityAllowList_shouldGroup() {
- val allowList = intArrayOf(10001, 10002, 10003)
- mockVisibilityAllowList(TEST_PACKAGE_1, allowList)
- mockVisibilityAllowList(TEST_PACKAGE_2, allowList)
-
- val broadcastParams: List<BroadcastParams> = broadcastHelper.getBroadcastParams(
- snapshot, packagesToChange, uidsToChange, TEST_USER_ID)
-
- assertThat(broadcastParams).hasSize(1)
- assertThat(broadcastParams[0].packageNames).asList().containsExactlyElementsIn(
- packagesToChange.toCollection(ArrayList()))
- assertThat(broadcastParams[0].uids).asList().containsExactlyElementsIn(
- uidsToChange.toCollection(ArrayList()))
- }
-
- @Test
- fun getBroadcastParams_withDifferentVisibilityAllowList_shouldNotGroup() {
- val allowList1 = intArrayOf(10001, 10002, 10003)
- val allowList2 = intArrayOf(10001, 10002, 10007)
- mockVisibilityAllowList(TEST_PACKAGE_1, allowList1)
- mockVisibilityAllowList(TEST_PACKAGE_2, allowList2)
-
- val broadcastParams: List<BroadcastParams> = broadcastHelper.getBroadcastParams(
- snapshot, packagesToChange, uidsToChange, TEST_USER_ID)
-
- assertThat(broadcastParams).hasSize(2)
- broadcastParams.forEachIndexed { i, params ->
- val changedPackages = params.packageNames
- val changedUids = params.uids
- assertThat(changedPackages[0]).isEqualTo(packagesToChange[i])
- assertThat(changedUids[0]).isEqualTo(uidsToChange[i])
- }
- }
-
- @Test
- fun getBroadcastParams_withNullVisibilityAllowList_shouldNotGroup() {
- val allowList = intArrayOf(10001, 10002, 10003)
- mockVisibilityAllowList(TEST_PACKAGE_1, allowList)
- mockVisibilityAllowList(TEST_PACKAGE_2, null)
-
- val broadcastParams: List<BroadcastParams> = broadcastHelper.getBroadcastParams(
- snapshot, packagesToChange, uidsToChange, TEST_USER_ID)
-
- assertThat(broadcastParams).hasSize(2)
- broadcastParams.forEachIndexed { i, params ->
- val changedPackages = params.packageNames
- val changedUids = params.uids
- assertThat(changedPackages[0]).isEqualTo(packagesToChange[i])
- assertThat(changedUids[0]).isEqualTo(uidsToChange[i])
- }
- }
-
- private fun mockVisibilityAllowList(pkgName: String, list: IntArray?) {
- whenever(snapshot.getVisibilityAllowList(pkgName, TEST_USER_ID))
- .thenReturn(list ?: IntArray(0))
- }
-} \ No newline at end of file
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/DistractingPackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/DistractingPackageHelperTest.kt
index d8770e510375..9f1cec3b595a 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/DistractingPackageHelperTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/DistractingPackageHelperTest.kt
@@ -29,7 +29,6 @@ import org.junit.runners.JUnit4
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.never
-import org.mockito.Mockito.times
import org.mockito.Mockito.verify
@RunWith(JUnit4::class)
@@ -53,7 +52,7 @@ class DistractingPackageHelperTest : PackageHelperTestBase() {
verify(pms).scheduleWritePackageRestrictions(eq(TEST_USER_ID))
verify(broadcastHelper).sendPackageBroadcast(eq(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED),
nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(),
- nullable(), nullable(), nullable())
+ nullable(), nullable(), nullable(), nullable())
val modifiedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
val distractionFlags = bundleCaptor.value.getInt(Intent.EXTRA_DISTRACTION_RESTRICTIONS)
@@ -78,7 +77,8 @@ class DistractingPackageHelperTest : PackageHelperTestBase() {
verify(pms, never()).scheduleWritePackageRestrictions(eq(TEST_USER_ID))
verify(broadcastHelper, never()).sendPackageBroadcast(
eq(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED), nullable(), bundleCaptor.capture(),
- anyInt(), nullable(), nullable(), any(), nullable(), nullable(), nullable())
+ anyInt(), nullable(), nullable(), any(), nullable(), nullable(), nullable(),
+ nullable())
assertThat(unactionedPackages).isEmpty()
}
@@ -156,7 +156,7 @@ class DistractingPackageHelperTest : PackageHelperTestBase() {
verify(pms).scheduleWritePackageRestrictions(eq(TEST_USER_ID))
verify(broadcastHelper).sendPackageBroadcast(eq(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED),
nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(),
- nullable(), nullable(), nullable())
+ nullable(), nullable(), nullable(), nullable())
val modifiedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
val distractionFlags = bundleCaptor.value.getInt(Intent.EXTRA_DISTRACTION_RESTRICTIONS)
assertThat(modifiedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2)
@@ -172,7 +172,7 @@ class DistractingPackageHelperTest : PackageHelperTestBase() {
verify(pms, never()).scheduleWritePackageRestrictions(eq(TEST_USER_ID))
verify(broadcastHelper, never()).sendPackageBroadcast(eq(
Intent.ACTION_DISTRACTING_PACKAGES_CHANGED), nullable(), nullable(), anyInt(),
- nullable(), nullable(), any(), nullable(), nullable(), nullable())
+ nullable(), nullable(), any(), nullable(), nullable(), nullable(), nullable())
}
@Test
@@ -183,7 +183,7 @@ class DistractingPackageHelperTest : PackageHelperTestBase() {
verify(pms, never()).scheduleWritePackageRestrictions(eq(TEST_USER_ID))
verify(broadcastHelper, never()).sendPackageBroadcast(eq(
Intent.ACTION_DISTRACTING_PACKAGES_CHANGED), nullable(), nullable(), anyInt(),
- nullable(), nullable(), any(), nullable(), nullable(), nullable())
+ nullable(), nullable(), any(), nullable(), nullable(), nullable(), nullable())
distractingPackageHelper.removeDistractingPackageRestrictions(pms.snapshotComputer(),
arrayOfNulls(0), TEST_USER_ID)
@@ -191,18 +191,17 @@ class DistractingPackageHelperTest : PackageHelperTestBase() {
verify(pms, never()).scheduleWritePackageRestrictions(eq(TEST_USER_ID))
verify(broadcastHelper, never()).sendPackageBroadcast(eq(
Intent.ACTION_DISTRACTING_PACKAGES_CHANGED), nullable(), nullable(), anyInt(),
- nullable(), nullable(), any(), nullable(), nullable(), nullable())
+ nullable(), nullable(), any(), nullable(), nullable(), nullable(), nullable())
}
@Test
- fun sendDistractingPackagesChanged_withSameVisibilityAllowList() {
- distractingPackageHelper.sendDistractingPackagesChanged(pms.snapshotComputer(),
- packagesToChange, uidsToChange, TEST_USER_ID,
- PackageManager.RESTRICTION_HIDE_NOTIFICATIONS)
+ fun sendDistractingPackagesChanged() {
+ distractingPackageHelper.sendDistractingPackagesChanged(packagesToChange, uidsToChange,
+ TEST_USER_ID, PackageManager.RESTRICTION_HIDE_NOTIFICATIONS)
testHandler.flush()
verify(broadcastHelper).sendPackageBroadcast(eq(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED),
nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(),
- nullable(), nullable(), nullable())
+ nullable(), nullable(), nullable(), nullable())
var changedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
var changedUids = bundleCaptor.value.getIntArray(Intent.EXTRA_CHANGED_UID_LIST)
@@ -210,49 +209,4 @@ class DistractingPackageHelperTest : PackageHelperTestBase() {
assertThat(changedUids).asList().containsExactly(
packageSetting1.appId, packageSetting2.appId)
}
-
- @Test
- fun sendDistractingPackagesChanged_withDifferentVisibilityAllowList() {
- mockDividedSeparatedBroadcastList(
- intArrayOf(10001, 10002, 10003), intArrayOf(10001, 10002, 10007))
-
- distractingPackageHelper.sendDistractingPackagesChanged(pms.snapshotComputer(),
- packagesToChange, uidsToChange, TEST_USER_ID,
- PackageManager.RESTRICTION_HIDE_NOTIFICATIONS)
- testHandler.flush()
- verify(broadcastHelper, times(2)).sendPackageBroadcast(
- eq(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED), nullable(), bundleCaptor.capture(),
- anyInt(), nullable(), nullable(), any(), nullable(), nullable(), nullable())
-
- bundleCaptor.allValues.forEachIndexed { i, it ->
- var changedPackages = it.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
- var changedUids = it.getIntArray(Intent.EXTRA_CHANGED_UID_LIST)
- assertThat(changedPackages?.size).isEqualTo(1)
- assertThat(changedUids?.size).isEqualTo(1)
- assertThat(changedPackages?.get(0)).isEqualTo(packagesToChange[i])
- assertThat(changedUids?.get(0)).isEqualTo(uidsToChange[i])
- }
- }
-
- @Test
- fun sendDistractingPackagesChanged_withNullVisibilityAllowList() {
- mockDividedSeparatedBroadcastList(intArrayOf(10001, 10002, 10003), null)
-
- distractingPackageHelper.sendDistractingPackagesChanged(pms.snapshotComputer(),
- packagesToChange, uidsToChange, TEST_USER_ID,
- PackageManager.RESTRICTION_HIDE_NOTIFICATIONS)
- testHandler.flush()
- verify(broadcastHelper, times(2)).sendPackageBroadcast(
- eq(Intent.ACTION_DISTRACTING_PACKAGES_CHANGED), nullable(), bundleCaptor.capture(),
- anyInt(), nullable(), nullable(), any(), nullable(), nullable(), nullable())
-
- bundleCaptor.allValues.forEachIndexed { i, it ->
- var changedPackages = it.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
- var changedUids = it.getIntArray(Intent.EXTRA_CHANGED_UID_LIST)
- assertThat(changedPackages?.size).isEqualTo(1)
- assertThat(changedUids?.size).isEqualTo(1)
- assertThat(changedPackages?.get(0)).isEqualTo(packagesToChange[i])
- assertThat(changedUids?.get(0)).isEqualTo(uidsToChange[i])
- }
- }
-} \ No newline at end of file
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageHelperTestBase.kt b/services/tests/mockingservicestests/src/com/android/server/pm/PackageHelperTestBase.kt
index 5f9ef584fa0f..4ac5e8690440 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageHelperTestBase.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageHelperTestBase.kt
@@ -28,7 +28,6 @@ import com.android.server.testutils.whenever
import org.junit.Before
import org.junit.Rule
import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito
@@ -102,7 +101,6 @@ open class PackageHelperTestBase {
whenever(rule.mocks().userManagerService.hasUserRestriction(
eq(UserManager.DISALLOW_UNINSTALL_APPS), eq(TEST_USER_ID))).thenReturn(true)
mockKnownPackages(pms)
- mockUnifiedSeparatedBroadcastList()
}
private fun mockKnownPackages(pms: PackageManagerService) {
@@ -139,26 +137,4 @@ open class PackageHelperTestBase {
rule.system().validateFinalState()
return pms
}
-
- protected fun mockUnifiedSeparatedBroadcastList() {
- whenever(broadcastHelper.getBroadcastParams(any(Computer::class.java),
- any() as Array<String>, any(IntArray::class.java), anyInt()
- )).thenReturn(ArrayList<BroadcastParams>().apply {
- this.add(BroadcastParams(packagesToChange[0], uidsToChange[0], IntArray(0),
- TEST_USER_ID).apply {
- this.addPackage(packagesToChange[1], uidsToChange[1])
- })
- })
- }
-
- protected fun mockDividedSeparatedBroadcastList(allowlist1: IntArray?, allowlist2: IntArray?) {
- whenever(broadcastHelper.getBroadcastParams(any(Computer::class.java),
- any() as Array<String>, any(IntArray::class.java), anyInt()
- )).thenReturn(ArrayList<BroadcastParams>().apply {
- this.add(BroadcastParams(packagesToChange[0], uidsToChange[0],
- allowlist1 ?: IntArray(0), TEST_USER_ID))
- this.add(BroadcastParams(packagesToChange[1], uidsToChange[1],
- allowlist2 ?: IntArray(0), TEST_USER_ID))
- })
- }
-} \ No newline at end of file
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
index df53dfe288c6..dc74469db213 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
@@ -45,11 +45,13 @@ class SuspendPackageHelperTest : PackageHelperTestBase() {
verify(pms).scheduleWritePackageRestrictions(eq(TEST_USER_ID))
verify(broadcastHelper).sendPackageBroadcast(eq(Intent.ACTION_PACKAGES_SUSPENDED),
nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(),
- nullable(), nullable(), nullable())
+ nullable(), nullable(), nullable(), nullable())
verify(broadcastHelper).doSendBroadcast(eq(Intent.ACTION_MY_PACKAGE_SUSPENDED), nullable(),
- nullable(), any(), eq(TEST_PACKAGE_1), nullable(), any(), any(), nullable(), nullable())
+ nullable(), any(), eq(TEST_PACKAGE_1), nullable(), any(), any(), nullable(),
+ nullable(), nullable())
verify(broadcastHelper).doSendBroadcast(eq(Intent.ACTION_MY_PACKAGE_SUSPENDED), nullable(),
- nullable(), any(), eq(TEST_PACKAGE_2), nullable(), any(), any(), nullable(), nullable())
+ nullable(), any(), eq(TEST_PACKAGE_2), nullable(), any(), any(), nullable(),
+ nullable(), nullable())
var modifiedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
assertThat(modifiedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2)
@@ -135,13 +137,13 @@ class SuspendPackageHelperTest : PackageHelperTestBase() {
verify(pms, times(2)).scheduleWritePackageRestrictions(eq(TEST_USER_ID))
verify(broadcastHelper).sendPackageBroadcast(eq(Intent.ACTION_PACKAGES_UNSUSPENDED),
nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(),
- nullable(), nullable(), nullable())
+ nullable(), nullable(), nullable(), nullable())
verify(broadcastHelper).doSendBroadcast(eq(Intent.ACTION_MY_PACKAGE_UNSUSPENDED),
nullable(), nullable(), any(), eq(TEST_PACKAGE_1), nullable(), any(), any(),
- nullable(), nullable())
+ nullable(), nullable(), nullable())
verify(broadcastHelper).doSendBroadcast(eq(Intent.ACTION_MY_PACKAGE_UNSUSPENDED),
nullable(), nullable(), any(), eq(TEST_PACKAGE_2), nullable(), any(), any(),
- nullable(), nullable())
+ nullable(), nullable(), nullable())
var modifiedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
assertThat(modifiedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2)
@@ -217,13 +219,13 @@ class SuspendPackageHelperTest : PackageHelperTestBase() {
verify(pms, times(2)).scheduleWritePackageRestrictions(eq(TEST_USER_ID))
verify(broadcastHelper).sendPackageBroadcast(eq(Intent.ACTION_PACKAGES_UNSUSPENDED),
nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(),
- nullable(), nullable(), nullable())
+ nullable(), nullable(), nullable(), nullable())
verify(broadcastHelper).doSendBroadcast(eq(Intent.ACTION_MY_PACKAGE_UNSUSPENDED),
nullable(), nullable(), any(), eq(TEST_PACKAGE_1), nullable(), any(), any(),
- nullable(), nullable())
+ nullable(), nullable(), nullable())
verify(broadcastHelper).doSendBroadcast(eq(Intent.ACTION_MY_PACKAGE_UNSUSPENDED),
nullable(), nullable(), any(), eq(TEST_PACKAGE_2), nullable(), any(), any(),
- nullable(), nullable())
+ nullable(), nullable(), nullable())
assertThat(suspendPackageHelper.getSuspendingPackage(pms.snapshotComputer(),
TEST_PACKAGE_1, TEST_USER_ID, deviceOwnerUid)).isNull()
@@ -297,12 +299,13 @@ class SuspendPackageHelperTest : PackageHelperTestBase() {
@Test
@Throws(Exception::class)
- fun sendPackagesSuspendedForUser_withSameVisibilityAllowList() {
- suspendPackageHelper.sendPackagesSuspendedForUser(pms.snapshotComputer(),
+ fun sendPackagesSuspendedForUser() {
+ suspendPackageHelper.sendPackagesSuspendedForUser(
Intent.ACTION_PACKAGES_SUSPENDED, packagesToChange, uidsToChange, TEST_USER_ID)
testHandler.flush()
verify(broadcastHelper).sendPackageBroadcast(any(), nullable(), bundleCaptor.capture(),
- anyInt(), nullable(), nullable(), any(), nullable(), nullable(), nullable())
+ anyInt(), nullable(), nullable(), any(), nullable(), nullable(), nullable(),
+ nullable())
var changedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
var changedUids = bundleCaptor.value.getIntArray(Intent.EXTRA_CHANGED_UID_LIST)
@@ -313,59 +316,14 @@ class SuspendPackageHelperTest : PackageHelperTestBase() {
@Test
@Throws(Exception::class)
- fun sendPackagesSuspendedForUser_withDifferentVisibilityAllowList() {
- mockDividedSeparatedBroadcastList(
- intArrayOf(10001, 10002, 10003), intArrayOf(10001, 10002, 10007))
-
- suspendPackageHelper.sendPackagesSuspendedForUser(pms.snapshotComputer(),
- Intent.ACTION_PACKAGES_SUSPENDED, packagesToChange, uidsToChange, TEST_USER_ID)
- testHandler.flush()
- verify(broadcastHelper, times(2)).sendPackageBroadcast(
- any(), nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(),
- nullable(), any(), nullable())
-
- bundleCaptor.allValues.forEachIndexed { i, it ->
- var changedPackages = it.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
- var changedUids = it.getIntArray(Intent.EXTRA_CHANGED_UID_LIST)
- assertThat(changedPackages?.size).isEqualTo(1)
- assertThat(changedUids?.size).isEqualTo(1)
- assertThat(changedPackages?.get(0)).isEqualTo(packagesToChange[i])
- assertThat(changedUids?.get(0)).isEqualTo(uidsToChange[i])
- }
- }
-
- @Test
- @Throws(Exception::class)
- fun sendPackagesSuspendedForUser_withNullVisibilityAllowList() {
- mockDividedSeparatedBroadcastList(intArrayOf(10001, 10002, 10003), null)
-
- suspendPackageHelper.sendPackagesSuspendedForUser(pms.snapshotComputer(),
- Intent.ACTION_PACKAGES_SUSPENDED, packagesToChange, uidsToChange, TEST_USER_ID)
- testHandler.flush()
- verify(broadcastHelper, times(2)).sendPackageBroadcast(
- any(), nullable(), bundleCaptor.capture(), anyInt(), nullable(), nullable(), any(),
- nullable(), nullable(), nullable())
-
- bundleCaptor.allValues.forEachIndexed { i, it ->
- var changedPackages = it.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
- var changedUids = it.getIntArray(Intent.EXTRA_CHANGED_UID_LIST)
- assertThat(changedPackages?.size).isEqualTo(1)
- assertThat(changedUids?.size).isEqualTo(1)
- assertThat(changedPackages?.get(0)).isEqualTo(packagesToChange[i])
- assertThat(changedUids?.get(0)).isEqualTo(uidsToChange[i])
- }
- }
-
- @Test
- @Throws(Exception::class)
fun sendPackagesSuspendModifiedForUser() {
- suspendPackageHelper.sendPackagesSuspendedForUser(pms.snapshotComputer(),
- Intent.ACTION_PACKAGES_SUSPENSION_CHANGED, packagesToChange, uidsToChange,
- TEST_USER_ID)
+ suspendPackageHelper.sendPackagesSuspendedForUser(
+ Intent.ACTION_PACKAGES_SUSPENSION_CHANGED, packagesToChange, uidsToChange, TEST_USER_ID)
testHandler.flush()
verify(broadcastHelper).sendPackageBroadcast(
eq(Intent.ACTION_PACKAGES_SUSPENSION_CHANGED), nullable(), bundleCaptor.capture(),
- anyInt(), nullable(), nullable(), any(), nullable(), nullable(), nullable())
+ anyInt(), nullable(), nullable(), any(), nullable(), nullable(), nullable(),
+ nullable())
var modifiedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
var modifiedUids = bundleCaptor.value.getIntArray(Intent.EXTRA_CHANGED_UID_LIST)
diff --git a/services/tests/mockingservicestests/src/com/android/server/tare/AgentTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/AgentTest.java
index f9f6fe919c3b..831a69a8d890 100644
--- a/services/tests/mockingservicestests/src/com/android/server/tare/AgentTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/tare/AgentTest.java
@@ -20,6 +20,8 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import android.app.AlarmManager;
@@ -92,7 +94,7 @@ public class AgentTest {
Ledger ledger = new Ledger();
doReturn(1_000_000L).when(mIrs).getConsumptionLimitLocked();
- doReturn(1_000_000L).when(mEconomicPolicy).getMaxSatiatedBalance();
+ doReturn(1_000_000L).when(mEconomicPolicy).getMaxSatiatedBalance(anyInt(), anyString());
Ledger.Transaction transaction = new Ledger.Transaction(0, 0, 0, null, 5, 0);
agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
@@ -121,7 +123,7 @@ public class AgentTest {
Ledger ledger = new Ledger();
doReturn(1000L).when(mIrs).getConsumptionLimitLocked();
- doReturn(1_000_000L).when(mEconomicPolicy).getMaxSatiatedBalance();
+ doReturn(1_000_000L).when(mEconomicPolicy).getMaxSatiatedBalance(anyInt(), anyString());
Ledger.Transaction transaction = new Ledger.Transaction(0, 0, 0, null, 5, 0);
agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
@@ -168,7 +170,7 @@ public class AgentTest {
Ledger ledger = new Ledger();
doReturn(1_000_000L).when(mIrs).getConsumptionLimitLocked();
- doReturn(1000L).when(mEconomicPolicy).getMaxSatiatedBalance();
+ doReturn(1000L).when(mEconomicPolicy).getMaxSatiatedBalance(anyInt(), anyString());
Ledger.Transaction transaction = new Ledger.Transaction(0, 0, 0, null, 5, 0);
agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
@@ -187,7 +189,7 @@ public class AgentTest {
assertEquals(1_000, ledger.getCurrentBalance());
// Shouldn't change in normal operation, but adding test case in case it does.
- doReturn(900L).when(mEconomicPolicy).getMaxSatiatedBalance();
+ doReturn(900L).when(mEconomicPolicy).getMaxSatiatedBalance(anyInt(), anyString());
transaction = new Ledger.Transaction(0, 0, 0, null, 500, 0);
agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
diff --git a/services/tests/servicestests/src/com/android/server/tare/AgentTrendCalculatorTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/AgentTrendCalculatorTest.java
index 9e986be99e95..2fac31e9e6fd 100644
--- a/services/tests/servicestests/src/com/android/server/tare/AgentTrendCalculatorTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/tare/AgentTrendCalculatorTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * 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.
@@ -17,7 +17,10 @@
package com.android.server.tare;
import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
import android.util.ArraySet;
import android.util.SparseLongArray;
@@ -60,7 +63,7 @@ public class AgentTrendCalculatorTest {
}
@Override
- long getMaxSatiatedBalance() {
+ long getMaxSatiatedBalance(int userId, String pkgName) {
return 0;
}
@@ -99,7 +102,9 @@ public class AgentTrendCalculatorTest {
@Before
public void setUp() {
- mEconomicPolicy = new MockEconomicPolicy(mock(InternalResourceService.class));
+ final InternalResourceService irs = mock(InternalResourceService.class);
+ when(irs.isVip(anyInt(), anyString())).thenReturn(false);
+ mEconomicPolicy = new MockEconomicPolicy(irs);
}
@Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/tare/AlarmManagerEconomicPolicyTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/AlarmManagerEconomicPolicyTest.java
index 2e200c395e9d..fb3e8f298424 100644
--- a/services/tests/mockingservicestests/src/com/android/server/tare/AlarmManagerEconomicPolicyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/tare/AlarmManagerEconomicPolicyTest.java
@@ -134,8 +134,11 @@ public class AlarmManagerEconomicPolicyTest {
mEconomicPolicy.getInitialSatiatedConsumptionLimit());
assertEquals(EconomyManager.DEFAULT_AM_HARD_CONSUMPTION_LIMIT_CAKES,
mEconomicPolicy.getHardSatiatedConsumptionLimit());
+ final String pkgRestricted = "com.pkg.restricted";
+ when(mIrs.isPackageRestricted(anyInt(), eq(pkgRestricted))).thenReturn(true);
+ assertEquals(0, mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted));
assertEquals(EconomyManager.DEFAULT_AM_MAX_SATIATED_BALANCE_CAKES,
- mEconomicPolicy.getMaxSatiatedBalance());
+ mEconomicPolicy.getMaxSatiatedBalance(0, "com.any.other.app"));
final String pkgExempted = "com.pkg.exempted";
when(mIrs.isPackageExempted(anyInt(), eq(pkgExempted))).thenReturn(true);
assertEquals(EconomyManager.DEFAULT_AM_MIN_SATIATED_BALANCE_EXEMPTED_CAKES,
@@ -154,7 +157,10 @@ public class AlarmManagerEconomicPolicyTest {
assertEquals(arcToCake(5), mEconomicPolicy.getInitialSatiatedConsumptionLimit());
assertEquals(arcToCake(25), mEconomicPolicy.getHardSatiatedConsumptionLimit());
- assertEquals(arcToCake(10), mEconomicPolicy.getMaxSatiatedBalance());
+ final String pkgRestricted = "com.pkg.restricted";
+ when(mIrs.isPackageRestricted(anyInt(), eq(pkgRestricted))).thenReturn(true);
+ assertEquals(arcToCake(0), mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted));
+ assertEquals(arcToCake(10), mEconomicPolicy.getMaxSatiatedBalance(0, "com.any.other.app"));
final String pkgExempted = "com.pkg.exempted";
when(mIrs.isPackageExempted(anyInt(), eq(pkgExempted))).thenReturn(true);
assertEquals(arcToCake(9), mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted));
@@ -172,7 +178,10 @@ public class AlarmManagerEconomicPolicyTest {
assertEquals(arcToCake(1), mEconomicPolicy.getInitialSatiatedConsumptionLimit());
assertEquals(arcToCake(1), mEconomicPolicy.getHardSatiatedConsumptionLimit());
- assertEquals(arcToCake(1), mEconomicPolicy.getMaxSatiatedBalance());
+ final String pkgRestricted = "com.pkg.restricted";
+ when(mIrs.isPackageRestricted(anyInt(), eq(pkgRestricted))).thenReturn(true);
+ assertEquals(arcToCake(0), mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted));
+ assertEquals(arcToCake(1), mEconomicPolicy.getMaxSatiatedBalance(0, "com.any.other.app"));
final String pkgExempted = "com.pkg.exempted";
when(mIrs.isPackageExempted(anyInt(), eq(pkgExempted))).thenReturn(true);
assertEquals(arcToCake(0), mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted));
@@ -187,7 +196,8 @@ public class AlarmManagerEconomicPolicyTest {
assertEquals(arcToCake(5), mEconomicPolicy.getInitialSatiatedConsumptionLimit());
assertEquals(arcToCake(5), mEconomicPolicy.getHardSatiatedConsumptionLimit());
- assertEquals(arcToCake(13), mEconomicPolicy.getMaxSatiatedBalance());
+ assertEquals(arcToCake(0), mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted));
+ assertEquals(arcToCake(13), mEconomicPolicy.getMaxSatiatedBalance(0, "com.any.other.app"));
assertEquals(arcToCake(13), mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted));
assertEquals(arcToCake(13), mEconomicPolicy.getMinSatiatedBalance(0, "com.any.other.app"));
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/tare/CompleteEconomicPolicyTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/CompleteEconomicPolicyTest.java
index 45c97e4c5d80..47155a1eadd3 100644
--- a/services/tests/mockingservicestests/src/com/android/server/tare/CompleteEconomicPolicyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/tare/CompleteEconomicPolicyTest.java
@@ -142,9 +142,12 @@ public class CompleteEconomicPolicyTest {
assertEquals(EconomyManager.DEFAULT_JS_HARD_CONSUMPTION_LIMIT_CAKES
+ EconomyManager.DEFAULT_AM_HARD_CONSUMPTION_LIMIT_CAKES,
mEconomicPolicy.getHardSatiatedConsumptionLimit());
+ final String pkgRestricted = "com.pkg.restricted";
+ when(mIrs.isPackageRestricted(anyInt(), eq(pkgRestricted))).thenReturn(true);
+ assertEquals(0, mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted));
assertEquals(EconomyManager.DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES
+ EconomyManager.DEFAULT_AM_MAX_SATIATED_BALANCE_CAKES,
- mEconomicPolicy.getMaxSatiatedBalance());
+ mEconomicPolicy.getMaxSatiatedBalance(0, "com.any.other.app"));
final String pkgExempted = "com.pkg.exempted";
when(mIrs.isPackageExempted(anyInt(), eq(pkgExempted))).thenReturn(true);
assertEquals(EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_EXEMPTED_CAKES
@@ -170,7 +173,10 @@ public class CompleteEconomicPolicyTest {
assertEquals(arcToCake(10), mEconomicPolicy.getInitialSatiatedConsumptionLimit());
assertEquals(arcToCake(50), mEconomicPolicy.getHardSatiatedConsumptionLimit());
- assertEquals(arcToCake(20), mEconomicPolicy.getMaxSatiatedBalance());
+ final String pkgRestricted = "com.pkg.restricted";
+ when(mIrs.isPackageRestricted(anyInt(), eq(pkgRestricted))).thenReturn(true);
+ assertEquals(arcToCake(0), mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted));
+ assertEquals(arcToCake(20), mEconomicPolicy.getMaxSatiatedBalance(0, "com.any.other.app"));
final String pkgExempted = "com.pkg.exempted";
when(mIrs.isPackageExempted(anyInt(), eq(pkgExempted))).thenReturn(true);
assertEquals(arcToCake(13), mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted));
diff --git a/services/tests/mockingservicestests/src/com/android/server/tare/JobSchedulerEconomicPolicyTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/JobSchedulerEconomicPolicyTest.java
index 03ce91aea58b..19b798da5aab 100644
--- a/services/tests/mockingservicestests/src/com/android/server/tare/JobSchedulerEconomicPolicyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/tare/JobSchedulerEconomicPolicyTest.java
@@ -134,8 +134,11 @@ public class JobSchedulerEconomicPolicyTest {
mEconomicPolicy.getInitialSatiatedConsumptionLimit());
assertEquals(EconomyManager.DEFAULT_JS_HARD_CONSUMPTION_LIMIT_CAKES,
mEconomicPolicy.getHardSatiatedConsumptionLimit());
+ final String pkgRestricted = "com.pkg.restricted";
+ when(mIrs.isPackageRestricted(anyInt(), eq(pkgRestricted))).thenReturn(true);
+ assertEquals(0, mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted));
assertEquals(EconomyManager.DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES,
- mEconomicPolicy.getMaxSatiatedBalance());
+ mEconomicPolicy.getMaxSatiatedBalance(0, "com.any.other.app"));
final String pkgExempted = "com.pkg.exempted";
when(mIrs.isPackageExempted(anyInt(), eq(pkgExempted))).thenReturn(true);
assertEquals(EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_EXEMPTED_CAKES,
@@ -154,7 +157,10 @@ public class JobSchedulerEconomicPolicyTest {
assertEquals(arcToCake(5), mEconomicPolicy.getInitialSatiatedConsumptionLimit());
assertEquals(arcToCake(25), mEconomicPolicy.getHardSatiatedConsumptionLimit());
- assertEquals(arcToCake(10), mEconomicPolicy.getMaxSatiatedBalance());
+ final String pkgRestricted = "com.pkg.restricted";
+ when(mIrs.isPackageRestricted(anyInt(), eq(pkgRestricted))).thenReturn(true);
+ assertEquals(arcToCake(0), mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted));
+ assertEquals(arcToCake(10), mEconomicPolicy.getMaxSatiatedBalance(0, "com.any.other.app"));
final String pkgExempted = "com.pkg.exempted";
when(mIrs.isPackageExempted(anyInt(), eq(pkgExempted))).thenReturn(true);
assertEquals(arcToCake(9), mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted));
@@ -172,7 +178,10 @@ public class JobSchedulerEconomicPolicyTest {
assertEquals(arcToCake(1), mEconomicPolicy.getInitialSatiatedConsumptionLimit());
assertEquals(arcToCake(1), mEconomicPolicy.getHardSatiatedConsumptionLimit());
- assertEquals(arcToCake(1), mEconomicPolicy.getMaxSatiatedBalance());
+ final String pkgRestricted = "com.pkg.restricted";
+ when(mIrs.isPackageRestricted(anyInt(), eq(pkgRestricted))).thenReturn(true);
+ assertEquals(arcToCake(0), mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted));
+ assertEquals(arcToCake(1), mEconomicPolicy.getMaxSatiatedBalance(0, "com.any.other.app"));
final String pkgExempted = "com.pkg.exempted";
when(mIrs.isPackageExempted(anyInt(), eq(pkgExempted))).thenReturn(true);
assertEquals(arcToCake(0), mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted));
@@ -187,7 +196,8 @@ public class JobSchedulerEconomicPolicyTest {
assertEquals(arcToCake(5), mEconomicPolicy.getInitialSatiatedConsumptionLimit());
assertEquals(arcToCake(5), mEconomicPolicy.getHardSatiatedConsumptionLimit());
- assertEquals(arcToCake(13), mEconomicPolicy.getMaxSatiatedBalance());
+ assertEquals(arcToCake(0), mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted));
+ assertEquals(arcToCake(13), mEconomicPolicy.getMaxSatiatedBalance(0, "com.any.other.app"));
assertEquals(arcToCake(13), mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted));
assertEquals(arcToCake(13), mEconomicPolicy.getMinSatiatedBalance(0, "com.any.other.app"));
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java
index 13510adbb960..d90d8b8bfac0 100644
--- a/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/tare/ScribeTest.java
@@ -68,7 +68,7 @@ public class ScribeTest {
private MockitoSession mMockingSession;
private Scribe mScribeUnderTest;
private File mTestFileDir;
- private final List<PackageInfo> mInstalledPackages = new ArrayList<>();
+ private final List<InstalledPackageInfo> mInstalledPackages = new ArrayList<>();
private final List<Analyst.Report> mReports = new ArrayList<>();
@Mock
@@ -455,6 +455,6 @@ public class ScribeTest {
ApplicationInfo applicationInfo = new ApplicationInfo();
applicationInfo.uid = UserHandle.getUid(userId, Math.abs(pkgName.hashCode()));
pkgInfo.applicationInfo = applicationInfo;
- mInstalledPackages.add(pkgInfo);
+ mInstalledPackages.add(new InstalledPackageInfo(pkgInfo));
}
}
diff --git a/services/tests/servicestests/res/xml/usertypes_test_profile.xml b/services/tests/servicestests/res/xml/usertypes_test_profile.xml
index daa7d7b7341a..b27f49d3fbeb 100644
--- a/services/tests/servicestests/res/xml/usertypes_test_profile.xml
+++ b/services/tests/servicestests/res/xml/usertypes_test_profile.xml
@@ -30,6 +30,10 @@
<item res='@*android:color/profile_badge_2' />
</badge-colors>
<default-restrictions no_remove_user='true' no_bluetooth='true' />
+ <user-properties
+ showInLauncher='2020'
+ startWithParent='false'
+ />
</profile-type>
<profile-type name='custom.test.1' max-allowed-per-parent='14' />
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInteractionControllerNodeRequestsTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInteractionControllerNodeRequestsTest.java
index 4a16874c7acf..842b23c91e41 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInteractionControllerNodeRequestsTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInteractionControllerNodeRequestsTest.java
@@ -17,13 +17,13 @@
package com.android.server.accessibility;
+import static android.view.accessibility.AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_ANCESTORS;
import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_BREADTH_FIRST;
import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_DEPTH_FIRST;
import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS_HYBRID;
import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS;
import static android.view.accessibility.AccessibilityNodeInfo.FLAG_PREFETCH_UNINTERRUPTIBLE;
-import static android.view.accessibility.AccessibilityNodeInfo.FLAG_SERVICE_REQUESTS_INCLUDE_NOT_IMPORTANT_VIEWS;
import static android.view.accessibility.AccessibilityNodeInfo.ROOT_NODE_ID;
import static org.junit.Assert.assertEquals;
@@ -528,8 +528,7 @@ public class AccessibilityInteractionControllerNodeRequestsTest {
// different client that holds different fetch flags for TextView1.
sendNodeRequestToController(nodeId, mMockClientCallback2,
mMockClient2InteractionId,
- FLAG_PREFETCH_SIBLINGS
- | FLAG_SERVICE_REQUESTS_INCLUDE_NOT_IMPORTANT_VIEWS
+ FLAG_PREFETCH_SIBLINGS | FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
| FLAG_PREFETCH_ANCESTORS);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/am/BroadcastRecordTest.java b/services/tests/servicestests/src/com/android/server/am/BroadcastRecordTest.java
index bce99a09c6d2..2b6be37302ea 100644
--- a/services/tests/servicestests/src/com/android/server/am/BroadcastRecordTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/BroadcastRecordTest.java
@@ -430,7 +430,8 @@ public class BroadcastRecordTest {
userId,
false /* allowBackgroundActivityStarts */,
null /* activityStartsToken */,
- false /* timeoutExempt */ );
+ false /* timeoutExempt */,
+ null /* filterExtrasForReceiver */);
}
private static int getAppId(int i) {
diff --git a/services/tests/servicestests/src/com/android/server/app/GameManagerServiceSettingsTests.java b/services/tests/servicestests/src/com/android/server/app/GameManagerServiceSettingsTests.java
index 5be05d8aa55a..e8dd541b0531 100644
--- a/services/tests/servicestests/src/com/android/server/app/GameManagerServiceSettingsTests.java
+++ b/services/tests/servicestests/src/com/android/server/app/GameManagerServiceSettingsTests.java
@@ -16,9 +16,13 @@
package com.android.server.app;
-import static org.hamcrest.CoreMatchers.is;
-import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import android.app.GameManager;
import android.content.Context;
import android.platform.test.annotations.Presubmit;
import android.util.AtomicFile;
@@ -28,6 +32,9 @@ import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.server.app.GameManagerService.GamePackageConfiguration;
+import com.android.server.app.GameManagerService.GamePackageConfiguration.GameModeConfiguration;
+
import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -66,6 +73,9 @@ public class GameManagerServiceSettingsTests {
+ " <package name=\"com.android.app1\" gameMode=\"1\">\n"
+ " </package>\n"
+ " <package name=\"com.android.app2\" gameMode=\"2\">\n"
+ + " <gameModeConfig gameMode=\"2\" scaling=\"0.99\" "
+ + "useAngle=\"true\" fps=\"90\" loadingBoost=\"123\"></gameModeConfig>\n"
+ + " <gameModeConfig gameMode=\"3\"></gameModeConfig>\n"
+ " </package>\n"
+ " <package name=\"com.android.app3\" gameMode=\"3\">\n"
+ " </package>\n"
@@ -92,40 +102,159 @@ public class GameManagerServiceSettingsTests {
writeGameServiceXml();
}
- private void verifyGameServiceSettingsData(GameManagerSettings settings) {
- assertThat(settings.getGameModeLocked(PACKAGE_NAME_1), is(1));
- assertThat(settings.getGameModeLocked(PACKAGE_NAME_2), is(2));
- assertThat(settings.getGameModeLocked(PACKAGE_NAME_3), is(3));
- }
-
@After
public void tearDown() throws Exception {
deleteFolder(InstrumentationRegistry.getTargetContext().getFilesDir());
}
- /** read in data and verify */
@Test
public void testReadGameServiceSettings() {
- /* write out files and read */
writeOldFiles();
final Context context = InstrumentationRegistry.getContext();
GameManagerSettings settings = new GameManagerSettings(context.getFilesDir());
- assertThat(settings.readPersistentDataLocked(), is(true));
- verifyGameServiceSettingsData(settings);
+ assertTrue(settings.readPersistentDataLocked());
+
+ // test game modes
+ assertEquals(1, settings.getGameModeLocked(PACKAGE_NAME_1));
+ assertEquals(2, settings.getGameModeLocked(PACKAGE_NAME_2));
+ assertEquals(3, settings.getGameModeLocked(PACKAGE_NAME_3));
+
+ // test game mode configs
+ assertNull(settings.getConfigOverride(PACKAGE_NAME_1));
+ assertNull(settings.getConfigOverride(PACKAGE_NAME_3));
+ final GamePackageConfiguration config = settings.getConfigOverride(PACKAGE_NAME_2);
+ assertNotNull(config);
+
+ assertNull(config.getGameModeConfiguration(GameManager.GAME_MODE_STANDARD));
+ final GameModeConfiguration performanceConfig = config.getGameModeConfiguration(
+ GameManager.GAME_MODE_PERFORMANCE);
+ assertNotNull(performanceConfig);
+ assertEquals(performanceConfig.getScaling(), 0.99, 0.01f);
+ assertEquals(performanceConfig.getLoadingBoostDuration(), 123);
+ assertEquals(performanceConfig.getFpsStr(), "90");
+ assertTrue(performanceConfig.getUseAngle());
+ final GameModeConfiguration batteryConfig = config.getGameModeConfiguration(
+ GameManager.GAME_MODE_BATTERY);
+ assertNotNull(batteryConfig);
+ assertEquals(batteryConfig.getScaling(), GameModeConfiguration.DEFAULT_SCALING, 0.01f);
+ assertEquals(batteryConfig.getLoadingBoostDuration(),
+ GameModeConfiguration.DEFAULT_LOADING_BOOST_DURATION);
+ assertEquals(batteryConfig.getFpsStr(), GameModeConfiguration.DEFAULT_FPS);
+ assertFalse(batteryConfig.getUseAngle());
}
- /** read in data, write it out, and read it back in. Verify same. */
+ @Test
+ public void testReadGameServiceSettings_invalidConfigAttributes() {
+ writeFile(new File(InstrumentationRegistry.getContext().getFilesDir(),
+ "system/game-manager-service.xml"),
+ ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
+ + "<packages>\n"
+ + " <package name=\"com.android.app1\" gameMode=\"1\">\n"
+ + " <gameModeConfig gameMode=\"3\" scaling=\"invalid\" "
+ + "useAngle=\"invalid\" fps=\"invalid\" "
+ + "loadingBoost=\"invalid\"></gameModeConfig>\n"
+ + " </package>\n"
+ + "</packages>\n").getBytes());
+ final Context context = InstrumentationRegistry.getContext();
+ GameManagerSettings settings = new GameManagerSettings(context.getFilesDir());
+ assertTrue(settings.readPersistentDataLocked());
+
+ final GamePackageConfiguration config = settings.getConfigOverride(PACKAGE_NAME_1);
+ assertNotNull(config);
+ final GameModeConfiguration batteryConfig = config.getGameModeConfiguration(
+ GameManager.GAME_MODE_BATTERY);
+ assertNotNull(batteryConfig);
+ assertEquals(batteryConfig.getScaling(), GameModeConfiguration.DEFAULT_SCALING, 0.01f);
+ assertEquals(batteryConfig.getLoadingBoostDuration(),
+ GameModeConfiguration.DEFAULT_LOADING_BOOST_DURATION);
+ assertEquals(batteryConfig.getFpsStr(), "invalid");
+ assertFalse(batteryConfig.getUseAngle());
+ }
+
+ @Test
+ public void testReadGameServiceSettings_invalidTags() {
+ writeFile(new File(InstrumentationRegistry.getContext().getFilesDir(),
+ "system/game-manager-service.xml"),
+ ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
+ + "<packages>\n"
+ + " <package gameMode=\"1\">\n"
+ + " </package>\n"
+ + " <package name=\"com.android.app2\" gameMode=\"2\">\n"
+ + " <unknown></unknown>"
+ + " <gameModeConfig gameMode=\"3\" fps=\"90\"></gameModeConfig>\n"
+ + " foo bar"
+ + " </package>\n"
+ + " <unknownTag></unknownTag>\n"
+ + " foo bar\n"
+ + " <package name=\"com.android.app3\" gameMode=\"3\">\n"
+ + " </package>\n"
+ + "</packages>\n").getBytes());
+ final Context context = InstrumentationRegistry.getContext();
+ GameManagerSettings settings = new GameManagerSettings(context.getFilesDir());
+ assertTrue(settings.readPersistentDataLocked());
+ assertEquals(0, settings.getGameModeLocked(PACKAGE_NAME_1));
+ assertEquals(2, settings.getGameModeLocked(PACKAGE_NAME_2));
+ assertEquals(3, settings.getGameModeLocked(PACKAGE_NAME_3));
+
+ final GamePackageConfiguration config = settings.getConfigOverride(PACKAGE_NAME_2);
+ assertNotNull(config);
+ final GameModeConfiguration batteryConfig = config.getGameModeConfiguration(
+ GameManager.GAME_MODE_BATTERY);
+ assertNotNull(batteryConfig);
+ assertEquals(batteryConfig.getFpsStr(), "90");
+ }
+
+
@Test
public void testWriteGameServiceSettings() {
- // write out files and read
- writeOldFiles();
final Context context = InstrumentationRegistry.getContext();
GameManagerSettings settings = new GameManagerSettings(context.getFilesDir());
- assertThat(settings.readPersistentDataLocked(), is(true));
- // write out, read back in and verify the same
+ // set package settings and write out to file
+ settings.setGameModeLocked(PACKAGE_NAME_1, GameManager.GAME_MODE_BATTERY);
+ settings.setGameModeLocked(PACKAGE_NAME_2, GameManager.GAME_MODE_PERFORMANCE);
+ settings.setGameModeLocked(PACKAGE_NAME_3, GameManager.GAME_MODE_STANDARD);
+ GamePackageConfiguration config = new GamePackageConfiguration(PACKAGE_NAME_2);
+ GameModeConfiguration performanceConfig = config.getOrAddDefaultGameModeConfiguration(
+ GameManager.GAME_MODE_PERFORMANCE);
+ performanceConfig.setLoadingBoostDuration(321);
+ performanceConfig.setScaling(0.66f);
+ performanceConfig.setUseAngle(true);
+ performanceConfig.setFpsStr("60");
+ GameModeConfiguration batteryConfig = config.getOrAddDefaultGameModeConfiguration(
+ GameManager.GAME_MODE_BATTERY);
+ batteryConfig.setScaling(0.77f);
+ settings.setConfigOverride(PACKAGE_NAME_2, config);
settings.writePersistentDataLocked();
- assertThat(settings.readPersistentDataLocked(), is(true));
- verifyGameServiceSettingsData(settings);
+
+ // clear the settings in memory
+ settings.removeGame(PACKAGE_NAME_1);
+ settings.removeGame(PACKAGE_NAME_2);
+ settings.removeGame(PACKAGE_NAME_3);
+
+ // read back in and verify
+ assertTrue(settings.readPersistentDataLocked());
+ assertEquals(3, settings.getGameModeLocked(PACKAGE_NAME_1));
+ assertEquals(2, settings.getGameModeLocked(PACKAGE_NAME_2));
+ assertEquals(1, settings.getGameModeLocked(PACKAGE_NAME_3));
+
+ config = settings.getConfigOverride(PACKAGE_NAME_1);
+ assertNull(config);
+ config = settings.getConfigOverride(PACKAGE_NAME_2);
+ assertNotNull(config);
+ batteryConfig = config.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY);
+ assertNotNull(batteryConfig);
+ assertEquals(batteryConfig.getScaling(), 0.77f, 0.01f);
+ assertEquals(batteryConfig.getLoadingBoostDuration(),
+ GameModeConfiguration.DEFAULT_LOADING_BOOST_DURATION);
+ assertEquals(batteryConfig.getFpsStr(), GameModeConfiguration.DEFAULT_FPS);
+ assertFalse(batteryConfig.getUseAngle());
+
+ performanceConfig = config.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE);
+ assertNotNull(performanceConfig);
+ assertEquals(performanceConfig.getScaling(), 0.66f, 0.01f);
+ assertEquals(performanceConfig.getLoadingBoostDuration(), 321);
+ assertEquals(performanceConfig.getFpsStr(), "60");
+ assertTrue(performanceConfig.getUseAngle());
}
}
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
index e2c3a94fc187..4c939f077940 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
@@ -83,6 +83,7 @@ public class VirtualAudioControllerTest {
VirtualDeviceParams.ACTIVITY_POLICY_DEFAULT_ALLOWED,
/* activityListener= */ null,
/* activityBlockedCallback= */ null,
+ /* secureWindowCallback= */ null,
/* deviceProfile= */ DEVICE_PROFILE_APP_STREAMING);
}
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessEventTest.java b/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessEventTest.java
index e305957c6290..f69c5c2df6ce 100644
--- a/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessEventTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessEventTest.java
@@ -39,16 +39,23 @@ public final class BrightnessEventTest {
mBrightnessEvent = new BrightnessEvent(1);
mBrightnessEvent.setReason(
getReason(BrightnessReason.REASON_DOZE, BrightnessReason.MODIFIER_LOW_POWER));
+ mBrightnessEvent.setPhysicalDisplayId("test");
mBrightnessEvent.setLux(100.0f);
+ mBrightnessEvent.setFastAmbientLux(90.0f);
+ mBrightnessEvent.setSlowAmbientLux(85.0f);
mBrightnessEvent.setPreThresholdLux(150.0f);
mBrightnessEvent.setTime(System.currentTimeMillis());
+ mBrightnessEvent.setInitialBrightness(25.0f);
mBrightnessEvent.setBrightness(0.6f);
mBrightnessEvent.setRecommendedBrightness(0.6f);
mBrightnessEvent.setHbmMax(0.62f);
+ mBrightnessEvent.setRbcStrength(-1);
mBrightnessEvent.setThermalMax(0.65f);
+ mBrightnessEvent.setPowerFactor(0.2f);
mBrightnessEvent.setHbmMode(BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF);
mBrightnessEvent.setFlags(0);
mBrightnessEvent.setAdjustmentFlags(0);
+ mBrightnessEvent.setAutomaticBrightnessEnabled(true);
}
@Test
@@ -56,19 +63,41 @@ public final class BrightnessEventTest {
BrightnessEvent secondBrightnessEvent = new BrightnessEvent(1);
secondBrightnessEvent.copyFrom(mBrightnessEvent);
secondBrightnessEvent.setTime(0);
- assertEquals(secondBrightnessEvent.equalsMainData(mBrightnessEvent), true);
+ assertEquals(true, secondBrightnessEvent.equalsMainData(mBrightnessEvent));
}
@Test
public void testToStringWorksAsExpected() {
String actualString = mBrightnessEvent.toString(false);
String expectedString =
- "BrightnessEvent: disp=1, brt=0.6, rcmdBrt=0.6, preBrt=NaN, lux=100.0, preLux=150"
- + ".0, hbmMax=0.62, hbmMode=off, thrmMax=0.65, flags=, reason=doze [ "
- + "low_pwr ]";
- assertEquals(actualString, expectedString);
+ "BrightnessEvent: disp=1, physDisp=test, brt=0.6, initBrt=25.0, rcmdBrt=0.6,"
+ + " preBrt=NaN, lux=100.0, fastLux=90.0, slowLux=85.0, preLux=150.0, hbmMax=0.62,"
+ + " hbmMode=off, rbcStrength=-1, thrmMax=0.65, powerFactor=0.2, flags=, reason=doze"
+ + " [ low_pwr ], autoBrightness=true";
+ assertEquals(expectedString, actualString);
}
+ @Test
+ public void testFlagsToString() {
+ mBrightnessEvent.reset();
+ mBrightnessEvent.setFlags(mBrightnessEvent.getFlags() | BrightnessEvent.FLAG_IDLE_CURVE);
+ String actualString = mBrightnessEvent.flagsToString();
+ String expectedString = "idle_curve ";
+ assertEquals(expectedString, actualString);
+ }
+
+ @Test
+ public void testFlagsToString_multipleFlags() {
+ mBrightnessEvent.reset();
+ mBrightnessEvent.setFlags(mBrightnessEvent.getFlags()
+ | BrightnessEvent.FLAG_IDLE_CURVE
+ | BrightnessEvent.FLAG_LOW_POWER_MODE);
+ String actualString = mBrightnessEvent.flagsToString();
+ String expectedString = "idle_curve low_power_mode ";
+ assertEquals(expectedString, actualString);
+ }
+
+
private BrightnessReason getReason(int reason, int modifier) {
BrightnessReason brightnessReason = new BrightnessReason();
brightnessReason.setReason(reason);
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
index a4cccb320ff1..3477288f4cff 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
@@ -369,6 +369,8 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests {
throws Exception {
final LockscreenCredential parentPassword = newPassword("parentPassword");
final LockscreenCredential profilePassword = newPattern("12345");
+ mService.setSeparateProfileChallengeEnabled(
+ MANAGED_PROFILE_USER_ID, true, profilePassword);
initializeStorageWithCredential(PRIMARY_USER_ID, parentPassword);
// Create and verify separate profile credentials.
testCreateCredential(MANAGED_PROFILE_USER_ID, profilePassword);
@@ -550,11 +552,12 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests {
throws RemoteException {
assertEquals(0, mGateKeeperService.getSecureUserId(userId));
synchronized (mService.mSpManager) {
- mService.initializeSyntheticPasswordLocked(credential, userId);
+ mService.initializeSyntheticPasswordLocked(userId);
}
if (credential.isNone()) {
assertEquals(0, mGateKeeperService.getSecureUserId(userId));
} else {
+ assertTrue(mService.setLockCredential(credential, nonePassword(), userId));
assertNotEquals(0, mGateKeeperService.getSecureUserId(userId));
}
}
diff --git a/services/tests/servicestests/src/com/android/server/people/data/ConversationInfoTest.java b/services/tests/servicestests/src/com/android/server/people/data/ConversationInfoTest.java
index 8139310a4c3d..c90064eaa810 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/ConversationInfoTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/ConversationInfoTest.java
@@ -58,6 +58,7 @@ public final class ConversationInfoTest {
.setNotificationChannelId(NOTIFICATION_CHANNEL_ID)
.setParentNotificationChannelId(PARENT_NOTIFICATION_CHANNEL_ID)
.setLastEventTimestamp(100L)
+ .setCreationTimestamp(200L)
.setShortcutFlags(ShortcutInfo.FLAG_LONG_LIVED
| ShortcutInfo.FLAG_CACHED_NOTIFICATIONS)
.setImportant(true)
@@ -79,6 +80,7 @@ public final class ConversationInfoTest {
assertEquals(PARENT_NOTIFICATION_CHANNEL_ID,
conversationInfo.getParentNotificationChannelId());
assertEquals(100L, conversationInfo.getLastEventTimestamp());
+ assertEquals(200L, conversationInfo.getCreationTimestamp());
assertTrue(conversationInfo.isShortcutLongLived());
assertTrue(conversationInfo.isShortcutCachedForNotification());
assertTrue(conversationInfo.isImportant());
@@ -105,6 +107,7 @@ public final class ConversationInfoTest {
assertNull(conversationInfo.getNotificationChannelId());
assertNull(conversationInfo.getParentNotificationChannelId());
assertEquals(0L, conversationInfo.getLastEventTimestamp());
+ assertEquals(0L, conversationInfo.getCreationTimestamp());
assertFalse(conversationInfo.isShortcutLongLived());
assertFalse(conversationInfo.isShortcutCachedForNotification());
assertFalse(conversationInfo.isImportant());
@@ -131,6 +134,7 @@ public final class ConversationInfoTest {
.setNotificationChannelId(NOTIFICATION_CHANNEL_ID)
.setParentNotificationChannelId(PARENT_NOTIFICATION_CHANNEL_ID)
.setLastEventTimestamp(100L)
+ .setCreationTimestamp(200L)
.setShortcutFlags(ShortcutInfo.FLAG_LONG_LIVED)
.setImportant(true)
.setNotificationSilenced(true)
@@ -154,6 +158,7 @@ public final class ConversationInfoTest {
assertEquals(NOTIFICATION_CHANNEL_ID, destination.getNotificationChannelId());
assertEquals(PARENT_NOTIFICATION_CHANNEL_ID, destination.getParentNotificationChannelId());
assertEquals(100L, destination.getLastEventTimestamp());
+ assertEquals(200L, destination.getCreationTimestamp());
assertTrue(destination.isShortcutLongLived());
assertFalse(destination.isImportant());
assertTrue(destination.isNotificationSilenced());
@@ -164,4 +169,105 @@ public final class ConversationInfoTest {
assertThat(destination.getStatuses()).contains(cs);
assertThat(destination.getStatuses()).contains(cs2);
}
+
+ @Test
+ public void testBuildFromAnotherConversation_identicalConversation() {
+ ConversationStatus cs = new ConversationStatus.Builder("id", ACTIVITY_ANNIVERSARY).build();
+ ConversationStatus cs2 = new ConversationStatus.Builder("id2", ACTIVITY_GAME).build();
+
+ ConversationInfo source = new ConversationInfo.Builder()
+ .setShortcutId(SHORTCUT_ID)
+ .setLocusId(LOCUS_ID)
+ .setContactUri(CONTACT_URI)
+ .setContactPhoneNumber(PHONE_NUMBER)
+ .setNotificationChannelId(NOTIFICATION_CHANNEL_ID)
+ .setParentNotificationChannelId(PARENT_NOTIFICATION_CHANNEL_ID)
+ .setLastEventTimestamp(100L)
+ .setCreationTimestamp(200L)
+ .setShortcutFlags(ShortcutInfo.FLAG_LONG_LIVED)
+ .setImportant(true)
+ .setNotificationSilenced(true)
+ .setBubbled(true)
+ .setPersonImportant(true)
+ .setPersonBot(true)
+ .setContactStarred(true)
+ .addOrUpdateStatus(cs)
+ .addOrUpdateStatus(cs2)
+ .build();
+
+ ConversationInfo destination = new ConversationInfo.Builder(source).build();
+
+ assertEquals(SHORTCUT_ID, destination.getShortcutId());
+ assertEquals(LOCUS_ID, destination.getLocusId());
+ assertEquals(CONTACT_URI, destination.getContactUri());
+ assertEquals(PHONE_NUMBER, destination.getContactPhoneNumber());
+ assertEquals(NOTIFICATION_CHANNEL_ID, destination.getNotificationChannelId());
+ assertEquals(PARENT_NOTIFICATION_CHANNEL_ID, destination.getParentNotificationChannelId());
+ assertEquals(100L, destination.getLastEventTimestamp());
+ assertEquals(200L, destination.getCreationTimestamp());
+ assertTrue(destination.isShortcutLongLived());
+ assertTrue(destination.isImportant());
+ assertTrue(destination.isNotificationSilenced());
+ assertTrue(destination.isBubbled());
+ assertTrue(destination.isPersonImportant());
+ assertTrue(destination.isPersonBot());
+ assertTrue(destination.isContactStarred());
+ assertThat(destination.getStatuses()).contains(cs);
+ assertThat(destination.getStatuses()).contains(cs2);
+ // Also check equals() implementation
+ assertTrue(source.equals(destination));
+ assertTrue(destination.equals(source));
+ }
+
+ @Test
+ public void testBuildFromBackupPayload() {
+ ConversationStatus cs = new ConversationStatus.Builder("id", ACTIVITY_ANNIVERSARY).build();
+ ConversationStatus cs2 = new ConversationStatus.Builder("id2", ACTIVITY_GAME).build();
+
+ ConversationInfo conversationInfo = new ConversationInfo.Builder()
+ .setShortcutId(SHORTCUT_ID)
+ .setLocusId(LOCUS_ID)
+ .setContactUri(CONTACT_URI)
+ .setContactPhoneNumber(PHONE_NUMBER)
+ .setNotificationChannelId(NOTIFICATION_CHANNEL_ID)
+ .setParentNotificationChannelId(PARENT_NOTIFICATION_CHANNEL_ID)
+ .setLastEventTimestamp(100L)
+ .setCreationTimestamp(200L)
+ .setShortcutFlags(ShortcutInfo.FLAG_LONG_LIVED
+ | ShortcutInfo.FLAG_CACHED_NOTIFICATIONS)
+ .setImportant(true)
+ .setNotificationSilenced(true)
+ .setBubbled(true)
+ .setDemoted(true)
+ .setPersonImportant(true)
+ .setPersonBot(true)
+ .setContactStarred(true)
+ .addOrUpdateStatus(cs)
+ .addOrUpdateStatus(cs2)
+ .build();
+
+ ConversationInfo conversationInfoFromBackup =
+ ConversationInfo.readFromBackupPayload(conversationInfo.getBackupPayload());
+
+ assertEquals(SHORTCUT_ID, conversationInfoFromBackup.getShortcutId());
+ assertEquals(LOCUS_ID, conversationInfoFromBackup.getLocusId());
+ assertEquals(CONTACT_URI, conversationInfoFromBackup.getContactUri());
+ assertEquals(PHONE_NUMBER, conversationInfoFromBackup.getContactPhoneNumber());
+ assertEquals(
+ NOTIFICATION_CHANNEL_ID, conversationInfoFromBackup.getNotificationChannelId());
+ assertEquals(PARENT_NOTIFICATION_CHANNEL_ID,
+ conversationInfoFromBackup.getParentNotificationChannelId());
+ assertEquals(100L, conversationInfoFromBackup.getLastEventTimestamp());
+ assertEquals(200L, conversationInfoFromBackup.getCreationTimestamp());
+ assertTrue(conversationInfoFromBackup.isShortcutLongLived());
+ assertTrue(conversationInfoFromBackup.isShortcutCachedForNotification());
+ assertTrue(conversationInfoFromBackup.isImportant());
+ assertTrue(conversationInfoFromBackup.isNotificationSilenced());
+ assertTrue(conversationInfoFromBackup.isBubbled());
+ assertTrue(conversationInfoFromBackup.isDemoted());
+ assertTrue(conversationInfoFromBackup.isPersonImportant());
+ assertTrue(conversationInfoFromBackup.isPersonBot());
+ assertTrue(conversationInfoFromBackup.isContactStarred());
+ // ConversationStatus is a transient object and not persisted
+ }
}
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 2a4896a7b041..66c3f0730404 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
@@ -291,7 +291,8 @@ public final class DataManagerTest {
mShortcutChangeCallbackCaptor.capture());
mShortcutChangeCallback = mShortcutChangeCallbackCaptor.getValue();
- verify(mContext, times(2)).registerReceiver(any(), any());
+ verify(mContext, times(1)).registerReceiver(any(), any());
+ verify(mContext, times(1)).registerReceiver(any(), any(), anyInt());
}
@After
@@ -1163,6 +1164,76 @@ public final class DataManagerTest {
}
@Test
+ public void testUncacheOldestCachedShortcut_missingNotificationEvents() {
+ mDataManager.onUserUnlocked(USER_ID_PRIMARY);
+
+ 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,
+ buildPerson());
+ shortcut.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
+ mShortcutChangeCallback.onShortcutsAddedOrUpdated(
+ TEST_PKG_NAME,
+ Collections.singletonList(shortcut),
+ UserHandle.of(USER_ID_PRIMARY));
+ mLooper.dispatchAll();
+ }
+
+ // Only the shortcut #0 is uncached, all the others are not.
+ verify(mShortcutServiceInternal).uncacheShortcuts(
+ anyInt(), any(), eq(TEST_PKG_NAME),
+ eq(Collections.singletonList(TEST_SHORTCUT_ID + 0)), eq(USER_ID_PRIMARY),
+ eq(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS));
+ for (int i = 1; i < DataManager.MAX_CACHED_RECENT_SHORTCUTS + 1; i++) {
+ verify(mShortcutServiceInternal, never()).uncacheShortcuts(
+ anyInt(), anyString(), anyString(),
+ eq(Collections.singletonList(TEST_SHORTCUT_ID + i)), anyInt(),
+ eq(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS));
+ }
+ }
+
+ @Test
+ public void testUncacheOldestCachedShortcut_legacyConversation() {
+ mDataManager.onUserUnlocked(USER_ID_PRIMARY);
+
+ // Add an extra conversation with a legacy type (no creationTime)
+ ConversationStore conversationStore = mDataManager
+ .getUserDataForTesting(USER_ID_PRIMARY)
+ .getOrCreatePackageData(TEST_PKG_NAME)
+ .getConversationStore();
+ ConversationInfo.Builder builder = new ConversationInfo.Builder();
+ builder.setShortcutId(TEST_SHORTCUT_ID + 0);
+ builder.setShortcutFlags(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
+ mDataManager.updateConversationStoreThenNotifyListeners(
+ conversationStore,
+ builder.build(),
+ TEST_PKG_NAME, USER_ID_PRIMARY);
+ for (int i = 1; i < DataManager.MAX_CACHED_RECENT_SHORTCUTS + 1; i++) {
+ String shortcutId = TEST_SHORTCUT_ID + i;
+ ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, shortcutId,
+ buildPerson());
+ shortcut.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
+ mShortcutChangeCallback.onShortcutsAddedOrUpdated(
+ TEST_PKG_NAME,
+ Collections.singletonList(shortcut),
+ UserHandle.of(USER_ID_PRIMARY));
+ mLooper.dispatchAll();
+ }
+
+ // Only the shortcut #0 is uncached, all the others are not.
+ verify(mShortcutServiceInternal).uncacheShortcuts(
+ anyInt(), any(), eq(TEST_PKG_NAME),
+ eq(Collections.singletonList(TEST_SHORTCUT_ID + 0)), eq(USER_ID_PRIMARY),
+ eq(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS));
+ for (int i = 1; i < DataManager.MAX_CACHED_RECENT_SHORTCUTS + 1; i++) {
+ verify(mShortcutServiceInternal, never()).uncacheShortcuts(
+ anyInt(), anyString(), anyString(),
+ eq(Collections.singletonList(TEST_SHORTCUT_ID + i)), anyInt(),
+ eq(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS));
+ }
+ }
+
+ @Test
public void testBackupAndRestoration()
throws IntentFilter.MalformedMimeTypeException {
mDataManager.onUserUnlocked(USER_ID_PRIMARY);
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
new file mode 100644
index 000000000000..13a7a3ec7fed
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
@@ -0,0 +1,185 @@
+/*
+ * 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.pm;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.content.pm.UserProperties;
+import android.os.Parcel;
+import android.platform.test.annotations.Presubmit;
+import android.util.TypedXmlPullParser;
+import android.util.TypedXmlSerializer;
+import android.util.Xml;
+
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.function.Supplier;
+
+/**
+ * Tests for UserManager's {@link UserProperties}.
+ *
+ * Additional test coverage (that actually exercises the functionality) can be found in
+ * {@link UserManagerTest} and
+ * {@link UserManagerServiceUserTypeTest} (for {@link UserProperties#updateFromXml}).
+ *
+ * <p>Run with: atest UserManagerServiceUserPropertiesTest
+ */
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+@MediumTest
+public class UserManagerServiceUserPropertiesTest {
+
+ /** Test that UserProperties can properly read the xml information that it writes. */
+ @Test
+ public void testWriteReadXml() throws Exception {
+ final UserProperties defaultProps = new UserProperties.Builder()
+ .setShowInLauncher(21)
+ .setStartWithParent(false)
+ .build();
+ final UserProperties actualProps = new UserProperties(defaultProps);
+ actualProps.setShowInLauncher(14);
+
+ // Write the properties to xml.
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ final TypedXmlSerializer out = Xml.newFastSerializer();
+ out.setOutput(baos, StandardCharsets.UTF_8.name());
+ out.startDocument(null, true);
+ out.startTag(null, "testTag");
+ actualProps.writeToXml(out);
+ out.endTag(null, "testTag");
+ out.endDocument();
+
+ // Now read those properties from xml.
+ final ByteArrayInputStream input = new ByteArrayInputStream(baos.toByteArray());
+ final TypedXmlPullParser parser = Xml.newFastPullParser();
+ parser.setInput(input, StandardCharsets.UTF_8.name());
+ parser.nextTag();
+ final UserProperties readProps = new UserProperties(parser, defaultProps);
+
+ assertUserPropertiesEquals(actualProps, readProps);
+ }
+
+ /** Tests parcelling an object in which all properties are present. */
+ @Test
+ public void testParcelUnparcel() throws Exception {
+ final UserProperties originalProps = new UserProperties.Builder()
+ .setShowInLauncher(2145)
+ .build();
+ final UserProperties readProps = parcelThenUnparcel(originalProps);
+ assertUserPropertiesEquals(originalProps, readProps);
+ }
+
+ /** Tests copying a UserProperties object varying permissions. */
+ @Test
+ public void testCopyLacksPermissions() throws Exception {
+ final UserProperties defaultProps = new UserProperties.Builder()
+ .setShowInLauncher(2145)
+ .setStartWithParent(true)
+ .build();
+ final UserProperties orig = new UserProperties(defaultProps);
+ orig.setShowInLauncher(2841);
+ orig.setStartWithParent(false);
+
+ // Test every permission level. (Currently, it's linear so it's easy.)
+ for (int permLevel = 0; permLevel < 4; permLevel++) {
+ final boolean exposeAll = permLevel >= 3;
+ final boolean hasManage = permLevel >= 2;
+ final boolean hasQuery = permLevel >= 1;
+
+ // Make a possibly-not-full-permission (i.e. partial) copy and check that it is correct.
+ final UserProperties copy = new UserProperties(orig, exposeAll, hasManage, hasQuery);
+ verifyTestCopyLacksPermissions(orig, copy, exposeAll, hasManage, hasQuery);
+ if (permLevel < 1) {
+ // PropertiesPresent should definitely be different since not all items were copied.
+ assertThat(orig.getPropertiesPresent()).isNotEqualTo(copy.getPropertiesPresent());
+ }
+
+ // Now, just like in the SystemServer, parcel/unparcel the copy and make sure that the
+ // unparcelled version behaves just like the partial copy did.
+ final UserProperties readProps = parcelThenUnparcel(copy);
+ verifyTestCopyLacksPermissions(orig, readProps, exposeAll, hasManage, hasQuery);
+ }
+ }
+
+ /**
+ * Verifies that the copy of orig has the expected properties
+ * for the test {@link #testCopyLacksPermissions}.
+ */
+ private void verifyTestCopyLacksPermissions(
+ UserProperties orig,
+ UserProperties copy,
+ boolean exposeAll,
+ boolean hasManagePermission,
+ boolean hasQueryPermission) {
+
+ // Items requiring exposeAll.
+ assertEqualGetterOrThrows(orig::getStartWithParent, copy::getStartWithParent, exposeAll);
+
+ // Items requiring hasManagePermission - put them here using hasManagePermission.
+ // Items requiring hasQueryPermission - put them here using hasQueryPermission.
+
+ // Items with no permission requirements.
+ assertEqualGetterOrThrows(orig::getShowInLauncher, copy::getShowInLauncher, true);
+ }
+
+ /**
+ * If hasPerm, then asserts that value of actualGetter equals value of expectedGetter.
+ * If !hasPerm, then asserts that actualGetter throws a SecurityException.
+ */
+ @SuppressWarnings("ReturnValueIgnored")
+ private void assertEqualGetterOrThrows(
+ Supplier expectedGetter,
+ Supplier actualGetter,
+ boolean hasPerm) {
+ if (hasPerm) {
+ assertThat(expectedGetter.get()).isEqualTo(actualGetter.get());
+ } else {
+ assertThrows(SecurityException.class, actualGetter::get);
+ }
+ }
+
+ private UserProperties parcelThenUnparcel(UserProperties originalProps) {
+ final Parcel out = Parcel.obtain();
+ originalProps.writeToParcel(out, 0);
+ final byte[] data = out.marshall();
+ out.recycle();
+
+ final Parcel in = Parcel.obtain();
+ in.unmarshall(data, 0, data.length);
+ in.setDataPosition(0);
+ final UserProperties readProps = UserProperties.CREATOR.createFromParcel(in);
+ in.recycle();
+
+ return readProps;
+ }
+
+ /** Checks that two UserProperties get the same values. */
+ private void assertUserPropertiesEquals(UserProperties expected, UserProperties actual) {
+ assertThat(expected.getPropertiesPresent()).isEqualTo(actual.getPropertiesPresent());
+ assertThat(expected.getShowInLauncher()).isEqualTo(actual.getShowInLauncher());
+ assertThat(expected.getStartWithParent()).isEqualTo(actual.getStartWithParent());
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
index 971b036f7d0e..5f480044d44b 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
@@ -35,6 +35,7 @@ import static org.junit.Assert.assertTrue;
import static org.testng.Assert.assertThrows;
import android.content.pm.UserInfo;
+import android.content.pm.UserProperties;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.os.Bundle;
@@ -81,6 +82,8 @@ public class UserManagerServiceUserTypeTest {
DefaultCrossProfileIntentFilter.Direction.TO_PARENT,
/* flags= */0,
/* letsPersonalDataIntoProfile= */false).build());
+ final UserProperties.Builder userProps = new UserProperties.Builder()
+ .setShowInLauncher(17);
final UserTypeDetails type = new UserTypeDetails.Builder()
.setName("a.name")
.setEnabled(1)
@@ -98,6 +101,7 @@ public class UserManagerServiceUserTypeTest {
.setDefaultSystemSettings(systemSettings)
.setDefaultSecureSettings(secureSettings)
.setDefaultCrossProfileIntentFilters(filters)
+ .setDefaultUserProperties(userProps)
.createUserTypeDetails();
assertEquals("a.name", type.getName());
@@ -135,6 +139,8 @@ public class UserManagerServiceUserTypeTest {
assertEquals(filters.get(i), type.getDefaultCrossProfileIntentFilters().get(i));
}
+ assertEquals(17, type.getDefaultUserPropertiesReference().getShowInLauncher());
+
assertEquals(23, type.getBadgeLabel(0));
assertEquals(24, type.getBadgeLabel(1));
assertEquals(25, type.getBadgeLabel(2));
@@ -173,6 +179,11 @@ public class UserManagerServiceUserTypeTest {
assertTrue(type.getDefaultSecureSettings().isEmpty());
assertTrue(type.getDefaultCrossProfileIntentFilters().isEmpty());
+ final UserProperties props = type.getDefaultUserPropertiesReference();
+ assertNotNull(props);
+ assertFalse(props.getStartWithParent());
+ assertEquals(UserProperties.SHOW_IN_LAUNCHER_WITH_PARENT, props.getShowInLauncher());
+
assertFalse(type.hasBadge());
}
@@ -250,19 +261,24 @@ public class UserManagerServiceUserTypeTest {
// Mock some "AOSP defaults".
final Bundle restrictions = makeRestrictionsBundle("no_config_vpn", "no_config_tethering");
+ final UserProperties.Builder props = new UserProperties.Builder()
+ .setShowInLauncher(19)
+ .setStartWithParent(true);
final ArrayMap<String, UserTypeDetails.Builder> builders = new ArrayMap<>();
builders.put(userTypeAosp1, new UserTypeDetails.Builder()
.setName(userTypeAosp1)
.setBaseType(FLAG_PROFILE)
.setMaxAllowedPerParent(31)
- .setDefaultRestrictions(restrictions));
+ .setDefaultRestrictions(restrictions)
+ .setDefaultUserProperties(props));
builders.put(userTypeAosp2, new UserTypeDetails.Builder()
.setName(userTypeAosp1)
.setBaseType(FLAG_PROFILE)
.setMaxAllowedPerParent(32)
.setIconBadge(401)
.setBadgeColors(402, 403, 404)
- .setDefaultRestrictions(restrictions));
+ .setDefaultRestrictions(restrictions)
+ .setDefaultUserProperties(props));
final XmlResourceParser parser = mResources.getXml(R.xml.usertypes_test_profile);
UserTypeFactory.customizeBuilders(builders, parser);
@@ -272,6 +288,8 @@ public class UserManagerServiceUserTypeTest {
assertEquals(31, aospType.getMaxAllowedPerParent());
assertEquals(Resources.ID_NULL, aospType.getIconBadge());
assertTrue(UserRestrictionsUtils.areEqual(restrictions, aospType.getDefaultRestrictions()));
+ assertEquals(19, aospType.getDefaultUserPropertiesReference().getShowInLauncher());
+ assertEquals(true, aospType.getDefaultUserPropertiesReference().getStartWithParent());
// userTypeAosp2 should be modified.
aospType = builders.get(userTypeAosp2).createUserTypeDetails();
@@ -300,6 +318,8 @@ public class UserManagerServiceUserTypeTest {
assertTrue(UserRestrictionsUtils.areEqual(
makeRestrictionsBundle("no_remove_user", "no_bluetooth"),
aospType.getDefaultRestrictions()));
+ assertEquals(2020, aospType.getDefaultUserPropertiesReference().getShowInLauncher());
+ assertEquals(false, aospType.getDefaultUserPropertiesReference().getStartWithParent());
// userTypeOem1 should be created.
UserTypeDetails.Builder customType = builders.get(userTypeOem1);
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index 5d48501b9bc5..f567e8006bd7 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -31,6 +31,7 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
+import android.content.pm.UserProperties;
import android.content.res.Resources;
import android.os.Bundle;
import android.os.UserHandle;
@@ -564,6 +565,33 @@ public final class UserManagerTest {
assertThat(userManagerForUser.isProfile()).isEqualTo(userTypeDetails.isProfile());
}
+ /** Test that UserManager returns the correct UserProperties for a new managed profile. */
+ @MediumTest
+ @Test
+ public void testUserProperties() throws Exception {
+ assumeManagedUsersSupported();
+
+ // Get the default properties for a user type.
+ final UserTypeDetails userTypeDetails =
+ UserTypeFactory.getUserTypes().get(UserManager.USER_TYPE_PROFILE_MANAGED);
+ assertWithMessage("No %s type on device", UserManager.USER_TYPE_PROFILE_MANAGED)
+ .that(userTypeDetails).isNotNull();
+ final UserProperties typeProps = userTypeDetails.getDefaultUserPropertiesReference();
+
+ // Create an actual user (of this user type) and get its properties.
+ final int primaryUserId = mUserManager.getPrimaryUser().id;
+ final UserInfo userInfo = createProfileForUser("Managed",
+ UserManager.USER_TYPE_PROFILE_MANAGED, primaryUserId);
+ assertThat(userInfo).isNotNull();
+ final int userId = userInfo.id;
+ final UserProperties userProps = mUserManager.getUserProperties(UserHandle.of(userId));
+
+ // Check that this new user has the expected properties (relative to the defaults)
+ // provided that the test caller has the necessary permissions.
+ assertThat(userProps.getShowInLauncher()).isEqualTo(typeProps.getShowInLauncher());
+ assertThrows(SecurityException.class, userProps::getStartWithParent);
+ }
+
// Make sure only max managed profiles can be created
@MediumTest
@Test
diff --git a/services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java
index 36e988f36577..3848babdc912 100644
--- a/services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java
@@ -381,6 +381,7 @@ public class ThermalManagerServiceTest {
assertEquals(0, Arrays.asList(mService.mService.getCurrentTemperaturesWithType(
Temperature.TYPE_SKIN)).size());
assertEquals(Temperature.THROTTLING_NONE, mService.mService.getCurrentThermalStatus());
+ assertTrue(Float.isNaN(mService.mService.getThermalHeadroom(0)));
}
@Test
@@ -397,6 +398,14 @@ public class ThermalManagerServiceTest {
}
@Test
+ public void testGetThermalHeadroomInputRange() throws RemoteException {
+ assertTrue(Float.isNaN(mService.mService.getThermalHeadroom(
+ ThermalManagerService.MIN_FORECAST_SEC - 1)));
+ assertTrue(Float.isNaN(mService.mService.getThermalHeadroom(
+ ThermalManagerService.MAX_FORECAST_SEC + 1)));
+ }
+
+ @Test
public void testTemperatureWatcherUpdateSevereThresholds() throws RemoteException {
ThermalManagerService.TemperatureWatcher watcher = mService.mTemperatureWatcher;
watcher.mSevereThresholds.erase();
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
index 61a7f3853746..5c9348525861 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
@@ -28,6 +28,7 @@ import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.os.BatteryStatsHistory;
+import com.android.internal.os.Clock;
import org.junit.Before;
import org.junit.Test;
@@ -49,13 +50,14 @@ public class BatteryStatsHistoryTest {
private final Parcel mHistoryBuffer = Parcel.obtain();
private File mSystemDir;
private File mHistoryDir;
+ private final Clock mClock = new MockClock();
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
Context context = InstrumentationRegistry.getContext();
mSystemDir = context.getDataDir();
- mHistoryDir = new File(mSystemDir, BatteryStatsHistory.HISTORY_DIR);
+ mHistoryDir = new File(mSystemDir, "battery-history");
String[] files = mHistoryDir.list();
if (files != null) {
for (int i = 0; i < files.length; i++) {
@@ -67,8 +69,8 @@ public class BatteryStatsHistoryTest {
@Test
public void testConstruct() {
- BatteryStatsHistory history =
- new BatteryStatsHistory(mHistoryBuffer, mSystemDir, () -> 32);
+ BatteryStatsHistory history = new BatteryStatsHistory(mHistoryBuffer, mSystemDir, 32, 1024,
+ null, mClock);
createActiveFile(history);
verifyFileNumbers(history, Arrays.asList(0));
verifyActiveFile(history, "0.bin");
@@ -76,8 +78,8 @@ public class BatteryStatsHistoryTest {
@Test
public void testStartNextFile() {
- BatteryStatsHistory history =
- new BatteryStatsHistory(mHistoryBuffer, mSystemDir, () -> 32);
+ BatteryStatsHistory history = new BatteryStatsHistory(mHistoryBuffer, mSystemDir, 32, 1024,
+ null, mClock);
List<Integer> fileList = new ArrayList<>();
fileList.add(0);
createActiveFile(history);
@@ -114,13 +116,13 @@ public class BatteryStatsHistoryTest {
assertEquals(0, history.getHistoryUsedSize());
// create a new BatteryStatsHistory object, it will pick up existing history files.
- BatteryStatsHistory history2 =
- new BatteryStatsHistory(mHistoryBuffer, mSystemDir, () -> 32);
- // verify construct can pick up all files from file system.
+ BatteryStatsHistory history2 = new BatteryStatsHistory(mHistoryBuffer, mSystemDir, 32, 1024,
+ null, mClock);
+ // verify constructor can pick up all files from file system.
verifyFileNumbers(history2, fileList);
verifyActiveFile(history2, "33.bin");
- history2.resetAllFiles();
+ history2.reset();
createActiveFile(history2);
// verify all existing files are deleted.
for (int i = 2; i < 33; ++i) {
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java b/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
index 713e78638b95..570b2ee617f5 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
@@ -63,6 +63,7 @@ public class MockBatteryStatsImpl extends BatteryStatsImpl {
MockBatteryStatsImpl(Clock clock, File historyDirectory) {
super(clock, historyDirectory);
initTimersAndCounters();
+ setMaxHistoryBuffer(128 * 1024);
setExternalStatsSyncLocked(mExternalStatsSync);
informThatAllExternalStatsAreFlushed();
@@ -104,12 +105,6 @@ public class MockBatteryStatsImpl extends BatteryStatsImpl {
return mForceOnBattery ? true : super.isOnBattery();
}
- public void forceRecordAllHistory() {
- mHaveBatteryLevel = true;
- mRecordingHistory = true;
- mRecordAllHistory = true;
- }
-
public TimeBase getOnBatteryBackgroundTimeBase(int uid) {
return getUidStatsLocked(uid).mOnBatteryBackgroundTimeBase;
}
@@ -201,12 +196,14 @@ public class MockBatteryStatsImpl extends BatteryStatsImpl {
@GuardedBy("this")
public MockBatteryStatsImpl setMaxHistoryFiles(int maxHistoryFiles) {
mConstants.MAX_HISTORY_FILES = maxHistoryFiles;
+ mConstants.onChange();
return this;
}
@GuardedBy("this")
public MockBatteryStatsImpl setMaxHistoryBuffer(int maxHistoryBuffer) {
mConstants.MAX_HISTORY_BUFFER = maxHistoryBuffer;
+ mConstants.onChange();
return this;
}
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java
index fa3fcd9e9475..235849c1cd8b 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java
@@ -127,9 +127,17 @@ final class FakeVibratorControllerProvider {
}
@Override
- public long compose(PrimitiveSegment[] effects, long vibrationId) {
+ public long compose(PrimitiveSegment[] primitives, long vibrationId) {
+ if (mSupportedPrimitives == null) {
+ return 0;
+ }
+ for (PrimitiveSegment primitive : primitives) {
+ if (Arrays.binarySearch(mSupportedPrimitives, primitive.getPrimitiveId()) < 0) {
+ return 0;
+ }
+ }
long duration = 0;
- for (PrimitiveSegment primitive : effects) {
+ for (PrimitiveSegment primitive : primitives) {
duration += EFFECT_DURATION + primitive.getDelay();
recordEffectSegment(vibrationId, primitive);
}
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationTest.java
new file mode 100644
index 000000000000..b46929947fe4
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationTest.java
@@ -0,0 +1,46 @@
+/*
+ * 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.vibrator;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static java.util.stream.Collectors.toList;
+
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+
+/**
+ * Tests for {@link Vibration}.
+ *
+ * Build/Install/Run:
+ * atest FrameworksServicesTests:VibrationTest
+ */
+@Presubmit
+public class VibrationTest {
+
+ @Test
+ public void status_hasUniqueProtoEnumValues() {
+ assertThat(
+ Arrays.stream(Vibration.Status.values())
+ .map(Vibration.Status::getProtoEnumValue)
+ .collect(toList()))
+ .containsNoDuplicates();
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
index de5f6ed2ae5d..ca162efe0f6e 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -257,13 +257,18 @@ public class VibrationThreadTest {
assertTrue(mThread.isRunningVibrationId(vibrationId));
assertTrue(mControllers.get(VIBRATOR_ID).isVibrating());
- conductor.notifyCancelled(Vibration.Status.CANCELLED_SUPERSEDED, /* immediate= */ false);
+ Vibration.EndInfo cancelVibrationInfo = new Vibration.EndInfo(
+ Vibration.Status.CANCELLED_SUPERSEDED, /* endedByUid= */ 1,
+ /* endedByUsage= */ VibrationAttributes.USAGE_ALARM);
+ conductor.notifyCancelled(
+ cancelVibrationInfo,
+ /* immediate= */ false);
waitForCompletion();
assertFalse(mThread.isRunningVibrationId(vibrationId));
verify(mManagerHooks).noteVibratorOn(eq(UID), anyLong());
verify(mManagerHooks).noteVibratorOff(eq(UID));
- verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_SUPERSEDED);
+ verifyCallbacksTriggered(vibrationId, cancelVibrationInfo);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
List<Float> playedAmplitudes = fakeVibrator.getAmplitudes();
@@ -288,7 +293,9 @@ public class VibrationThreadTest {
VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect);
assertTrue(waitUntil(() -> !fakeVibrator.getAmplitudes().isEmpty(), TEST_TIMEOUT_MILLIS));
- conductor.notifyCancelled(Vibration.Status.CANCELLED_BY_USER, /* immediate= */ false);
+ conductor.notifyCancelled(
+ new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER),
+ /* immediate= */ false);
waitForCompletion();
verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);
@@ -319,7 +326,9 @@ public class VibrationThreadTest {
assertTrue(waitUntil(() -> !fakeVibrator.getEffectSegments(vibrationId).isEmpty(),
TEST_TIMEOUT_MILLIS));
- conductor.notifyCancelled(Vibration.Status.CANCELLED_BY_USER, /* immediate= */ false);
+ conductor.notifyCancelled(
+ new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER),
+ /* immediate= */ false);
waitForCompletion();
// PWLE size max was used to generate a single vibrate call with 10 segments.
@@ -348,11 +357,13 @@ public class VibrationThreadTest {
assertTrue(waitUntil(() -> !fakeVibrator.getEffectSegments(vibrationId).isEmpty(),
TEST_TIMEOUT_MILLIS));
- conductor.notifyCancelled(Vibration.Status.CANCELLED_SUPERSEDED, /* immediate= */ false);
+ conductor.notifyCancelled(
+ new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SCREEN_OFF),
+ /* immediate= */ false);
waitForCompletion();
// Composition size max was used to generate a single vibrate call with 10 primitives.
- verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_SUPERSEDED);
+ verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_SCREEN_OFF);
assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
assertEquals(10, fakeVibrator.getEffectSegments(vibrationId).size());
}
@@ -370,7 +381,9 @@ public class VibrationThreadTest {
VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect);
assertTrue(waitUntil(() -> !fakeVibrator.getAmplitudes().isEmpty(), TEST_TIMEOUT_MILLIS));
- conductor.notifyCancelled(Vibration.Status.CANCELLED_BY_USER, /* immediate= */ false);
+ conductor.notifyCancelled(
+ new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER),
+ /* immediate= */ false);
waitForCompletion();
verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);
@@ -394,7 +407,9 @@ public class VibrationThreadTest {
assertTrue(waitUntil(() -> fakeVibrator.getEffectSegments(vibrationId).size() > 1,
5000 + TEST_TIMEOUT_MILLIS));
- conductor.notifyCancelled(Vibration.Status.CANCELLED_BY_USER, /* immediate= */ false);
+ conductor.notifyCancelled(
+ new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER),
+ /* immediate= */ false);
waitForCompletion();
verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);
@@ -414,6 +429,8 @@ public class VibrationThreadTest {
public void vibrate_singleVibratorPredefinedCancel_cancelsVibrationImmediately()
throws Exception {
mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+ mVibratorProviders.get(VIBRATOR_ID).setSupportedPrimitives(
+ VibrationEffect.Composition.PRIMITIVE_CLICK);
long vibrationId = 1;
VibrationEffect effect = VibrationEffect.startComposition()
@@ -431,7 +448,9 @@ public class VibrationThreadTest {
// fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately.
Thread cancellingThread =
new Thread(() -> conductor.notifyCancelled(
- Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE, /* immediate= */ false));
+ new Vibration.EndInfo(
+ Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE),
+ /* immediate= */ false));
cancellingThread.start();
waitForCompletion(/* timeout= */ 50);
@@ -458,7 +477,9 @@ public class VibrationThreadTest {
// fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately.
Thread cancellingThread =
new Thread(() -> conductor.notifyCancelled(
- Vibration.Status.CANCELLED_BY_SCREEN_OFF, /* immediate= */ false));
+ new Vibration.EndInfo(
+ Vibration.Status.CANCELLED_BY_SCREEN_OFF),
+ /* immediate= */ false));
cancellingThread.start();
waitForCompletion(/* timeout= */ 50);
@@ -519,7 +540,7 @@ public class VibrationThreadTest {
startThreadAndDispatcher(vibrationId, effect);
waitForCompletion();
- verify(mManagerHooks, never()).noteVibratorOn(eq(UID), anyLong());
+ verify(mManagerHooks).noteVibratorOn(eq(UID), eq(0L));
verify(mManagerHooks, never()).noteVibratorOff(eq(UID));
verify(mControllerCallbacks, never()).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
verifyCallbacksTriggered(vibrationId, Vibration.Status.IGNORED_UNSUPPORTED);
@@ -530,6 +551,8 @@ public class VibrationThreadTest {
public void vibrate_singleVibratorComposed_runsVibration() throws Exception {
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+ fakeVibrator.setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK,
+ VibrationEffect.Composition.PRIMITIVE_TICK);
long vibrationId = 1;
VibrationEffect effect = VibrationEffect.startComposition()
@@ -559,7 +582,7 @@ public class VibrationThreadTest {
startThreadAndDispatcher(vibrationId, effect);
waitForCompletion();
- verify(mManagerHooks, never()).noteVibratorOn(eq(UID), anyLong());
+ verify(mManagerHooks).noteVibratorOn(eq(UID), eq(0L));
verify(mManagerHooks, never()).noteVibratorOff(eq(UID));
verify(mControllerCallbacks, never()).onComplete(eq(VIBRATOR_ID), eq(vibrationId));
verifyCallbacksTriggered(vibrationId, Vibration.Status.IGNORED_UNSUPPORTED);
@@ -570,6 +593,10 @@ public class VibrationThreadTest {
public void vibrate_singleVibratorLargeComposition_splitsVibratorComposeCalls() {
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+ fakeVibrator.setSupportedPrimitives(
+ VibrationEffect.Composition.PRIMITIVE_CLICK,
+ VibrationEffect.Composition.PRIMITIVE_TICK,
+ VibrationEffect.Composition.PRIMITIVE_SPIN);
fakeVibrator.setCompositionSizeMax(2);
long vibrationId = 1;
@@ -809,6 +836,8 @@ public class VibrationThreadTest {
mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
mVibratorProviders.get(3).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
mVibratorProviders.get(4).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+ mVibratorProviders.get(4).setSupportedPrimitives(
+ VibrationEffect.Composition.PRIMITIVE_CLICK);
long vibrationId = 1;
VibrationEffect composed = VibrationEffect.startComposition()
@@ -854,6 +883,8 @@ public class VibrationThreadTest {
mockVibrators(1, 2, 3);
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+ mVibratorProviders.get(2).setSupportedPrimitives(
+ VibrationEffect.Composition.PRIMITIVE_CLICK);
mVibratorProviders.get(3).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
long vibrationId = 1;
@@ -902,7 +933,11 @@ public class VibrationThreadTest {
long vibrationId = 1;
mockVibrators(vibratorIds);
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+ mVibratorProviders.get(1).setSupportedPrimitives(
+ VibrationEffect.Composition.PRIMITIVE_CLICK);
mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+ mVibratorProviders.get(2).setSupportedPrimitives(
+ VibrationEffect.Composition.PRIMITIVE_CLICK);
when(mManagerHooks.prepareSyncedVibration(anyLong(), eq(vibratorIds))).thenReturn(true);
when(mManagerHooks.triggerSyncedVibration(eq(vibrationId))).thenReturn(true);
@@ -939,6 +974,8 @@ public class VibrationThreadTest {
mockVibrators(vibratorIds);
mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
mVibratorProviders.get(4).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+ mVibratorProviders.get(4).setSupportedPrimitives(
+ VibrationEffect.Composition.PRIMITIVE_CLICK);
when(mManagerHooks.prepareSyncedVibration(anyLong(), any())).thenReturn(true);
when(mManagerHooks.triggerSyncedVibration(anyLong())).thenReturn(true);
@@ -1125,7 +1162,9 @@ public class VibrationThreadTest {
// fail at waitForCompletion(cancellingThread).
Thread cancellingThread = new Thread(
() -> conductor.notifyCancelled(
- Vibration.Status.CANCELLED_BY_USER, /* immediate= */ false));
+ new Vibration.EndInfo(
+ Vibration.Status.CANCELLED_BY_USER),
+ /* immediate= */ false));
cancellingThread.start();
// Cancelling the vibration should be fast and return right away, even if the thread is
@@ -1143,6 +1182,8 @@ public class VibrationThreadTest {
mockVibrators(1, 2);
mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+ mVibratorProviders.get(2).setSupportedPrimitives(
+ VibrationEffect.Composition.PRIMITIVE_CLICK);
long vibrationId = 1;
CombinedVibration effect = CombinedVibration.startParallel()
@@ -1163,13 +1204,15 @@ public class VibrationThreadTest {
// fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately.
Thread cancellingThread = new Thread(
() -> conductor.notifyCancelled(
- Vibration.Status.CANCELLED_SUPERSEDED, /* immediate= */ false));
+ new Vibration.EndInfo(
+ Vibration.Status.CANCELLED_BY_SCREEN_OFF),
+ /* immediate= */ false));
cancellingThread.start();
waitForCompletion(/* timeout= */ 50);
cancellingThread.join();
- verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_SUPERSEDED);
+ verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_SCREEN_OFF);
assertFalse(mControllers.get(1).isVibrating());
assertFalse(mControllers.get(2).isVibrating());
}
@@ -1195,9 +1238,11 @@ public class VibrationThreadTest {
// Run cancel in a separate thread so if VibrationThread.cancel blocks then this test should
// fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately.
- Thread cancellingThread =
- new Thread(() -> conductor.notifyCancelled(
- Vibration.Status.CANCELLED_BY_SCREEN_OFF, /* immediate= */ false));
+ Thread cancellingThread = new Thread(
+ () -> conductor.notifyCancelled(
+ new Vibration.EndInfo(
+ Vibration.Status.CANCELLED_BY_SCREEN_OFF),
+ /* immediate= */ false));
cancellingThread.start();
waitForCompletion(/* timeout= */ 50);
@@ -1266,7 +1311,7 @@ public class VibrationThreadTest {
// Vibration completed but vibrator not yet released.
verify(mManagerHooks, timeout(TEST_TIMEOUT_MILLIS)).onVibrationCompleted(eq(vibrationId),
- eq(Vibration.Status.FINISHED));
+ eq(new Vibration.EndInfo(Vibration.Status.FINISHED)));
verify(mManagerHooks, never()).onVibrationThreadReleased(anyLong());
// Thread still running ramp down.
@@ -1278,12 +1323,13 @@ public class VibrationThreadTest {
// Will stop the ramp down right away.
conductor.notifyCancelled(
- Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE, /* immediate= */ true);
+ new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE),
+ /* immediate= */ true);
waitForCompletion();
// Does not cancel already finished vibration, but releases vibrator.
verify(mManagerHooks, never()).onVibrationCompleted(eq(vibrationId),
- eq(Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE));
+ eq(new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE)));
verify(mManagerHooks).onVibrationThreadReleased(vibrationId);
}
@@ -1299,7 +1345,9 @@ public class VibrationThreadTest {
VibrationStepConductor conductor = startThreadAndDispatcher(vibrationId, effect);
assertTrue(waitUntil(() -> mControllers.get(VIBRATOR_ID).isVibrating(),
TEST_TIMEOUT_MILLIS));
- conductor.notifyCancelled(Vibration.Status.CANCELLED_BY_USER, /* immediate= */ false);
+ conductor.notifyCancelled(
+ new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER),
+ /* immediate= */ false);
waitForCompletion();
verifyCallbacksTriggered(vibrationId, Vibration.Status.CANCELLED_BY_USER);
@@ -1422,7 +1470,9 @@ public class VibrationThreadTest {
VibrationStepConductor conductor2 = startThreadAndDispatcher(vibrationId2, effect2);
// Effect2 won't complete on its own. Cancel it after a couple of repeats.
Thread.sleep(150); // More than two TICKs.
- conductor2.notifyCancelled(Vibration.Status.CANCELLED_BY_USER, /* immediate= */ false);
+ conductor2.notifyCancelled(
+ new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_USER),
+ /* immediate= */ false);
waitForCompletion();
startThreadAndDispatcher(vibrationId3, effect3);
@@ -1431,7 +1481,9 @@ public class VibrationThreadTest {
// Effect4 is a long oneshot, but it gets cancelled as fast as possible.
long start4 = System.currentTimeMillis();
VibrationStepConductor conductor4 = startThreadAndDispatcher(vibrationId4, effect4);
- conductor4.notifyCancelled(Vibration.Status.CANCELLED_SUPERSEDED, /* immediate= */ true);
+ conductor4.notifyCancelled(
+ new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SCREEN_OFF),
+ /* immediate= */ true);
waitForCompletion();
long duration4 = System.currentTimeMillis() - start4;
@@ -1469,7 +1521,7 @@ public class VibrationThreadTest {
fakeVibrator.getEffectSegments(vibrationId3));
// Effect4: cancelled quickly.
- verifyCallbacksTriggered(vibrationId4, Vibration.Status.CANCELLED_SUPERSEDED);
+ verifyCallbacksTriggered(vibrationId4, Vibration.Status.CANCELLED_BY_SCREEN_OFF);
assertTrue("Tested duration=" + duration4, duration4 < 2000);
// Effect5: normal oneshot. Don't worry about amplitude, as effect4 may or may not have
@@ -1580,7 +1632,11 @@ public class VibrationThreadTest {
}
private void verifyCallbacksTriggered(long vibrationId, Vibration.Status expectedStatus) {
- verify(mManagerHooks).onVibrationCompleted(eq(vibrationId), eq(expectedStatus));
+ verifyCallbacksTriggered(vibrationId, new Vibration.EndInfo(expectedStatus));
+ }
+
+ private void verifyCallbacksTriggered(long vibrationId, Vibration.EndInfo expectedEndInfo) {
+ verify(mManagerHooks).onVibrationCompleted(eq(vibrationId), eq(expectedEndInfo));
verify(mManagerHooks).onVibrationThreadReleased(vibrationId);
}
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorFrameworkStatsLoggerTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorFrameworkStatsLoggerTest.java
new file mode 100644
index 000000000000..c1ab1db2732e
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorFrameworkStatsLoggerTest.java
@@ -0,0 +1,127 @@
+/*
+ * 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.vibrator;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Handler;
+import android.os.test.TestLooper;
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+/**
+ * Tests for {@link VibratorFrameworkStatsLogger}.
+ *
+ * Build/Install/Run:
+ * atest FrameworksServicesTests:VibratorFrameworkStatsLoggerTest
+ */
+@Presubmit
+public class VibratorFrameworkStatsLoggerTest {
+
+ @Rule public MockitoRule rule = MockitoJUnit.rule();
+
+ private TestLooper mTestLooper;
+ private VibratorFrameworkStatsLogger mLogger;
+
+ @Before
+ public void setUp() {
+ mTestLooper = new TestLooper();
+ }
+
+ @Test
+ public void writeVibrationReportedAsync_afterMinInterval_writesRightAway() {
+ setUpLogger(/* minIntervalMillis= */ 10, /* queueMaxSize= */ 10);
+
+ VibrationStats.StatsInfo firstStats = newEmptyStatsInfo();
+ assertFalse(firstStats.isWritten());
+
+ mLogger.writeVibrationReportedAsync(firstStats);
+ mTestLooper.dispatchAll();
+ assertTrue(firstStats.isWritten());
+ }
+
+ @Test
+ public void writeVibrationReportedAsync_rightAfterLogging_schedulesToRunAfterRemainingDelay() {
+ setUpLogger(/* minIntervalMillis= */ 100, /* queueMaxSize= */ 10);
+
+ VibrationStats.StatsInfo firstStats = newEmptyStatsInfo();
+ VibrationStats.StatsInfo secondStats = newEmptyStatsInfo();
+ assertFalse(firstStats.isWritten());
+ assertFalse(secondStats.isWritten());
+
+ // Write first message at current SystemClock.uptimeMillis
+ mLogger.writeVibrationReportedAsync(firstStats);
+ mTestLooper.dispatchAll();
+ assertTrue(firstStats.isWritten());
+
+ // Second message is not written right away, it needs to wait the configured interval.
+ mLogger.writeVibrationReportedAsync(secondStats);
+ mTestLooper.dispatchAll();
+ assertFalse(secondStats.isWritten());
+
+ // Second message is written after delay passes.
+ mTestLooper.moveTimeForward(100);
+ mTestLooper.dispatchAll();
+ assertTrue(secondStats.isWritten());
+ }
+
+ @Test
+ public void writeVibrationReportedAsync_tooFast_logsUsingIntervalAndDropsMessagesFromQueue() {
+ setUpLogger(/* minIntervalMillis= */ 100, /* queueMaxSize= */ 2);
+
+ VibrationStats.StatsInfo firstStats = newEmptyStatsInfo();
+ VibrationStats.StatsInfo secondStats = newEmptyStatsInfo();
+ VibrationStats.StatsInfo thirdStats = newEmptyStatsInfo();
+
+ mLogger.writeVibrationReportedAsync(firstStats);
+ mLogger.writeVibrationReportedAsync(secondStats);
+ mLogger.writeVibrationReportedAsync(thirdStats);
+
+ // Only first message is logged.
+ mTestLooper.dispatchAll();
+ assertTrue(firstStats.isWritten());
+ assertFalse(secondStats.isWritten());
+ assertFalse(thirdStats.isWritten());
+
+ // Wait one interval to check only the second one is logged.
+ mTestLooper.moveTimeForward(100);
+ mTestLooper.dispatchAll();
+ assertTrue(secondStats.isWritten());
+ assertFalse(thirdStats.isWritten());
+
+ // Wait a long interval to check the third one was dropped and will never be logged.
+ mTestLooper.moveTimeForward(1_000);
+ mTestLooper.dispatchAll();
+ assertFalse(thirdStats.isWritten());
+ }
+
+ private void setUpLogger(int minIntervalMillis, int queueMaxSize) {
+ mLogger = new VibratorFrameworkStatsLogger(new Handler(mTestLooper.getLooper()),
+ minIntervalMillis, queueMaxSize);
+ }
+
+ private static VibrationStats.StatsInfo newEmptyStatsInfo() {
+ return new VibrationStats.StatsInfo(
+ 0, 0, 0, Vibration.Status.FINISHED, new VibrationStats(), 0L);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index 8a96febcd1e9..36bec750e3bc 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -34,6 +34,7 @@ import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -75,11 +76,13 @@ import android.os.vibrator.VibrationConfig;
import android.os.vibrator.VibrationEffectSegment;
import android.platform.test.annotations.Presubmit;
import android.provider.Settings;
+import android.util.SparseBooleanArray;
import android.view.InputDevice;
import androidx.test.InstrumentationRegistry;
import com.android.internal.app.IBatteryStats;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.internal.util.test.FakeSettingsProviderRule;
import com.android.server.LocalServices;
@@ -148,6 +151,8 @@ public class VibratorManagerServiceTest {
private IInputManager mIInputManagerMock;
@Mock
private IBatteryStats mBatteryStatsMock;
+ @Mock
+ private VibratorFrameworkStatsLogger mVibratorFrameworkStatsLoggerMock;
private final Map<Integer, FakeVibratorControllerProvider> mVibratorProviders = new HashMap<>();
@@ -233,6 +238,11 @@ public class VibratorManagerServiceTest {
}
@Override
+ VibratorFrameworkStatsLogger getFrameworkStatsLogger(Handler handler) {
+ return mVibratorFrameworkStatsLoggerMock;
+ }
+
+ @Override
VibratorController createVibratorController(int vibratorId,
VibratorController.OnVibrationCompleteListener listener) {
return mVibratorProviders.get(vibratorId)
@@ -806,11 +816,11 @@ public class VibratorManagerServiceTest {
service, TEST_TIMEOUT_MILLIS));
VibrationEffect repeatingEffect = VibrationEffect.createWaveform(
- new long[]{10_000, 10_000}, new int[]{128, 255}, 1);
+ new long[]{10, 10}, new int[]{128, 255}, 1);
vibrate(service, repeatingEffect, NOTIFICATION_ATTRS);
// VibrationThread will start this vibration async, so wait before checking it started.
- assertTrue(waitUntil(s -> mVibratorProviders.get(1).getAllEffectSegments().size() > 1,
+ assertTrue(waitUntil(s -> mVibratorProviders.get(1).getAllEffectSegments().size() > 2,
service, TEST_TIMEOUT_MILLIS));
// The second vibration should have recorded that the vibrators were turned on.
@@ -916,7 +926,11 @@ public class VibratorManagerServiceTest {
mockCapabilities(IVibratorManager.CAP_SYNC, IVibratorManager.CAP_PREPARE_COMPOSE);
mockVibrators(1, 2);
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+ mVibratorProviders.get(1).setSupportedPrimitives(
+ VibrationEffect.Composition.PRIMITIVE_CLICK);
mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+ mVibratorProviders.get(2).setSupportedPrimitives(
+ VibrationEffect.Composition.PRIMITIVE_CLICK);
// Mock alarm intensity equals to default value to avoid scaling in this test.
setUserSetting(Settings.System.ALARM_VIBRATION_INTENSITY,
mVibrator.getDefaultVibrationIntensity(VibrationAttributes.USAGE_ALARM));
@@ -1078,6 +1092,8 @@ public class VibratorManagerServiceTest {
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL,
IVibrator.CAP_COMPOSE_EFFECTS);
+ fakeVibrator.setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK,
+ VibrationEffect.Composition.PRIMITIVE_TICK);
VibratorManagerService service = createSystemReadyService();
vibrate(service, VibrationEffect.startComposition()
@@ -1380,6 +1396,373 @@ public class VibratorManagerServiceTest {
assertEquals(IExternalVibratorService.SCALE_MUTE, scale);
}
+ @Test
+ public void frameworkStats_externalVibration_reportsAllMetrics() throws Exception {
+ mockVibrators(1);
+ mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
+ createSystemReadyService();
+
+ AudioAttributes audioAttrs = new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_ALARM)
+ .build();
+
+ ExternalVibration vib = new ExternalVibration(UID, PACKAGE_NAME, audioAttrs,
+ mock(IExternalVibrationController.class));
+ mExternalVibratorService.onExternalVibrationStart(vib);
+
+ Thread.sleep(10);
+ mExternalVibratorService.onExternalVibrationStop(vib);
+
+ ArgumentCaptor<VibrationStats.StatsInfo> argumentCaptor =
+ ArgumentCaptor.forClass(VibrationStats.StatsInfo.class);
+ verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
+ .writeVibrationReportedAsync(argumentCaptor.capture());
+
+ VibrationStats.StatsInfo statsInfo = argumentCaptor.getValue();
+ assertEquals(UID, statsInfo.uid);
+ assertEquals(FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__EXTERNAL,
+ statsInfo.vibrationType);
+ assertEquals(VibrationAttributes.USAGE_ALARM, statsInfo.usage);
+ assertEquals(Vibration.Status.FINISHED.getProtoEnumValue(), statsInfo.status);
+ assertTrue(statsInfo.totalDurationMillis > 0);
+ assertTrue(
+ "Expected vibrator ON for at least 10ms, got " + statsInfo.vibratorOnMillis + "ms",
+ statsInfo.vibratorOnMillis >= 10);
+ assertEquals(2, statsInfo.halSetExternalControlCount);
+ }
+
+ @Test
+ public void frameworkStats_waveformVibration_reportsAllMetrics() throws Exception {
+ mockVibrators(1);
+ mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+
+ VibratorManagerService service = createSystemReadyService();
+ vibrateAndWaitUntilFinished(service,
+ VibrationEffect.createWaveform(new long[] {0, 10, 20, 10}, -1), RINGTONE_ATTRS);
+
+ verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
+ .writeVibratorStateOnAsync(eq(UID), anyLong());
+ verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
+ .writeVibratorStateOffAsync(eq(UID));
+
+ ArgumentCaptor<VibrationStats.StatsInfo> argumentCaptor =
+ ArgumentCaptor.forClass(VibrationStats.StatsInfo.class);
+ verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
+ .writeVibrationReportedAsync(argumentCaptor.capture());
+
+ VibrationStats.StatsInfo metrics = argumentCaptor.getValue();
+ assertEquals(UID, metrics.uid);
+ assertEquals(FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__SINGLE,
+ metrics.vibrationType);
+ assertEquals(VibrationAttributes.USAGE_RINGTONE, metrics.usage);
+ assertEquals(Vibration.Status.FINISHED.getProtoEnumValue(), metrics.status);
+ assertTrue("Total duration was too low, " + metrics.totalDurationMillis + "ms",
+ metrics.totalDurationMillis >= 20);
+ assertTrue("Vibrator ON duration was too low, " + metrics.vibratorOnMillis + "ms",
+ metrics.vibratorOnMillis >= 20);
+
+ // All unrelated metrics are empty.
+ assertEquals(0, metrics.repeatCount);
+ assertEquals(0, metrics.halComposeCount);
+ assertEquals(0, metrics.halComposePwleCount);
+ assertEquals(0, metrics.halPerformCount);
+ assertEquals(0, metrics.halSetExternalControlCount);
+ assertEquals(0, metrics.halCompositionSize);
+ assertEquals(0, metrics.halPwleSize);
+ assertNull(metrics.halSupportedCompositionPrimitivesUsed);
+ assertNull(metrics.halSupportedEffectsUsed);
+ assertNull(metrics.halUnsupportedCompositionPrimitivesUsed);
+ assertNull(metrics.halUnsupportedEffectsUsed);
+
+ // Accommodate for ramping off config that might add extra setAmplitudes.
+ assertEquals(2, metrics.halOnCount);
+ assertTrue(metrics.halOffCount > 0);
+ assertTrue(metrics.halSetAmplitudeCount >= 2);
+ }
+
+ @Test
+ public void frameworkStats_repeatingVibration_reportsAllMetrics() throws Exception {
+ mockVibrators(1);
+ mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+
+ VibratorManagerService service = createSystemReadyService();
+ vibrate(service, VibrationEffect.createWaveform(new long[] {10, 100}, 1), RINGTONE_ATTRS);
+
+ verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
+ .writeVibratorStateOnAsync(eq(UID), anyLong());
+
+ // Wait for at least one loop before cancelling it.
+ Thread.sleep(100);
+ service.cancelVibrate(VibrationAttributes.USAGE_RINGTONE, service);
+
+ verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
+ .writeVibratorStateOffAsync(eq(UID));
+
+ ArgumentCaptor<VibrationStats.StatsInfo> argumentCaptor =
+ ArgumentCaptor.forClass(VibrationStats.StatsInfo.class);
+ verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
+ .writeVibrationReportedAsync(argumentCaptor.capture());
+
+ VibrationStats.StatsInfo metrics = argumentCaptor.getValue();
+ assertEquals(UID, metrics.uid);
+ assertEquals(FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__REPEATED,
+ metrics.vibrationType);
+ assertEquals(VibrationAttributes.USAGE_RINGTONE, metrics.usage);
+ assertEquals(Vibration.Status.CANCELLED_BY_USER.getProtoEnumValue(), metrics.status);
+ assertTrue("Total duration was too low, " + metrics.totalDurationMillis + "ms",
+ metrics.totalDurationMillis >= 100);
+ assertTrue("Vibrator ON duration was too low, " + metrics.vibratorOnMillis + "ms",
+ metrics.vibratorOnMillis >= 100);
+
+ // All unrelated metrics are empty.
+ assertTrue(metrics.repeatCount > 0);
+ assertEquals(0, metrics.halComposeCount);
+ assertEquals(0, metrics.halComposePwleCount);
+ assertEquals(0, metrics.halPerformCount);
+ assertEquals(0, metrics.halSetExternalControlCount);
+ assertEquals(0, metrics.halCompositionSize);
+ assertEquals(0, metrics.halPwleSize);
+ assertNull(metrics.halSupportedCompositionPrimitivesUsed);
+ assertNull(metrics.halSupportedEffectsUsed);
+ assertNull(metrics.halUnsupportedCompositionPrimitivesUsed);
+ assertNull(metrics.halUnsupportedEffectsUsed);
+
+ // Accommodate for ramping off config that might add extra setAmplitudes.
+ assertTrue(metrics.halOnCount > 0);
+ assertTrue(metrics.halOffCount > 0);
+ assertTrue(metrics.halSetAmplitudeCount > 0);
+ }
+
+ @Test
+ public void frameworkStats_prebakedAndComposedVibrations_reportsAllMetrics() throws Exception {
+ mockVibrators(1);
+ mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+ mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+ mVibratorProviders.get(1).setSupportedPrimitives(
+ VibrationEffect.Composition.PRIMITIVE_TICK);
+
+ VibratorManagerService service = createSystemReadyService();
+ vibrateAndWaitUntilFinished(service,
+ VibrationEffect.startComposition()
+ .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK))
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK)
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK)
+ .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK))
+ .addEffect(VibrationEffect.createPredefined(VibrationEffect.EFFECT_TICK))
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+ .compose(),
+ ALARM_ATTRS);
+
+ verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
+ .writeVibratorStateOnAsync(eq(UID), anyLong());
+ verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
+ .writeVibratorStateOffAsync(eq(UID));
+
+ ArgumentCaptor<VibrationStats.StatsInfo> argumentCaptor =
+ ArgumentCaptor.forClass(VibrationStats.StatsInfo.class);
+ verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
+ .writeVibrationReportedAsync(argumentCaptor.capture());
+
+ VibrationStats.StatsInfo metrics = argumentCaptor.getValue();
+ assertEquals(UID, metrics.uid);
+ assertEquals(FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__SINGLE,
+ metrics.vibrationType);
+ assertEquals(VibrationAttributes.USAGE_ALARM, metrics.usage);
+ assertEquals(Vibration.Status.FINISHED.getProtoEnumValue(), metrics.status);
+
+ // At least 4 effect/primitive played, 20ms each, plus configured fallback.
+ assertTrue("Total duration was too low, " + metrics.totalDurationMillis + "ms",
+ metrics.totalDurationMillis >= 80);
+ assertTrue("Vibrator ON duration was too low, " + metrics.vibratorOnMillis + "ms",
+ metrics.vibratorOnMillis >= 80);
+
+ // Related metrics were collected.
+ assertEquals(2, metrics.halComposeCount); // TICK+TICK, then CLICK+CLICK
+ assertEquals(3, metrics.halPerformCount); // CLICK, TICK, then CLICK
+ assertEquals(4, metrics.halCompositionSize); // 2*TICK + 2*CLICK
+ // No repetitions in reported effect/primitive IDs.
+ assertArrayEquals(new int[] {VibrationEffect.Composition.PRIMITIVE_TICK},
+ metrics.halSupportedCompositionPrimitivesUsed);
+ assertArrayEquals(new int[] {VibrationEffect.Composition.PRIMITIVE_CLICK},
+ metrics.halUnsupportedCompositionPrimitivesUsed);
+ assertArrayEquals(new int[] {VibrationEffect.EFFECT_CLICK},
+ metrics.halSupportedEffectsUsed);
+ assertArrayEquals(new int[] {VibrationEffect.EFFECT_TICK},
+ metrics.halUnsupportedEffectsUsed);
+
+ // All unrelated metrics are empty.
+ assertEquals(0, metrics.repeatCount);
+ assertEquals(0, metrics.halComposePwleCount);
+ assertEquals(0, metrics.halSetExternalControlCount);
+ assertEquals(0, metrics.halPwleSize);
+
+ // Accommodate for ramping off config that might add extra setAmplitudes
+ // for the effect that plays the fallback instead of "perform".
+ assertTrue(metrics.halOnCount > 0);
+ assertTrue(metrics.halOffCount > 0);
+ assertTrue(metrics.halSetAmplitudeCount > 0);
+ }
+
+ @Test
+ public void frameworkStats_interruptingVibrations_reportsAllMetrics() throws Exception {
+ mockVibrators(1);
+ VibratorManagerService service = createSystemReadyService();
+
+ vibrate(service, VibrationEffect.createOneShot(1_000, 128), HAPTIC_FEEDBACK_ATTRS);
+
+ // VibrationThread will start this vibration async, so wait until vibration is triggered.
+ assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getAllEffectSegments().isEmpty(),
+ service, TEST_TIMEOUT_MILLIS));
+
+ vibrateAndWaitUntilFinished(service, VibrationEffect.createOneShot(10, 255), ALARM_ATTRS);
+
+ ArgumentCaptor<VibrationStats.StatsInfo> argumentCaptor =
+ ArgumentCaptor.forClass(VibrationStats.StatsInfo.class);
+ verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS).times(2))
+ .writeVibrationReportedAsync(argumentCaptor.capture());
+
+ VibrationStats.StatsInfo touchMetrics = argumentCaptor.getAllValues().get(0);
+ assertEquals(UID, touchMetrics.uid);
+ assertEquals(VibrationAttributes.USAGE_TOUCH, touchMetrics.usage);
+ assertEquals(Vibration.Status.CANCELLED_SUPERSEDED.getProtoEnumValue(),
+ touchMetrics.status);
+ assertTrue(touchMetrics.endedBySameUid);
+ assertEquals(VibrationAttributes.USAGE_ALARM, touchMetrics.endedByUsage);
+ assertEquals(-1, touchMetrics.interruptedUsage);
+
+ VibrationStats.StatsInfo alarmMetrics = argumentCaptor.getAllValues().get(1);
+ assertEquals(UID, alarmMetrics.uid);
+ assertEquals(VibrationAttributes.USAGE_ALARM, alarmMetrics.usage);
+ assertEquals(Vibration.Status.FINISHED.getProtoEnumValue(), alarmMetrics.status);
+ assertFalse(alarmMetrics.endedBySameUid);
+ assertEquals(-1, alarmMetrics.endedByUsage);
+ assertEquals(VibrationAttributes.USAGE_TOUCH, alarmMetrics.interruptedUsage);
+ }
+
+ @Test
+ public void frameworkStats_ignoredVibration_reportsStatus() throws Exception {
+ setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
+ Vibrator.VIBRATION_INTENSITY_OFF);
+
+ mockVibrators(1);
+ VibratorManagerService service = createSystemReadyService();
+ mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
+
+ // Haptic feedback ignored in low power state
+ vibrateAndWaitUntilFinished(service, VibrationEffect.createOneShot(100, 128),
+ HAPTIC_FEEDBACK_ATTRS);
+ // Ringtone vibration user settings are off
+ vibrateAndWaitUntilFinished(service, VibrationEffect.createOneShot(200, 128),
+ RINGTONE_ATTRS);
+
+ ArgumentCaptor<VibrationStats.StatsInfo> argumentCaptor =
+ ArgumentCaptor.forClass(VibrationStats.StatsInfo.class);
+ verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS).times(2))
+ .writeVibrationReportedAsync(argumentCaptor.capture());
+
+ VibrationStats.StatsInfo touchMetrics = argumentCaptor.getAllValues().get(0);
+ assertEquals(UID, touchMetrics.uid);
+ assertEquals(VibrationAttributes.USAGE_TOUCH, touchMetrics.usage);
+ assertEquals(Vibration.Status.IGNORED_FOR_POWER.getProtoEnumValue(), touchMetrics.status);
+
+ VibrationStats.StatsInfo ringtoneMetrics = argumentCaptor.getAllValues().get(1);
+ assertEquals(UID, ringtoneMetrics.uid);
+ assertEquals(VibrationAttributes.USAGE_RINGTONE, ringtoneMetrics.usage);
+ assertEquals(Vibration.Status.IGNORED_FOR_SETTINGS.getProtoEnumValue(),
+ ringtoneMetrics.status);
+
+ for (VibrationStats.StatsInfo metrics : argumentCaptor.getAllValues()) {
+ // Latencies are empty since vibrations never started
+ assertEquals(0, metrics.startLatencyMillis);
+ assertEquals(0, metrics.endLatencyMillis);
+ assertEquals(0, metrics.vibratorOnMillis);
+
+ // All unrelated metrics are empty.
+ assertEquals(0, metrics.repeatCount);
+ assertEquals(0, metrics.halComposeCount);
+ assertEquals(0, metrics.halComposePwleCount);
+ assertEquals(0, metrics.halOffCount);
+ assertEquals(0, metrics.halOnCount);
+ assertEquals(0, metrics.halPerformCount);
+ assertEquals(0, metrics.halSetExternalControlCount);
+ assertEquals(0, metrics.halCompositionSize);
+ assertEquals(0, metrics.halPwleSize);
+ assertNull(metrics.halSupportedCompositionPrimitivesUsed);
+ assertNull(metrics.halSupportedEffectsUsed);
+ assertNull(metrics.halUnsupportedCompositionPrimitivesUsed);
+ assertNull(metrics.halUnsupportedEffectsUsed);
+ }
+ }
+
+ @Test
+ public void frameworkStats_multiVibrators_reportsAllMetrics() throws Exception {
+ mockVibrators(1, 2);
+ mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+ mVibratorProviders.get(1).setSupportedPrimitives(
+ VibrationEffect.Composition.PRIMITIVE_TICK);
+ mVibratorProviders.get(2).setSupportedEffects(VibrationEffect.EFFECT_TICK);
+
+ VibratorManagerService service = createSystemReadyService();
+ vibrateAndWaitUntilFinished(service,
+ CombinedVibration.startParallel()
+ .addVibrator(1,
+ VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK)
+ .compose())
+ .addVibrator(2,
+ VibrationEffect.createPredefined(VibrationEffect.EFFECT_TICK))
+ .combine(),
+ NOTIFICATION_ATTRS);
+
+ SparseBooleanArray expectedEffectsUsed = new SparseBooleanArray();
+ expectedEffectsUsed.put(VibrationEffect.EFFECT_TICK, true);
+
+ SparseBooleanArray expectedPrimitivesUsed = new SparseBooleanArray();
+ expectedPrimitivesUsed.put(VibrationEffect.Composition.PRIMITIVE_TICK, true);
+
+ verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
+ .writeVibratorStateOnAsync(eq(UID), anyLong());
+ verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
+ .writeVibratorStateOffAsync(eq(UID));
+
+ ArgumentCaptor<VibrationStats.StatsInfo> argumentCaptor =
+ ArgumentCaptor.forClass(VibrationStats.StatsInfo.class);
+ verify(mVibratorFrameworkStatsLoggerMock, timeout(TEST_TIMEOUT_MILLIS))
+ .writeVibrationReportedAsync(argumentCaptor.capture());
+
+ VibrationStats.StatsInfo metrics = argumentCaptor.getValue();
+ assertEquals(UID, metrics.uid);
+ assertEquals(FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__SINGLE,
+ metrics.vibrationType);
+ assertEquals(VibrationAttributes.USAGE_NOTIFICATION, metrics.usage);
+ assertEquals(Vibration.Status.FINISHED.getProtoEnumValue(), metrics.status);
+ assertTrue(metrics.totalDurationMillis >= 20);
+
+ // vibratorOnMillis accumulates both vibrators, it's 20 for each constant.
+ assertEquals(40, metrics.vibratorOnMillis);
+
+ // Related metrics were collected.
+ assertEquals(1, metrics.halComposeCount);
+ assertEquals(1, metrics.halPerformCount);
+ assertEquals(1, metrics.halCompositionSize);
+ assertEquals(2, metrics.halOffCount);
+ assertArrayEquals(new int[] {VibrationEffect.Composition.PRIMITIVE_TICK},
+ metrics.halSupportedCompositionPrimitivesUsed);
+ assertArrayEquals(new int[] {VibrationEffect.EFFECT_TICK},
+ metrics.halSupportedEffectsUsed);
+
+ // All unrelated metrics are empty.
+ assertEquals(0, metrics.repeatCount);
+ assertEquals(0, metrics.halComposePwleCount);
+ assertEquals(0, metrics.halOnCount);
+ assertEquals(0, metrics.halSetAmplitudeCount);
+ assertEquals(0, metrics.halSetExternalControlCount);
+ assertEquals(0, metrics.halPwleSize);
+ assertNull(metrics.halUnsupportedCompositionPrimitivesUsed);
+ assertNull(metrics.halUnsupportedEffectsUsed);
+ }
+
private VibrationEffectSegment expectedPrebaked(int effectId) {
return expectedPrebaked(effectId, VibrationEffect.EFFECT_STRENGTH_MEDIUM);
}
@@ -1429,6 +1812,20 @@ public class VibratorManagerServiceTest {
mContextSpy.getContentResolver(), settingName, value, UserHandle.USER_CURRENT);
}
+ private void vibrateAndWaitUntilFinished(VibratorManagerService service, VibrationEffect effect,
+ VibrationAttributes attrs) throws InterruptedException {
+ vibrateAndWaitUntilFinished(service, CombinedVibration.createParallel(effect), attrs);
+ }
+
+ private void vibrateAndWaitUntilFinished(VibratorManagerService service,
+ CombinedVibration effect, VibrationAttributes attrs) throws InterruptedException {
+ Vibration vib =
+ service.vibrateInternal(UID, PACKAGE_NAME, effect, attrs, "some reason", service);
+ if (vib != null) {
+ vib.waitForEnd();
+ }
+ }
+
private void vibrate(VibratorManagerService service, VibrationEffect effect,
VibrationAttributes attrs) {
vibrate(service, CombinedVibration.createParallel(effect), attrs);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
index 911fb6a87e96..08c2c6e6f26e 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
@@ -1301,6 +1301,21 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase {
}
@Test
+ public void testA11yCrossUserEventNotSent() throws Exception {
+ final Notification n = new Builder(getContext(), "test")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon).build();
+ int userId = mUser.getIdentifier() + 1;
+ StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 0, mTag, mUid,
+ mPid, n, UserHandle.of(userId), null, System.currentTimeMillis());
+ NotificationRecord r = new NotificationRecord(getContext(), sbn,
+ new NotificationChannel("test", "test", IMPORTANCE_HIGH));
+
+ mService.buzzBeepBlinkLocked(r);
+
+ verify(mAccessibilityService, never()).sendAccessibilityEvent(any(), anyInt());
+ }
+
+ @Test
public void testLightsScreenOn() {
mService.mScreenOn = true;
NotificationRecord r = getLightsNotification();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 44ca9f432a46..40cda34f1272 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -150,6 +150,7 @@ import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.drawable.Icon;
import android.media.AudioManager;
+import android.media.IRingtonePlayer;
import android.media.session.MediaSession;
import android.net.Uri;
import android.os.Binder;
@@ -7738,6 +7739,34 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ public void testOnBubbleMetadataChangedToSuppressNotification_soundStopped()
+ throws RemoteException {
+ IRingtonePlayer mockPlayer = mock(IRingtonePlayer.class);
+ when(mAudioManager.getRingtonePlayer()).thenReturn(mockPlayer);
+ // Set up volume to be above 0 for the sound to actually play
+ when(mAudioManager.getStreamVolume(anyInt())).thenReturn(10);
+
+ setUpPrefsForBubbles(PKG, mUid,
+ true /* global */,
+ BUBBLE_PREFERENCE_ALL /* app */,
+ true /* channel */);
+
+ // Post a bubble notification
+ NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel, "tag");
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(),
+ nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
+ waitForIdle();
+
+ // Test: suppress notification via bubble metadata update
+ mService.mNotificationDelegate.onBubbleMetadataFlagChanged(nr.getKey(),
+ Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION);
+ waitForIdle();
+
+ // Check audio is stopped
+ verify(mockPlayer).stopAsync();
+ }
+
+ @Test
public void testGrantInlineReplyUriPermission_recordExists() throws Exception {
int userId = UserManager.isHeadlessSystemUserMode()
? UserHandle.getUserId(UID_HEADLESS)
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ReviewNotificationPermissionsJobServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ReviewNotificationPermissionsJobServiceTest.java
index 5a4ce5da676e..3a6c0eb8fc2a 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ReviewNotificationPermissionsJobServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ReviewNotificationPermissionsJobServiceTest.java
@@ -20,10 +20,8 @@ import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
import android.app.job.JobInfo;
import android.app.job.JobParameters;
@@ -75,9 +73,6 @@ public class ReviewNotificationPermissionsJobServiceTest extends UiServiceTestCa
@Test
public void testScheduleJob() {
- // if asked, the job doesn't currently exist yet
- when(mMockJobScheduler.getPendingJob(anyInt())).thenReturn(null);
-
final int rescheduleTimeMillis = 350; // arbitrary number
// attempt to schedule the job
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java
index 8ac729e29424..c7905a0e8056 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeFilteringTest.java
@@ -8,7 +8,7 @@
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distriZenbuted on an "AS IS" BASIS,
+ * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
@@ -397,10 +397,10 @@ public class ZenModeFilteringTest extends UiServiceTestCase {
Bundle inputWrong = makeExtrasBundleWithPeople(new String[]{"mailto:nope"});
assertTrue(ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
policy, UserHandle.SYSTEM,
- inputMatches, null, 0, 0));
+ inputMatches, null, 0, 0, 0));
assertFalse(ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
policy, UserHandle.SYSTEM,
- inputWrong, null, 0, 0));
+ inputWrong, null, 0, 0, 0));
}
@Test
@@ -428,19 +428,19 @@ public class ZenModeFilteringTest extends UiServiceTestCase {
assertTrue("identical numbers should match",
ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
policy, UserHandle.SYSTEM,
- identical, null, 0, 0));
+ identical, null, 0, 0, 0));
assertTrue("equivalent but non-identical numbers should match",
ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
policy, UserHandle.SYSTEM,
- same, null, 0, 0));
+ same, null, 0, 0, 0));
assertFalse("non-equivalent numbers should not match",
ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
policy, UserHandle.SYSTEM,
- different, null, 0, 0));
+ different, null, 0, 0, 0));
assertFalse("non-tel strings should not match",
ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
policy, UserHandle.SYSTEM,
- garbage, null, 0, 0));
+ garbage, null, 0, 0, 0));
}
@Test
@@ -469,23 +469,23 @@ public class ZenModeFilteringTest extends UiServiceTestCase {
assertTrue("same number 1 should match",
ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
policy, UserHandle.SYSTEM,
- same1, null, 0, 0));
+ same1, null, 0, 0, 0));
assertTrue("same number 2 should match",
ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
policy, UserHandle.SYSTEM,
- same2, null, 0, 0));
+ same2, null, 0, 0, 0));
assertTrue("same number 3 should match",
ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
policy, UserHandle.SYSTEM,
- same3, null, 0, 0));
+ same3, null, 0, 0, 0));
assertFalse("different number 1 should not match",
ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
policy, UserHandle.SYSTEM,
- different1, null, 0, 0));
+ different1, null, 0, 0, 0));
assertFalse("different number 2 should not match",
ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
policy, UserHandle.SYSTEM,
- different2, null, 0, 0));
+ different2, null, 0, 0, 0));
}
@Test
@@ -516,14 +516,14 @@ public class ZenModeFilteringTest extends UiServiceTestCase {
assertTrue("contact number 1 should match",
ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
policy, UserHandle.SYSTEM,
- tel1, null, 0, 0));
+ tel1, null, 0, 0, 0));
assertTrue("contact number 2 should match",
ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
policy, UserHandle.SYSTEM,
- tel2, null, 0, 0));
+ tel2, null, 0, 0, 0));
assertFalse("different number should not match",
ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
policy, UserHandle.SYSTEM,
- different, null, 0, 0));
+ different, null, 0, 0, 0));
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 5146616a5cab..15d1a3c48ccd 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -2314,6 +2314,8 @@ public class ActivityRecordTests extends WindowTestsBase {
assertEquals(launchCookie, activity2.mLaunchCookie);
assertNull(activity1.mLaunchCookie);
+ activity2.makeFinishingLocked();
+ assertTrue(activity1.getTask().getTaskInfo().launchCookies.contains(launchCookie));
}
private void verifyProcessInfoUpdate(ActivityRecord activity, State state,
@@ -2464,6 +2466,7 @@ public class ActivityRecordTests extends WindowTestsBase {
activity.addWindow(appWindow);
spyOn(appWindow);
doNothing().when(appWindow).onStartFreezingScreen();
+ doNothing().when(mWm).startFreezingDisplay(anyInt(), anyInt(), any(), anyInt());
// Set initial orientation and update.
performRotation(displayRotation, Surface.ROTATION_90);
@@ -2472,8 +2475,6 @@ public class ActivityRecordTests extends WindowTestsBase {
// Update the rotation to perform 180 degree rotation and check that resize was reported.
performRotation(displayRotation, Surface.ROTATION_270);
assertTrue(appWindow.mResizeReported);
-
- appWindow.removeImmediately();
}
private void performRotation(DisplayRotation spiedRotation, int rotationToReport) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
index 74154609b22e..f61effa284ef 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
@@ -474,7 +474,7 @@ public class AppTransitionTests extends WindowTestsBase {
}
@Test
- public void testActivityRecordReparentToTaskFragment() {
+ public void testActivityRecordReparentedToTaskFragment() {
final ActivityRecord activity = createActivityRecord(mDc);
final SurfaceControl activityLeash = mock(SurfaceControl.class);
doNothing().when(activity).setDropInputMode(anyInt());
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 28d2aa157e0a..7a73f082c25e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1832,19 +1832,18 @@ public class DisplayContentTests extends WindowTestsBase {
@Test
public void testRemoteRotation() {
- DisplayContent dc = createNewDisplay();
-
+ final DisplayContent dc = mDisplayContent;
final DisplayRotation dr = dc.getDisplayRotation();
- doCallRealMethod().when(dr).updateRotationUnchecked(anyBoolean());
+ spyOn(dr);
// Rotate 180 degree so the display doesn't have configuration change. This condition is
// used for the later verification of stop-freezing (without setting mWaitingForConfig).
doReturn((dr.getRotation() + 2) % 4).when(dr).rotationForOrientation(anyInt(), anyInt());
final boolean[] continued = new boolean[1];
- doAnswer(
- invocation -> {
- continued[0] = true;
- return true;
- }).when(dc).updateDisplayOverrideConfigurationLocked();
+ doAnswer(invocation -> {
+ continued[0] = true;
+ mAtm.addWindowLayoutReasons(ActivityTaskManagerService.LAYOUT_REASON_CONFIG_CHANGED);
+ return true;
+ }).when(dc).updateDisplayOverrideConfigurationLocked();
final boolean[] called = new boolean[1];
mWm.mDisplayChangeController =
new IDisplayChangeWindowController.Stub() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
index 8546763aebec..88e58eab58aa 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
@@ -281,7 +281,7 @@ public class RecentsAnimationControllerTest extends WindowTestsBase {
verify(mMockRunner).onAnimationCanceled(null /* taskIds */, null /* taskSnapshots */);
// Simulate the app transition finishing
- mController.mAppTransitionListener.onAppTransitionStartingLocked(false, false, 0, 0, 0);
+ mController.mAppTransitionListener.onAppTransitionStartingLocked(0, 0);
verify(mAnimationCallbacks).onAnimationFinished(REORDER_KEEP_IN_PLACE, false);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index 8b3cff8039d4..da72030b313d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -158,7 +158,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
mController.onTaskFragmentAppeared(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
mController.dispatchPendingEvents();
- verify(mOrganizer, never()).onTaskFragmentAppeared(any());
+ verify(mOrganizer, never()).onTaskFragmentAppeared(any(), any());
// Send callback when the TaskFragment is attached.
setupMockParent(mTaskFragment, mTask);
@@ -166,7 +166,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
mController.onTaskFragmentAppeared(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
mController.dispatchPendingEvents();
- verify(mOrganizer).onTaskFragmentAppeared(any());
+ verify(mOrganizer).onTaskFragmentAppeared(any(), any());
}
@Test
@@ -179,13 +179,13 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
mTaskFragment);
mController.dispatchPendingEvents();
- verify(mOrganizer, never()).onTaskFragmentInfoChanged(any());
+ verify(mOrganizer, never()).onTaskFragmentInfoChanged(any(), any());
// Call onTaskFragmentAppeared first.
mController.onTaskFragmentAppeared(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
mController.dispatchPendingEvents();
- verify(mOrganizer).onTaskFragmentAppeared(any());
+ verify(mOrganizer).onTaskFragmentAppeared(any(), any());
// No callback if the info is not changed.
doReturn(true).when(mTaskFragmentInfo).equalsForTaskFragmentOrganizer(any());
@@ -195,7 +195,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
mTaskFragment);
mController.dispatchPendingEvents();
- verify(mOrganizer, never()).onTaskFragmentInfoChanged(any());
+ verify(mOrganizer, never()).onTaskFragmentInfoChanged(any(), any());
// Trigger callback if the info is changed.
doReturn(false).when(mTaskFragmentInfo).equalsForTaskFragmentOrganizer(any());
@@ -204,7 +204,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
mTaskFragment);
mController.dispatchPendingEvents();
- verify(mOrganizer).onTaskFragmentInfoChanged(mTaskFragmentInfo);
+ verify(mOrganizer).onTaskFragmentInfoChanged(any(), eq(mTaskFragmentInfo));
}
@Test
@@ -215,7 +215,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
mController.onTaskFragmentVanished(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
mController.dispatchPendingEvents();
- verify(mOrganizer).onTaskFragmentVanished(any());
+ verify(mOrganizer).onTaskFragmentVanished(any(), any());
}
@Test
@@ -228,10 +228,10 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
mController.onTaskFragmentVanished(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
mController.dispatchPendingEvents();
- verify(mOrganizer, never()).onTaskFragmentAppeared(any());
- verify(mOrganizer, never()).onTaskFragmentInfoChanged(any());
- verify(mOrganizer, never()).onTaskFragmentParentInfoChanged(anyInt(), any());
- verify(mOrganizer).onTaskFragmentVanished(mTaskFragmentInfo);
+ verify(mOrganizer, never()).onTaskFragmentAppeared(any(), any());
+ verify(mOrganizer, never()).onTaskFragmentInfoChanged(any(), any());
+ verify(mOrganizer, never()).onTaskFragmentParentInfoChanged(any(), anyInt(), any());
+ verify(mOrganizer).onTaskFragmentVanished(any(), eq(mTaskFragmentInfo));
// Not trigger onTaskFragmentInfoChanged.
// Call onTaskFragmentAppeared before calling onTaskFragmentInfoChanged.
@@ -244,10 +244,10 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
mController.onTaskFragmentVanished(mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
mController.dispatchPendingEvents();
- verify(mOrganizer, never()).onTaskFragmentAppeared(any());
- verify(mOrganizer, never()).onTaskFragmentInfoChanged(any());
- verify(mOrganizer, never()).onTaskFragmentParentInfoChanged(anyInt(), any());
- verify(mOrganizer).onTaskFragmentVanished(mTaskFragmentInfo);
+ verify(mOrganizer, never()).onTaskFragmentAppeared(any(), any());
+ verify(mOrganizer, never()).onTaskFragmentInfoChanged(any(), any());
+ verify(mOrganizer, never()).onTaskFragmentParentInfoChanged(any(), anyInt(), any());
+ verify(mOrganizer).onTaskFragmentVanished(any(), eq(mTaskFragmentInfo));
}
@Test
@@ -260,7 +260,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
mController.dispatchPendingEvents();
- verify(mOrganizer).onTaskFragmentParentInfoChanged(eq(mTask.mTaskId), any());
+ verify(mOrganizer).onTaskFragmentParentInfoChanged(any(), eq(mTask.mTaskId), any());
// No extra callback if the info is not changed.
clearInvocations(mOrganizer);
@@ -269,7 +269,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
mController.dispatchPendingEvents();
- verify(mOrganizer, never()).onTaskFragmentParentInfoChanged(anyInt(), any());
+ verify(mOrganizer, never()).onTaskFragmentParentInfoChanged(any(), anyInt(), any());
// Trigger callback if the size is changed.
mTask.getConfiguration().smallestScreenWidthDp = 100;
@@ -277,7 +277,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
mController.dispatchPendingEvents();
- verify(mOrganizer).onTaskFragmentParentInfoChanged(eq(mTask.mTaskId), any());
+ verify(mOrganizer).onTaskFragmentParentInfoChanged(any(), eq(mTask.mTaskId), any());
// Trigger callback if the windowing mode is changed.
clearInvocations(mOrganizer);
@@ -286,7 +286,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
mController.dispatchPendingEvents();
- verify(mOrganizer).onTaskFragmentParentInfoChanged(eq(mTask.mTaskId), any());
+ verify(mOrganizer).onTaskFragmentParentInfoChanged(any(), eq(mTask.mTaskId), any());
}
@Test
@@ -298,11 +298,12 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
mErrorToken, null /* taskFragment */, -1 /* opType */, exception);
mController.dispatchPendingEvents();
- verify(mOrganizer).onTaskFragmentError(eq(mErrorToken), eq(null), eq(-1), eq(exception));
+ verify(mOrganizer).onTaskFragmentError(any(), eq(mErrorToken), eq(null), eq(-1),
+ eq(exception));
}
@Test
- public void testOnActivityReparentToTask_activityInOrganizerProcess_useActivityToken() {
+ public void testOnActivityReparentedToTask_activityInOrganizerProcess_useActivityToken() {
// Make sure the activity pid/uid is the same as the organizer caller.
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
@@ -314,17 +315,18 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
task.effectiveUid = uid;
// No need to notify organizer if it is not embedded.
- mController.onActivityReparentToTask(activity);
+ mController.onActivityReparentedToTask(activity);
mController.dispatchPendingEvents();
- verify(mOrganizer, never()).onActivityReparentToTask(anyInt(), any(), any());
+ verify(mOrganizer, never()).onActivityReparentedToTask(any(), anyInt(), any(), any());
// Notify organizer if it was embedded before entered Pip.
activity.mLastTaskFragmentOrganizerBeforePip = mIOrganizer;
- mController.onActivityReparentToTask(activity);
+ mController.onActivityReparentedToTask(activity);
mController.dispatchPendingEvents();
- verify(mOrganizer).onActivityReparentToTask(task.mTaskId, activity.intent, activity.token);
+ verify(mOrganizer).onActivityReparentedToTask(any(), eq(task.mTaskId), eq(activity.intent),
+ eq(activity.token));
// Notify organizer if there is any embedded in the Task.
final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
@@ -335,15 +337,16 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
DEFAULT_TASK_FRAGMENT_ORGANIZER_PROCESS_NAME);
activity.reparent(taskFragment, POSITION_TOP);
activity.mLastTaskFragmentOrganizerBeforePip = null;
- mController.onActivityReparentToTask(activity);
+ mController.onActivityReparentedToTask(activity);
mController.dispatchPendingEvents();
verify(mOrganizer, times(2))
- .onActivityReparentToTask(task.mTaskId, activity.intent, activity.token);
+ .onActivityReparentedToTask(any(), eq(task.mTaskId), eq(activity.intent),
+ eq(activity.token));
}
@Test
- public void testOnActivityReparentToTask_activityNotInOrganizerProcess_useTemporaryToken() {
+ public void testOnActivityReparentedToTask_activityNotInOrganizerProcess_useTemporaryToken() {
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
mTaskFragment.setTaskFragmentOrganizer(mOrganizer.getOrganizerToken(), uid,
@@ -364,11 +367,11 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
// Notify organizer if it was embedded before entered Pip.
// Create a temporary token since the activity doesn't belong to the same process.
activity.mLastTaskFragmentOrganizerBeforePip = mIOrganizer;
- mController.onActivityReparentToTask(activity);
+ mController.onActivityReparentedToTask(activity);
mController.dispatchPendingEvents();
// Allow organizer to reparent activity in other process using the temporary token.
- verify(mOrganizer).onActivityReparentToTask(eq(task.mTaskId), eq(activity.intent),
+ verify(mOrganizer).onActivityReparentedToTask(any(), eq(task.mTaskId), eq(activity.intent),
token.capture());
final IBinder temporaryToken = token.getValue();
assertNotEquals(activity.token, temporaryToken);
@@ -798,7 +801,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
mController.dispatchPendingEvents();
// Verifies that event was not sent
- verify(mOrganizer, never()).onTaskFragmentInfoChanged(any());
+ verify(mOrganizer, never()).onTaskFragmentInfoChanged(any(), any());
}
@Test
@@ -824,7 +827,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
mController.dispatchPendingEvents();
// Verifies that event was not sent
- verify(mOrganizer, never()).onTaskFragmentInfoChanged(any());
+ verify(mOrganizer, never()).onTaskFragmentInfoChanged(any(), any());
// Mock the task becomes visible, and activity resumed
doReturn(true).when(task).shouldBeVisible(any());
@@ -832,7 +835,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
// Verifies that event is sent.
mController.dispatchPendingEvents();
- verify(mOrganizer).onTaskFragmentInfoChanged(any());
+ verify(mOrganizer).onTaskFragmentInfoChanged(any(), any());
}
/**
@@ -866,7 +869,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
reset(mOrganizer);
mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment);
mController.dispatchPendingEvents();
- verify(mOrganizer).onTaskFragmentInfoChanged(any());
+ verify(mOrganizer).onTaskFragmentInfoChanged(any(), any());
}
/**
@@ -884,8 +887,8 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
.createActivityCount(1)
.build();
final ActivityRecord embeddedActivity = taskFragment.getTopNonFinishingActivity();
- // Add another activity in the Task so that it always contains a non-finishing activitiy.
- final ActivityRecord nonEmbeddedActivity = createActivityRecord(task);
+ // Add another activity in the Task so that it always contains a non-finishing activity.
+ createActivityRecord(task);
assertTrue(task.shouldBeVisible(null));
// Dispatch pending info changed event from creating the activity
@@ -893,21 +896,21 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
taskFragment.mTaskFragmentAppearedSent = true;
mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment);
mController.dispatchPendingEvents();
- verify(mOrganizer).onTaskFragmentInfoChanged(any());
+ verify(mOrganizer).onTaskFragmentInfoChanged(any(), any());
// Verify the info changed callback is not called when the task is invisible
reset(mOrganizer);
doReturn(false).when(task).shouldBeVisible(any());
mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment);
mController.dispatchPendingEvents();
- verify(mOrganizer, never()).onTaskFragmentInfoChanged(any());
+ verify(mOrganizer, never()).onTaskFragmentInfoChanged(any(), any());
// Finish the embedded activity, and verify the info changed callback is called because the
// TaskFragment is becoming empty.
embeddedActivity.finishing = true;
mController.onTaskFragmentInfoChanged(mIOrganizer, taskFragment);
mController.dispatchPendingEvents();
- verify(mOrganizer).onTaskFragmentInfoChanged(any());
+ verify(mOrganizer).onTaskFragmentInfoChanged(any(), any());
}
/**
@@ -1017,7 +1020,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
// The pending event will be dispatched on the handler (from requestTraversal).
waitHandlerIdle(mWm.mAnimationHandler);
- verify(mOrganizer).onTaskFragmentError(eq(mErrorToken), any(),
+ verify(mOrganizer).onTaskFragmentError(any(), eq(mErrorToken), any(),
eq(HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT),
any(SecurityException.class));
}
@@ -1056,7 +1059,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
// The pending event will be dispatched on the handler (from requestTraversal).
waitHandlerIdle(mWm.mAnimationHandler);
- verify(mOrganizer).onTaskFragmentError(eq(mErrorToken), any(),
+ verify(mOrganizer).onTaskFragmentError(any(), eq(mErrorToken), any(),
eq(HIERARCHY_OP_TYPE_REPARENT_CHILDREN), any(SecurityException.class));
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index 88eadfceedb6..83f17897eb62 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -134,6 +134,23 @@ public class TaskFragmentTest extends WindowTestsBase {
}
@Test
+ public void testStartChangeTransition_doNotFreezeWhenOnlyMoved() {
+ final Rect startBounds = new Rect(0, 0, 1000, 1000);
+ final Rect endBounds = new Rect(startBounds);
+ endBounds.offset(500, 0);
+ mTaskFragment.setBounds(startBounds);
+ doReturn(true).when(mTaskFragment).isVisible();
+ doReturn(true).when(mTaskFragment).isVisibleRequested();
+
+ clearInvocations(mTransaction);
+ mTaskFragment.setBounds(endBounds);
+
+ // No change transition, but update the organized surface position.
+ verify(mTaskFragment, never()).initializeChangeTransition(any(), any());
+ verify(mTransaction).setPosition(mLeash, endBounds.left, endBounds.top);
+ }
+
+ @Test
public void testNotOkToAnimate_doNotStartChangeTransition() {
mockSurfaceFreezerSnapshot(mTaskFragment.mSurfaceFreezer);
final Rect startBounds = new Rect(0, 0, 1000, 1000);
@@ -323,7 +340,7 @@ public class TaskFragmentTest extends WindowTestsBase {
activity.reparent(task, POSITION_TOP);
// Notify the organizer about the reparent.
- verify(mAtm.mTaskFragmentOrganizerController).onActivityReparentToTask(activity);
+ verify(mAtm.mTaskFragmentOrganizerController).onActivityReparentedToTask(activity);
assertNull(activity.mLastTaskFragmentOrganizerBeforePip);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
index 851be9d77348..13da1543cfb8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -245,7 +245,7 @@ class TestWindowManagerPolicy implements WindowManagerPolicy {
}
@Override
- public void userActivity() {
+ public void userActivity(int displayGroupId, int event) {
}
@Override
@@ -310,11 +310,11 @@ class TestWindowManagerPolicy implements WindowManagerPolicy {
}
@Override
- public void startKeyguardExitAnimation(long startTime, long fadeoutDuration) {
+ public void startKeyguardExitAnimation(long startTime) {
}
@Override
- public int applyKeyguardOcclusionChange(boolean keyguardOccludingStarted) {
+ public int applyKeyguardOcclusionChange(boolean notify) {
return 0;
}
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 5a2d45686043..85ac7bd96854 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -36,8 +36,10 @@ import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER;
import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
import static android.window.TransitionInfo.isIndependent;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.server.wm.WindowContainer.POSITION_TOP;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -1082,6 +1084,39 @@ public class TransitionTests extends WindowTestsBase {
assertTrue((info.getChanges().get(1).getFlags() & FLAG_IS_EMBEDDED) != 0);
}
+ @Test
+ public void testIncludeEmbeddedActivityReparent() {
+ final Transition transition = createTestTransition(TRANSIT_OPEN);
+ final Task task = createTask(mDisplayContent);
+ task.setBounds(new Rect(0, 0, 2000, 1000));
+ final ActivityRecord activity = createActivityRecord(task);
+ activity.mVisibleRequested = true;
+ // Skip manipulate the SurfaceControl.
+ doNothing().when(activity).setDropInputMode(anyInt());
+ final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+ mAtm.mTaskFragmentOrganizerController.registerOrganizer(
+ ITaskFragmentOrganizer.Stub.asInterface(organizer.getOrganizerToken().asBinder()));
+ final TaskFragment embeddedTf = new TaskFragmentBuilder(mAtm)
+ .setParentTask(task)
+ .setOrganizer(organizer)
+ .build();
+ // TaskFragment with different bounds from Task.
+ embeddedTf.setBounds(new Rect(0, 0, 1000, 1000));
+
+ // Start states.
+ transition.collect(activity);
+ transition.collectExistenceChange(embeddedTf);
+
+ // End states.
+ activity.reparent(embeddedTf, POSITION_TOP);
+
+ // Verify that both activity and TaskFragment are included.
+ final ArrayList<WindowContainer> targets = Transition.calculateTargets(
+ transition.mParticipants, transition.mChanges);
+ assertTrue(targets.contains(embeddedTf));
+ assertTrue(targets.contains(activity));
+ }
+
private static void makeTaskOrganized(Task... tasks) {
final ITaskOrganizer organizer = mock(ITaskOrganizer.class);
for (Task t : tasks) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index 05cc0cf14081..46b4b76dc12f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -24,6 +24,7 @@ import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
@@ -41,6 +42,7 @@ import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -52,16 +54,20 @@ import static org.mockito.Mockito.when;
import android.graphics.Rect;
import android.os.Binder;
+import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
+import android.util.MergedConfiguration;
import android.view.IWindowSessionCallback;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
import android.view.InsetsVisibilities;
+import android.view.SurfaceControl;
import android.view.View;
import android.view.WindowManager;
+import android.window.ClientWindowFrames;
import android.window.WindowContainerToken;
import androidx.test.filters.SmallTest;
@@ -166,6 +172,32 @@ public class WindowManagerServiceTests extends WindowTestsBase {
}
@Test
+ public void testRelayoutExitingWindow() {
+ final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, "appWin");
+ final WindowSurfaceController surfaceController = mock(WindowSurfaceController.class);
+ doReturn(true).when(surfaceController).hasSurface();
+ spyOn(win);
+ doReturn(true).when(win).isExitAnimationRunningSelfOrParent();
+ win.mWinAnimator.mSurfaceController = surfaceController;
+ win.mViewVisibility = View.VISIBLE;
+ win.mHasSurface = true;
+ win.mActivityRecord.mAppStopped = true;
+ win.mActivityRecord.mVisibleRequested = false;
+ win.mActivityRecord.setVisible(false);
+ mWm.mWindowMap.put(win.mClient.asBinder(), win);
+ final int w = 100;
+ final int h = 200;
+ mWm.relayoutWindow(win.mSession, win.mClient, win.mAttrs, w, h, View.GONE, 0, 0, 0,
+ new ClientWindowFrames(), new MergedConfiguration(), new SurfaceControl(),
+ new InsetsState(), new InsetsSourceControl[0], new Bundle());
+ // Because the window is already invisible, it doesn't need to apply exiting animation
+ // and WMS#tryStartExitingAnimation() will destroy the surface directly.
+ assertFalse(win.mAnimatingExit);
+ assertFalse(win.mHasSurface);
+ assertNull(win.mWinAnimator.mSurfaceController);
+ }
+
+ @Test
public void testMoveWindowTokenToDisplay_NullToken_DoNothing() {
mWm.moveWindowTokenToDisplay(null, mDisplayContent.getDisplayId());
diff --git a/startop/view_compiler/dex_builder_test/Android.bp b/startop/view_compiler/dex_builder_test/Android.bp
index 2048964f4af2..bcba2febdcdc 100644
--- a/startop/view_compiler/dex_builder_test/Android.bp
+++ b/startop/view_compiler/dex_builder_test/Android.bp
@@ -46,7 +46,6 @@ genrule {
android_test {
name: "dex-builder-test",
srcs: [
- "src/android/startop/test/ApkLayoutCompilerTest.java",
"src/android/startop/test/DexBuilderTest.java",
"src/android/startop/test/LayoutCompilerTest.java",
"src/android/startop/test/TestClass.java",
diff --git a/startop/view_compiler/dex_builder_test/src/android/startop/test/ApkLayoutCompilerTest.java b/startop/view_compiler/dex_builder_test/src/android/startop/test/ApkLayoutCompilerTest.java
deleted file mode 100644
index 230e8df1e687..000000000000
--- a/startop/view_compiler/dex_builder_test/src/android/startop/test/ApkLayoutCompilerTest.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package android.startop.test;
-
-import android.content.Context;
-import androidx.test.InstrumentationRegistry;
-import android.view.View;
-import dalvik.system.PathClassLoader;
-import java.lang.reflect.Method;
-import org.junit.Assert;
-import org.junit.BeforeClass;
-import org.junit.Test;
-
-public class ApkLayoutCompilerTest {
- static ClassLoader loadDexFile() throws Exception {
- Context context = InstrumentationRegistry.getTargetContext();
- return new PathClassLoader(context.getCodeCacheDir() + "/compiled_view.dex",
- ClassLoader.getSystemClassLoader());
- }
-
- @BeforeClass
- public static void setup() throws Exception {
- // ensure PackageManager has compiled the layouts.
- Process pm = Runtime.getRuntime().exec("pm compile --compile-layouts android.startop.test");
- pm.waitFor();
- }
-
- @Test
- public void loadAndInflateLayout1() throws Exception {
- ClassLoader dex_file = loadDexFile();
- Class compiled_view = dex_file.loadClass("android.startop.test.CompiledView");
- Method layout1 = compiled_view.getMethod("layout1", Context.class, int.class);
- Context context = InstrumentationRegistry.getTargetContext();
- layout1.invoke(null, context, R.layout.layout1);
- }
-
- @Test
- public void loadAndInflateLayout2() throws Exception {
- ClassLoader dex_file = loadDexFile();
- Class compiled_view = dex_file.loadClass("android.startop.test.CompiledView");
- Method layout2 = compiled_view.getMethod("layout2", Context.class, int.class);
- Context context = InstrumentationRegistry.getTargetContext();
- layout2.invoke(null, context, R.layout.layout2);
- }
-}
diff --git a/telephony/OWNERS b/telephony/OWNERS
index 9681ee8b6c75..e0c5f8fa214e 100644
--- a/telephony/OWNERS
+++ b/telephony/OWNERS
@@ -10,3 +10,10 @@ jayachandranc@google.com
chinmayd@google.com
amruthr@google.com
sasindran@google.com
+
+# Temporarily reduced the owner during refactoring
+per-file SubscriptionManager.java=set noparent
+per-file SubscriptionManager.java=jackyu@google.com,amruthr@google.com
+per-file SubscriptionInfo.java=set noparent
+per-file SubscriptionInfo.java=jackyu@google.com,amruthr@google.com
+
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 4d18dfe6a62c..4af8cde4364c 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -39,6 +39,7 @@ import android.service.carrier.CarrierService;
import android.telecom.TelecomManager;
import android.telephony.AccessNetworkConstants.AccessNetworkType;
import android.telephony.data.ApnSetting;
+import android.telephony.data.DataCallResponse;
import android.telephony.gba.TlsParams;
import android.telephony.gba.UaSecurityProtocolIdentifier;
import android.telephony.ims.ImsReasonInfo;
@@ -1125,6 +1126,27 @@ public class CarrierConfigManager {
public static final String KEY_DEFAULT_MTU_INT = "default_mtu_int";
/**
+ * The data call retry configuration for different types of APN.
+ * @hide
+ */
+ public static final String KEY_CARRIER_DATA_CALL_RETRY_CONFIG_STRINGS =
+ "carrier_data_call_retry_config_strings";
+
+ /**
+ * Delay in milliseconds between trying APN from the pool
+ * @hide
+ */
+ public static final String KEY_CARRIER_DATA_CALL_APN_DELAY_DEFAULT_LONG =
+ "carrier_data_call_apn_delay_default_long";
+
+ /**
+ * Faster delay in milliseconds between trying APN from the pool
+ * @hide
+ */
+ public static final String KEY_CARRIER_DATA_CALL_APN_DELAY_FASTER_LONG =
+ "carrier_data_call_apn_delay_faster_long";
+
+ /**
* Delay in milliseconds for retrying APN after disconnect
* @hide
*/
@@ -1132,25 +1154,94 @@ public class CarrierConfigManager {
"carrier_data_call_apn_retry_after_disconnect_long";
/**
+ * The maximum times for telephony to retry data setup on the same APN requested by
+ * network through the data setup response retry timer
+ * {@link DataCallResponse#getRetryDurationMillis()}. This is to prevent that network keeps
+ * asking device to retry data setup forever and causes power consumption issue. For infinite
+ * retring same APN, configure this as 2147483647 (i.e. {@link Integer#MAX_VALUE}).
+ *
+ * Note if network does not suggest any retry timer, frameworks uses the retry configuration
+ * from {@link #KEY_CARRIER_DATA_CALL_RETRY_CONFIG_STRINGS}, and the maximum retry times could
+ * be configured there.
+ * @hide
+ */
+ public static final String KEY_CARRIER_DATA_CALL_RETRY_NETWORK_REQUESTED_MAX_COUNT_INT =
+ "carrier_data_call_retry_network_requested_max_count_int";
+
+ /**
* Data call setup permanent failure causes by the carrier
*/
public static final String KEY_CARRIER_DATA_CALL_PERMANENT_FAILURE_STRINGS =
"carrier_data_call_permanent_failure_strings";
/**
- * Default APN types that are metered by the carrier
- * @hide
+ * A string array indicating the default APN types that are metered by the carrier.
+ *
+ * The string in the array is the name of the APN type. For example, "default" for
+ * {@link ApnSetting#TYPE_DEFAULT}, "mms" for {@link ApnSetting#TYPE_MMS}, etc.
+ *
+ * The default value is {@code {"default", "mms", "dun", "supl"}}.
+ *
+ * @see ApnSetting#TYPE_DEFAULT
+ * @see ApnSetting#TYPE_MMS
+ * @see ApnSetting#TYPE_SUPL
+ * @see ApnSetting#TYPE_DUN
+ * @see ApnSetting#TYPE_HIPRI
+ * @see ApnSetting#TYPE_FOTA
+ * @see ApnSetting#TYPE_IMS
+ * @see ApnSetting#TYPE_CBS
+ * @see ApnSetting#TYPE_IA
+ * @see ApnSetting#TYPE_EMERGENCY
+ * @see ApnSetting#TYPE_MCX
+ * @see ApnSetting#TYPE_XCAP
+ * @see ApnSetting#TYPE_BIP
+ * @see ApnSetting#TYPE_VSIM
+ * @see ApnSetting#TYPE_ENTERPRISE
*/
public static final String KEY_CARRIER_METERED_APN_TYPES_STRINGS =
"carrier_metered_apn_types_strings";
+
/**
- * Default APN types that are roaming-metered by the carrier
- * @hide
+ * A string array indicating the default APN types that are roaming-metered by the carrier.
+ *
+ * The string in the array is the name of the APN type. For example, "default" for
+ * {@link ApnSetting#TYPE_DEFAULT}, "mms" for {@link ApnSetting#TYPE_MMS}, etc.
+ *
+ * The default value is {@code {"default", "mms", "dun", "supl"}}.
+ *
+ * @see ApnSetting#TYPE_DEFAULT
+ * @see ApnSetting#TYPE_MMS
+ * @see ApnSetting#TYPE_SUPL
+ * @see ApnSetting#TYPE_DUN
+ * @see ApnSetting#TYPE_HIPRI
+ * @see ApnSetting#TYPE_FOTA
+ * @see ApnSetting#TYPE_IMS
+ * @see ApnSetting#TYPE_CBS
+ * @see ApnSetting#TYPE_IA
+ * @see ApnSetting#TYPE_EMERGENCY
+ * @see ApnSetting#TYPE_MCX
+ * @see ApnSetting#TYPE_XCAP
+ * @see ApnSetting#TYPE_BIP
+ * @see ApnSetting#TYPE_VSIM
+ * @see ApnSetting#TYPE_ENTERPRISE
*/
public static final String KEY_CARRIER_METERED_ROAMING_APN_TYPES_STRINGS =
"carrier_metered_roaming_apn_types_strings";
/**
+ * APN types that are not allowed on cellular
+ * @hide
+ */
+ public static final String KEY_CARRIER_WWAN_DISALLOWED_APN_TYPES_STRING_ARRAY =
+ "carrier_wwan_disallowed_apn_types_string_array";
+
+ /**
+ * APN types that are not allowed on IWLAN
+ * @hide
+ */
+ public static final String KEY_CARRIER_WLAN_DISALLOWED_APN_TYPES_STRING_ARRAY =
+ "carrier_wlan_disallowed_apn_types_string_array";
+ /**
* CDMA carrier ERI (Enhanced Roaming Indicator) file name
* @hide
*/
@@ -8369,6 +8460,7 @@ public class CarrierConfigManager {
* "1800000, maximum_retries=20" means for those capabilities, retry happens in 2.5s, 3s, 5s,
* 10s, 15s, 20s, 40s, 1m, 2m, 4m, 10m, 20m, 30m, 30m, 30m, until reaching 20 retries.
*
+ * // TODO: remove KEY_CARRIER_DATA_CALL_RETRY_CONFIG_STRINGS
* @hide
*/
public static final String KEY_TELEPHONY_DATA_SETUP_RETRY_RULES_STRING_ARRAY =
@@ -8770,13 +8862,27 @@ public class CarrierConfigManager {
sDefaults.putBoolean(KEY_BROADCAST_EMERGENCY_CALL_STATE_CHANGES_BOOL, false);
sDefaults.putBoolean(KEY_ALWAYS_SHOW_EMERGENCY_ALERT_ONOFF_BOOL, false);
sDefaults.putInt(KEY_DEFAULT_MTU_INT, 1500);
+ sDefaults.putStringArray(KEY_CARRIER_DATA_CALL_RETRY_CONFIG_STRINGS, new String[]{
+ "default:default_randomization=2000,5000,10000,20000,40000,80000:5000,160000:5000,"
+ + "320000:5000,640000:5000,1280000:5000,1800000:5000",
+ "mms:default_randomization=2000,5000,10000,20000,40000,80000:5000,160000:5000,"
+ + "320000:5000,640000:5000,1280000:5000,1800000:5000",
+ "ims:max_retries=10, 5000, 5000, 5000",
+ "others:max_retries=3, 5000, 5000, 5000"});
+ sDefaults.putLong(KEY_CARRIER_DATA_CALL_APN_DELAY_DEFAULT_LONG, 20000);
+ sDefaults.putLong(KEY_CARRIER_DATA_CALL_APN_DELAY_FASTER_LONG, 3000);
sDefaults.putLong(KEY_CARRIER_DATA_CALL_APN_RETRY_AFTER_DISCONNECT_LONG, 3000);
+ sDefaults.putInt(KEY_CARRIER_DATA_CALL_RETRY_NETWORK_REQUESTED_MAX_COUNT_INT, 3);
sDefaults.putString(KEY_CARRIER_ERI_FILE_NAME_STRING, "eri.xml");
sDefaults.putInt(KEY_DURATION_BLOCKING_DISABLED_AFTER_EMERGENCY_INT, 7200);
sDefaults.putStringArray(KEY_CARRIER_METERED_APN_TYPES_STRINGS,
new String[]{"default", "mms", "dun", "supl"});
sDefaults.putStringArray(KEY_CARRIER_METERED_ROAMING_APN_TYPES_STRINGS,
new String[]{"default", "mms", "dun", "supl"});
+ sDefaults.putStringArray(KEY_CARRIER_WWAN_DISALLOWED_APN_TYPES_STRING_ARRAY,
+ new String[]{""});
+ sDefaults.putStringArray(KEY_CARRIER_WLAN_DISALLOWED_APN_TYPES_STRING_ARRAY,
+ new String[]{""});
sDefaults.putIntArray(KEY_ONLY_SINGLE_DC_ALLOWED_INT_ARRAY,
new int[] {TelephonyManager.NETWORK_TYPE_CDMA, TelephonyManager.NETWORK_TYPE_1xRTT,
TelephonyManager.NETWORK_TYPE_EVDO_0, TelephonyManager.NETWORK_TYPE_EVDO_A,
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index f28408e1abc9..4fb65874044f 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -139,24 +139,19 @@ public class SubscriptionManager {
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public static final Uri CONTENT_URI = SimInfo.CONTENT_URI;
- /** @hide */
- public static final String CACHE_KEY_DEFAULT_SUB_ID_PROPERTY =
+ private static final String CACHE_KEY_DEFAULT_SUB_ID_PROPERTY =
"cache_key.telephony.get_default_sub_id";
- /** @hide */
- public static final String CACHE_KEY_DEFAULT_DATA_SUB_ID_PROPERTY =
+ private static final String CACHE_KEY_DEFAULT_DATA_SUB_ID_PROPERTY =
"cache_key.telephony.get_default_data_sub_id";
- /** @hide */
- public static final String CACHE_KEY_DEFAULT_SMS_SUB_ID_PROPERTY =
+ private static final String CACHE_KEY_DEFAULT_SMS_SUB_ID_PROPERTY =
"cache_key.telephony.get_default_sms_sub_id";
- /** @hide */
- public static final String CACHE_KEY_ACTIVE_DATA_SUB_ID_PROPERTY =
+ private static final String CACHE_KEY_ACTIVE_DATA_SUB_ID_PROPERTY =
"cache_key.telephony.get_active_data_sub_id";
- /** @hide */
- public static final String CACHE_KEY_SLOT_INDEX_PROPERTY =
+ private static final String CACHE_KEY_SLOT_INDEX_PROPERTY =
"cache_key.telephony.get_slot_index";
/** @hide */
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 789399205f23..c9a63c654a40 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -140,6 +140,7 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -234,6 +235,54 @@ public class TelephonyManager {
public static final int NETWORK_SELECTION_MODE_AUTO = 1;
public static final int NETWORK_SELECTION_MODE_MANUAL = 2;
+ /**
+ * Reasons for Radio being powered off.
+ *
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"RADIO_POWER_REASON_"},
+ value = {
+ RADIO_POWER_REASON_USER,
+ RADIO_POWER_REASON_THERMAL,
+ RADIO_POWER_REASON_CARRIER,
+ RADIO_POWER_REASON_NEARBY_DEVICE})
+ public @interface RadioPowerReason {}
+
+ /**
+ * This reason is used when users want to turn off radio, e.g., users turn on airplane mode.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int RADIO_POWER_REASON_USER = 0;
+ /**
+ * This reason is used when radio needs to be turned off due to thermal.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int RADIO_POWER_REASON_THERMAL = 1;
+ /**
+ * This reason is used when carriers want to turn off radio. A privileged app can request to
+ * turn off radio via the system service
+ * {@link com.android.carrierdefaultapp.CaptivePortalLoginActivity}, which subsequently calls
+ * the system APIs {@link requestRadioPowerOffForReason} and
+ * {@link clearRadioPowerOffForReason}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int RADIO_POWER_REASON_CARRIER = 2;
+ /**
+ * Used to reduce power on a battery-constrained device when Telephony services are available
+ * via a paired device which is nearby.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int RADIO_POWER_REASON_NEARBY_DEVICE = 3;
+
/** The otaspMode passed to PhoneStateListener#onOtaspChanged */
/** @hide */
static public final int OTASP_UNINITIALIZED = 0;
@@ -9039,7 +9088,7 @@ public class TelephonyManager {
* @param executor The executor through which the callback should be invoked. Since the scan
* request may trigger multiple callbacks and they must be invoked in the same order as
* they are received by the platform, the user should provide an executor which executes
- * tasks one at a time in serial order. For example AsyncTask.SERIAL_EXECUTOR.
+ * tasks one at a time in serial order.
* @param callback Returns network scan results or errors.
* @return A NetworkScan obj which contains a callback which can be used to stop the scan.
*/
@@ -9083,7 +9132,7 @@ public class TelephonyManager {
* @param executor The executor through which the callback should be invoked. Since the scan
* request may trigger multiple callbacks and they must be invoked in the same order as
* they are received by the platform, the user should provide an executor which executes
- * tasks one at a time in serial order. For example AsyncTask.SERIAL_EXECUTOR.
+ * tasks one at a time in serial order.
* @param callback Returns network scan results or errors.
* @return A NetworkScan obj which contains a callback which can be used to stop the scan.
*/
@@ -10506,34 +10555,155 @@ public class TelephonyManager {
}
}
- /** @hide */
+ /**
+ * @deprecated - use the APIs {@link requestRadioPowerOffForReason} and
+ * {@link clearRadioPowerOffForReason}.
+ *
+ * @hide
+ */
+ @Deprecated
@SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
public boolean setRadio(boolean turnOn) {
+ boolean result = true;
+ try {
+ if (turnOn) {
+ clearRadioPowerOffForReason(RADIO_POWER_REASON_USER);
+ } else {
+ requestRadioPowerOffForReason(RADIO_POWER_REASON_USER);
+ }
+ } catch (Exception e) {
+ String calledFunction =
+ turnOn ? "clearRadioPowerOffForReason" : "requestRadioPowerOffForReason";
+ Log.e(TAG, "Error calling " + calledFunction, e);
+ result = false;
+ }
+ return result;
+ }
+
+ /**
+ * @deprecated - use the APIs {@link requestRadioPowerOffForReason} and
+ * {@link clearRadioPowerOffForReason}.
+ *
+ * @hide
+ */
+ @Deprecated
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
+ public boolean setRadioPower(boolean turnOn) {
+ boolean result = true;
+ try {
+ if (turnOn) {
+ clearRadioPowerOffForReason(RADIO_POWER_REASON_USER);
+ } else {
+ requestRadioPowerOffForReason(RADIO_POWER_REASON_USER);
+ }
+ } catch (Exception e) {
+ String calledFunction =
+ turnOn ? "clearRadioPowerOffForReason" : "requestRadioPowerOffForReason";
+ Log.e(TAG, "Error calling " + calledFunction, e);
+ result = false;
+ }
+ return result;
+ }
+
+ /**
+ * Vote on powering off the radio for a reason. The radio will be turned on only when there is
+ * no reason to power it off. When any of the voters want to power it off, it will be turned
+ * off. In case of emergency, the radio will be turned on even if there are some reasons for
+ * powering it off, and these radio off votes will be cleared.
+ * Multiple apps can vote for the same reason and the last vote will take effect. Each app is
+ * responsible for its vote. A powering-off vote of a reason will be maintained until it is
+ * cleared by calling {@link clearRadioPowerOffForReason} for that reason, or an emergency call
+ * is made, or the device is rebooted. When an app comes backup from a crash, it needs to make
+ * sure if its vote is as expected. An app can use the API {@link getRadioPowerOffReasons} to
+ * check its vote.
+ *
+ * @param reason The reason for powering off radio.
+ * @throws SecurityException if the caller does not have MODIFY_PHONE_STATE permission.
+ * @throws IllegalStateException if the Telephony service is not currently available.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
+ public void requestRadioPowerOffForReason(@RadioPowerReason int reason) {
try {
ITelephony telephony = getITelephony();
- if (telephony != null)
- return telephony.setRadio(turnOn);
+ if (telephony != null) {
+ if (!telephony.requestRadioPowerOffForReason(getSubId(), reason)) {
+ throw new IllegalStateException("Telephony service is not available.");
+ }
+ } else {
+ throw new IllegalStateException("Telephony service is null.");
+ }
} catch (RemoteException e) {
- Log.e(TAG, "Error calling ITelephony#setRadio", e);
+ Log.e(TAG, "Error calling ITelephony#requestRadioPowerOffForReason", e);
+ e.rethrowAsRuntimeException();
}
- return false;
}
- /** @hide */
+ /**
+ * Remove the vote on powering off the radio for a reason, as requested by
+ * {@link requestRadioPowerOffForReason}.
+ *
+ * @param reason The reason for powering off radio.
+ * @throws SecurityException if the caller does not have MODIFY_PHONE_STATE permission.
+ * @throws IllegalStateException if the Telephony service is not currently available.
+ *
+ * @hide
+ */
@SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
- public boolean setRadioPower(boolean turnOn) {
+ public void clearRadioPowerOffForReason(@RadioPowerReason int reason) {
try {
ITelephony telephony = getITelephony();
- if (telephony != null)
- return telephony.setRadioPower(turnOn);
+ if (telephony != null) {
+ if (!telephony.clearRadioPowerOffForReason(getSubId(), reason)) {
+ throw new IllegalStateException("Telephony service is not available.");
+ }
+ } else {
+ throw new IllegalStateException("Telephony service is null.");
+ }
} catch (RemoteException e) {
- Log.e(TAG, "Error calling ITelephony#setRadioPower", e);
+ Log.e(TAG, "Error calling ITelephony#clearRadioPowerOffForReason", e);
+ e.rethrowAsRuntimeException();
}
- return false;
+ }
+
+ /**
+ * Get reasons for powering off radio, as requested by {@link requestRadioPowerOffForReason}.
+ * If the reason set is empty, the radio is on in all cases.
+ *
+ * @return Set of reasons for powering off radio.
+ * @throws SecurityException if the caller does not have READ_PRIVILEGED_PHONE_STATE permission.
+ * @throws IllegalStateException if the Telephony service is not currently available.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
+ @NonNull
+ public Set<Integer> getRadioPowerOffReasons() {
+ Set<Integer> result = new HashSet<>();
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ result.addAll(telephony.getRadioPowerOffReasons(getSubId(),
+ mContext.getOpPackageName(), mContext.getAttributionTag()));
+ } else {
+ throw new IllegalStateException("Telephony service is null.");
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling ITelephony#getRadioPowerOffReasons", e);
+ e.rethrowAsRuntimeException();
+ }
+ return result;
}
/**
@@ -13037,20 +13207,21 @@ public class TelephonyManager {
*
* @param enabled control enable or disable radio.
* @see #resetAllCarrierActions()
+ *
+ * @deprecated - use the APIs {@link requestRadioPowerOffForReason} and
+ * {@link clearRadioPowerOffForReason}.
+ *
* @hide
*/
+ @Deprecated
@SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
public void setRadioEnabled(boolean enabled) {
- try {
- ITelephony service = getITelephony();
- if (service != null) {
- service.carrierActionSetRadioEnabled(
- getSubId(SubscriptionManager.getDefaultDataSubscriptionId()), enabled);
- }
- } catch (RemoteException e) {
- Log.e(TAG, "Error calling ITelephony#carrierActionSetRadioEnabled", e);
+ if (enabled) {
+ clearRadioPowerOffForReason(RADIO_POWER_REASON_CARRIER);
+ } else {
+ requestRadioPowerOffForReason(RADIO_POWER_REASON_CARRIER);
}
}
@@ -16273,7 +16444,12 @@ public class TelephonyManager {
* the appropriate callback method on the callback object and passes the current (updated)
* values.
* <p>
- *
+ * Note: Be aware of the permission requirements stated on the {@link TelephonyCallback}
+ * listeners you implement. Your application must be granted these permissions in order to
+ * register a {@link TelephonyCallback} which requires them; a {@link SecurityException} will be
+ * thrown if you do not hold the required permissions for all {@link TelephonyCallback}
+ * listeners you implement.
+ * <p>
* If this TelephonyManager object has been created with {@link #createForSubscriptionId},
* applies to the given subId. Otherwise, applies to
* {@link SubscriptionManager#getDefaultSubscriptionId()}. To register events for multiple
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index da1ffcdea812..42cac6630506 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -244,6 +244,44 @@ interface ITelephony {
boolean setRadioPower(boolean turnOn);
/**
+ * Vote on powering off the radio for a reason. The radio will be turned on only when there is
+ * no reason to power it off. When any of the voters want to power it off, it will be turned
+ * off. In case of emergency, the radio will be turned on even if there are some reasons for
+ * powering it off, and these radio off votes will be cleared.
+ * Multiple apps can vote for the same reason and the last vote will take effect. Each app is
+ * responsible for its vote. A powering-off vote of a reason will be maintained until it is
+ * cleared by calling {@link clearRadioPowerOffForReason} for that reason, or an emergency call
+ * is made, or the device is rebooted. When an app comes backup from a crash, it needs to make
+ * sure if its vote is as expected. An app can use the API {@link getRadioPowerOffReasons} to
+ * check its vote.
+ *
+ * @param subId The subscription ID.
+ * @param reason The reason for powering off radio.
+ * @return true on success and false on failure.
+ */
+ boolean requestRadioPowerOffForReason(int subId, int reason);
+
+ /**
+ * Remove the vote on powering off the radio for a reasonas, requested by
+ * {@link requestRadioPowerOffForReason}.
+ *
+ * @param subId The subscription ID.
+ * @param reason The reason for powering off radio.
+ * @return true on success and false on failure.
+ */
+ boolean clearRadioPowerOffForReason(int subId, int reason);
+
+ /**
+ * Get reasons for powering off radio, as requested by {@link requestRadioPowerOffForReason}.
+ *
+ * @param subId The subscription ID.
+ * @param callingPackage The package making the call.
+ * @param callingFeatureId The feature in the package.
+ * @return List of reasons for powering off radio.
+ */
+ List getRadioPowerOffReasons(int subId, String callingPackage, String callingFeatureId);
+
+ /**
* This method has been removed due to security and stability issues.
*/
@UnsupportedAppUsage
@@ -2510,6 +2548,9 @@ interface ITelephony {
CellIdentity getLastKnownCellIdentity(int subId, String callingPackage,
String callingFeatureId);
+ /** Check if telephony new data stack is enabled. */
+ boolean isUsingNewDataStack();
+
/**
* @return true if the modem service is set successfully, false otherwise.
*/
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
index e138d332bb17..be7fb7315955 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
@@ -38,7 +38,10 @@ abstract class BaseTest @JvmOverloads constructor(
) {
init {
testSpec.setIsTablet(
- WindowManagerStateHelper(instrumentation).currentState.wmState.isTablet
+ WindowManagerStateHelper(
+ instrumentation,
+ clearCacheAfterParsing = false
+ ).currentState.wmState.isTablet
)
tapl.setExpectedRotationCheckEnabled(true)
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
index 5e21252f3ebd..472a0fa376bf 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
@@ -43,6 +43,19 @@ fun FlickerTestParameter.navBarWindowIsAlwaysVisible() {
}
/**
+ * Checks that [ComponentMatcher.NAV_BAR] window is visible and above the app windows at the start
+ * and end of the WM trace
+ */
+fun FlickerTestParameter.navBarWindowIsVisibleAtStartAndEnd() {
+ assertWmStart {
+ this.isAboveAppWindowVisible(ComponentMatcher.NAV_BAR)
+ }
+ assertWmEnd {
+ this.isAboveAppWindowVisible(ComponentMatcher.NAV_BAR)
+ }
+}
+
+/**
* Checks that [ComponentMatcher.TASK_BAR] window is visible and above the app windows in
* all WM trace entries
*/
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt
index 75900df978df..d08cb5525d5e 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt
@@ -26,4 +26,9 @@ import com.android.server.wm.flicker.rules.ChangeDisplayOrientationRule
* @param rotation New device rotation
*/
fun Flicker.setRotation(rotation: Int) =
- ChangeDisplayOrientationRule.setRotation(rotation, instrumentation, wmHelper)
+ ChangeDisplayOrientationRule.setRotation(
+ rotation,
+ instrumentation,
+ clearCacheAfterParsing = false,
+ wmHelper = wmHelper
+)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest_ShellTransit.kt
index 457e973392f7..a8c0a0b55009 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest_ShellTransit.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest_ShellTransit.kt
@@ -17,14 +17,18 @@
package com.android.server.wm.flicker.ime
import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.Presubmit
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.annotation.Group4
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.navBarWindowIsVisibleAtStartAndEnd
+import com.android.server.wm.traces.common.ComponentMatcher
import org.junit.Assume
import org.junit.Before
import org.junit.FixMethodOrder
+import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@@ -65,4 +69,20 @@ class SwitchImeWindowsFromGestureNavTest_ShellTransit(
@Test
override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
+ /** {@inheritDoc} */
+ @Ignore("Nav bar window becomes invisible during quick switch")
+ @Test
+ override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
+
+ /**
+ * Checks that [ComponentMatcher.NAV_BAR] window is visible and above the app windows at the start
+ * and end of the WM trace
+ */
+ @Presubmit
+ @Test
+ fun navBarWindowIsVisibleAtStartAndEnd() {
+ Assume.assumeFalse(testSpec.isTablet)
+ testSpec.navBarWindowIsVisibleAtStartAndEnd()
+ }
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt
index e007fe354994..2607ee5bb0ef 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt
@@ -17,14 +17,18 @@
package com.android.server.wm.flicker.quickswitch
import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.Presubmit
import android.platform.test.annotations.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.annotation.Group1
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.navBarWindowIsVisibleAtStartAndEnd
+import com.android.server.wm.traces.common.ComponentMatcher
import org.junit.Assume
import org.junit.Before
import org.junit.FixMethodOrder
+import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@@ -76,4 +80,20 @@ open class QuickSwitchBetweenTwoAppsBackTest_ShellTransit(
@Test
override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
+ /** {@inheritDoc} */
+ @Ignore("Nav bar window becomes invisible during quick switch")
+ @Test
+ override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
+
+ /**
+ * Checks that [ComponentMatcher.NAV_BAR] window is visible and above the app windows at the start
+ * and end of the WM trace
+ */
+ @Presubmit
+ @Test
+ fun navBarWindowIsVisibleAtStartAndEnd() {
+ Assume.assumeFalse(testSpec.isTablet)
+ testSpec.navBarWindowIsVisibleAtStartAndEnd()
+ }
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt
index 6f78ba8dc0f6..27ae12566e94 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt
@@ -17,14 +17,18 @@
package com.android.server.wm.flicker.quickswitch
import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.Presubmit
import android.platform.test.annotations.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.annotation.Group1
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.navBarWindowIsVisibleAtStartAndEnd
+import com.android.server.wm.traces.common.ComponentMatcher
import org.junit.Assume
import org.junit.Before
import org.junit.FixMethodOrder
+import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@@ -66,4 +70,20 @@ open class QuickSwitchBetweenTwoAppsForwardTest_ShellTransit(
@FlakyTest(bugId = 228009808)
@Test
override fun endsWithApp2BeingOnTop() = super.endsWithApp2BeingOnTop()
+
+ /** {@inheritDoc} */
+ @Ignore("Nav bar window becomes invisible during quick switch")
+ @Test
+ override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
+
+ /**
+ * Checks that [ComponentMatcher.NAV_BAR] window is visible and above the app windows at the start
+ * and end of the WM trace
+ */
+ @Presubmit
+ @Test
+ fun navBarWindowIsVisibleAtStartAndEnd() {
+ Assume.assumeFalse(testSpec.isTablet)
+ testSpec.navBarWindowIsVisibleAtStartAndEnd()
+ }
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
index 510043b680e5..c79b55251c74 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
@@ -29,9 +29,12 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group1
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.navBarWindowIsVisibleAtStartAndEnd
import com.android.server.wm.traces.common.ComponentMatcher
import com.android.server.wm.traces.common.Rect
+import org.junit.Assume
import org.junit.FixMethodOrder
+import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@@ -296,6 +299,22 @@ class QuickSwitchFromLauncherTest(testSpec: FlickerTestParameter) : BaseTest(tes
override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+ /** {@inheritDoc} */
+ @Ignore("Nav bar window becomes invisible during quick switch")
+ @Test
+ override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
+
+ /**
+ * Checks that [ComponentMatcher.NAV_BAR] window is visible and above the app windows at the start
+ * and end of the WM trace
+ */
+ @Presubmit
+ @Test
+ fun navBarWindowIsVisibleAtStartAndEnd() {
+ Assume.assumeFalse(testSpec.isTablet)
+ testSpec.navBarWindowIsVisibleAtStartAndEnd()
+ }
+
companion object {
/** {@inheritDoc} */
private var startDisplayBounds = Rect.EMPTY
diff --git a/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java b/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java
index c4cb33da4a6d..44265511ee50 100644
--- a/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java
+++ b/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java
@@ -123,16 +123,27 @@ public class StagedInstallInternalTest extends BaseHostJUnit4Test {
}
boolean found = false;
+ boolean remountSystem = false;
+ boolean remountVendor = false;
for (String file : files) {
CommandResult result = getDevice().executeShellV2Command("ls " + file);
if (result.getStatus() == CommandStatus.SUCCESS) {
found = true;
- break;
+ if (file.startsWith("/system")) {
+ remountSystem = true;
+ } else if (file.startsWith("/vendor")) {
+ remountVendor = true;
+ }
}
}
if (found) {
- getDevice().remountSystemWritable();
+ if (remountSystem) {
+ getDevice().remountSystemWritable();
+ }
+ if (remountVendor) {
+ getDevice().remountVendorWritable();
+ }
for (String file : files) {
getDevice().executeShellCommand("rm -rf " + file);
}
@@ -150,7 +161,11 @@ public class StagedInstallInternalTest extends BaseHostJUnit4Test {
if (!getDevice().isAdbRoot()) {
getDevice().enableAdbRoot();
}
- getDevice().remountSystemWritable();
+ if ("system".equals(partition)) {
+ getDevice().remountSystemWritable();
+ } else if ("vendor".equals(partition)) {
+ getDevice().remountVendorWritable();
+ }
assertTrue(getDevice().pushFile(apex, "/" + partition + "/apex/" + fileName));
}
@@ -158,7 +173,7 @@ public class StagedInstallInternalTest extends BaseHostJUnit4Test {
if (!getDevice().isAdbRoot()) {
getDevice().enableAdbRoot();
}
- getDevice().remountSystemWritable();
+ getDevice().remountVendorWritable();
File file = File.createTempFile("test-vendor-apex-allow-list", ".xml");
try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) {
final String fmt =
diff --git a/tests/TouchLatency/app/src/main/java/com/prefabulated/touchlatency/TouchLatencyActivity.java b/tests/TouchLatency/app/src/main/java/com/prefabulated/touchlatency/TouchLatencyActivity.java
index 1664746f4636..a2842b6e214d 100644
--- a/tests/TouchLatency/app/src/main/java/com/prefabulated/touchlatency/TouchLatencyActivity.java
+++ b/tests/TouchLatency/app/src/main/java/com/prefabulated/touchlatency/TouchLatencyActivity.java
@@ -22,6 +22,7 @@ import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.os.Bundle;
+import android.os.Trace;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Display;
@@ -30,9 +31,9 @@ import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
-import android.os.Trace;
import android.view.Window;
import android.view.WindowManager;
+
import java.math.RoundingMode;
import java.text.DecimalFormat;
@@ -280,6 +281,17 @@ public class TouchLatencyActivity extends Activity {
WindowManager.LayoutParams params = w.getAttributes();
int modeIndex = (mCurrentModeIndex + 1) % mDisplayModes.length;
+ while (modeIndex != mCurrentModeIndex) {
+ // skip modes with different resolutions
+ Mode currentMode = mDisplayModes[mCurrentModeIndex];
+ Mode nextMode = mDisplayModes[modeIndex];
+ if (currentMode.getPhysicalHeight() == nextMode.getPhysicalHeight()
+ && currentMode.getPhysicalWidth() == nextMode.getPhysicalWidth()) {
+ break;
+ }
+ modeIndex = (modeIndex + 1) % mDisplayModes.length;
+ }
+
params.preferredDisplayModeId = mDisplayModes[modeIndex].getModeId();
w.setAttributes(params);
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt b/tools/lint/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt
index d19f4cceeaaf..12f3a1659313 100644
--- a/tools/lint/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt
+++ b/tools/lint/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt
@@ -19,6 +19,8 @@ package com.google.android.lint
import com.android.tools.lint.client.api.IssueRegistry
import com.android.tools.lint.client.api.Vendor
import com.android.tools.lint.detector.api.CURRENT_API
+import com.google.android.lint.aidl.EnforcePermissionDetector
+import com.google.android.lint.aidl.ManualPermissionCheckDetector
import com.google.android.lint.parcel.SaferParcelChecker
import com.google.auto.service.AutoService
@@ -36,6 +38,7 @@ class AndroidFrameworkIssueRegistry : IssueRegistry() {
CallingSettingsNonUserGetterMethodsDetector.ISSUE_NON_USER_GETTER_CALLED,
EnforcePermissionDetector.ISSUE_MISSING_ENFORCE_PERMISSION,
EnforcePermissionDetector.ISSUE_MISMATCHING_ENFORCE_PERMISSION,
+ ManualPermissionCheckDetector.ISSUE_USE_ENFORCE_PERMISSION_ANNOTATION,
SaferParcelChecker.ISSUE_UNSAFE_API_USAGE,
PackageVisibilityDetector.ISSUE_PACKAGE_NAME_NO_PACKAGE_VISIBILITY_FILTERS
)
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/Constants.kt b/tools/lint/checks/src/main/java/com/google/android/lint/Constants.kt
new file mode 100644
index 000000000000..82eb8ed8f621
--- /dev/null
+++ b/tools/lint/checks/src/main/java/com/google/android/lint/Constants.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.google.android.lint
+
+import com.google.android.lint.model.Method
+
+const val CLASS_STUB = "Stub"
+const val CLASS_CONTEXT = "android.content.Context"
+const val CLASS_ACTIVITY_MANAGER_SERVICE = "com.android.server.am.ActivityManagerService"
+const val CLASS_ACTIVITY_MANAGER_INTERNAL = "android.app.ActivityManagerInternal"
+
+// Enforce permission APIs
+val ENFORCE_PERMISSION_METHODS = listOf(
+ Method(CLASS_CONTEXT, "checkPermission"),
+ Method(CLASS_CONTEXT, "checkCallingPermission"),
+ Method(CLASS_CONTEXT, "checkCallingOrSelfPermission"),
+ Method(CLASS_CONTEXT, "enforcePermission"),
+ Method(CLASS_CONTEXT, "enforceCallingPermission"),
+ Method(CLASS_CONTEXT, "enforceCallingOrSelfPermission"),
+ Method(CLASS_ACTIVITY_MANAGER_SERVICE, "checkPermission"),
+ Method(CLASS_ACTIVITY_MANAGER_INTERNAL, "enforceCallingPermission")
+)
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/PackageVisibilityDetector.kt b/tools/lint/checks/src/main/java/com/google/android/lint/PackageVisibilityDetector.kt
index 192dba17dd5b..48540b1da565 100644
--- a/tools/lint/checks/src/main/java/com/google/android/lint/PackageVisibilityDetector.kt
+++ b/tools/lint/checks/src/main/java/com/google/android/lint/PackageVisibilityDetector.kt
@@ -30,13 +30,13 @@ import com.android.tools.lint.detector.api.interprocedural.CallGraphResult
import com.android.tools.lint.detector.api.interprocedural.searchForPaths
import com.intellij.psi.PsiAnonymousClass
import com.intellij.psi.PsiMethod
+import java.util.LinkedList
import org.jetbrains.uast.UCallExpression
import org.jetbrains.uast.UElement
import org.jetbrains.uast.UMethod
import org.jetbrains.uast.UParameter
import org.jetbrains.uast.USimpleNameReferenceExpression
import org.jetbrains.uast.visitor.AbstractUastVisitor
-import java.util.LinkedList
/**
* A lint checker to detect potential package visibility issues for system's APIs. APIs working
@@ -362,14 +362,18 @@ class PackageVisibilityDetector : Detector(), SourceCodeScanner {
name: String,
matchArgument: Boolean = true,
checkCaller: Boolean = false
- ): this(clazz, name) {
+ ) : this(clazz, name) {
this.matchArgument = matchArgument
this.checkCaller = checkCaller
}
constructor(
method: PsiMethod
- ): this(method.containingClass?.qualifiedName ?: "", method.name)
+ ) : this(method.containingClass?.qualifiedName ?: "", method.name)
+
+ constructor(
+ method: com.google.android.lint.model.Method
+ ) : this(method.clazz, method.name)
}
/**
@@ -380,7 +384,7 @@ class PackageVisibilityDetector : Detector(), SourceCodeScanner {
val typeName: String,
val parameterName: String
) {
- constructor(uParameter: UParameter): this(
+ constructor(uParameter: UParameter) : this(
uParameter.type.canonicalText,
uParameter.name.lowercase()
)
@@ -405,19 +409,13 @@ class PackageVisibilityDetector : Detector(), SourceCodeScanner {
// A valid call path list needs to contain a start node and a sink node
private const val VALID_CALL_PATH_NODES_SIZE = 2
- private const val CLASS_STUB = "Stub"
private const val CLASS_STRING = "java.lang.String"
private const val CLASS_PACKAGE_MANAGER = "android.content.pm.PackageManager"
private const val CLASS_IPACKAGE_MANAGER = "android.content.pm.IPackageManager"
private const val CLASS_APPOPS_MANAGER = "android.app.AppOpsManager"
- private const val CLASS_CONTEXT = "android.content.Context"
private const val CLASS_BINDER = "android.os.Binder"
private const val CLASS_PACKAGE_MANAGER_INTERNAL =
"android.content.pm.PackageManagerInternal"
- private const val CLASS_ACTIVITY_MANAGER_SERVICE =
- "com.android.server.am.ActivityManagerService"
- private const val CLASS_ACTIVITY_MANAGER_INTERNAL =
- "android.app.ActivityManagerInternal"
// Patterns of package name parameter
private val PACKAGE_NAME_PATTERNS = setOf(
@@ -455,16 +453,9 @@ class PackageVisibilityDetector : Detector(), SourceCodeScanner {
)
// Enforce permission APIs
- private val ENFORCE_PERMISSION_METHODS = listOf(
- Method(CLASS_CONTEXT, "checkPermission"),
- Method(CLASS_CONTEXT, "checkCallingPermission"),
- Method(CLASS_CONTEXT, "checkCallingOrSelfPermission"),
- Method(CLASS_CONTEXT, "enforcePermission"),
- Method(CLASS_CONTEXT, "enforceCallingPermission"),
- Method(CLASS_CONTEXT, "enforceCallingOrSelfPermission"),
- Method(CLASS_ACTIVITY_MANAGER_SERVICE, "checkPermission"),
- Method(CLASS_ACTIVITY_MANAGER_INTERNAL, "enforceCallingPermission")
- )
+ private val ENFORCE_PERMISSION_METHODS =
+ com.google.android.lint.ENFORCE_PERMISSION_METHODS
+ .map(PackageVisibilityDetector::Method)
private val BYPASS_STUBS = listOf(
"android.content.pm.IPackageDataObserver.Stub",
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/aidl/Constants.kt b/tools/lint/checks/src/main/java/com/google/android/lint/aidl/Constants.kt
new file mode 100644
index 000000000000..8ee3763e5079
--- /dev/null
+++ b/tools/lint/checks/src/main/java/com/google/android/lint/aidl/Constants.kt
@@ -0,0 +1,73 @@
+/*
+ * 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.google.android.lint.aidl
+
+const val ANNOTATION_ENFORCE_PERMISSION = "android.annotation.EnforcePermission"
+const val ANNOTATION_REQUIRES_NO_PERMISSION = "android.annotation.RequiresNoPermission"
+const val ANNOTATION_PERMISSION_MANUALLY_ENFORCED = "android.annotation.PermissionManuallyEnforced"
+
+val AIDL_PERMISSION_ANNOTATIONS = listOf(
+ ANNOTATION_ENFORCE_PERMISSION,
+ ANNOTATION_REQUIRES_NO_PERMISSION,
+ ANNOTATION_PERMISSION_MANUALLY_ENFORCED
+)
+
+const val IINTERFACE_INTERFACE = "android.os.IInterface"
+
+/**
+ * If a non java (e.g. c++) backend is enabled, the @EnforcePermission
+ * annotation cannot be used. At time of writing, the mechanism
+ * is not implemented for non java backends.
+ * TODO: b/242564874 (have lint know which interfaces have the c++ backend enabled)
+ * rather than hard coding this list?
+ */
+val EXCLUDED_CPP_INTERFACES = listOf(
+ "AdbTransportType",
+ "FingerprintAndPairDevice",
+ "IAdbCallback",
+ "IAdbManager",
+ "PairDevice",
+ "IStatsBootstrapAtomService",
+ "StatsBootstrapAtom",
+ "StatsBootstrapAtomValue",
+ "FixedSizeArrayExample",
+ "PlaybackTrackMetadata",
+ "RecordTrackMetadata",
+ "SinkMetadata",
+ "SourceMetadata",
+ "IUpdateEngineStable",
+ "IUpdateEngineStableCallback",
+ "AudioCapabilities",
+ "ConfidenceLevel",
+ "ModelParameter",
+ "ModelParameterRange",
+ "Phrase",
+ "PhraseRecognitionEvent",
+ "PhraseRecognitionExtra",
+ "PhraseSoundModel",
+ "Properties",
+ "RecognitionConfig",
+ "RecognitionEvent",
+ "RecognitionMode",
+ "RecognitionStatus",
+ "SoundModel",
+ "SoundModelType",
+ "Status",
+ "IThermalService",
+ "IPowerManager",
+ "ITunerResourceManager"
+)
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/EnforcePermissionDetector.kt b/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt
index 9f216189ad62..a415217105a8 100644
--- a/tools/lint/checks/src/main/java/com/google/android/lint/EnforcePermissionDetector.kt
+++ b/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt
@@ -14,15 +14,15 @@
* limitations under the License.
*/
-package com.google.android.lint
+package com.google.android.lint.aidl
import com.android.tools.lint.client.api.UElementHandler
import com.android.tools.lint.detector.api.AnnotationInfo
import com.android.tools.lint.detector.api.AnnotationOrigin
import com.android.tools.lint.detector.api.AnnotationUsageInfo
import com.android.tools.lint.detector.api.AnnotationUsageType
-import com.android.tools.lint.detector.api.ConstantEvaluator
import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.ConstantEvaluator
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Implementation
import com.android.tools.lint.detector.api.Issue
@@ -34,8 +34,8 @@ import com.intellij.psi.PsiAnnotation
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiMethod
import org.jetbrains.uast.UAnnotation
-import org.jetbrains.uast.UElement
import org.jetbrains.uast.UClass
+import org.jetbrains.uast.UElement
import org.jetbrains.uast.UMethod
/**
@@ -54,12 +54,11 @@ import org.jetbrains.uast.UMethod
*/
class EnforcePermissionDetector : Detector(), SourceCodeScanner {
- val ENFORCE_PERMISSION = "android.annotation.EnforcePermission"
val BINDER_CLASS = "android.os.Binder"
val JAVA_OBJECT = "java.lang.Object"
override fun applicableAnnotations(): List<String> {
- return listOf(ENFORCE_PERMISSION)
+ return listOf(ANNOTATION_ENFORCE_PERMISSION)
}
override fun getApplicableUastTypes(): List<Class<out UElement>> {
@@ -99,8 +98,8 @@ class EnforcePermissionDetector : Detector(), SourceCodeScanner {
overriddenMethod: PsiMethod,
checkEquivalence: Boolean = true
) {
- val overridingAnnotation = overridingMethod.getAnnotation(ENFORCE_PERMISSION)
- val overriddenAnnotation = overriddenMethod.getAnnotation(ENFORCE_PERMISSION)
+ val overridingAnnotation = overridingMethod.getAnnotation(ANNOTATION_ENFORCE_PERMISSION)
+ val overriddenAnnotation = overriddenMethod.getAnnotation(ANNOTATION_ENFORCE_PERMISSION)
val location = context.getLocation(element)
val overridingClass = overridingMethod.parent as PsiClass
val overriddenClass = overriddenMethod.parent as PsiClass
@@ -133,8 +132,8 @@ class EnforcePermissionDetector : Detector(), SourceCodeScanner {
extendedClass: PsiClass,
checkEquivalence: Boolean = true
) {
- val newAnnotation = newClass.getAnnotation(ENFORCE_PERMISSION)
- val extendedAnnotation = extendedClass.getAnnotation(ENFORCE_PERMISSION)
+ val newAnnotation = newClass.getAnnotation(ANNOTATION_ENFORCE_PERMISSION)
+ val extendedAnnotation = extendedClass.getAnnotation(ANNOTATION_ENFORCE_PERMISSION)
val location = context.getLocation(element)
val newClassName = newClass.qualifiedName
@@ -180,7 +179,7 @@ class EnforcePermissionDetector : Detector(), SourceCodeScanner {
override fun createUastHandler(context: JavaContext): UElementHandler {
return object : UElementHandler() {
override fun visitAnnotation(node: UAnnotation) {
- if (node.qualifiedName != ENFORCE_PERMISSION) {
+ if (node.qualifiedName != ANNOTATION_ENFORCE_PERMISSION) {
return
}
val method = node.uastParent as? UMethod
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt b/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt
new file mode 100644
index 000000000000..510611161ea8
--- /dev/null
+++ b/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionFix.kt
@@ -0,0 +1,118 @@
+/*
+ * 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.google.android.lint.aidl
+
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Location
+import com.intellij.psi.PsiVariable
+import org.jetbrains.uast.UCallExpression
+import org.jetbrains.uast.ULiteralExpression
+import org.jetbrains.uast.UQualifiedReferenceExpression
+import org.jetbrains.uast.USimpleNameReferenceExpression
+import org.jetbrains.uast.asRecursiveLogString
+
+/**
+ * Helper ADT class that facilitates the creation of lint auto fixes
+ *
+ * Handles "Single" permission checks that should be migrated to @EnforcePermission(...), as well as consecutive checks
+ * that should be migrated to @EnforcePermission(allOf={...})
+ *
+ * TODO: handle anyOf style annotations
+ */
+sealed class EnforcePermissionFix {
+ abstract fun locations(): List<Location>
+ abstract fun javaAnnotationParameter(): String
+
+ fun javaAnnotation(): String = "@$ANNOTATION_ENFORCE_PERMISSION(${javaAnnotationParameter()})"
+
+ companion object {
+ fun fromCallExpression(callExpression: UCallExpression, context: JavaContext): SingleFix =
+ SingleFix(
+ getPermissionCheckLocation(context, callExpression),
+ getPermissionCheckArgumentValue(callExpression)
+ )
+
+ fun maybeAddManifestPrefix(permissionName: String): String =
+ if (permissionName.contains(".")) permissionName
+ else "android.Manifest.permission.$permissionName"
+
+ /**
+ * Given a permission check, get its proper location
+ * so that a lint fix can remove the entire expression
+ */
+ private fun getPermissionCheckLocation(
+ context: JavaContext,
+ callExpression: UCallExpression
+ ):
+ Location {
+ val javaPsi = callExpression.javaPsi!!
+ return Location.create(
+ context.file,
+ javaPsi.containingFile?.text,
+ javaPsi.textRange.startOffset,
+ // unfortunately the element doesn't include the ending semicolon
+ javaPsi.textRange.endOffset + 1
+ )
+ }
+
+ /**
+ * Given a permission check and an argument,
+ * pull out the permission value that is being used
+ */
+ private fun getPermissionCheckArgumentValue(
+ callExpression: UCallExpression,
+ argumentPosition: Int = 0
+ ): String {
+
+ val identifier = when (
+ val argument = callExpression.valueArguments.getOrNull(argumentPosition)
+ ) {
+ is UQualifiedReferenceExpression -> when (val selector = argument.selector) {
+ is USimpleNameReferenceExpression ->
+ ((selector.resolve() as PsiVariable).computeConstantValue() as String)
+
+ else -> throw RuntimeException(
+ "Couldn't resolve argument: ${selector.asRecursiveLogString()}"
+ )
+ }
+
+ is USimpleNameReferenceExpression -> (
+ (argument.resolve() as PsiVariable).computeConstantValue() as String)
+
+ is ULiteralExpression -> argument.value as String
+
+ else -> throw RuntimeException(
+ "Couldn't resolve argument: ${argument?.asRecursiveLogString()}"
+ )
+ }
+
+ return identifier.substringAfterLast(".")
+ }
+ }
+}
+
+data class SingleFix(val location: Location, val permissionName: String) : EnforcePermissionFix() {
+ override fun locations(): List<Location> = listOf(this.location)
+ override fun javaAnnotationParameter(): String = maybeAddManifestPrefix(this.permissionName)
+}
+data class AllOfFix(val checks: List<SingleFix>) : EnforcePermissionFix() {
+ override fun locations(): List<Location> = this.checks.map { it.location }
+ override fun javaAnnotationParameter(): String =
+ "allOf={${
+ this.checks.joinToString(", ") { maybeAddManifestPrefix(it.permissionName) }
+ }}"
+}
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/aidl/ManualPermissionCheckDetector.kt b/tools/lint/checks/src/main/java/com/google/android/lint/aidl/ManualPermissionCheckDetector.kt
new file mode 100644
index 000000000000..2cea39423f1d
--- /dev/null
+++ b/tools/lint/checks/src/main/java/com/google/android/lint/aidl/ManualPermissionCheckDetector.kt
@@ -0,0 +1,199 @@
+/*
+ * 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.google.android.lint.aidl
+
+import com.android.tools.lint.client.api.UElementHandler
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import com.google.android.lint.CLASS_STUB
+import com.google.android.lint.ENFORCE_PERMISSION_METHODS
+import com.intellij.psi.PsiAnonymousClass
+import org.jetbrains.uast.UBlockExpression
+import org.jetbrains.uast.UCallExpression
+import org.jetbrains.uast.UElement
+import org.jetbrains.uast.UIfExpression
+import org.jetbrains.uast.UMethod
+import org.jetbrains.uast.UQualifiedReferenceExpression
+
+/**
+ * Looks for methods implementing generated AIDL interface stubs
+ * that can have simple permission checks migrated to
+ * @EnforcePermission annotations
+ *
+ * TODO: b/242564870 (enable parse and autoFix of .aidl files)
+ */
+@Suppress("UnstableApiUsage")
+class ManualPermissionCheckDetector : Detector(), SourceCodeScanner {
+ override fun getApplicableUastTypes(): List<Class<out UElement?>> =
+ listOf(UMethod::class.java)
+
+ override fun createUastHandler(context: JavaContext): UElementHandler = AidlStubHandler(context)
+
+ private inner class AidlStubHandler(val context: JavaContext) : UElementHandler() {
+ override fun visitMethod(node: UMethod) {
+ val interfaceName = getContainingAidlInterface(node)
+ .takeUnless(EXCLUDED_CPP_INTERFACES::contains) ?: return
+ val body = (node.uastBody as? UBlockExpression) ?: return
+ val fix = accumulateSimplePermissionCheckFixes(body) ?: return
+
+ val javaRemoveFixes = fix.locations().map {
+ fix()
+ .replace()
+ .reformat(true)
+ .range(it)
+ .with("")
+ .autoFix()
+ .build()
+ }
+
+ val javaAnnotateFix = fix()
+ .annotate(fix.javaAnnotation())
+ .range(context.getLocation(node))
+ .autoFix()
+ .build()
+
+ val message =
+ "$interfaceName permission check can be converted to @EnforcePermission annotation"
+
+ context.report(
+ ISSUE_USE_ENFORCE_PERMISSION_ANNOTATION,
+ fix.locations().last(),
+ message,
+ fix().composite(*javaRemoveFixes.toTypedArray(), javaAnnotateFix)
+ )
+ }
+
+ /**
+ * Walk the expressions in the method, looking for simple permission checks.
+ *
+ * If a single permission check is found at the beginning of the method,
+ * this should be migrated to @EnforcePermission(value).
+ *
+ * If multiple consecutive permission checks are found,
+ * these should be migrated to @EnforcePermission(allOf={value1, value2, ...})
+ *
+ * As soon as something other than a permission check is encountered, stop looking,
+ * as some other business logic is happening that prevents an automated fix.
+ */
+ private fun accumulateSimplePermissionCheckFixes(methodBody: UBlockExpression):
+ EnforcePermissionFix? {
+ val singleFixes = mutableListOf<SingleFix>()
+ for (expression in methodBody.expressions) {
+ singleFixes.add(getPermissionCheckFix(expression) ?: break)
+ }
+ return when (singleFixes.size) {
+ 0 -> null
+ 1 -> singleFixes[0]
+ else -> AllOfFix(singleFixes)
+ }
+ }
+
+ /**
+ * If an expression boils down to a permission check, return
+ * the helper for creating a lint auto fix to @EnforcePermission
+ */
+ private fun getPermissionCheckFix(startingExpression: UElement?):
+ SingleFix? {
+ return when (startingExpression) {
+ is UQualifiedReferenceExpression -> getPermissionCheckFix(
+ startingExpression.selector
+ )
+
+ is UIfExpression -> getPermissionCheckFix(startingExpression.condition)
+
+ is UCallExpression -> {
+ return if (isPermissionCheck(startingExpression))
+ EnforcePermissionFix.fromCallExpression(startingExpression, context)
+ else null
+ }
+
+ else -> null
+ }
+ }
+ }
+
+ companion object {
+
+ private val EXPLANATION = """
+ Whenever possible, method implementations of AIDL interfaces should use the @EnforcePermission
+ annotation to declare the permissions to be enforced. The verification code is then
+ generated by the AIDL compiler, which also takes care of annotating the generated java
+ code.
+
+ This reduces the risk of bugs around these permission checks (that often become vulnerabilities).
+ It also enables easier auditing and review.
+
+ Please migrate to an @EnforcePermission annotation. (See: go/aidl-enforce-howto)
+ """.trimIndent()
+
+ @JvmField
+ val ISSUE_USE_ENFORCE_PERMISSION_ANNOTATION = Issue.create(
+ id = "UseEnforcePermissionAnnotation",
+ briefDescription = "Manual permission check can be @EnforcePermission annotation",
+ explanation = EXPLANATION,
+ category = Category.SECURITY,
+ priority = 5,
+ severity = Severity.WARNING,
+ implementation = Implementation(
+ ManualPermissionCheckDetector::class.java,
+ Scope.JAVA_FILE_SCOPE
+ ),
+ enabledByDefault = false, // TODO: enable once b/241171714 is resolved
+ )
+
+ private fun isPermissionCheck(callExpression: UCallExpression): Boolean {
+ val method = callExpression.resolve() ?: return false
+ val className = method.containingClass?.qualifiedName
+ return ENFORCE_PERMISSION_METHODS.any {
+ it.clazz == className && it.name == method.name
+ }
+ }
+
+ /**
+ * given a UMethod, determine if this method is
+ * an entrypoint to an interface generated by AIDL,
+ * returning the interface name if so
+ */
+ fun getContainingAidlInterface(node: UMethod): String? {
+ if (!isInClassCalledStub(node)) return null
+ for (superMethod in node.findSuperMethods()) {
+ for (extendsInterface in superMethod.containingClass?.extendsList?.referenceElements
+ ?: continue) {
+ if (extendsInterface.qualifiedName == IINTERFACE_INTERFACE) {
+ return superMethod.containingClass?.name
+ }
+ }
+ }
+ return null
+ }
+
+ private fun isInClassCalledStub(node: UMethod): Boolean {
+ (node.containingClass as? PsiAnonymousClass)?.let {
+ return it.baseClassReference.referenceName == CLASS_STUB
+ }
+ return node.containingClass?.extendsList?.referenceElements?.any {
+ it.referenceName == CLASS_STUB
+ } ?: false
+ }
+ }
+}
diff --git a/lowpan/java/android/net/lowpan/ILowpanManagerListener.aidl b/tools/lint/checks/src/main/java/com/google/android/lint/model/Method.kt
index d4846f6740b9..3939b6109eaa 100644
--- a/lowpan/java/android/net/lowpan/ILowpanManagerListener.aidl
+++ b/tools/lint/checks/src/main/java/com/google/android/lint/model/Method.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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.
@@ -14,12 +14,13 @@
* limitations under the License.
*/
-package android.net.lowpan;
+package com.google.android.lint.model
-import android.net.lowpan.ILowpanInterface;
-
-/** {@hide} */
-interface ILowpanManagerListener {
- oneway void onInterfaceAdded(ILowpanInterface lowpanInterface);
- oneway void onInterfaceRemoved(ILowpanInterface lowpanInterface);
+/**
+ * Data class to represent a Method
+ */
+data class Method(val clazz: String, val name: String) {
+ override fun toString(): String {
+ return "$clazz#$name"
+ }
}
diff --git a/tools/lint/checks/src/test/java/com/google/android/lint/EnforcePermissionDetectorTest.kt b/tools/lint/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt
index 2cfc3fbcefcb..3c1d1e8e8a59 100644
--- a/tools/lint/checks/src/test/java/com/google/android/lint/EnforcePermissionDetectorTest.kt
+++ b/tools/lint/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.google.android.lint
+package com.google.android.lint.aidl
import com.android.tools.lint.checks.infrastructure.LintDetectorTest
import com.android.tools.lint.checks.infrastructure.TestFile
diff --git a/tools/lint/checks/src/test/java/com/google/android/lint/aidl/ManualPermissionCheckDetectorTest.kt b/tools/lint/checks/src/test/java/com/google/android/lint/aidl/ManualPermissionCheckDetectorTest.kt
new file mode 100644
index 000000000000..1a1c6bc77785
--- /dev/null
+++ b/tools/lint/checks/src/test/java/com/google/android/lint/aidl/ManualPermissionCheckDetectorTest.kt
@@ -0,0 +1,211 @@
+/*
+ * 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.google.android.lint.aidl
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.checks.infrastructure.TestFile
+import com.android.tools.lint.checks.infrastructure.TestLintTask
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+
+@Suppress("UnstableApiUsage")
+class ManualPermissionCheckDetectorTest : LintDetectorTest() {
+ override fun getDetector(): Detector = ManualPermissionCheckDetector()
+ override fun getIssues(): List<Issue> = listOf(
+ ManualPermissionCheckDetector
+ .ISSUE_USE_ENFORCE_PERMISSION_ANNOTATION
+ )
+
+ override fun lint(): TestLintTask = super.lint().allowMissingSdk()
+
+ fun testClass() {
+ lint().files(
+ java(
+ """
+ import android.content.Context;
+ import android.test.ITest;
+ public class Foo extends ITest.Stub {
+ private Context mContext;
+ @Override
+ public void test() throws android.os.RemoteException {
+ mContext.enforceCallingOrSelfPermission("android.Manifest.permission.READ_CONTACTS", "foo");
+ }
+ }
+ """
+ ).indented(),
+ *stubs
+ )
+ .run()
+ .expect(
+ """
+ src/Foo.java:7: Warning: ITest permission check can be converted to @EnforcePermission annotation [UseEnforcePermissionAnnotation]
+ mContext.enforceCallingOrSelfPermission("android.Manifest.permission.READ_CONTACTS", "foo");
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ 0 errors, 1 warnings
+ """
+ )
+ .expectFixDiffs(
+ """
+ Fix for src/Foo.java line 7: Annotate with @EnforcePermission:
+ @@ -5 +5
+ + @android.annotation.EnforcePermission(android.Manifest.permission.READ_CONTACTS)
+ @@ -7 +8
+ - mContext.enforceCallingOrSelfPermission("android.Manifest.permission.READ_CONTACTS", "foo");
+ """
+ )
+ }
+
+ fun testAnonClass() {
+ lint().files(
+ java(
+ """
+ import android.content.Context;
+ import android.test.ITest;
+ public class Foo {
+ private Context mContext;
+ private ITest itest = new ITest.Stub() {
+ @Override
+ public void test() throws android.os.RemoteException {
+ mContext.enforceCallingOrSelfPermission(
+ "android.Manifest.permission.READ_CONTACTS", "foo");
+ }
+ };
+ }
+ """
+ ).indented(),
+ *stubs
+ )
+ .run()
+ .expect(
+ """
+ src/Foo.java:8: Warning: ITest permission check can be converted to @EnforcePermission annotation [UseEnforcePermissionAnnotation]
+ mContext.enforceCallingOrSelfPermission(
+ ^
+ 0 errors, 1 warnings
+ """
+ )
+ .expectFixDiffs(
+ """
+ Fix for src/Foo.java line 8: Annotate with @EnforcePermission:
+ @@ -6 +6
+ + @android.annotation.EnforcePermission(android.Manifest.permission.READ_CONTACTS)
+ @@ -8 +9
+ - mContext.enforceCallingOrSelfPermission(
+ - "android.Manifest.permission.READ_CONTACTS", "foo");
+ """
+ )
+ }
+
+ fun testAllOf() {
+ lint().files(
+ java(
+ """
+ import android.content.Context;
+ import android.test.ITest;
+ public class Foo {
+ private Context mContext;
+ private ITest itest = new ITest.Stub() {
+ @Override
+ public void test() throws android.os.RemoteException {
+ mContext.enforceCallingOrSelfPermission(
+ "android.Manifest.permission.READ_CONTACTS", "foo");
+ mContext.enforceCallingOrSelfPermission(
+ "android.Manifest.permission.WRITE_CONTACTS", "foo");
+ }
+ };
+ }
+ """
+ ).indented(),
+ *stubs
+ )
+ .run()
+ .expect(
+ """
+ src/Foo.java:10: Warning: ITest permission check can be converted to @EnforcePermission annotation [UseEnforcePermissionAnnotation]
+ mContext.enforceCallingOrSelfPermission(
+ ^
+ 0 errors, 1 warnings
+ """
+ )
+ .expectFixDiffs(
+ """
+ Fix for src/Foo.java line 10: Annotate with @EnforcePermission:
+ @@ -6 +6
+ + @android.annotation.EnforcePermission(allOf={android.Manifest.permission.READ_CONTACTS, android.Manifest.permission.WRITE_CONTACTS})
+ @@ -8 +9
+ - mContext.enforceCallingOrSelfPermission(
+ - "android.Manifest.permission.READ_CONTACTS", "foo");
+ - mContext.enforceCallingOrSelfPermission(
+ - "android.Manifest.permission.WRITE_CONTACTS", "foo");
+ """
+ )
+ }
+
+ fun testPrecedingExpressions() {
+ lint().files(
+ java(
+ """
+ import android.os.Binder;
+ import android.test.ITest;
+ public class Foo extends ITest.Stub {
+ private mContext Context;
+ @Override
+ public void test() throws android.os.RemoteException {
+ long uid = Binder.getCallingUid();
+ mContext.enforceCallingOrSelfPermission("android.Manifest.permission.READ_CONTACTS", "foo");
+ }
+ }
+ """
+ ).indented(),
+ *stubs
+ )
+ .run()
+ .expectClean()
+ }
+
+ companion object {
+ private val aidlStub: TestFile = java(
+ """
+ package android.test;
+ public interface ITest extends android.os.IInterface {
+ public static abstract class Stub extends android.os.Binder implements android.test.ITest {}
+ public void test() throws android.os.RemoteException;
+ }
+ """
+ ).indented()
+
+ private val contextStub: TestFile = java(
+ """
+ package android.content;
+ public class Context {
+ public void enforceCallingOrSelfPermission(String permission, String message) {}
+ }
+ """
+ ).indented()
+
+ private val binderStub: TestFile = java(
+ """
+ package android.os;
+ public class Binder {
+ public static int getCallingUid() {}
+ }
+ """
+ ).indented()
+
+ val stubs = arrayOf(aidlStub, contextStub, binderStub)
+ }
+}